Browse Source

Finish rating.

pull/6/head
t123yh 7 years ago
parent
commit
5fcf8fc94a
  1. 77
      libs/rating.js
  2. 87
      modules/admin.js
  3. 1
      views/admin_header.ejs
  4. 34
      views/admin_rating.ejs

77
libs/rating.js

@ -0,0 +1,77 @@
// Code by Dicint
const util = require('util');
const _ = require('lodash');
function getEloWinProbability(ra, rb) {
return 1.0 / (1 + Math.pow(10, (rb - ra) / 400.0));
}
// 传入具体某个人的情况
function getContestantSeed(contestantIndex, allContestants) {
let seed = 1;
let rating = allContestants[contestantIndex].currentRating;
for (let i = 0; i < allContestants.length; i++) {
if (contestantIndex != i) {
seed += getEloWinProbability(allContestants[i].currentRating, rating);
}
}
return seed;
}
function getRatingSeed(rating, allContestants) {
return 1 + _.sum(allContestants.map(c => getEloWinProbability(c.currentRating, rating)));
}
function getAverageRank(contestant, allContestants) {
const realRank = allContestants[contestant].rank;
const expectedRank = getContestantSeed(contestant, allContestants);
const average = Math.sqrt(realRank * expectedRank);
return average;
}
function getRatingToRank(contestantIndex, allContestants) {
let averageRank = getAverageRank(contestantIndex, allContestants);
let left = 1;// contestant.getPrevRating() - 2 * minDelta;
let right = 8000;// contestant.getPrevRating() + 2 * maxDelta;
while (right - left > 1) {
const mid = (left + right) / 2;
const seed = getRatingSeed(mid, allContestants);
if (seed < averageRank) {
right = mid;
} else {
left = mid;
}
}
return left;
}
function calculateDeltas(allContestants) {
let deltas = [];
const numberOfContestants = allContestants.length;
for (let i = 0; i < allContestants.length; i++) {
const expR = getRatingToRank(i, allContestants);
deltas[i] = ((expR - allContestants[i].currentRating) / 2);
}
// Total sum should not be more than zero.
const deltaSum = _.sum(deltas);
const inc = -deltaSum / numberOfContestants - 1;
deltas = deltas.map(d => d + inc);
// Sum of top-4*sqrt should be adjusted to zero.
const zeroSumCount = Math.min(Math.trunc(4 * Math.round(Math.sqrt(numberOfContestants))), numberOfContestants);
const deltaSum2 = _.sum(deltas.slice(0, zeroSumCount));
const inc2 = Math.min(Math.max(-deltaSum2 / zeroSumCount, -10), 0);
deltas = deltas.map(d => d + inc2);
return deltas;
}
module.exports = function(allContestants) {
const deltas = calculateDeltas(allContestants);
return allContestants.map((contestant, i) => ({ user: contestant.user, currentRating: contestant.currentRating + deltas[i] }));
}

87
modules/admin.js

@ -23,6 +23,10 @@ let Article = syzoj.model('article');
let Contest = syzoj.model('contest');
let User = syzoj.model('user');
let UserPrivilege = syzoj.model('user_privilege');
const RatingCalculation = syzoj.model('rating_calculation');
const RatingHistory = syzoj.model('rating_history');
let ContestPlayer = syzoj.model('contest_player');
const calcRating = require('../libs/rating');
let db = syzoj.db;
@ -198,6 +202,86 @@ app.post('/admin/privilege', async (req, res) => {
}
});
app.get('/admin/rating', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
const contests = await Contest.query(null, {}, [['start_time', 'desc']]);
const calcs = await RatingCalculation.query(null, {}, [['id', 'desc']]);
const util = require('util');
for (const calc of calcs) await calc.loadRelationships();
res.render('admin_rating', {
contests: contests,
calcs: calcs
});
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
})
}
});
app.post('/admin/rating/add', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
const contest = await Contest.fromID(req.body.contest);
if (!contest) throw new ErrorMessage('无此比赛');
await contest.loadRelationships();
const newcalc = await RatingCalculation.create(contest.id);
await newcalc.save();
if (!contest.ranklist || contest.ranklist.ranklist.player_num <= 1) {
throw new ErrorMessage("比赛人数太少。");
}
const players = [];
for (let i = 1; i <= contest.ranklist.ranklist.player_num; i++) {
const user = await User.fromID((await ContestPlayer.fromID(contest.ranklist.ranklist[i])).user_id);
players.push({
user: user,
rank: i,
currentRating: user.rating
});
}
const newRating = calcRating(players);
for (let i = 0; i < newRating.length; i++) {
const user = newRating[i].user;
user.rating = newRating[i].currentRating;
await user.save();
const newHistory = await RatingHistory.create(newcalc.id, user.id, user.rating);
await newHistory.save();
}
res.redirect(syzoj.utils.makeUrl(['admin', 'rating']));
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.post('/admin/rating/delete', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
const calcList = await RatingCalculation.query(null, { id: { $gte: req.body.calc_id } }, [['id', 'desc']]);
if (calcList.length === 0) throw new ErrorMessage('ID 不正确');
for (let i = 0; i < calcList.length; i++) {
await calcList[i].delete();
}
res.redirect(syzoj.utils.makeUrl(['admin', 'rating']));
} catch (e) {
syzoj.log(e);
res.render('error', {
err: e
});
}
});
app.get('/admin/other', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');
@ -233,7 +317,7 @@ app.post('/admin/other', async (req, res) => {
if (req.body.type === 'reset_count') {
const problems = await Problem.query();
for(const p of problems) {
for (const p of problems) {
await p.resetSubmissionCount();
}
} else {
@ -248,7 +332,6 @@ app.post('/admin/other', async (req, res) => {
})
}
});
app.post('/admin/rejudge', async (req, res) => {
try {
if (!res.locals.user || !res.locals.user.is_admin) throw new ErrorMessage('您没有权限进行此操作。');

1
views/admin_header.ejs

@ -5,6 +5,7 @@ let items = {
privilege: '权限管理',
rejudge: '一键重测',
links: '友链管理',
rating: 'Rating 管理',
raw: '配置文件',
other: '其他操作'
};

34
views/admin_rating.ejs

@ -0,0 +1,34 @@
<% this.adminPage = 'rating'; %>
<% include admin_header %>
<form action="<%= syzoj.utils.makeUrl(['admin', 'rating', 'add']) %>" method="post" class="ui form">
<div class="field">
<label>比赛</label>
<div class="ui fluid search selection dropdown">
<input type="hidden" name="contest">
<i class="dropdown icon"></i>
<div class="default text">选择比赛</div>
<div class="menu">
<% for (const contest of contests) { %>
<div class="item" data-value="<%= contest.id %>"><%= contest.title %></div>
<% } %>
</div>
</div>
</div>
<button class="ui blue button" name="type" value="doit" type="submit">计算此比赛的 Rating</button>
</form>
注意:如果删除一个比赛的 Rating,则该比赛之上的所有比赛也将被删除,Rating 将还原至该比赛之前的状态!
<form class="have-csrf" action="<%= syzoj.utils.makeUrl(['admin', 'rating', 'delete']) %>" method="POST">
<input type="hidden" name="_csrf" value="<%= req.csrfToken() %>" />
<div class="ui relaxed divided list">
<% for (const calc of calcs) { %>
<div class="item">
<%= calc.contest.title %>
<button name="calc_id" value="<%= calc.id %>" type="submit" style="color: #000; padding: 0; border: none; background: none;"><i class="remove icon"></i></button>
</div>
<% } %>
</div>
</div>
</form>
<% include admin_footer %>
<script>$('.selection.dropdown').dropdown();</script>
Loading…
Cancel
Save