t123yh
7 years ago
4 changed files with 197 additions and 2 deletions
@ -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] })); |
||||||
|
} |
@ -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…
Reference in new issue