AtCoderColorStandings

コンテスト参加者の色(Rating)別の正解率を表示します.

// ==UserScript==
// @name         AtCoderColorStandings
// @namespace    http://tampermonkey.net/
// @version      1.51
// @description  コンテスト参加者の色(Rating)別の正解率を表示します.
// @author       Shobonvip
// @match        https://atcoder.jp/*standings*
// @exclude      https://atcoder.jp/*standings/json
// @grant        none
// @license      MIT
// ==/UserScript==

$(function () {
  // RGBからカラーコードに変換する
  function rgb2hex ( rgb ) {
    return "#" + rgb.map( function ( value ) {
      return ( "0" + value.toString( 16 ) ).slice( -2 ) ;
    }).join( "" ) ;
  }

  // 詳細か否か
  let detailmode = 0;
  // Ratedのみを集めるか否か
  let ratedmode = 0;
  // 1回以上提出を集めるか否か
  let submitmode = 1;

  // 表を先頭に追加
  $("#vue-standings").prepend(`<div><button class="btn btn-default" id="colorstanding-detail">詳細に切り替え</button><button class="btn btn-default" id="colorstanding-rated">Unratedも表示中</button><button class="btn btn-default" id="colorstanding-submit">1回以上提出を表示中</button><button class="btn btn-default" id="colorstanding-off">非表示</button><table id="accs-table" class="table table-bordered table-hover th-center td-middle"><thead></thead><tbody></tbody></table></div>`);

  // 表の更新
  function update () {
    vueStandings.$watch("standings", function (new_val, old_val) {
      if (detailmode == 1) {
        $("#colorstanding-detail").empty();
        $("#colorstanding-detail").append(`詳細表示中`);
      } else {
        $("#colorstanding-detail").empty();
        $("#colorstanding-detail").append(`詳細に切り替え`);
      }
      if (ratedmode == 1) {
        $("#colorstanding-rated").empty();
        $("#colorstanding-rated").append(`Ratedのみを表示中`);
      } else {
        $("#colorstanding-rated").empty();
        $("#colorstanding-rated").append(`Unratedも表示中`);
      }
      if (submitmode == 1) {
        $("#colorstanding-submit").empty();
        $("#colorstanding-submit").append(`1回以上提出を表示中`);
      } else {
        $("#colorstanding-submit").empty();
        $("#colorstanding-submit").append(`提出なしも表示中`);
      }
      if (!new_val) {
        return;
      }
      let task = new_val.TaskInfo;
      let data = new_val.StandingsData;

      let ratecolor = ["#000000", "#666666", "#663300", "#006600", "#009999", "#0000cc", "#cc9900", "#ff9900", "#ff3333"];
      let ratename = ["黒", "灰", "茶", "緑", "水", "青", "黄", "橙", "赤"]
      let ratelist = [1, 400, 800, 1200, 1600, 2000, 2400, 2800, 9999];

      if (detailmode == 1){
        ratelist = [1, 33, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000, 1100, 1200, 1300, 1400, 1500, 1600, 1700, 1800, 1900, 2000, 2100, 2200, 2300, 2400, 2500, 2600, 2700, 2800, 2900, 3000, 3100, 3200, 3300, 3400, 3500, 3600, 9999];
        ratename = ["0", "1", "33", "100", "200", "300", "400", "500", "600", "700", "800", "900", "1000", "1100", "1200", "1300", "1400", "1500", "1600", "1700", "1800", "1900", "2000", "2100", "2200", "2300", "2400", "2500", "2600", "2700", "2800", "2900", "3000", "3100", "3200", "3300", "3400", "3500", "金"];
        ratecolor = ["#000000", "#666666", "#666666", "#666666", "#666666", "#666666", "#663300", "#663300", "#663300", "#663300", "#006600", "#006600", "#006600", "#006600", "#009999", "#009999", "#009999", "#009999", "#0000cc", "#0000cc", "#0000cc", "#0000cc", "#cc9900", "#cc9900", "#cc9900", "#cc9900", "#ff9900", "#ff9900", "#ff9900", "#ff9900", "#ff3333", "#ff3333", "#ff3333", "#ff3333", "#990000", "#990000", "#990000", "#990000", "#461900"];
      }

      // コンテスト前とコンテスト後ではjsonの挙動が違う
      let ratingmode = 0;
      for (let cnt = 0; cnt < data.length; cnt++) {
        if (data[cnt]["TotalResult"]["Count"] >= 1) {
          if (data[cnt]["OldRating"] > 0) {
            ratingmode = 1;
            break;
          }
        }
      }

      let nowrating = "Rating";
      if (ratingmode == 1) {
        nowrating = "OldRating"
      }

      // 参加者一覧の表を作る
      let contestant = [];
      for (let i = 0; i < ratelist.length; i++) {
        contestant.push([]);
      }

      // 参加者カウント
      for (let cnt = 0; cnt < data.length; cnt++) {
        for (let color = 0; color < ratelist.length; color++){
          if (data[cnt]["IsRated"] == true || ratedmode == 0) {
            if (data[cnt]["TotalResult"]["Count"] >= 1 || submitmode == 0) {
              if (data[cnt][nowrating] < ratelist[color]) {
                contestant[color].push(cnt);
                break;
              }
            }
          }
        }
      }

       // 配列の用意
      var colordata = [];
      for (let i = 0; i < task.length; i++) {
        colordata.push([]);
        for (let j = 0; j < ratelist.length; j++) {
          colordata[i].push(0);
        }
      }
      var colornum = [];
      for (let i = 0; i < ratelist.length; i++) {
        colornum[i] = contestant[i].length;
      }

      // 色別の正解数を取得
      for (let colors = 0; colors < contestant.length; colors++) {
        for (let cnt = 0; cnt < task.length; cnt++) {
          let probid = task[cnt].TaskScreenName;
          for (let player = 0; player < contestant[colors].length; player++) {
            try {
              if (data[contestant[colors][player]]["TaskResults"][probid]["Status"] === 1) {
                colordata[cnt][colors] += 1;
              }
            } catch {
              null;
            }
          }
        }
      }

      // 表の先頭
      let t = `<tr style="font-weight: bold;"><td align="center">Rate</td><td align="center">参加者数</td>`;
      for (let i = 0; i < task.length; i++) {
        t += `<td align="center">` + task[i].Assignment + `</td>`;
      }
      t += `</tr>`;

      // 表の途中
      for (let colors = 0; colors < ratecolor.length; colors++) {
        t += `<tr><td align="center" style="padding: 2px;"><font color="` + ratecolor[colors] + `"><b>` + ratename[colors] + `</b></td><td align="right">` + colornum[colors] + `</td>`;
        for (let i = 0; i < task.length; i++) {
          // 正解率を求める
          let dat = 0;
          if (colornum[colors] != 0) {
            dat = colordata[i][colors] / colornum[colors];
          }
          // 正解率によって背景色を定める
          let targ = 0;
          let cr = 0;
          let cg = 0;
          let cb = 0;
          if (dat >= 0.9) {
            targ = (1-dat)*1000;
            cr = Math.floor(255-targ);
            cg = 255;
            cb = Math.floor(155+targ);
          } else if (dat >= 0.75) {
            targ = (0.9-dat)/1.5*1000;
            cr = 155;
            cg = 255;
            cb = Math.floor(255-targ);
          } else if (dat >= 0.5) {
            targ = (0.75-dat)/2.5*1000;
            cr = Math.floor(155+targ);
            cg = Math.floor(255-targ*0.5);
            cb = 155;
          } else if (dat >= 0.25) {
            targ = (0.5-dat)/2.5*1000;
            cr = Math.floor(255-targ*0.5);
            cg = 205;
            cb = Math.floor(155+targ*0.5);
          } else if (dat >= 0.05) {
            targ = (0.25-dat)/2*1000;
            cr = Math.floor(205-targ*0.5);
            cg = Math.floor(205-targ*0.5);
            cb = Math.floor(205-targ*0.5);
          } else {
            targ = (0.05-dat)/0.5*1000;
            cr = Math.floor(155-targ*0.2);
            cg = Math.floor(155-targ*0.2);
            cb = Math.floor(155-targ*0.2);
          }
          t += `<td align="right" bgcolor="` + rgb2hex( [cr, cg, cb] ) + `">` + (dat*100).toFixed(2) + `%</td>`;
        }
        t += `</tr>`;
      }

      $("#accs-table > tbody").empty();
      $("#accs-table > tbody").append(t);
    }, { deep: true, immediate: true });
  }

  update();
  document.getElementById("colorstanding-detail").addEventListener("click", () => {
    detailmode = 1 - detailmode
    update();
  });
  document.getElementById("colorstanding-rated").addEventListener("click", () => {
    ratedmode = 1 - ratedmode
    update();
  });
  document.getElementById("colorstanding-submit").addEventListener("click", () => {
    submitmode = 1 - submitmode
    update();
  });
  document.getElementById("colorstanding-off").addEventListener("click", () => {
    $("#accs-table > tbody").empty();
  });
});