AtCoder Difficulty Display

AtCoder Problemsの難易度を表示します。

Mint 2020.08.02.. Lásd a legutóbbi verzió

  1. // ==UserScript==
  2. // @name AtCoder Difficulty Display
  3. // @namespace https://github.com/hotarunx
  4. // @homepage https://github.com/hotarunx/AtCoderDifficultyDisplay
  5. // @supportURL https://github.com/hotarunx/AtCoderDifficultyDisplay/issues
  6. // @version 0.6.1
  7. // @description AtCoder Problemsの難易度を表示します。
  8. // @description:en display a difficulty of AtCoder Problems.
  9. // @author hotarunx
  10. // @match https://atcoder.jp/contests/*/tasks/*
  11. // @grant none
  12. // @connect https://kenkoooo.com/atcoder/resources/*
  13. // @connect https://kenkoooo.com/atcoder/atcoder-api/*
  14. // @license MIT
  15. //
  16. // Copyright(c) 2020 hotarunx
  17. // This software is released under the MIT License, see LICENSE or https://github.com/hotarunx/AtCoderMyExtensions/blob/master/LICENSE.
  18. //
  19. // ==/UserScript==
  20.  
  21. (function () {
  22.  
  23. // -------------------------------------------------------------------------
  24. // 設定
  25. // 次の変数の値を書き換えることで各数値を表示するかどうかを変更できます
  26.  
  27. // 難易度を表示するかどうか
  28. const displayDifficulty = true;
  29.  
  30. // 提出状況を表示するかどうか
  31. const displaySubmissionStatus = true;
  32.  
  33. // true: 表示する
  34. // false: 表示しない
  35. // -------------------------------------------------------------------------
  36.  
  37. // 問題のコンテストが開催中ならば全ての処理をスキップする。
  38. if (isContestRunning()) return;
  39.  
  40. // URL of Estimated difficulties of the problems
  41. const SUBMISSION_API = "https://kenkoooo.com/atcoder/atcoder-api/results?user=" + userScreenName;
  42. const SUBMISSIONS_DATASET = "https://kenkoooo.com/atcoder/resources/problem-models.json";
  43.  
  44. if (displayDifficulty)
  45. fetch(SUBMISSIONS_DATASET)
  46. .then((response) => response.json())
  47. .then((jsonData) => {
  48. addDifficultyText(jsonData);
  49. });
  50.  
  51. if (displaySubmissionStatus && userScreenName != "")
  52. fetch(SUBMISSION_API)
  53. .then((response) => response.json())
  54. .then((submissionData) => {
  55. addSubmissionStatusText(submissionData);
  56. });
  57.  
  58. })();
  59.  
  60. // 今開いている問題のコンテストが実行中かどうか判定する
  61. function isContestRunning() {
  62. // コンテスト時間を取得
  63. const start = Math.floor(Date.parse(startTime._i) / 1000);
  64. const end = Math.floor(Date.parse(endTime._i) / 1000);
  65.  
  66. // 現在時間を取得
  67. const time = Math.floor(Date.now() / 1000);
  68.  
  69. // 緩衝時間
  70. const bufferTime = 10 * 60;
  71.  
  72. // 現在時間 > コンテスト終了時間+緩衝時間かどうか判定
  73. if (time < end + bufferTime) return true;
  74. return false;
  75. }
  76.  
  77. // Webページの問題ステータス(実行時間制限とメモリ制限が書かれた部分)のHTMLオブジェクトを取得
  78. function getElementOfProblemStatus() {
  79. let element_status;
  80.  
  81. const main_container = document.getElementById('main-container');
  82. const elements_p = main_container.getElementsByTagName("p");
  83.  
  84. for (let i = 0; i < elements_p.length; i++) {
  85. const element = elements_p[i];
  86. if (element.textContent.match("メモリ制限:") || element.textContent.match("Memory Limit:")) {
  87. element_status = element;
  88. break
  89. }
  90. }
  91.  
  92. return element_status;
  93. }
  94.  
  95. // レーティングに対応する色のカラーコードを返す
  96. function colorRating(rating) {
  97. let color;
  98. if (rating < 400) color = '#808080'; // gray
  99. else if (rating < 800) color = '#804000'; // brown
  100. else if (rating < 1200) color = '#008000'; // green
  101. else if (rating < 1600) color = '#00C0C0'; // cyan
  102. else if (rating < 2000) color = '#0000FF'; // blue
  103. else if (rating < 2400) color = '#C0C000'; // yellow
  104. else if (rating < 2800) color = '#FF8000'; // orange
  105. else color = '#FF0000'; // red
  106.  
  107. return color;
  108. }
  109.  
  110. // 難易度円→◒の文章を生成する
  111. function generateDifficultyCircle(rating) {
  112.  
  113. if (rating < 3200) {
  114. // 色と円がどのぐらい満ちているかを計算
  115. const color = colorRating(rating);
  116. const percentFull = (rating % 400) / 400 * 100;
  117.  
  118. // ◒を生成
  119. return "<span style = 'display: inline-block; border-radius: 50%; border-style: solid;border-width: 1px; margin-right: 5px; height: 12px; width: 12px;border-color: " + color + "; background: linear-gradient(to top, " + color + " 0%, " + color + " " + percentFull + "%, rgba(0, 0, 0, 0) " + percentFull + "%, rgba(0, 0, 0, 0) 100%); '></span>"
  120.  
  121. }
  122. // 金銀銅は例外処理
  123. else if (rating < 3600) {
  124. return '<span style="display: inline-block; border-radius: 50%; border-style: solid;border-width: 1px; margin-right: 5px; height: 12px; width: 12px; border-color: rgb(150, 92, 44); background: linear-gradient(to right, rgb(150, 92, 44), rgb(255, 218, 189), rgb(150, 92, 44));"></span>';
  125.  
  126. } else if (rating < 4000) {
  127. return '<span style="display: inline-block; border-radius: 50%; border-style: solid;border-width: 1px; margin-right: 5px; height: 12px; width: 12px; border-color: rgb(128, 128, 128); background: linear-gradient(to right, rgb(128, 128, 128), white, rgb(128, 128, 128));"></span>';
  128.  
  129. } else {
  130. return '<span style="display: inline-block; border-radius: 50%; border-style: solid;border-width: 1px; margin-right: 5px; height: 12px; width: 12px; border-color: rgb(255, 215, 0); background: linear-gradient(to right, rgb(255, 215, 0), white, rgb(255, 215, 0));"></span>';
  131.  
  132. }
  133. }
  134.  
  135. // レーティングを0以上に補正
  136. // 参考 https://qiita.com/anqooqie/items/92005e337a0d2569bdbd#%E6%80%A7%E8%B3%AA4-%E5%88%9D%E5%BF%83%E8%80%85%E3%81%B8%E3%81%AE%E6%85%88%E6%82%B2
  137. function correctLowerRating(rating) {
  138. if (rating >= 400) return rating;
  139. do {
  140. rating = 400 / Math.exp((400 - rating) / 400);
  141. } while (rating < 0);
  142. return rating;
  143. }
  144.  
  145. // 難易度を表示する文字列を生成
  146. function generateDifficultyText(difficulty, is_experimental) {
  147. // 難易度を0にして四捨五入
  148. difficulty = correctLowerRating(difficulty);
  149. difficulty = difficulty.toFixed();
  150.  
  151. // テキストを生成
  152. let difficultyText = "Difficulty: ";
  153. if (is_experimental) difficultyText = "🧪" + difficultyText;
  154. difficultyText += difficulty;
  155. difficultyText += generateDifficultyCircle(difficulty);
  156.  
  157. // 色つけ
  158. const color = colorRating(difficulty);
  159. difficultyText = "<span style='color: " + color + ";'>" + difficultyText + "</span>";
  160.  
  161. // Problemsへのリンクを追加
  162. const atcoderProblemsUrl = "https://kenkoooo.com/atcoder/#/table/" + userScreenName;
  163. difficultyText = "<a href='" + atcoderProblemsUrl + "'>" + difficultyText + "</a>";
  164.  
  165. return " / " + difficultyText;
  166. }
  167.  
  168. function addDifficultyText(jsonData) {
  169. // URLから問題IDを取得
  170. const path = location.pathname.split("/");
  171. const id = path[path.length - 1];
  172. // 問題データを取得
  173. const problem = jsonData[id];
  174.  
  175. // 問題が存在しなければ終了
  176. if (problem == null || problem.difficulty == null) { return; }
  177.  
  178. // 難易度を表示する文字列を生成
  179. const text = generateDifficultyText(problem.difficulty, problem.is_experimental);
  180.  
  181. // 問題ステータスのHTMLオブジェクトを探してtextを追加
  182. let status = getElementOfProblemStatus();
  183. status.insertAdjacentHTML('beforeend', text);
  184. }
  185.  
  186. function addSubmissionStatusText(submissionData) {
  187. // URLから問題IDを取得
  188. const path = location.pathname.split("/");
  189. const id = path[path.length - 1];
  190.  
  191. // コンテスト時間を取得
  192. const start = Math.floor(Date.parse(startTime._i) / 1000);
  193. const end = Math.floor(Date.parse(endTime._i) / 1000);
  194.  
  195. // 4つの提出状況記録変数
  196. // コンテスト中にACした、コンテスト外にACした、コンテスト中に提出した、コンテスト外に提出した
  197. let contestAccepted = false, accepted = false, contestSubmitted = false, submitted = false;
  198.  
  199. let latestAcceptedSubmission, latestSubmission;
  200.  
  201. // この問題への提出をすべて探索して提出状況を更新する
  202. const submissions = submissionData.filter(function (item, index) { if (item.problem_id == id) return true; });
  203. submissions.sort((a, b) => a.epoch_second - b.epoch_second);
  204.  
  205. for (const item of submissions) {
  206. const time = item["epoch_second"];
  207. const isDuringContest = start <= time && time <= end;
  208. const isAccepted = item["result"] == "AC";
  209.  
  210. if (isDuringContest) {
  211. contestSubmitted = true;
  212. if (isAccepted) contestAccepted = true;
  213. } else {
  214. submitted = true;
  215. if (isAccepted) accepted = true;
  216. }
  217.  
  218. if (isAccepted) latestAcceptedSubmission = item;
  219. else latestSubmission = item;
  220. }
  221.  
  222. // 提出状況を表す文字列を生成
  223. let text;
  224. if (contestAccepted) text = "<span style='color: #5CB85C;'>★Accepted</span>";
  225. else if (accepted) text = "<span style='color: #5CB85C;'>Accepted</span>";
  226. else if (submitted) text = "<span style='color: #F0AD4E;'>Trying</span>";
  227. else if (contestSubmitted) text = "<span style='color: #F0AD4E;'>★Trying</span>";
  228. else text = "Trying";
  229.  
  230. // 最新のAC提出または提出へのリンクを追加
  231. if (submitted || contestSubmitted) {
  232. const submission = (latestAcceptedSubmission != null ? latestAcceptedSubmission : latestSubmission);
  233.  
  234. const url = "https://atcoder.jp/contests/" + submission.contest_id + "/submissions/" + submission.id;
  235.  
  236. text = "<a href='" + url + "'>" + text + "</a>";
  237. }
  238.  
  239. // 問題ステータスのHTMLオブジェクトを探してtextを追加
  240. let status = getElementOfProblemStatus();
  241. status.insertAdjacentHTML('beforeend', " / " + text);
  242. }