UOJ Predictor

Plugin to calculate predicted rating changes of UOJ-like Online Judges.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

// ==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();
})();