UOJ Predictor

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

目前為 2021-10-28 提交的版本,檢視 最新版本

  1. // ==UserScript==
  2. // @name UOJ Predictor
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.2
  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. // ==/UserScript==
  13.  
  14. (function() {
  15. 'use strict';
  16. // transformed from https://github.com/vfleaking/uoj/blob/9f1302c774f2499af0dc52d3faa7dd7404d03b13/uoj/1/app/uoj-contest-lib.php
  17. function calcPredictedRatingChanges(K = 400){
  18. var delta = 500;
  19. var n = standings.length;
  20. var rating = [];
  21. var i, j;
  22. for(i = 0; i < n; i ++)
  23. rating.push(standings[i][2][1]);
  24. var rank = [];
  25. var foot = [];
  26. for(i = 0; i < n; ){
  27. j = i;
  28. while(j + 1 < n && standings[j+1][3] == standings[j][3])
  29. ++ j;
  30. var our_rk = 0.5 * ((i+1) + (j+1));
  31. while(i <= j){
  32. rank.push(our_rk);
  33. foot.push(n - rank[i]);
  34. i ++;
  35. }
  36. }
  37. var weight = [];
  38. for(i = 0; i < n; i ++)
  39. weight.push(Math.pow(7, rating[i] / delta));
  40. var exp = [];
  41. for(i = 0; i < n; i ++)
  42. exp.push(0);
  43. for(i = 0; i < n; i ++)
  44. for(j = 0; j < n; j ++)
  45. if(j != i)
  46. exp[i] += weight[i] / (weight[i] + weight[j]);
  47. var new_rating = [];
  48. for(i = 0; i < n; i ++)
  49. new_rating.push(rating[i] + Math.ceil(K * (foot[i] - exp[i]) / (n - 1)));
  50. for(i = n - 1; i >= 0; i --){
  51. if(i + 1 < n && standings[i][3] != standings[i+1][3])
  52. break;
  53. if(new_rating[i] > rating[i])
  54. new_rating[i] = rating[i];
  55. }
  56. for(i = 0; i < n; i ++)
  57. if(new_rating[i] < 0)
  58. new_rating[i] = 0;
  59. return new_rating;
  60. }
  61. function getUsernameTable(){
  62. var users = {};
  63. var n = standings.length;
  64. for(var i = 0; i < n; i ++)
  65. users[standings[i][2][0]] = [i, standings[i][2][1]];
  66. return users;
  67. }
  68. var predicted_rating_changes = calcPredictedRatingChanges();
  69. var username_table = getUsernameTable();
  70. var rating_changes_colors = ["rgb(0, 204, 0)", "rgb(102, 102, 102)", "rgb(204, 0, 0)"];
  71. function displayRatingChanges(){
  72. var st = $("#standings > .table-responsive > table");
  73. st.find("thead > tr").append(`<th style="width: 8em">Delta (max. = 400)</th>`)
  74. st.find("tbody > tr").each(function(){
  75. var username = $(this).children().eq(1).find("a").text();
  76. var rating_changes = predicted_rating_changes[username_table[username][0]] - username_table[username][1];
  77. var color = "", content = "";
  78. if(rating_changes > 0)
  79. color = rating_changes_colors[0], content = '+' + rating_changes;
  80. else if(rating_changes == 0)
  81. color = rating_changes_colors[1], content = '' + rating_changes;
  82. else
  83. color = rating_changes_colors[2], content = '' + rating_changes;
  84. $(this).append(`<td><div><span style='color: ${color}'>${content}</span></div></td>`)
  85. })
  86. }
  87. $.fn.long_table = function(data, cur_page, header_row, get_row_str, config) {
  88. return this.each(function() {
  89. var table_div = this;
  90.  
  91. $(table_div).html('');
  92.  
  93. var page_len = config.page_len != undefined ? config.page_len : 10;
  94.  
  95. if (!config.echo_full) {
  96. var n_rows = data.length;
  97. var n_pages = Math.max(Math.ceil(n_rows / page_len), 1);
  98. if (cur_page == undefined) {
  99. cur_page = 1;
  100. }
  101. if (cur_page < 1) {
  102. cur_page = 1;
  103. } else if (cur_page > n_pages) {
  104. cur_page = n_pages;
  105. }
  106. var cur_start = (cur_page - 1) * page_len;
  107. } else {
  108. var n_rows = data.length;
  109. var n_pages = 1;
  110. cur_page = 1;
  111. var cur_start = (cur_page - 1) * page_len;
  112. }
  113.  
  114. var div_classes = config.div_classes != undefined ? config.div_classes : ['table-responsive'];
  115. var table_classes = config.table_classes != undefined ? config.table_classes : ['table', 'table-bordered', 'table-hover', 'table-striped', 'table-text-center'];
  116.  
  117. var now_cnt = 0;
  118. var tbody = $('<tbody />')
  119. for (var i = 0; i < page_len && cur_start + i < n_rows; i++) {
  120. now_cnt++;
  121. if (config.get_row_index) {
  122. tbody.append(get_row_str(data[cur_start + i], cur_start + i));
  123. } else {
  124. tbody.append(get_row_str(data[cur_start + i]));
  125. }
  126. }
  127. if (now_cnt == 0) {
  128. tbody.append('<tr><td colspan="233">无</td></tr>');
  129. }
  130.  
  131. $(table_div).append(
  132. $('<div class="' + div_classes.join(' ') + '" />').append(
  133. $('<table class="' + table_classes.join(' ') + '" />').append(
  134. $('<thead>' + header_row + '</thead>')
  135. ).append(
  136. tbody
  137. )
  138. )
  139. );
  140.  
  141. if (config.print_after_table != undefined) {
  142. $(table_div).append(config.print_after_table());
  143. }
  144.  
  145. var get_page_li = function(p, h) {
  146. if (p == -1) {
  147. return $('<li></li>').addClass('disabled').append($('<a></a>').append(h));
  148. }
  149.  
  150. var li = $('<li></li>');
  151. if (p == cur_page) {
  152. li.addClass('active');
  153. }
  154. li.append(
  155. $('<a></a>').attr('href', '#' + table_div.id).append(h).click(function(e) {
  156. if (config.prevent_focus_on_click) {
  157. e.preventDefault();
  158. }
  159. $(table_div).long_table(data, p, header_row, get_row_str, config);
  160. })
  161. );
  162. return li;
  163. };
  164.  
  165. if (n_pages > 1) {
  166. var pagination = $('<ul class="pagination top-buffer-no bot-buffer-sm"></ul>');
  167. if (cur_page > 1) {
  168. pagination.append(get_page_li(cur_page - 1, '<span class="glyphicon glyphicon glyphicon-backward"></span>'));
  169. } else {
  170. pagination.append(get_page_li(-1, '<span class="glyphicon glyphicon glyphicon-backward"></span>'));
  171. }
  172. var max_extend = config.max_extend != undefined ? config.max_extend : 5;
  173. for (var i = Math.max(cur_page - max_extend, 1); i <= Math.min(cur_page + max_extend, n_pages); i++) {
  174. pagination.append(get_page_li(i, i.toString()));
  175. }
  176. if (cur_page < n_pages) {
  177. pagination.append(get_page_li(cur_page + 1, '<span class="glyphicon glyphicon glyphicon-forward"></span>'));
  178. } else {
  179. pagination.append(get_page_li(-1, '<span class="glyphicon glyphicon glyphicon-forward"></span>'));
  180. }
  181. $(table_div).append($('<div class="text-center"></div>').append(pagination));
  182. }
  183. displayRatingChanges();
  184. });
  185. };
  186. displayRatingChanges();
  187. })();