您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
順位表のjsonを集計し、上部にテーブルを追加します。
您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
- // ==UserScript==
- // @name AtCoderStandingsAnalysis
- // @namespace https://github.com/RTnF/AtCoderStandingsAnalysis
- // @version 0.2.1
- // @description 順位表のjsonを集計し、上部にテーブルを追加します。
- // @author RTnF
- // @match https://atcoder.jp/*standings*
- // @exclude https://atcoder.jp/*standings/json
- // @grant none
- // @license CC0-1.0
- // ==/UserScript==
- // ソート済み配列のうちval未満が何個あるか求める
- function countLower(arr, val) {
- var lo = -1;
- var hi = arr.length;
- while (hi - lo > 1) {
- var mid = Math.floor((hi + lo) / 2);
- if (arr[mid] < val) {
- lo = mid;
- } else {
- hi = mid;
- }
- }
- return hi;
- }
- // 換算: Rating -> innerRating
- function innerRating(rate, comp) {
- var ret = rate;
- if (rate <= 0) {
- throw "rate <= 0";
- }
- if (ret < 400) {
- ret = 400 * (1 - Math.log(400 / rate));
- }
- ret += 1200 * (Math.sqrt(1 - Math.pow(0.81, comp)) / (1 - Math.pow(0.9, comp)) - 1) / (Math.sqrt(19) - 1);
- return ret;
- }
- $(function () {
- 'use strict';
- const cols = ["#808080", "#804000", "#008000", "#00C0C0", "#0000FF", "#C0C000", "#FF8000", "#FF0000"];
- const threshold = [-10000, 400, 800, 1200, 1600, 2000, 2400, 2800];
- const canvasWidth = 250;
- const canvasHeight = 25;
- // 表を先頭に追加
- $('#vue-standings').prepend(`
- <div>
- <table id="acsa-table" class="table table-bordered table-hover th-center td-center td-middle">
- <thead>
- </thead>
- <tbody>
- </tbody>
- </table>
- </div>
- `);
- // 表の更新
- vueStandings.$watch('standings', function (newVal, oldVal) {
- if (!newVal) {
- return;
- }
- var data;
- var task = newVal.TaskInfo;
- if (vueStandings.filtered) {
- data = vueStandings.filteredStandings;
- } else {
- data = newVal.StandingsData;
- }
- $('#acsa-table > tbody').empty();
- $('#acsa-table > tbody').append(`
- <tr style="font-weight: bold;">
- <td>問題</td>
- <td>得点</td>
- <td>人数</td>
- <td>正解率</td>
- <td>平均ペナ</td>
- <td>ペナ率</td>
- <td>内部レート</td>
- </tr>
- `);
- for (let i = 0; i < task.length; i++) {
- var isTried = vueStandings.tries[i] > 0;
- $('#acsa-table > tbody').append(`
- <tr>
- <td style="padding: 4px;">` + task[i].Assignment + `</td>
- <td style="padding: 4px;">-</td>
- <td style="padding: 4px;">` + vueStandings.ac[i] + ` / ` + vueStandings.tries[i] + `</td>
- <td style="padding: 4px;">` + (isTried ? (vueStandings.ac[i] / vueStandings.tries[i] * 100).toFixed(2) + "%" : "-") + `</td>
- <td style="padding: 4px;">-</td>
- <td style="padding: 4px;">-</td>
- <td style="padding: 4px; width: ` + canvasWidth + `px;"><canvas style="vertical-align: middle;" width="` + canvasWidth + `px" height="` + canvasHeight +`px"></canvas></td>
- </tr>
- `);
- if (!isTried) {
- continue;
- }
- // トップの得点を満点とみなす
- var maxScore = -1;
- var myScore = -1;
- // 不正解数 / 提出者数
- var avePenalty = 0;
- // ペナルティ >= 1 の人数 / 提出者数
- var ratioPenalty = 0;
- var rates = [];
- for (let j = 0; j < data.length; j++) {
- // 参加登録していない
- if (!data[j].TaskResults) {
- continue;
- }
- // アカウント削除
- if (data[j].UserIsDeleted) {
- continue;
- }
- var result = data[j].TaskResults[task[i].TaskScreenName];
- // 未提出のときresult === undefined
- if (result) {
- if (data[j].UserScreenName === vueStandings.userScreenName) {
- myScore = result.Score;
- }
- // 赤い括弧内の数字
- var penalty = result.Score === 0 ? result.Failure : result.Penalty;
- avePenalty += penalty;
- if (penalty > 0) {
- ratioPenalty++;
- }
- if (maxScore < result.Score) {
- maxScore = result.Score;
- }
- }
- }
- // 正解者の内部レート配列を作成する
- // 初出場はカウントしない
- if (maxScore > 0) {
- for (let j = 0; j < data.length; j++) {
- if (data[j].Competitions > 0
- && data[j].TaskResults[task[i].TaskScreenName]
- && data[j].TaskResults[task[i].TaskScreenName].Score === maxScore) {
- rates.push(innerRating(Math.max(data[j].Rating, 1), data[j].Competitions));
- }
- }
- rates.sort(function (a, b) { return a - b; });
- }
- myScore /= 100;
- maxScore /= 100;
- avePenalty /= vueStandings.tries[i];
- ratioPenalty /= vueStandings.tries[i];
- ratioPenalty *= 100;
- $('#acsa-table > tbody > tr:eq(' + (i+1) + ') > td:eq(1)').text(myScore >= 0 ? myScore.toFixed() : "-");
- $('#acsa-table > tbody > tr:eq(' + (i+1) + ') > td:eq(4)').text(avePenalty.toFixed(2));
- $('#acsa-table > tbody > tr:eq(' + (i+1) + ') > td:eq(5)').text(ratioPenalty.toFixed(2) + "%");
- if (maxScore > 0) {
- var canvas = $('#acsa-table > tbody > tr:eq(' + (i+1) + ') > td:eq(6) > canvas')[0];
- if (canvas.getContext) {
- var context = canvas.getContext('2d');
- for (let k = 0; k < 8; k++) {
- context.fillStyle = cols[k];
- // 色の境界から右端までの矩形描画
- var x = Math.round(countLower(rates, threshold[k]) / rates.length * canvasWidth);
- context.fillRect(x, 0, canvasWidth - x, canvasHeight);
- }
- }
- }
- }
- }, {deep: true, immediate: true})
- });