// ==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;
}
})();