您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
コンテスト中にAtCoderのパフォーマンスを予測します
当前为
// ==UserScript== // @name ac-predictor // @namespace http://ac-predictor.azurewebsites.net/ // @version 1.0.1 // @description コンテスト中にAtCoderのパフォーマンスを予測します // @author keymoon // @license MIT // @supportURL https://github.com/key-moon/ac-predictor.user.js/issues // @match https://beta.atcoder.jp/* // @exclude https://beta.atcoder.jp/*/json // ==/UserScript== //NameSpace SideMenu = {}; SideMenu.Version = 1.0; //共有データセット(HistoryとかStandingsとか) SideMenu.Datas = {}; //共有データセットそれぞれをUpdateする関数を入れておく SideMenu.Datas.Update = {} //Datas.Update内に関数を追加 //History SideMenu.Datas.History = null; SideMenu.Datas.Update.History = (() => { var d = $.Deferred(); try { $.ajax({ url: `https://beta.atcoder.jp/users/${userScreenName}/history/json`, type: "GET", dataType: "json" }).done(history => { SideMenu.Datas.History = history; d.resolve(); }).fail(() => { d.reject(); }); } catch (e) { d.reject(e); } return d.promise(); }); //Standings SideMenu.Datas.Standings = null; SideMenu.Datas.Update.Standings = (() => { var d = $.Deferred(); try { $.ajax({ url: `https://beta.atcoder.jp/contests/${contestScreenName}/standings/json`, type: "GET", dataType: "json" }).done(standings => { SideMenu.Datas.Standings = standings; d.resolve(); }).fail(() => { d.reject(); }); } catch (e) { d.reject(); } return d.promise(); }); //APerfs SideMenu.Datas.APerfs = null; SideMenu.Datas.Update.APerfs = (() => { var d = $.Deferred(); try { $.ajax({ url: `https://ac-predictor.azurewebsites.net/api/aperfs/${contestScreenName}`, type: "GET", dataType: "json" }).done(aperfs => { SideMenu.Datas.APerfs = aperfs d.resolve(); }).fail(() => { d.reject(); }); } catch (e) { d.reject(); } return d.promise(); }); //ライブラリを追加するやつ SideMenu.appendLibrary = function (source) { var defferd = $.Deferred(); $.ajax({ url: source }).done(((src) => { $('head').append(`<script>${src}</script>`); defferd.resolve(); })(() => { defferd.fail(); })); return defferd.promise(); }; //通知関連 SideMenu.Notifications = {}; SideMenu.Notifications.CanSend = false; (async () => { if (Notification.permission === 'denied') return; if (Notification.permission === 'default') { await (async () => { var defferd = $.Deferred(); Notification.requestPermission((permission) => { SideMenu.Notifications.CanSend = permission === 'granted'; defferd.resolve(); })(); return defferd.promise(); }); if (!Notification.permission) return; } })(); SideMenu.appendLibrary("https://koba-e964.github.io/atcoder-rating-estimator/atcoder_rating.js"); //サイドメニュー追加(将来仕様変更が起きる可能性大) SideMenu.appendToSideMenu = async function (match, title, elemFunc) { var defferd = $.Deferred(); try { if (!match.test(location.href)) return; //アコーディオンメニュー var dom = `<div class="menu-wrapper"> <div class="menu-header"> <h4 class="sidemenu-txt">${title}<span class="glyphicon glyphicon-menu-up" style="float: right"></span></h4> </div> <div class="menu-box"><div class="menu-content">${await elemFunc()}</div></div> </div>` $('#sidemenu').append(dom); var contents = $('.menu-content'); var contentElem = contents[contents.length - 1]; $(contentElem).parents('.menu-box').css('height', contentElem.scrollHeight) defferd.resolve(); } catch (e) { console.error(e); defferd.reject(); } return defferd.promise(); }; //サイドメニューを生成 (() => { var menuWidth = 350 var keyWidth = 50 var speed = 150 var sideMenuScript = `<script>//参考:http://blog.8bit.co.jp/?p=12308 (() => { const activeClass = 'sidemenu-active' var menuWrap = '#menu_wrap' var sideMenu = '#sidemenu' var sideMenuKey = '#sidemenu-key' var menuWidth = ${menuWidth} var keyWidth = ${keyWidth} var speed = ${speed} var windowHeight = $(window).height(); $(sideMenu).height(windowHeight); //メニュー開閉 $(sideMenuKey).click(function () { //ボタンの文言を変更する部分をCSSTransitionでやらせたい $(sideMenuKey).toggleClass('glyphicon-menu-left'); $(sideMenuKey).toggleClass('glyphicon-menu-right'); $(menuWrap).toggleClass(activeClass); }); //画面リサイズ時にheightを読み直し var timer = false; $(window).resize(function () { if (timer !== false) { clearTimeout(timer); } timer = setTimeout(function () { windowHeight = $(window).height(); $(sideMenu).height(windowHeight); }, 50); }); //アコーディオンメニューのなんか $('#sidemenu').on('click','.menu-header',(event) => { //$(event.target).parents('.menu-wrapper').find('.menu-content').toggleClass('menu-content-collapse') $(event.target).parents('.menu-wrapper').find('.menu-box').toggleClass('menu-box-collapse') $(event.target).find('.glyphicon').toggleClass('glyphicon-menu-down') $(event.target).find('.glyphicon').toggleClass('glyphicon-menu-up') }) /*$('.menu-content').exResize((event) => { $(event.target).parents('.menu-box').css('height',event.target.clientHeight) });*/ })(); </script>` var sideMenuStyle = `<style>#menu_wrap{ display:block; position:fixed; top:0; width:${keyWidth + menuWidth}px; right:-${menuWidth}px; transition: all ${speed}ms 0ms ease; margin-top:50px; } #sidemenu{ background:#000; opacity:0.85; } #sidemenu-key{ border-radius:5px 0px 0px 5px; background:#000; opacity:0.75; color:#FFF; padding:30px 0; cursor:pointer; margin-top:100px; text-align: center; } #sidemenu{ display:inline-block; width:${menuWidth}px; float:right; } #sidemenu-key{ display:inline-block; width:${keyWidth}px; float:right; } .sidemenu-active{ transform: translateX(-${menuWidth}px); } .sidemenu-txt{ color: #DDD; } /*アコーディオンメニュー*/ .menu-wrapper{ border-bottom: 1px solid #FFF; } .menu-header h4{ user-select: none; } .menu-header{ margin:10px 20px 10px 20px; } .menu-box{ overflow: hidden; transition: all 300ms 0s ease; } .menu-box-collapse{ height:0px !important;/**/ } .menu-content{ padding: 10px 20px 10px 20px; transition: all 300ms 0s ease; } .menu-box-collapse .menu-content{ transform: translateY(-100%); } </style>` var ratingScript = `<script>from: https://koba-e964.github.io/atcoder-rating-estimator/atcoder_rating.js function bigf(n) { var num = 1.0; var den = 1.0; for (var i = 0; i < n; ++i) { num *= 0.81; den *= 0.9; } num = (1 - num) * 0.81 / 0.19; den = (1 - den) * 0.9 / 0.1; return Math.sqrt(num) / den; } function f(n) { var finf = bigf(400); return (bigf(n) - finf) / (bigf(1) - finf) * 1200.0; } // Returns unpositivized ratings. function calc_rating(arr) { var n = arr.length; var num = 0.0; var den = 0.0; for (var i = n - 1; i >= 0; --i) { num *= 0.9; num += 0.9 * Math.pow(2, arr[i] / 800.0); den *= 0.9; den += 0.9; } var rating = Math.log2(num / den) * 800.0; rating -= f(n); return rating; } // Takes and returns unpositivized ratings. function calc_rating_from_last(last, perf, n) { last += f(n); var wei = 9 - 9 * 0.9 ** n; var num = wei * (2 ** (last / 800.0)) + 2 ** (perf / 800.0) ; var den = 1 + wei; var rating = Math.log2(num / den) * 800.0; rating -= f(n + 1); return rating; } // (-inf, inf) -> (0, inf) function positivize_rating(r) { if (r >= 400.0) { return r; } return 400.0 * Math.exp((r - 400.0) / 400.0); } // (0, inf) -> (-inf, inf) function unpositivize_rating(r) { if (r >= 400.0) { return r; } return 400.0 + 400.0 * Math.log(r / 400.0); }</script>`; $('#main-div').append(`<div id="menu_wrap"><div id="sidemenu" class="container"></div><div id="sidemenu-key" class="glyphicon glyphicon-menu-left"></div>${ratingScript}${sideMenuScript}${sideMenuStyle}</div>`); })(); //IndexedDB DB SideMenu.DataBase = {}; SideMenu.DataBase.Name = "PredictorDB"; SideMenu.DataBase.StoreNames = ["APerfs", "Standings"]; indexedDB.open(SideMenu.DataBase.Name, SideMenu.Version).onupgradeneeded = (event) => { var db = event.target.result; SideMenu.DataBase.StoreNames.forEach(store => { db.createObjectStore(store, { keyPath: "id" }); }); }; SideMenu.DataBase.SetData = (store, key, value) => { var defferd = $.Deferred(); try { indexedDB.open(SideMenu.DataBase.Name).onsuccess = (e) => { var db = e.target.result; var trans = db.transaction(store, 'readwrite'); var objStore = trans.objectStore(store); var data = { id: key, data: value }; var putReq = objStore.put(data); putReq.onsuccess = function () { defferd.resolve(); } } } catch (e) { defferd.reject(e); } return defferd.promise(); }; SideMenu.DataBase.GetData = (store, key) => { var defferd = $.Deferred(); try { indexedDB.open(SideMenu.DataBase.Name).onsuccess = (e) => { var db = e.target.result; var trans = db.transaction(store, 'readwrite'); var objStore = trans.objectStore(store); objStore.get(key).onsuccess = function (event) { var result = event.target.result; if (!result) defferd.reject("key was not found"); else defferd.resolve(result.data); }; } } catch (e) { defferd.reject(e); } return defferd.promise(); }; //サイドメニュー要素の入れ物 SideMenu.Elements = {}; SideMenu.ViewOrder = ["Predictor", "Estimator"]; SideMenu.Colors = ["unrated", "gray", "brown", "green", "cyan", "blue", "yellow", "orange", "red"]; SideMenu.GetColor = (rating) => { var colorIndex = 0 if (rating > 0) { colorIndex = Math.min(Math.floor(rating / 400) + 1, 8) } return SideMenu.Colors[colorIndex] }; //Estimator SideMenu.Elements.Estimator = (async () => { await SideMenu.appendToSideMenu(/beta.atcoder.jp/,'Estimator',getElem); async function getElem() { $("#estimator-input").val(localStorage.getItem("sidemenu_estimator_value")); if (!SideMenu.Datas.History) await SideMenu.Datas.Update.History(); var js = `(() => { var estimator_state = localStorage.getItem("sidemenu_estimator_state"); \$("#estimator-input").val(localStorage.getItem("sidemenu_estimator_value")); updateInputs(); \$("#estimator-input").keyup(updateInputs); \$("#estimator-toggle").click(function () { \$("#estimator-input").val(\$("#estimator-res").val()); estimator_state = (estimator_state + 1) % 2; updateInputs(); }) function updateInputs() { var input = \$("#estimator-input").val(); if (!isFinite(input)) { displayAlert("数字ではありません") return; } var history = SideMenu.Datas.History.filter(x => x.IsRated) history.sort(function (a, b) { if (a.EndTime < b.EndTime) return 1; if (a.EndTime > b.EndTime) return -1; return 0; }) history = history.map(x => x.InnerPerformance) var input = parseInt(input) var res = -1; if (estimator_state === 0) { \/\/ binary search var goal_rating = unpositivize_rating(input) var lo = -10000.0; var hi = 10000.0; for (var i = 0; i < 100; ++i) { var mid = (hi + lo) \/ 2; var r = calc_rating([mid].concat(history)); if (r >= goal_rating) { hi = mid; } else { lo = mid; } } res = (hi + lo) \/ 2; \$("#estimator-input-desc").text("目標レーティング"); \$("#estimator-res-desc").text("必要パフォーマンス"); } else { res = calc_rating([input].concat(history)); \$("#estimator-input-desc").text("パフォーマンス"); \$("#estimator-res-desc").text("到達レーティング"); } res = Math.round(res * 100) \/ 100 if (!isNaN(res)) \$("#estimator-res").val(res); updateLocalStorage(); updateTweetBtn(); } function updateLocalStorage() { localStorage.setItem("sidemenu_estimator_state", estimator_state); localStorage.setItem("sidemenu_estimator_value", \$("#estimator-input").val()); } function updateTweetBtn() { var tweetStr = \`AtCoderのハンドルネーム: \${userScreenName}%0A \${estimator_state == 0 ? "目標レーティング" : "パフォーマンス"}: \${\$("#estimator-input").val()}%0A \${estimator_state == 0 ? "必要パフォーマンス" : "到達レーティング"}: \${\$("#estimator-res").val()}\` \$('#estimator-tweet').attr("href", \`https:\/\/twitter.com\/intent\/tweet?text=\${tweetStr}\`) } function displayAlert(message) { var alertDiv = document.createElement('div') alertDiv.setAttribute("role", "alert") alertDiv.setAttribute("class", "alert alert-warning alert-dismissible") var closeButton = document.createElement('button') closeButton.setAttribute("type", "button") closeButton.setAttribute("class", "close") closeButton.setAttribute("data-dismiss", "alert") closeButton.setAttribute("aria-label", "閉じる") var closeSpan = document.createElement('span') closeSpan.setAttribute("aria-hidden", "true") closeSpan.textContent = "×" closeButton.appendChild(closeSpan) var messageContent = document.createTextNode(message) alertDiv.appendChild(closeButton) alertDiv.appendChild(messageContent) \$("#estimator-alert").append(alertDiv) } })();`; var style = ``; var dom = `<div id="estimator-alert"><\/div> <div class="row"> <div class="input-group"> <span class="input-group-addon" id="estimator-input-desc">目標レーティング<\/span> <input type="number" class="form-control" id="estimator-input"> <\/div> <\/div> <div class="row"> <div class="input-group"> <span class="input-group-addon" id="estimator-res-desc">必要パフォーマンス<\/span> <input class="form-control" id="estimator-res" disabled="disabled"> <span class="input-group-btn"> <button class="btn btn-default" id="estimator-toggle">入替<\/button> <\/span> <\/div> <\/div> <div class="row" style="margin: 10px 0px;"> <a class="btn btn-default col-xs-offset-8 col-xs-4" rel="nofollow" onClick="window.open(encodeURI(decodeURI(this.href)),'twwindow','width=550, height=450, personalbar=0, toolbar=0, scrollbars=1'); return false;" id='estimator-tweet'>ツイート<\/a> <\/div>`; return `${dom} <script>${js}</script> <style>${style}</style>`; } }); //Predictor SideMenu.Elements.Predictor = (async () => { await SideMenu.appendToSideMenu(/beta.atcoder.jp\/contests\/.*/,'Predictor',getElem); async function getElem() { //NameSpace SideMenu.Predictor = {}; var maxDic = [ [/^abc\d{3}$/, 1600], [/^arc\d{3}$/, 3200], [/^agc\d{3}$/, 8192], [/^apc\d{3}$/, 8192], [/^cf\d{2}-final-open$/, 8192], [/^soundhound2018-summer-qual$/, 2400], [/.*/, -1] ]; SideMenu.Predictor.maxPerf = maxDic.filter(x => x[0].exec(contestScreenName))[0][1]; if (!SideMenu.Datas.History) await SideMenu.Datas.Update.History().done(() => { isDone = true }); var js = `(() => { \/\/各参加者の結果 var eachParticipationResults = {}; var isAlreadyAppendRowToStandings = false; const specialContest = ['practice', 'APG4b', 'abs']; const predictorElements = ['predictor-input-rank', 'predictor-input-perf', 'predictor-input-rate', 'predictor-current', 'predictor-reload', 'predictor-tweet']; const firstContestDate = moment("2016-07-16 21:00"); const Interval = 30000; const ratedLimit = contestScreenName === "SoundHound Inc. Programming Contest 2018 -Masters Tournament-" ? 2000 : (\/abc\\d{3}\/.test(contestScreenName) ? 1200 : (\/arc\\d{3}\/.test(contestScreenName) ? 2800 : Infinity)); const defaultAPerf = \/abc\\d{3}\/.test(contestScreenName) ? 800 : 1600; const isStandingsPage = \/standings(\\\/.*)?\$\/.test(document.location); \$('[data-toggle="tooltip"]').tooltip(); \$('#predictor-reload').click(function () { UpdatePredictorsData(); }); \$('#predictor-current').click(function () { \/\/自分の順位を確認 var myRank = 0; var ratedCount = 0; var lastRank = 0; var rank = 1; var isContainedMe = false; \/\/全員回して自分が出てきたら順位更新フラグを立てる SideMenu.Datas.Standings.StandingsData.forEach(function (element) { if (lastRank !== element.Rank) { if (isContainedMe) { myRank = rank + Math.max(0, ratedCount - 1) \/ 2; isContainedMe = false; } rank += ratedCount; ratedCount = 0; } if (userScreenName === element.UserScreenName) isContainedMe = true; if (element.IsRated && element.TotalResult.Count !== 0) ratedCount++; lastRank = element.Rank; }) if (isContainedMe) { myRank = rank + ratedCount \/ 2; } if (myRank === 0) return; \$('#predictor-input-rank').val(myRank); lastUpdated = 0; drawPredictor(); }); \$('#predictor-input-rank').keyup(function (event) { lastUpdated = 0; drawPredictor(); }); \$('#predictor-input-perf').keyup(function (event) { lastUpdated = 1; drawPredictor(); }); \$('#predictor-input-rate').keyup(function (event) { lastUpdated = 2; drawPredictor(); }); var lastUpdated = 0; if (!startTime.isBefore()) { disabled(); AddAlert('コンテストは始まっていません'); return; } if (moment(startTime) < firstContestDate) { disabled(); AddAlert('現行レートシステム以前のコンテストです'); return; } if (specialContest.indexOf(contestScreenName) >= 0) { disabled(); AddAlert('コンテストではありません'); return; } if (!endTime.isBefore()) { SetUpdateInterval(); return; } \$.when( SideMenu.DataBase.GetData("APerfs", contestScreenName), SideMenu.DataBase.GetData("Standings", contestScreenName) ).done((aperfs, standings) => { SideMenu.Datas.APerfs = aperfs; SideMenu.Datas.Standings = standings; CalcActivePerf(); drawPredictor(); enabled(); AddAlert('ローカルストレージから取得されました。'); if (isStandingsPage) { updateResultsData(); addPerfToStandings(); } }).fail(() => { UpdatePredictorsData(); }) \/\/再描画をの期間を再更新する function SetUpdateInterval() { UpdatePredictorsData(); if (!endTime.isBefore()) setTimeout(SetUpdateInterval, Interval); } \/\/自分のレートをパフォから求める function getRate(perf) { return positivize_rating(calc_rating(SideMenu.Datas.History.filter(x => x.IsRated).map(x => x.Performance).concat(perf).reverse())); } \/\/パフォを順位から求める() function getPerf(rank) { var upper = 8192 var lower = -8192 while (upper - lower > 0.5) { if (rank - 0.5 > calcRankVal(lower + (upper - lower) \/ 2)) upper -= (upper - lower) \/ 2 else lower += (upper - lower) \/ 2 } var innerPerf = Math.round(lower + (upper - lower) \/ 2) return Math.min(innerPerf, SideMenu.Predictor.maxPerf); } \/\/パフォを求める際に出てくるパフォごとの順位を求める function calcRankVal(X) { var res = 0; activePerf.forEach(function (APerf) { res += 1.0 \/ (1.0 + Math.pow(6.0, ((X - APerf) \/ 400.0))) }) return res; } \/\/データを更新して描画する function UpdatePredictorsData() { if (!startTime.isBefore()) { disabled(); AddAlert('コンテストは始まっていません'); return; } if (moment(startTime) < firstContestDate) { disabled(); AddAlert('現行レートシステム以前のコンテストです'); return; } if (specialContest.indexOf(contestScreenName) >= 0) { disabled(); AddAlert('コンテストではありません'); return; } \$('#predictor-reload').button('loading'); AddAlert('順位表読み込み中…'); SideMenu.Datas.Update.APerfs().then(SideMenu.Datas.Update.Standings).then(() => { if (Object.keys(SideMenu.Datas.APerfs).length === 0) { disabled(); AddAlert('APerfのデータが提供されていません'); return; } if (SideMenu.Datas.Standings.Fixed) { SideMenu.DataBase.SetData('APerfs', contestScreenName, SideMenu.Datas.APerfs); SideMenu.DataBase.SetData('Standings', contestScreenName, SideMenu.Datas.Standings); } CalcActivePerf(); if (isStandingsPage) { updateResultsData(); addPerfToStandings(); } drawPredictor(); enabled(); AddAlert(\`最終更新 : \${moment().format('HH:mm:ss')}\`); }).fail(() => { disabled(); AddAlert('データの読み込みに失敗しました'); }); } \/\/ActivePerfの再計算 function CalcActivePerf() { activePerf = []; var isSomebodyRated = false; \/\/Perf計算時に使うパフォ(Ratedオンリー) SideMenu.Datas.Standings.StandingsData.forEach(function (element) { if (element.IsRated && element.TotalResult.Count !== 0) { isSomebodyRated = true; if (!(SideMenu.Datas.APerfs[element.UserScreenName])) { activePerf.push(defaultAPerf); } else { activePerf.push(SideMenu.Datas.APerfs[element.UserScreenName]) } } }); if (!isSomebodyRated) { SideMenu.Datas.Standings.Fixed = false; \/\/元はRatedだったと推測できる場合、通常のRatedと同じような扱い activePerf = []; for (var i = 0; i < SideMenu.Datas.Standings.StandingsData.length; i++) { var element = SideMenu.Datas.Standings.StandingsData[i]; if (element.OldRating >= ratedLimit || element.TotalResult.Count === 0) continue; SideMenu.Datas.Standings.StandingsData[i].IsRated = true; if (!(SideMenu.Datas.APerfs[element.UserScreenName])) { activePerf.push(defaultAPerf); continue; } activePerf.push(SideMenu.Datas.APerfs[element.UserScreenName]); } } } \/\/フォームを更新 function drawPredictor() { switch (lastUpdated) { case 0: UpdatePredictorFromRank(); break; case 1: UpdatePredictorFromPerf(); break; case 2: UpdatePredictorFromRate(); break; } function UpdatePredictorFromRank() { var rank = \$("#predictor-input-rank").val(); var perf = getPerf(rank); var rate = getRate(perf); lastUpdated = 0; UpdatePredictor(rank, perf, rate); } function UpdatePredictorFromPerf() { var perf = \$("#predictor-input-perf").val(); var upper = 16384 var lower = 0 while (upper - lower > 0.125) { if (perf > getPerf(lower + (upper - lower) \/ 2)) upper -= (upper - lower) \/ 2 else lower += (upper - lower) \/ 2 } lastUpdated = 1 var rank = lower + (upper - lower) \/ 2; var rate = getRate(perf) UpdatePredictor(rank, perf, rate) } function UpdatePredictorFromRate() { var rate = \$("#predictor-input-rate").val(); var upper = 16384 var lower = 0 while (upper - lower > 0.125) { if (rate < getRate(lower + (upper - lower) \/ 2)) upper -= (upper - lower) \/ 2 else lower += (upper - lower) \/ 2 } lastUpdated = 2 var perf = lower + (upper - lower) \/ 2; upper = 16384 lower = 0 while (upper - lower > 0.125) { if (perf > getPerf(lower + (upper - lower) \/ 2)) upper -= (upper - lower) \/ 2 else lower += (upper - lower) \/ 2 } var rank = lower + (upper - lower) \/ 2; UpdatePredictor(rank, perf, rate) } function UpdatePredictor(rank, perf, rate) { \$("#predictor-input-rank").val(round(rank)) \$("#predictor-input-perf").val(round(perf)) \$("#predictor-input-rate").val(round(rate)) updatePredictorTweetBtn() function round(val) { return Math.round(val * 100) \/ 100; } } \/\/ツイートボタンを更新する function updatePredictorTweetBtn() { var tweetStr = \`Rated内順位: \${\$("#predictor-input-rank").val()}位%0A パフォーマンス: \${\$("#predictor-input-perf").val()}%0A レート: \${\$("#predictor-input-rate").val()}\` \$('#predictor-tweet').attr("href", \`https:\/\/twitter.com\/intent\/tweet?text=\${tweetStr}\`) } } \/\/最終更新などの要素を追加する function AddAlert(content) { \$("#predictor-alert").html(\`<h5 class='sidemenu-txt'>\${content}<\/h5>\`); } \/\/要素のDisableadを外す function enabled() { \$('#predictor-reload').button('reset'); predictorElements.forEach(element => { \$(\`#\${element}\`).removeAttr("disabled"); }); } \/\/要素にDisableadをつける function disabled() { \$('#predictor-reload').button('reset'); predictorElements.forEach(element => { \$(\`#\${element}\`).attr("disabled", true); }); } \/\/全員の結果データを更新する function updateResultsData() { eachParticipationResults = {}; const IsFixed = SideMenu.Datas.Standings.Fixed; \/\/タイの人を入れる(順位が変わったら描画→リストを空に) var tiedList = []; var rank = 1; var lastRank = 0; var ratedCount = 0; var maxPerf = ratedLimit === Infinity ? getPerf(1) : ratedLimit + 400; var currentPerf = maxPerf - 0.5; var rankVal = calcRankVal(currentPerf); \/\/全員回す SideMenu.Datas.Standings.StandingsData.forEach(function (element) { if (lastRank !== element.Rank) { addRow(); rank += ratedCount; ratedCount = 0; tiedList = []; } tiedList.push(element); lastRank = element.Rank; if (element.IsRated && element.TotalResult.Count !== 0) ratedCount++; }); \/\/最後に更新してあげる addRow(); \/\/タイリストの人全員行追加 function addRow() { var fixRank = rank + Math.max(0, ratedCount - 1) \/ 2; while (rankVal < fixRank - 0.5 && currentPerf >= -8192) { currentPerf--; rankVal = calcRankVal(currentPerf); } tiedList.forEach(e => { var isRated = e.IsRated && e.TotalResult.Count !== 0; var isSubmitted = e.TotalResult.Count !== 0; var matches = e.Competitions - (IsFixed && isRated ? 1 : 0); var perf = currentPerf + 0.5; var oldRate = (IsFixed && isSubmitted ? e.OldRating : e.Rating); var newRate = (IsFixed ? e.Rating : Math.floor(positivize_rating(matches !== 0 ? calc_rating_from_last(oldRate, perf, matches) : perf - 1200))); eachParticipationResults[e.UserScreenName] = { perf: perf, oldRate: oldRate, newRate: newRate, isRated: isRated, isSubmitted: isSubmitted }; }); } } \/\/結果データを順位表に追加する function addPerfToStandings() { if (!isStandingsPage) return; if (!isAlreadyAppendRowToStandings) { (new MutationObserver(() => { addPerfToStandings(); })).observe(document.getElementById('standings-tbody'), { childList: true }); \$('thead > tr').append('<th class="standings-result-th" style="width:84px;min-width:84px;">perf<\/th><th class="standings-result-th" style="width:168px;min-width:168px;">レート変化<\/th>'); isAlreadyAppendRowToStandings = true; } \$('#standings-tbody > tr').each((index, elem) => { var userName = \$('.standings-username .username', elem).text(); var perfArr = eachParticipationResults[userName]; if (!perfArr) { \$(elem).append(\`<td class="standings-result">-<\/td>\`); \$(elem).append(\`<td class="standings-result">-<\/td>\`); return; } var perf = perfArr.isSubmitted ? ratingSpan(perfArr.perf) : '<span class="user-unrated">-<\/span>'; var oldRate = perfArr.oldRate; var newRate = perfArr.newRate; var IsRated = perfArr.isRated; \$(elem).append(\`<td class="standings-result">\${ratingSpan(perf)}<\/td>\`); \$(elem).append(\`<td class="standings-result">\${getRatingChangeStr(oldRate,newRate)}<\/td>\`); function getRatingChangeStr(oldRate, newRate) { return IsRated ? \`\${ratingSpan(oldRate)} -> \${ratingSpan(newRate)}(\${(newRate >= oldRate ? '+' : '')}\${newRate - oldRate})\` : \`\${ratingSpan(oldRate)}(unrated)\`; } function ratingSpan(rate) { return \`<span class="user-\${SideMenu.GetColor(rate)}">\${rate}<\/span>\`; } }); } })();`; var style = ``; var dom = `<div id="predictor-alert" class="row"><h5 class='sidemenu-txt'>順位表読み込み中…<\/h5><\/div> <div id="predictor-data" class="row"> <div class="input-group col-xs-offset-1 col-xs-10"> <span class="input-group-addon">順位<span class="glyphicon glyphicon-question-sign" aria-hidden="true" data-html="true" data-toggle="tooltip" data-placement="right" title="" data-original-title="Rated内の順位です。複数人同順位の際は人数を加味します(5位が4人居たら6.5位として計算)"><\/span><\/span> <input class="form-control" id="predictor-input-rank"> <span class="input-group-addon">位<\/span> <\/div> <div class="input-group col-xs-offset-1 col-xs-10"> <span class="input-group-addon">パフォーマンス<\/span> <input class="form-control" id="predictor-input-perf"> <\/div> <div class="input-group col-xs-offset-1 col-xs-10"> <span class="input-group-addon">レーティング<\/span> <input class="form-control" id="predictor-input-rate"> <\/div> <\/div> <div class="row"> <div class="btn-group col-xs-offset-1"> <button class="btn btn-default" id="predictor-current">現在の順位<\/button> <button type="button" class="btn btn-primary" id="predictor-reload" data-loading-text="更新中…">更新<\/button> <a class="btn btn-default" rel="nofollow" onClick="window.open(encodeURI(decodeURI(this.href)),'twwindow','width=550, height=450, personalbar=0, toolbar=0, scrollbars=1'); return false;" id='predictor-tweet'>ツイート<\/a> <!--<button class="btn btn-default" id="predictor-solved" disabled>現問題AC後<\/button>--> <\/div> <\/div>`; return `${dom} <script>${js}</script> <style>${style}</style>`; } }); SideMenu.ViewOrder.forEach(async (elem) => { await SideMenu.Elements[elem](); });