// ==UserScript== // @name ac-predictor // @namespace http://ac-predictor.azurewebsites.net/ // @version 0.1.3 // @description コンテスト中にAtCoderのパフォーマンスを予測します // @author keymoon // @license MIT // @homepage https://github.com/key-moon/ac-predictor // @supportURL https://github.com/key-moon/ac-predictor/issues // @match https://beta.atcoder.jp/* // @downloadURL none // ==/UserScript== //NameSpace SideMenu = {}; //サイドメニューを生成 (function() { var menuWidth = 350 var keyWidth = 50 var speed = 150 var sideMenuScript = `` var sideMenuStyle = `` var ratingScript = `` $('#main-div').append(``); })(); //サイドメニュー追加(将来仕様変更が起きる可能性大です) SideMenu.appendToSideMenu = function (match, title, elemFunc) { try { if (!match.test(location.href)) return; //アコーディオンメニュー var dom = `` $('#sidemenu').append(dom); var contents = $('.menu-content'); var contentElem = contents[contents.length - 1]; $(contentElem).parents('.menu-box').css('height', contentElem.scrollHeight) } catch (e) { console.error(e); } }; //ライブラリを追加するやつ(objがimportされてなかったらsourceURLをヘッダに追加する) SideMenu.appendLibrary = function (source) { $('head').append(``); }; //Predictor (() => { SideMenu.appendToSideMenu(/beta.atcoder.jp\/contests\//,'Predictor',getElem); function getElem() { //NameSpace SideMenu.Predictor = {}; SideMenu.Predictor.historyJsonURL = `https://beta.atcoder.jp/users/${userScreenName}/history/json` SideMenu.Predictor.standingsJsonURL = `https://beta.atcoder.jp/contests/${contestScreenName}/standings/json` SideMenu.Predictor.aperfsJsonURL = `https://ac-predictor.azurewebsites.net/api/aperfs/${contestScreenName}` var maxDic = [ [/^abc\d{3}$/, 1600], [/^arc\d{3}$/, 3200], [/^soundhound2018-summer-qual$/, 2400], [/.*/, 8192] ]; SideMenu.Predictor.maxPerf = maxDic.filter(x => x[0].exec(contestScreenName))[0][1]; //データのロードをしていこうな $.ajax({ url: SideMenu.Predictor.historyJsonURL, type: "GET", dataType: "json" }).done(function (history) { SideMenu.Predictor.historyObj = history }) var predictorScript = `` var dom = `
順位
パフォーマンス
レーティング
ツイート
${predictorScript}`; return dom; } })(); //Estimator (() => { SideMenu.appendToSideMenu(/beta.atcoder.jp/,'Estimator',getElem); function getElem() { var estimatorScript = `var estimator_state = 0; $("#estimator-input").keyup(function () { var input = $("#estimator-input").val(); if (!isFinite(input)) { displayAlert("数字ではありません") return; } var history = SideMenu.Predictor.historyObj.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, 10) 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; } else { res = calc_rating([input].concat(history)); } res = Math.round(res * 100) / 100 $("#estimator-res").val(res) updateTweetBtn() }); $("#estimator-toggle").click(function () { if (estimator_state === 0) { $("#estimator-input-desc").text("パフォーマンス") $("#estimator-res-desc").text("到達レーティング") estimator_state = 1; } else { $("#estimator-input-desc").text("目標レーティング") $("#estimator-res-desc").text("必要パフォーマンス") estimator_state = 0; } var val = $("#estimator-res").val(); $("#estimator-res").val($("#estimator-input").val()) $("#estimator-input").val(val) updateTweetBtn() }) 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 dom = `
目標レート
必要パフォーマンス
ツイート
` return dom; } })(); //Graph (() => { SideMenu.appendToSideMenu(/$^/, 'Graph', getElem); function getElem() { if (typeof Chart === "undefined") SideMenu.appendLibrary("//cdnjs.cloudflare.com/ajax/libs/Chart.js/2.1.4/Chart.min.js"); var canvasScript = `// (() => { //カスタム背景色が指定可能なぐらふ Chart.defaults.customizableBGLine = Chart.defaults.line Chart.controllers.customizableBGLine = Chart.controllers.line.extend({ draw: function(ease) { Chart.controllers.line.prototype.draw.call(this, ease); console.log(this) var meta = this.getMeta(); } }); var ctx = document.getElementById("Graph").getContext('2d'); function drawTable(data) { new Chart(ctx, { type: 'line', data: { labels: data.map(x => x.t) datasets: [ { data: data } ] }, options: { scales: { xAxes: [{ type: 'time', time: { unit: 'month' } }] } } }); } var StandingsURL = \`/users/${userScreenName}/history/json\` $.ajax({ type: 'GET', dataType: 'json', url: StandingsURL }) .done( (data) => { drawTable(data.map(x => {return {t:x.EndTime,y:x.NewRating}})) } ) })(); ` var renderScript = ` ` var dom = `` return dom; } })();