UOJ Predictor

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

  1. // ==UserScript==
  2. // @name UOJ Predictor
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.6
  5. // @description Plugin to calculate predicted rating changes of UOJ-like Online Judges.
  6. // @author tiger2005
  7. // @match *://zhengruioi.com/contest/*/standings*
  8. // @match *://uoj.ac/contest/*/standings*
  9. // @match *://www.zhengruioi.com/contest/*/standings*
  10. // @match *://www.uoj.ac/contest/*/standings*
  11. // @grant none
  12. // @license MIT
  13. // ==/UserScript==
  14.  
  15. (function() {
  16. 'use strict';
  17. // transformed from https://github.com/vfleaking/uoj/blob/9f1302c774f2499af0dc52d3faa7dd7404d03b13/uoj/1/app/uoj-contest-lib.php
  18. function getColOfRating(rating) {
  19. if (rating < 1500) {
  20. var H = 300 - (1500 - 850) * 300 / 1650, S = 30 + (1500 - 850) * 70 / 1650, V = 50 + (1500 - 850) * 50 / 1650;
  21. if (rating < 300) rating = 300;
  22. var k = (rating - 300) / 1200;
  23. return ColorConverter.toStr(ColorConverter.toRGB(new HSV(H + (300 - H) * (1 - k), 30 + (S - 30) * k, 50 + (V - 50) * k)));
  24. }
  25. if (rating > 2500) {
  26. rating = 2500;
  27. }
  28. return ColorConverter.toStr(ColorConverter.toRGB(new HSV(300 - (rating - 850) * 300 / 1650, 30 + (rating - 850) * 70 / 1650, 50 + (rating - 850) * 50 / 1650)));
  29. }
  30. function calcPredictedRatingChanges(K = 400){
  31. var delta = 500;
  32. var n = standings.length;
  33. var rating = [];
  34. var i, j;
  35. for(i = 0; i < n; i ++)
  36. rating.push(standings[i][2][1]);
  37. var rank = [];
  38. var foot = [];
  39. for(i = 0; i < n; ){
  40. j = i;
  41. while(j + 1 < n && standings[j+1][3] == standings[j][3])
  42. ++ j;
  43. var our_rk = 0.5 * ((i+1) + (j+1));
  44. while(i <= j){
  45. rank.push(our_rk);
  46. foot.push(n - rank[i]);
  47. i ++;
  48. }
  49. }
  50. var weight = [];
  51. for(i = 0; i < n; i ++)
  52. weight.push(Math.pow(7, rating[i] / delta));
  53. var exp = [];
  54. for(i = 0; i < n; i ++)
  55. exp.push(0);
  56. for(i = 0; i < n; i ++)
  57. for(j = 0; j < n; j ++)
  58. if(j != i)
  59. exp[i] += weight[i] / (weight[i] + weight[j]);
  60. var new_rating = [];
  61. for(i = 0; i < n; i ++)
  62. new_rating.push(rating[i] + Math.ceil(K * (foot[i] - exp[i]) / (n - 1)));
  63. for(i = n - 1; i >= 0; i --){
  64. if(i + 1 < n && standings[i][3] != standings[i+1][3])
  65. break;
  66. if(new_rating[i] > rating[i])
  67. new_rating[i] = rating[i];
  68. }
  69. for(i = 0; i < n; i ++)
  70. if(new_rating[i] < 0)
  71. new_rating[i] = 0;
  72. return new_rating;
  73. }
  74. var max_rating_changes = localStorage.getItem("_MAX_RATING_CHANGES");
  75. if(max_rating_changes == undefined){
  76. localStorage.setItem("_MAX_RATING_CHANGES", 400);
  77. max_rating_changes = 400;
  78. }
  79. else
  80. max_rating_changes = Number(max_rating_changes);
  81. function getUsernameTable(){
  82. var users = {};
  83. var n = standings.length;
  84. for(var i = 0; i < n; i ++)
  85. users[standings[i][2][0]] = [i, standings[i][2][1]];
  86. return users;
  87. }
  88. var predicted_rating_changes = calcPredictedRatingChanges(max_rating_changes);
  89. var username_table = getUsernameTable();
  90. var rating_changes_colors = ["rgb(0, 204, 0)", "rgb(102, 102, 102)", "rgb(204, 0, 0)"];
  91. function changeMaxRatingChanges(){
  92. var str = prompt("Insert max rating changes, ranged [0, 1000]");
  93. var result = Number(str);
  94. if(typeof(result) != "number" || isNaN(result) || result < 0 || result > 1000)
  95. alert("Invalid input!");
  96. else{
  97. var st = $("#standings > .table-responsive > table");
  98. st.find("thead > tr > th:last-child").remove();
  99. st.find("tbody > tr > td:last-child").remove();
  100. st.find("thead > tr > th:last-child").remove();
  101. st.find("tbody > tr > td:last-child").remove();
  102. max_rating_changes = Math.floor(result);
  103. localStorage.setItem("_MAX_RATING_CHANGES", max_rating_changes);
  104. predicted_rating_changes = calcPredictedRatingChanges(max_rating_changes);
  105. displayRatingChanges();
  106. }
  107. }
  108. function formatAsUsername(str){
  109. var ret = "";
  110. for(var i=0; i<str.length; i++)
  111. if(str[i].charCodeAt() > 32 && str[i].charCodeAt() < 128)
  112. ret += str[i];
  113. return ret;
  114. }
  115. function displayRatingChanges(){
  116. var st = $("#standings > .table-responsive > table");
  117. if(max_rating_changes == 0)
  118. st.find("thead > tr").append(`<th style="width: 6em"><a class="changeMaxRatingChanges" style="cursor: pointer">Unrated</a></th>`)
  119. else
  120. st.find("thead > tr").append(`<th style="width: 6em"><a class="changeMaxRatingChanges" style="cursor: pointer">Delta<br/>(max. = ${max_rating_changes})</a></th>`);
  121. st.find("thead > tr").append(`<th style="width: 6em"><a>New rating</a></th>`);
  122. st.find("tbody > tr").each(function(){
  123. var username = $(this).children().eq(1).find("a").text();
  124. username = formatAsUsername(username);
  125. var rating_changes = predicted_rating_changes[username_table[username][0]] - username_table[username][1];
  126. var color = "", content = "";
  127. if(rating_changes > 0)
  128. color = rating_changes_colors[0], content = '+' + rating_changes;
  129. else if(rating_changes == 0)
  130. color = rating_changes_colors[1], content = '' + rating_changes;
  131. else
  132. color = rating_changes_colors[2], content = '' + rating_changes;
  133. $(this).append(`<td><div><span style='color: ${color}'>${content}</span></div></td>`);
  134. $(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>`);
  135. })
  136. $(".changeMaxRatingChanges").click(function(){
  137. changeMaxRatingChanges();
  138. })
  139. }
  140. $.fn.long_table = function(data, cur_page, header_row, get_row_str, config) {
  141. return this.each(function() {
  142. var table_div = this;
  143.  
  144. $(table_div).html('');
  145.  
  146. var page_len = config.page_len != undefined ? config.page_len : 10;
  147.  
  148. if (!config.echo_full) {
  149. var n_rows = data.length;
  150. var n_pages = Math.max(Math.ceil(n_rows / page_len), 1);
  151. if (cur_page == undefined) {
  152. cur_page = 1;
  153. }
  154. if (cur_page < 1) {
  155. cur_page = 1;
  156. } else if (cur_page > n_pages) {
  157. cur_page = n_pages;
  158. }
  159. var cur_start = (cur_page - 1) * page_len;
  160. } else {
  161. var n_rows = data.length;
  162. var n_pages = 1;
  163. cur_page = 1;
  164. var cur_start = (cur_page - 1) * page_len;
  165. }
  166.  
  167. var div_classes = config.div_classes != undefined ? config.div_classes : ['table-responsive'];
  168. var table_classes = config.table_classes != undefined ? config.table_classes : ['table', 'table-bordered', 'table-hover', 'table-striped', 'table-text-center'];
  169.  
  170. var now_cnt = 0;
  171. var tbody = $('<tbody />')
  172. for (var i = 0; i < page_len && cur_start + i < n_rows; i++) {
  173. now_cnt++;
  174. if (config.get_row_index) {
  175. tbody.append(get_row_str(data[cur_start + i], cur_start + i));
  176. } else {
  177. tbody.append(get_row_str(data[cur_start + i]));
  178. }
  179. }
  180. if (now_cnt == 0) {
  181. tbody.append('<tr><td colspan="233">无</td></tr>');
  182. }
  183.  
  184. $(table_div).append(
  185. $('<div class="' + div_classes.join(' ') + '" />').append(
  186. $('<table class="' + table_classes.join(' ') + '" />').append(
  187. $('<thead>' + header_row + '</thead>')
  188. ).append(
  189. tbody
  190. )
  191. )
  192. );
  193.  
  194. if (config.print_after_table != undefined) {
  195. $(table_div).append(config.print_after_table());
  196. }
  197.  
  198. var get_page_li = function(p, h) {
  199. if (p == -1) {
  200. return $('<li></li>').addClass('disabled').append($('<a></a>').append(h));
  201. }
  202.  
  203. var li = $('<li></li>');
  204. if (p == cur_page) {
  205. li.addClass('active');
  206. }
  207. li.append(
  208. $('<a></a>').attr('href', '#' + table_div.id).append(h).click(function(e) {
  209. if (config.prevent_focus_on_click) {
  210. e.preventDefault();
  211. }
  212. $(table_div).long_table(data, p, header_row, get_row_str, config);
  213. })
  214. );
  215. return li;
  216. };
  217.  
  218. if (n_pages > 1) {
  219. var pagination = $('<ul class="pagination top-buffer-no bot-buffer-sm"></ul>');
  220. if (cur_page > 1) {
  221. pagination.append(get_page_li(cur_page - 1, '<span class="glyphicon glyphicon glyphicon-backward"></span>'));
  222. } else {
  223. pagination.append(get_page_li(-1, '<span class="glyphicon glyphicon glyphicon-backward"></span>'));
  224. }
  225. var max_extend = config.max_extend != undefined ? config.max_extend : 5;
  226. for (var i = Math.max(cur_page - max_extend, 1); i <= Math.min(cur_page + max_extend, n_pages); i++) {
  227. pagination.append(get_page_li(i, i.toString()));
  228. }
  229. if (cur_page < n_pages) {
  230. pagination.append(get_page_li(cur_page + 1, '<span class="glyphicon glyphicon glyphicon-forward"></span>'));
  231. } else {
  232. pagination.append(get_page_li(-1, '<span class="glyphicon glyphicon glyphicon-forward"></span>'));
  233. }
  234. $(table_div).append($('<div class="text-center"></div>').append(pagination));
  235. }
  236. displayRatingChanges();
  237. });
  238. };
  239. displayRatingChanges();
  240. })();