AtCoder Problems Team Standings (alpha ver.)

Team Standingsを作成します。

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         AtCoder Problems Team Standings (alpha ver.)
// @namespace    AtCoder Problems
// @version      0.1
// @description  Team Standingsを作成します。
// @author       harurun
// @match        https://kenkoooo.com/atcoder/*
// @grant        GM_getValue
// @grant        GM_setValue
// ==/UserScript==

/*グローバル変数*/
//ユーザ名とチーム名の対応連想配列
var user_team={};
//上記の逆の連想配列
var user_to_team={};
//コンテスト名
var contest="";
//各問題の点数
var problem=[];
//チームスコア
var team_sc_data={};
//チームの集合
var teams=new Set();
//settingユーザ集合
var users=new Set();
//ペナタイム
var penalty_time=300;
//各問題を管理
var problem_name={};
//開始時間
var start_time=0;
//終了時間
var end_time=0;


/*コンテスト名を返す*/
function get_contest(){
  contest=document.getElementsByTagName("h1")[0].textContent;
  penalty_time=parseInt(document.getElementsByTagName("th")[1].nextElementSibling.textContent.split(" ")[0]);
  return;
}

/*チームスコアの初期化関数*/
function set_team(cnt/*問題数*/){
  var team_list=[...teams];
  for(var i in team_list){
    team_sc_data[team_list[i]]=[];
    for(var j=0;j<cnt;j++){
      team_sc_data[team_list[i]].push([-1,0]);
    }
  }
  return;
}

/*各チームの点数を数える*/
function get_users(i,tr_elements,cnt){
  for(;i<tr_elements.length;i++){
    var u=tr_elements[i].children;
    var user_name=u[1].children[0].textContent//名前
    if(!users.has(user_name))continue;
    var team=user_team[user_name];
    for(var j=3;j<cnt+3;j++){
      if(u[j].textContent==="-")continue;//未提出
      var t=u[j].children[0].children;//スコアとペナ//
      if(t.length===0)continue;
      var sc=t[0].textContent;//点数//部分点はそもそもProblemsにないので使わない
      var pnl_int=0;
      if(t.length==2){//0ペナでも必ず2つあるらしい
        var pnl_str=t[1].textContent;//ペナ数
        if(!(pnl_str==="")){
          pnl_int=pnl_str.substr(1,pnl_str.length-2);//ペナ数の()を外す
        }
      }
      //ペナを足す
      team_sc_data[team][j-3][1]+=parseInt(pnl_int);
      if(sc==="0")continue;//WA
      var spend_time=u[j].children[1].textContent.split(":");//時間
      //素の時間で比較する。ペナは足すだけ
      if(parseInt(team_sc_data[team][j-3][0])===-1){
        team_sc_data[team][j-3][0]=parseInt(spend_time[0]*3600)+parseInt(spend_time[1])*60+parseInt(spend_time[2]);
      }else if(parseInt(spend_time[0]*3600)+parseInt(spend_time[1])*60+parseInt(spend_time[2])<parseInt(team_sc_data[team][j-3][0])){
        team_sc_data[team][j-3][0]=parseInt(spend_time[0]*3600)+parseInt(spend_time[1])*60+parseInt(spend_time[2]);
      }
    }/*各ユーザの点数for*/
  }/*各ユーザ*/
  return;
}

//問題の点数を数える関数
function get_score(){
  var start_flag=false;
  var tr_elements=document.getElementsByTagName("tr");
  for(var i=0;i<tr_elements.length;i++){
    var f=tr_elements[i].children;
    //終了
    if(f[0].textContent==="#"){
    //各ユーザの時間を取得
      set_team(problem.length);
      get_users(i+1,tr_elements,problem.length);
      break;
    }
    //開始
    if(f[f.length-1].textContent==="Score"){
      start_flag=true;
      continue;
    }
    //カウント
    if(start_flag){
      var ret=f[f.length-1].textContent;
      if(ret===""){
        ret=1;
      }
      problem.push(parseInt(ret));
      var prob_url_txt=String(f[1].children[0].href).split("/");
      problem_name[parseInt(f[0].textContent)-1]=prob_url_txt[prob_url_txt.length-1];
    }
  }
  return;
}

/*チームスコアを表示する*/
function display_score(){
  //それぞれのチームの得点を計算してソートする。
  var scores=[];//[合計,ペナ入り時間,チーム名]
  for(var key in team_sc_data){
    var team_sc=team_sc_data[key];
    var point=0;
    var time_sec=0;
    var cnt_pnl=0;
    var pure_time=0;
    for(var i=0;i<team_sc.length;i++){
      if(team_sc[i][0]===-1)continue;
      if(team_sc[i][0]!==-1){
        point+=problem[i];
      }
      if(team_sc[i][1]!==-1){
        time_sec=Math.max(time_sec,team_sc[i][0]+team_sc[i][1]*penalty_time);
        pure_time=Math.max(pure_time,team_sc[i][0]);
        cnt_pnl+=team_sc[i][1];
      }
    }
    scores.push([point,time_sec,key,pure_time,cnt_pnl]);
  }
  scores.sort(function(a,b){
    if(a[0]===b[0]){
      if(a[1]===b[1]){
        return a[4]-b[4];//ペナが少ない方
      }
      return a[1]-b[1];//スコアが同じ時はペナ入りの時間で
    }else{
      return b[0]-a[0];
    }
  })
   
  var div_my2=document.createElement("div");
  var div_title=document.createElement("div");

  var title_h4=document.createElement("h4");
  var h4_text=document.createTextNode("Team-Standings")
  title_h4.appendChild(h4_text);
  div_title.appendChild(title_h4);

  div_my2.appendChild(div_title);

  //div1,div2->
  var div1 = document.createElement("div");
  div1.classList.add("row");

  var div2=document.createElement("div");
  div2.classList.add("col-sm-12");

  //table->
  var main_table = document.createElement("table");
  
  //thead->
  var main_thead = document.createElement("thead");
  var head_tr=document.createElement("tr");
  head_tr.classList.add("text-center");
  
  var sharp_th=document.createElement("th");
  var sharp_text=document.createTextNode("##");
  sharp_th.appendChild(sharp_text);
  head_tr.appendChild(sharp_th);

  var te_th=document.createElement("th");
  var te_text=document.createTextNode("Team");
  te_th.appendChild(te_text);
  head_tr.appendChild(te_th);

  var sc_th=document.createElement("th");
  var sc_text=document.createTextNode("Score");
  sc_th.appendChild(sc_text);
  head_tr.appendChild(sc_th);

  for(var i=0;i<problem.length;i++){
    var num_th=document.createElement("th");
    var num_text=document.createTextNode(String(i+1));
    num_th.appendChild(num_text);
    head_tr.appendChild(num_th);
  }
  
  main_table.appendChild(head_tr);
  //<-thead

  //tbody->
  var team_list=[...teams];
  var main_tbody=document.createElement("tbody");
  for(var i=0;i<team_list.length;i++){
    var now_team=scores[i];
    var create_tr=document.createElement("tr");
    //thを追加していく
    //id を付ける

    //順位
    var create_rank=document.createElement("th");
    var rank_text=document.createTextNode(String(i+1));
    create_rank.appendChild(rank_text);
    create_tr.appendChild(create_rank);

    //チーム名
    var team_th=document.createElement("th");
    var display_name=String(now_team[2])+"(";
    for(var l=0;l<user_to_team[now_team[2]].length;l++){
      display_name+=user_to_team[now_team[2]][l];
      display_name+=","
    }
    display_name=display_name.slice(0,-1)+")";
    var team_text=document.createTextNode(display_name);
    team_th.appendChild(team_text);
    create_tr.appendChild(team_th);

    //スコア合計
    var sum_td=document.createElement("td");

    var score_p1=document.createElement("p");
    score_p1.style="text-align: center; margin: 0px;";

    var score_span1=document.createElement("span");
    score_span1.style="color: limegreen; font-weight: bold;";
    var span1_text=document.createTextNode(String(now_team[0]));
    score_span1.appendChild(span1_text);
    score_p1.appendChild(score_span1);

    var score_span2=document.createElement("span");
    score_span2.style="color: red;";
    if(now_team[4]!==0){
      var span2_text=document.createTextNode(" ("+String(now_team[4])+")");
      score_span2.appendChild(span2_text);
    }
    score_p1.appendChild(score_span2);

    sum_td.appendChild(score_p1);
    
    if(now_team[1]!==0){

    var score_p2=document.createElement("p");
    score_p2.style="text-align: center; margin: 0px;";

    var score_span3=document.createElement("span");
    score_span3.style="color: gray;";
    var time_hour=now_team[1]/3600|0;
    var time_min=(now_team[1]-time_hour*3600)/60|0;
    var time_s=now_team[1]-time_hour*3600-time_min*60;
    var span3_text=document.createTextNode(String(time_hour)+":"+(("00"+String(time_min)).slice(-2))+":"+(("00"+String(time_s)).slice(-2)));//合計はペナ入り時間
    score_span3.appendChild(span3_text);
    score_p2.appendChild(score_span3);

    sum_td.appendChild(score_p2);
    }else{
      var score_p2=document.createElement("p");
      score_p2.style="text-align: center; margin: 0px;";
      var score_span3=document.createElement("span");
      score_span3.style="color: gray;";
      var span3_text=document.createTextNode("-");
      score_span3.appendChild(span3_text);
      score_p2.appendChild(score_span3);

      sum_td.appendChild(score_p2);
    }
    create_tr.appendChild(sum_td);
    //<-スコア

    //各問題の点数
    var now_team_sc=team_sc_data[now_team[2]];
    for(var j=0;j<problem.length;j++){
      var num_td=document.createElement("td");

      var score_p1=document.createElement("p");
      score_p1.style="text-align: center; margin: 0px;";
      
      var sp_time=now_team_sc[j][0];
      var pn_cnt=now_team_sc[j][1];

      if(sp_time!==-1){
        var score_span1=document.createElement("span");
        score_span1.style="color: limegreen; font-weight: bold;";
        var span1_text=document.createTextNode(String(problem[j]));
        score_span1.appendChild(span1_text);
        score_p1.appendChild(score_span1);

        var score_span2=document.createElement("span");
        score_span2.style="color: red;";
        if(pn_cnt!==0){
          var span2_text=document.createTextNode(" ("+String(pn_cnt)+")");
          score_span2.appendChild(span2_text);
        }
        score_p1.appendChild(score_span2);

        num_td.appendChild(score_p1);

        var score_p2=document.createElement("p");
        score_p2.style="text-align: center; margin: 0px;"

        var score_span3=document.createElement("span");
        score_span3.style="color: gray;";
        var time_hour=sp_time/3600|0;
        var time_min=(sp_time-time_hour*3600)/60|0;
        var time_s=sp_time-time_hour*3600-time_min*60;
        var span3_text=document.createTextNode(String(time_hour)+":"+(("00"+String(time_min)).slice(-2))+":"+(("00"+String(time_s)).slice(-2)));
        score_span3.appendChild(span3_text);
        score_p2.appendChild(score_span3);
    
        num_td.appendChild(score_p2);
        create_tr.appendChild(num_td);
      }else{//未提出
        num_td.classList.add("text-center");
        var non_text=document.createTextNode("-");
        num_td.appendChild(non_text);
        create_tr.appendChild(num_td);
      }
    }
    main_tbody.appendChild(create_tr);
  }
  main_table.appendChild(main_tbody);
  div2.appendChild(main_table);
  div1.appendChild(div2);
  div_my2.appendChild(div1);

  var before_node=document.getElementsByClassName("my-2");
  before_node[2].parentNode.insertBefore(div_my2,before_node[2].nextElementSibling);
  return;
}

/*GMから設定を読み込む*/
function get_settings() {
  var settings=GM_getValue(contest)
  if(settings===undefined){
    return false;
  }
  var config=settings.split(",");
  for(var i=0;i<config.length;i++){
    var user=config[i].split(":")[0];
    var team=config[i].split(":")[1];
    user_team[user]=team;
    if(user_to_team[team]===undefined){
      user_to_team[team]=[user];
    }else{
      user_to_team[team].push(user);
    }
    teams.add(team);
    users.add(user);
  }
  return true;
}

/*GMに設定を保存する*/
function save_settings() {
  var settings="";
  for(var key in user_team){
    var ret=key+":"+user_team[key]+",";
    settings+=ret;
  }
  settings=settings.slice(0,-1);
  GM_setValue(contest,settings);
  location.reload();//saveしたあとはリロード
  return;
}

/*ファイルを開いて内容をGMに保存する*/
function open_file(evt){
  var reader=new FileReader();
  reader.readAsText(evt.target.files[0]);
  reader.addEventListener("load",()=>{
    var ts=reader.result.replace(/\r?\n/g,"").replace("{","").replace("}","").replace(/\s+/g, "");
    var tx=ts.split(",");
    user_team={};//初期化
    user_to_team={};//初期化
    teams.clear();//初期化
    users.clear();//初期化
    for(var i=0;i<tx.length;i++){
      var te=tx[i].split(":");
      user_team[te[0]]=te[1];
      if(user_to_team[te[1]]===undefined){
        user_to_team[te[1]]=[te[0]];
      }else{
        user_to_team[te[1]].push(te[0]);
      }
      teams.add(te[1]);
      users.add(te[0]);
    }
    save_settings();
    get_score();
    display_score();
  })
  setting_flag=true;
  console.log("setting is saved");
  return;
}

/*ファイルを開くボタンを追加する*/
function add_file_button() {
  var button=document.createElement("input");
  button.id="add_file";
  button.type="file";
  var ref=document.getElementsByTagName("h4")[0];
  var my_parent=ref.parentNode;
  my_parent.appendChild(button);
  button.addEventListener('change',open_file,false);
  console.log("file button added")
  return;
}

/*auto_refreshを削除*/
function remove_auto(){
  var auto_button=document.getElementById("autoRefresh");
  var auto_label=auto_button.nextElementSibling;
  auto_label.remove();
  auto_button.remove();
  return;
}

/*pin meを削除*/
function remove_pin(){
  try{
    var pin_me=document.getElementById("pinMe");
    var pin_label=pin_me.nextElementSibling;
    pin_label.remove();
    pin_me.remove();
  }catch(e){
    return;
  }
}

function get_contest_time(){
  var elements_tbody=document.getElementsByTagName("tbody");
  var time_list=elements_tbody[0].children[0].children[1].textContent.split(" ");
  start_time_str=time_list[0]+" "+time_list[1];
  end_time_str=time_list[4]+" "+time_list[5];
  start_time=Date.parse(start_time_str);
  end_time=Date.parse(end_time_str);
  return;
}

function out_file(){
  var file_text=`{"contest":"${contest}","start_time":${start_time},"end_time":${end_time},"penalty_time":${penalty_time},`;
  var problems_text="\"problems\":{";
  for(var i=0; i<problem.length;i++){
    problems_text+=`${i+1}:{"problem_id":"${problem_name[i]}","score":${problem[i]}},`;
  }
  problems_text=problems_text.slice(0,-1)+"}";
  file_text+=problems_text+",";
  var team_text="\"teams\":{";
  for(var key in user_to_team){
    var ret=`"${key}":[`;
    var local_users=user_to_team[key];
    for(var i=0; i<local_users.length;i++){
      ret+=`"${local_users[i]}",`;
    }
    team_text+=ret.slice(0,-1)+"],";
  }
  file_text+=team_text.slice(0,-1)+"}}";
  return file_text;
}

function add_download_button(){
  if(new Date(end_time)-new Date()>0)return;
  var file_text=out_file();
  var file_name=`${contest}_result.json`;
  var download_link=document.createElement("a");
  download_link.href="data:text/plain,"+encodeURIComponent(file_text);
  download_link.download=file_name;
  download_link.textContent="result";
  var ref=document.getElementById("add_file");
  var my_parent=ref.parentNode;
  my_parent.appendChild(download_link);
  console.log("download link is added");
  return;
}

function main(){
  //コンテスト以外のページの場合は即return
  if(!location.href.match("https://kenkoooo.com/atcoder/#/contest/show/.*")){
    return;
  }
  get_contest();//コンテスト名を取得
  add_file_button();//ファイルを開くボタンを追加
  remove_auto();//auto_refreshを削除する(確実にバグるので)=>beta版では実装する予定
  remove_pin();//pin_meを削除=>背景色を付ける予定(未定)
  get_contest_time();
  var flags=get_settings();//GM_getValueする
  if(flags){
    get_score();//スコアを取得する
    display_score();//チームスコアを表示
    add_download_button();
  }
  return;
}

setTimeout(()=>{
  main();
},1000)