UOJ Predictor

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

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

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