Greasy Fork is available in English.

AtCoderStandingsAnalysis

順位表のjsonを集計し、上部にテーブルを追加します。

La data de 22-03-2020. Vezi ultima versiune.

  1. // ==UserScript==
  2. // @name AtCoderStandingsAnalysis
  3. // @namespace https://github.com/RTnF/AtCoderStandingsAnalysis
  4. // @version 0.1.0
  5. // @description 順位表のjsonを集計し、上部にテーブルを追加します。
  6. // @author RTnF
  7. // @match https://atcoder.jp/*standings*
  8. // @exclude https://atcoder.jp/*standings/json
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. // ソート済み配列のうちval未満が何個あるか求める
  13. function countLower(arr, val) {
  14. var lo = -1;
  15. var hi = arr.length;
  16. while (hi - lo > 1) {
  17. var mid = Math.floor((hi + lo) / 2);
  18. if (arr[mid] < val) {
  19. lo = mid;
  20. } else {
  21. hi = mid;
  22. }
  23. }
  24. return hi;
  25. }
  26.  
  27. // 換算: Rating -> innerRating
  28. function innerRating(rate, comp) {
  29. var ret = rate;
  30. if (ret < 400) {
  31. ret = 400 * (1 - Math.log(400 / rate));
  32. }
  33. ret += 1200 * (Math.sqrt(1 - Math.pow(0.81, comp)) / (1 - Math.pow(0.9, comp)) - 1) / (Math.sqrt(19) - 1);
  34. return ret;
  35. }
  36.  
  37. $(function () {
  38. 'use strict';
  39.  
  40. // 表のヘッダーを先頭に追加する
  41. $('#vue-standings').prepend(`
  42. <div>
  43. <table id="acsa-table" class="table table-bordered table-hover th-center td-center td-middle">
  44. <thead>
  45. <tr>
  46. <th>問題</th>
  47. <th>得点</th>
  48. <th>人数</th>
  49. <th>正解率</th>
  50. <th>平均ペナ</th>
  51. <th>ペナ率</th>
  52. <th>レート</th>
  53. </tr>
  54. </thead>
  55. <tbody>
  56. </tbody>
  57. </table>
  58. </div>
  59. `);
  60.  
  61. // 表の更新
  62. setInterval(function () {
  63. var data;
  64. var task = vueStandings.standings.TaskInfo;
  65. if (vueStandings.filtered) {
  66. data = vueStandings.filteredStandings;
  67. } else {
  68. data = vueStandings.standings.StandingsData;
  69. }
  70. const cols = ["#808080", "#804000", "#008000", "#00C0C0", "#0000FF", "#C0C000", "#FF8000", "#FF0000"];
  71. const threshold = [-10000, 400, 800, 1200, 1600, 2000, 2400, 2800];
  72. const canvasWidth = 250;
  73. const canvasHeight = 20;
  74.  
  75. $('#acsa-table > tbody').empty();
  76. for (let i = 0; i < task.length; i++) {
  77. var isTried = vueStandings.tries[i] > 0;
  78. $('#acsa-table > tbody').append(`
  79. <tr>
  80. <td>` + task[i].Assignment + `</td>
  81. <td>-</td>
  82. <td>` + vueStandings.ac[i] + ` / ` + vueStandings.tries[i] + `</td>
  83. <td>` + (isTried ? (vueStandings.ac[i] / vueStandings.tries[i] * 100).toFixed(2) + "%" : "-") + `</td>
  84. <td>-</td>
  85. <td>-</td>
  86. <td><canvas width="` + canvasWidth + `px" height="` + canvasHeight +`px"></canvas></td>
  87. </tr>
  88. `);
  89. if (!isTried) {
  90. continue;
  91. }
  92.  
  93. // トップの得点を満点とみなす
  94. var maxScore = -1;
  95. var myScore = -1;
  96. // 不正解数 / 提出者数
  97. var avePenalty = 0;
  98. // ペナルティ >= 1 の人数 / 提出者数
  99. var ratioPenalty = 0;
  100. var rates = [];
  101. for (let j = 0; j < data.length; j++) {
  102. // 参加登録していない
  103. if (!data[j].TaskResults) {
  104. continue;
  105. }
  106. var result = data[j].TaskResults[task[i].TaskScreenName];
  107. // 未提出のときresult === undefined
  108. if (result) {
  109. if (data[j].UserScreenName === vueStandings.userScreenName) {
  110. myScore = result.Score;
  111. }
  112. // 赤い括弧内の数字
  113. var penalty = result.Score === 0 ? result.Failure : result.Penalty;
  114. avePenalty += penalty;
  115. if (penalty > 0) {
  116. ratioPenalty++;
  117. }
  118. if (maxScore < result.Score) {
  119. maxScore = result.Score;
  120. }
  121. }
  122. }
  123.  
  124. // 正解者の内部レート配列を作成する
  125. for (let j = 0; j < data.length; j++) {
  126. if (data[j].Competitions > 0
  127. && data[j].TaskResults[task[i].TaskScreenName]
  128. && data[j].TaskResults[task[i].TaskScreenName].Score === maxScore) {
  129. rates.push(innerRating(data[j].Rating, data[j].Competitions));
  130. }
  131. }
  132. rates.sort(function (a, b) { return a - b; });
  133.  
  134. myScore /= 100;
  135. maxScore /= 100;
  136. avePenalty /= vueStandings.tries[i];
  137. ratioPenalty /= vueStandings.tries[i];
  138. ratioPenalty *= 100;
  139.  
  140. $('#acsa-table > tbody > tr:eq(' + i + ') > td:eq(1)').text(myScore >= 0 ? myScore.toFixed() : "-");
  141. $('#acsa-table > tbody > tr:eq(' + i + ') > td:eq(4)').text(avePenalty.toFixed(2));
  142. $('#acsa-table > tbody > tr:eq(' + i + ') > td:eq(5)').text(ratioPenalty.toFixed(2) + "%");
  143. var canvas = $('#acsa-table > tbody > tr:eq(' + i + ') > td:eq(6) > canvas')[0];
  144. if (canvas.getContext) {
  145. var context = canvas.getContext('2d');
  146. for (let k = 0; k < 8; k++) {
  147. context.fillStyle = cols[k];
  148. // 色の境界から右端までの矩形描画
  149. var x = Math.round(countLower(rates, threshold[k]) / rates.length * canvasWidth);
  150. context.fillRect(x, 0, canvasWidth - x, canvasHeight);
  151. }
  152. }
  153. }
  154. }, 5000);
  155. });