// ==UserScript==
// @name UOJ Predictor
// @namespace http://tampermonkey.net/
// @version 0.6
// @description Plugin to calculate predicted rating changes of UOJ-like Online Judges.
// @author tiger2005
// @match *://zhengruioi.com/contest/*/standings*
// @match *://uoj.ac/contest/*/standings*
// @match *://www.zhengruioi.com/contest/*/standings*
// @match *://www.uoj.ac/contest/*/standings*
// @grant none
// @license MIT
// ==/UserScript==
(function() {
'use strict';
// transformed from https://github.com/vfleaking/uoj/blob/9f1302c774f2499af0dc52d3faa7dd7404d03b13/uoj/1/app/uoj-contest-lib.php
function getColOfRating(rating) {
if (rating < 1500) {
var H = 300 - (1500 - 850) * 300 / 1650, S = 30 + (1500 - 850) * 70 / 1650, V = 50 + (1500 - 850) * 50 / 1650;
if (rating < 300) rating = 300;
var k = (rating - 300) / 1200;
return ColorConverter.toStr(ColorConverter.toRGB(new HSV(H + (300 - H) * (1 - k), 30 + (S - 30) * k, 50 + (V - 50) * k)));
}
if (rating > 2500) {
rating = 2500;
}
return ColorConverter.toStr(ColorConverter.toRGB(new HSV(300 - (rating - 850) * 300 / 1650, 30 + (rating - 850) * 70 / 1650, 50 + (rating - 850) * 50 / 1650)));
}
function calcPredictedRatingChanges(K = 400){
var delta = 500;
var n = standings.length;
var rating = [];
var i, j;
for(i = 0; i < n; i ++)
rating.push(standings[i][2][1]);
var rank = [];
var foot = [];
for(i = 0; i < n; ){
j = i;
while(j + 1 < n && standings[j+1][3] == standings[j][3])
++ j;
var our_rk = 0.5 * ((i+1) + (j+1));
while(i <= j){
rank.push(our_rk);
foot.push(n - rank[i]);
i ++;
}
}
var weight = [];
for(i = 0; i < n; i ++)
weight.push(Math.pow(7, rating[i] / delta));
var exp = [];
for(i = 0; i < n; i ++)
exp.push(0);
for(i = 0; i < n; i ++)
for(j = 0; j < n; j ++)
if(j != i)
exp[i] += weight[i] / (weight[i] + weight[j]);
var new_rating = [];
for(i = 0; i < n; i ++)
new_rating.push(rating[i] + Math.ceil(K * (foot[i] - exp[i]) / (n - 1)));
for(i = n - 1; i >= 0; i --){
if(i + 1 < n && standings[i][3] != standings[i+1][3])
break;
if(new_rating[i] > rating[i])
new_rating[i] = rating[i];
}
for(i = 0; i < n; i ++)
if(new_rating[i] < 0)
new_rating[i] = 0;
return new_rating;
}
var max_rating_changes = localStorage.getItem("_MAX_RATING_CHANGES");
if(max_rating_changes == undefined){
localStorage.setItem("_MAX_RATING_CHANGES", 400);
max_rating_changes = 400;
}
else
max_rating_changes = Number(max_rating_changes);
function getUsernameTable(){
var users = {};
var n = standings.length;
for(var i = 0; i < n; i ++)
users[standings[i][2][0]] = [i, standings[i][2][1]];
return users;
}
var predicted_rating_changes = calcPredictedRatingChanges(max_rating_changes);
var username_table = getUsernameTable();
var rating_changes_colors = ["rgb(0, 204, 0)", "rgb(102, 102, 102)", "rgb(204, 0, 0)"];
function changeMaxRatingChanges(){
var str = prompt("Insert max rating changes, ranged [0, 1000]");
var result = Number(str);
if(typeof(result) != "number" || isNaN(result) || result < 0 || result > 1000)
alert("Invalid input!");
else{
var st = $("#standings > .table-responsive > table");
st.find("thead > tr > th:last-child").remove();
st.find("tbody > tr > td:last-child").remove();
st.find("thead > tr > th:last-child").remove();
st.find("tbody > tr > td:last-child").remove();
max_rating_changes = Math.floor(result);
localStorage.setItem("_MAX_RATING_CHANGES", max_rating_changes);
predicted_rating_changes = calcPredictedRatingChanges(max_rating_changes);
displayRatingChanges();
}
}
function formatAsUsername(str){
var ret = "";
for(var i=0; i<str.length; i++)
if(str[i].charCodeAt() > 32 && str[i].charCodeAt() < 128)
ret += str[i];
return ret;
}
function displayRatingChanges(){
var st = $("#standings > .table-responsive > table");
if(max_rating_changes == 0)
st.find("thead > tr").append(`<th style="width: 6em"><a class="changeMaxRatingChanges" style="cursor: pointer">Unrated</a></th>`)
else
st.find("thead > tr").append(`<th style="width: 6em"><a class="changeMaxRatingChanges" style="cursor: pointer">Delta<br/>(max. = ${max_rating_changes})</a></th>`);
st.find("thead > tr").append(`<th style="width: 6em"><a>New rating</a></th>`);
st.find("tbody > tr").each(function(){
var username = $(this).children().eq(1).find("a").text();
username = formatAsUsername(username);
var rating_changes = predicted_rating_changes[username_table[username][0]] - username_table[username][1];
var color = "", content = "";
if(rating_changes > 0)
color = rating_changes_colors[0], content = '+' + rating_changes;
else if(rating_changes == 0)
color = rating_changes_colors[1], content = '' + rating_changes;
else
color = rating_changes_colors[2], content = '' + rating_changes;
$(this).append(`<td><div><span style='color: ${color}'>${content}</span></div></td>`);
$(this).append(`<td><div><span><span style='color: ${getColOfRating(username_table[username][1])}'>${username_table[username][1]}</span> → <span style='color: ${getColOfRating(predicted_rating_changes[username_table[username][0]])}'>${predicted_rating_changes[username_table[username][0]]}</span></span></div></td>`);
})
$(".changeMaxRatingChanges").click(function(){
changeMaxRatingChanges();
})
}
$.fn.long_table = function(data, cur_page, header_row, get_row_str, config) {
return this.each(function() {
var table_div = this;
$(table_div).html('');
var page_len = config.page_len != undefined ? config.page_len : 10;
if (!config.echo_full) {
var n_rows = data.length;
var n_pages = Math.max(Math.ceil(n_rows / page_len), 1);
if (cur_page == undefined) {
cur_page = 1;
}
if (cur_page < 1) {
cur_page = 1;
} else if (cur_page > n_pages) {
cur_page = n_pages;
}
var cur_start = (cur_page - 1) * page_len;
} else {
var n_rows = data.length;
var n_pages = 1;
cur_page = 1;
var cur_start = (cur_page - 1) * page_len;
}
var div_classes = config.div_classes != undefined ? config.div_classes : ['table-responsive'];
var table_classes = config.table_classes != undefined ? config.table_classes : ['table', 'table-bordered', 'table-hover', 'table-striped', 'table-text-center'];
var now_cnt = 0;
var tbody = $('<tbody />')
for (var i = 0; i < page_len && cur_start + i < n_rows; i++) {
now_cnt++;
if (config.get_row_index) {
tbody.append(get_row_str(data[cur_start + i], cur_start + i));
} else {
tbody.append(get_row_str(data[cur_start + i]));
}
}
if (now_cnt == 0) {
tbody.append('<tr><td colspan="233">无</td></tr>');
}
$(table_div).append(
$('<div class="' + div_classes.join(' ') + '" />').append(
$('<table class="' + table_classes.join(' ') + '" />').append(
$('<thead>' + header_row + '</thead>')
).append(
tbody
)
)
);
if (config.print_after_table != undefined) {
$(table_div).append(config.print_after_table());
}
var get_page_li = function(p, h) {
if (p == -1) {
return $('<li></li>').addClass('disabled').append($('<a></a>').append(h));
}
var li = $('<li></li>');
if (p == cur_page) {
li.addClass('active');
}
li.append(
$('<a></a>').attr('href', '#' + table_div.id).append(h).click(function(e) {
if (config.prevent_focus_on_click) {
e.preventDefault();
}
$(table_div).long_table(data, p, header_row, get_row_str, config);
})
);
return li;
};
if (n_pages > 1) {
var pagination = $('<ul class="pagination top-buffer-no bot-buffer-sm"></ul>');
if (cur_page > 1) {
pagination.append(get_page_li(cur_page - 1, '<span class="glyphicon glyphicon glyphicon-backward"></span>'));
} else {
pagination.append(get_page_li(-1, '<span class="glyphicon glyphicon glyphicon-backward"></span>'));
}
var max_extend = config.max_extend != undefined ? config.max_extend : 5;
for (var i = Math.max(cur_page - max_extend, 1); i <= Math.min(cur_page + max_extend, n_pages); i++) {
pagination.append(get_page_li(i, i.toString()));
}
if (cur_page < n_pages) {
pagination.append(get_page_li(cur_page + 1, '<span class="glyphicon glyphicon glyphicon-forward"></span>'));
} else {
pagination.append(get_page_li(-1, '<span class="glyphicon glyphicon glyphicon-forward"></span>'));
}
$(table_div).append($('<div class="text-center"></div>').append(pagination));
}
displayRatingChanges();
});
};
displayRatingChanges();
})();