ac-predictor

コンテスト中にAtCoderのパフォーマンスを予測します

当前为 2019-06-27 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 9);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {

module.exports = jQuery;

/***/ }),
/* 1 */
/***/ (function(module, exports) {

module.exports = usLibs.rating;

/***/ }),
/* 2 */
/***/ (function(module, exports) {

module.exports = usLibs.global;

/***/ }),
/* 3 */
/***/ (function(module, exports) {

module.exports = usLibs.data;

/***/ }),
/* 4 */
/***/ (function(module, exports) {

module.exports = sidemenu;

/***/ }),
/* 5 */
/***/ (function(module, exports) {

module.exports = moment;

/***/ }),
/* 6 */
/***/ (function(module, exports) {

module.exports = "<div id=\"predictor-alert\" class=\"row\"><h5 class='sidemenu-txt'>読み込み中…</h5></div>\n<div id=\"predictor-data\" class=\"row\">\n    <div class=\"input-group col-xs-12\">\n        <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>\n        <input class=\"form-control\" id=\"predictor-input-rank\">\n        <span class=\"input-group-addon\">位</span>\n    </div>\n        \n    <div class=\"input-group col-xs-12\">\n        <span class=\"input-group-addon\">パフォーマンス</span>\n        <input class=\"form-control\" id=\"predictor-input-perf\">\n    </div>\n\n    <div class=\"input-group col-xs-12\">\n        <span class=\"input-group-addon\">レーティング</span>\n        <input class=\"form-control\" id=\"predictor-input-rate\">\n    </div>\n</div>\n<div class=\"row\">\n    <div class=\"btn-group\">\n        <button class=\"btn btn-default\" id=\"predictor-current\">現在の順位</button>\n        <button type=\"button\" class=\"btn btn-primary\" id=\"predictor-reload\" data-loading-text=\"更新中…\">更新</button>\n        <!--<button class=\"btn btn-default\" id=\"predictor-solved\" disabled>現問題AC後</button>-->\n    </div>\n</div>";

/***/ }),
/* 7 */
/***/ (function(module, exports) {

module.exports = usLibs.contestInformation;

/***/ }),
/* 8 */
/***/ (function(module, exports) {

module.exports = "<div id=\"estimator-alert\"></div>\n<div class=\"row\">\n\t<div class=\"input-group\">\n\t\t<span class=\"input-group-addon\" id=\"estimator-input-desc\"></span>\n\t\t<input type=\"number\" class=\"form-control\" id=\"estimator-input\">\n\t</div>\n</div>\n<div class=\"row\">\n\t<div class=\"input-group\">\n\t\t<span class=\"input-group-addon\" id=\"estimator-res-desc\"></span>\n\t\t<input class=\"form-control\" id=\"estimator-res\" disabled=\"disabled\">\n\t\t<span class=\"input-group-btn\">\n\t\t\t<button class=\"btn btn-default\" id=\"estimator-toggle\">入替</button>\n\t\t</span>\n\t</div>\n</div>\n<div class=\"row\" style=\"margin: 10px 0px;\">\n\t<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>\n</div>";

/***/ }),
/* 9 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
__webpack_require__.r(__webpack_exports__);

// EXTERNAL MODULE: external "sidemenu"
var external_sidemenu_ = __webpack_require__(4);

// EXTERNAL MODULE: external "jQuery"
var external_jQuery_ = __webpack_require__(0);

// EXTERNAL MODULE: ./src/elements/predictor/dom.html
var dom = __webpack_require__(6);
var dom_default = /*#__PURE__*/__webpack_require__.n(dom);

// EXTERNAL MODULE: external "moment"
var external_moment_ = __webpack_require__(5);
var external_moment_default = /*#__PURE__*/__webpack_require__.n(external_moment_);

// CONCATENATED MODULE: ./src/libs/database/database.js
/**
 * オブジェクト生成用のコンストラクタです
 * @param {Function} [getNewData] 更新の際に新たなデータオブジェクトを返す関数です。
 * @param {string} [lsKey] 保存に用いるローカルストレージのkeyです。
 * @param {Function} [onUpdate] 更新の際に呼ばれる関数です。
 */
class DataBase {
    /**
     * オブジェクト生成用のコンストラクタです
     * @param {string} [name] indexedDBにアクセスする際に用いる名前です。
     * @param {Number} [version] indexedDBにアクセスする際に用いるバージョンです。
     */
    constructor(name, version, update) {
        this.name = name;
        this.version = version;
        indexedDB.open(name, version).onupgradeneeded = update;
    }

    /**
     * データをデータベースに追加/更新します。
     * @param {string} [storeName] indexedDBからストアを取得する際の名前です。
     * @param {string} [key] ストアにセットする際に用いるkeyです。
     * @param {Object} [value] ストアにセットする値です。
     * @returns {Promise} 非同期のpromiseです。
     */
    async setData(storeName, key, value) {
        return new Promise((resolve, reject) => {
            try {
                indexedDB.open(this.name).onsuccess = e => {
                    const db = e.target.result;
                    const trans = db.transaction(storeName, "readwrite");
                    const objStore = trans.objectStore(storeName);
                    const data = { id: key, data: value };
                    const putReq = objStore.put(data);
                    putReq.onsuccess = () => {
                        db.close();
                        resolve();
                    };
                };
            } catch (e) {
                reject(e);
            }
        });
    }

    /**
     * データをデータベースから取得します。存在しなかった場合はrejectされます。
     * @param {string} [storeName] indexedDBからストアを取得する際の名前です。
     * @param {string} [key] ストアにセットする際に用いるkeyです。
     * @returns {Promise} 非同期のpromiseです。
     */
    async getData(storeName, key) {
        return new Promise((resolve, reject) => {
            try {
                indexedDB.open(this.name).onsuccess = openEvent => {
                    const db = openEvent.target.result;
                    const trans = db.transaction(storeName, "readwrite");
                    const objStore = trans.objectStore(storeName);
                    objStore.get(key).onsuccess = getEvent => {
                        const result = getEvent.target.result;
                        db.close();
                        if (!result)
                            reject(
                                `key '${key}' not found in store '${storeName}'`
                            );
                        else resolve(result.data);
                    };
                };
            } catch (e) {
                reject(e);
            }
        });
    }
}

// CONCATENATED MODULE: ./src/libs/database/predictorDB.js


const StoreKeys = { aperfs: "APerfs", standings: "Standings" };
class predictorDB_PredictorDB extends DataBase {
    constructor() {
        super("PredictorDB", 1, event => {
            const db = event.target.result;
            const storeNames = ["APerfs", "Standings"];
            storeNames.forEach(store => {
                db.createObjectStore(store, { keyPath: "id" });
            });
        });
    }
}

// CONCATENATED MODULE: ./src/libs/contest/results/result.js
class Result {
    /***
     * @param {boolean} isRated
     * @param {boolean} isSubmitted
     * @param {string} userScreenName
     * @param {number} performance
     * @param {number} place
     * @param {number} ratedRank
     * @param {number} competitions
     * @param {number} innerPerformance
     * @param {number} oldRating
     * @param {number} newRating
     */
    constructor(
        isRated,
        isSubmitted,
        userScreenName,
        place,
        ratedRank,
        oldRating,
        newRating,
        competitions,
        performance,
        innerPerformance
    ) {
        this.IsRated = isRated;
        this.IsSubmitted = isSubmitted;
        this.UserScreenName = userScreenName;
        this.Place = place;
        this.RatedRank = ratedRank;
        this.OldRating = oldRating;
        this.NewRating = newRating;
        this.Competitions = competitions;
        this.Performance = performance;
        this.InnerPerformance = innerPerformance;
    }
}

// CONCATENATED MODULE: ./src/libs/contest/contest.js


class contest_Contest {
    constructor(contestScreenName, contestInformation, standings, aPerfs) {
        this.ratedLimit = contestInformation.RatedRange[1] + 1;
        this.perfLimit = this.ratedLimit + 400;
        this.standings = standings;
        this.aPerfs = aPerfs;
        this.rankMemo = {};

        const analyzedData = analyzeStandingsData(
            standings.Fixed,
            standings.StandingsData,
            aPerfs,
            { 2000: 800, 2800: 1000, Infinity: 1200 }[this.ratedLimit] || 1200,
            this.ratedLimit
        );
        this.contestantAPerf = analyzedData.contestantAPerf;
        this.templateResults = analyzedData.templateResults;
        this.IsRated = analyzedData.isRated;

        /** @return {{contestantAPerf: number[], templateResults: Object<string, Result>, isRated: boolean}} */
        function analyzeStandingsData(
            fixed,
            standingsData,
            aPerfs,
            defaultAPerf,
            ratedLimit
        ) {
            let analyzedData = analyze(
                data => data.IsRated && data.TotalResult.Count !== 0
            );
            analyzedData.isRated = true;
            if (analyzedData.contestantAPerf.length === 0) {
                analyzedData = analyze(
                    data =>
                        data.OldRating < ratedLimit &&
                        data.TotalResult.Count !== 0
                );
                analyzedData.isRated = false;
            }
            return analyzedData;

            /** @return {{contestantAPerf: number[], templateResults: Object.<string, Result>}}*/
            function analyze(isUserRated) {
                let contestantAPerf = [];
                let templateResults = {};

                let currentRatedRank = 1;

                let lastRank = 0;
                let tiedUsers = [];
                let ratedInTiedUsers = 0;
                function applyTiedUsers() {
                    tiedUsers.forEach(data => {
                        if (isUserRated(data)) {
                            contestantAPerf.push(
                                aPerfs[data.UserScreenName] || defaultAPerf
                            );
                            ratedInTiedUsers++;
                        }
                    });

                    let ratedRank =
                        currentRatedRank +
                        Math.max(0, ratedInTiedUsers - 1) / 2;
                    tiedUsers.forEach(data => {
                        templateResults[data.UserScreenName] = new Result(
                            isUserRated(data),
                            data.TotalResult.Count !== 0,
                            data.UserScreenName,
                            data.Rank,
                            ratedRank,
                            fixed ? data.OldRating : data.Rating,
                            null,
                            data.Competitions,
                            null,
                            null
                        );
                    });
                    currentRatedRank += ratedInTiedUsers;
                    tiedUsers.length = 0;
                    ratedInTiedUsers = 0;
                }

                standingsData.forEach(data => {
                    if (lastRank !== data.Rank) applyTiedUsers();
                    lastRank = data.Rank;
                    tiedUsers.push(data);
                });
                applyTiedUsers();

                return {
                    contestantAPerf: contestantAPerf,
                    templateResults: templateResults
                };
            }
        }
    }

    getRatedRank(X) {
        if (this.rankMemo[X]) return this.rankMemo[X];
        return (this.rankMemo[X] = this.contestantAPerf.reduce(
            (val, APerf) =>
                val + 1.0 / (1.0 + Math.pow(6.0, (X - APerf) / 400.0)),
            0
        ));
    }

    getPerf(ratedRank) {
        return Math.min(this.getInnerPerf(ratedRank), this.perfLimit);
    }

    getInnerPerf(ratedRank) {
        let upper = 6144;
        let lower = -2048;
        while (upper - lower > 0.5) {
            const mid = (upper + lower) / 2;
            if (ratedRank - 0.5 > this.getRatedRank(mid)) upper = mid;
            else lower = mid;
        }
        return Math.round((upper + lower) / 2);
    }
}

// CONCATENATED MODULE: ./src/libs/contest/results/results.js
class Results {
    constructor() {}
    /**
     * @param {string} userScreenName
     * @return {Result}
     */
    getUserResult(userScreenName) {}
}

// EXTERNAL MODULE: external "usLibs.rating"
var external_usLibs_rating_ = __webpack_require__(1);

// CONCATENATED MODULE: ./src/libs/contest/results/standingsResults.js



class standingsResults_OnDemandResults extends Results {
    /**
     * @param {Contest} contest
     * @param {Results[]} templateResults
     */
    constructor(contest, templateResults) {
        super();
        this.Contest = contest;
        this.TemplateResults = templateResults;
    }
    /**
     * @param {string} userScreenName
     * @return {Result}
     */
    getUserResult(userScreenName) {
        const baseResults = this.TemplateResults[userScreenName];
        if (!baseResults) return null;
        if (!baseResults.Performance) {
            baseResults.InnerPerformance = this.Contest.getInnerPerf(
                baseResults.RatedRank
            );
            baseResults.Performance = Math.min(
                baseResults.InnerPerformance,
                this.Contest.perfLimit
            );
            baseResults.NewRating = Math.round(
                Object(external_usLibs_rating_["positivizeRating"])(
                    Object(external_usLibs_rating_["calcRatingFromLast"])(
                        Object(external_usLibs_rating_["unpositivizeRating"])(baseResults.OldRating),
                        baseResults.Performance,
                        baseResults.Competitions
                    )
                )
            );
        }
        return baseResults;
    }
}

// CONCATENATED MODULE: ./src/libs/contest/results/fIxedResults.js


class fIxedResults_FixedResults extends Results {
    /**
     * @param {Result[]} results
     */
    constructor(results) {
        super();
        this.resultsDic = {};
        results.forEach(result => {
            this.resultsDic[result.UserScreenName] = result;
        });
    }
    /**
     * @param {string} userScreenName
     * @return {Result}
     */
    getUserResult(userScreenName) {
        return this.resultsDic[userScreenName] || null;
    }
}

// CONCATENATED MODULE: ./src/elements/predictor/model/PredictorModel.js
class PredictorModel {
    /**
     * @param {PredictorModel} [model]
     */
    constructor(model) {
        this.enabled = model.enabled;
        this.contest = model.contest;
        this.history = model.history;
        this.updateInformation(model.information);
        this.updateData(model.rankValue, model.perfValue, model.rateValue);
    }

    /**
     * @param {boolean} state
     */
    setEnable(state) {
        this.enabled = state;
    }

    /**
     * @param {string} information
     */
    updateInformation(information) {
        this.information = information;
    }

    /**
     * @param {number} rankValue
     * @param {number} perfValue
     * @param {number} rateValue
     */
    updateData(rankValue, perfValue, rateValue) {
        this.rankValue = rankValue;
        this.perfValue = perfValue;
        this.rateValue = rateValue;
    }
}

// CONCATENATED MODULE: ./src/elements/predictor/model/calcFromRankModel.js



class calcFromRankModel_CalcFromRankModel extends PredictorModel {
    updateData(rankValue, perfValue, rateValue) {
        perfValue = this.contest.getPerf(rankValue);
        rateValue = Object(external_usLibs_rating_["positivizeRating"])(
            Object(external_usLibs_rating_["calcRatingFromHistory"])([perfValue].concat(this.history))
        );
        super.updateData(rankValue, perfValue, rateValue);
    }
}

// CONCATENATED MODULE: ./src/elements/predictor/model/calcFromPerfModel.js



class calcFromPerfModel_CalcFromPerfModel extends PredictorModel {
    updateData(rankValue, perfValue, rateValue) {
        rankValue = this.contest.getRatedRank(perfValue);
        rateValue = Object(external_usLibs_rating_["positivizeRating"])(
            Object(external_usLibs_rating_["calcRatingFromHistory"])([perfValue].concat(this.history))
        );
        super.updateData(rankValue, perfValue, rateValue);
    }
}

// CONCATENATED MODULE: ./src/elements/predictor/model/calcFromRateModel.js



class calcFromRateModel_CalcFromRateModel extends PredictorModel {
    updateData(rankValue, perfValue, rateValue) {
        perfValue = Object(external_usLibs_rating_["calcRequiredPerformance"])(
            Object(external_usLibs_rating_["unpositivizeRating"])(rateValue),
            this.history
        );
        rankValue = this.contest.getRatedRank(perfValue);
        super.updateData(rankValue, perfValue, rateValue);
    }
}

// CONCATENATED MODULE: ./src/libs/utils/roundValue.js
function roundValue(value, digit) {
    return Math.round(value * 10 ** digit) / 10 ** digit;
}

// EXTERNAL MODULE: external "usLibs.data"
var external_usLibs_data_ = __webpack_require__(3);

// EXTERNAL MODULE: external "usLibs.global"
var external_usLibs_global_ = __webpack_require__(2);

// EXTERNAL MODULE: external "usLibs.contestInformation"
var external_usLibs_contestInformation_ = __webpack_require__(7);

// CONCATENATED MODULE: ./src/elements/predictor/script.js



















let predictor = new external_sidemenu_["SideMenuElement"](
    "predictor",
    "Predictor",
    /atcoder.jp\/contests\/.+/,
    dom_default.a,
    afterAppend
);

const firstContestDate = external_moment_default()("2016-07-16 21:00");
const predictorElements = [
    "predictor-input-rank",
    "predictor-input-perf",
    "predictor-input-rate",
    "predictor-current",
    "predictor-reload",
    "predictor-tweet"
];
const aPerfUpdatedTimeKey = "predictor-aperf-last-updated";
const updateDuration = 10 * 60 * 1000;

async function afterAppend() {
    const isStandingsPage = /standings([^/]*)?$/.test(document.location.href);
    const predictorDB = new predictorDB_PredictorDB();
    const contestInformation = await Object(external_usLibs_contestInformation_["fetchContestInformation"])(external_usLibs_global_["contestScreenName"]);

    /** @type Results */
    let results;

    /** @type Contest */
    let contest;

    /** @type PredictorModel */
    let model = new PredictorModel({
        rankValue: 0,
        perfValue: 0,
        rateValue: 0,
        enabled: false,
        history: Object(external_usLibs_data_["getPerformanceHistories"])(await Object(external_usLibs_data_["getMyHistoryData"])())
    });

    external_jQuery_('[data-toggle="tooltip"]').tooltip();

    if (!shouldEnabledPredictor().verdict) {
        model.updateInformation(shouldEnabledPredictor().message);
        updateView();
        return;
    }

    try {
        await initPredictor();
    } catch (e) {
        model.updateInformation(e.message);
        model.setEnable(false);
        updateView();
    }

    subscribeEvents();

    function subscribeEvents() {
        external_jQuery_("#predictor-reload").click(async () => {
            model.updateInformation("読み込み中…");
            external_jQuery_("#predictor-reload").button("loading");
            updateView();
            await updateStandingsFromAPI();
            external_jQuery_("#predictor-reload").button("reset");
            updateView();
        });
        external_jQuery_("#predictor-current").click(function() {
            const myResult = contest.templateResults[external_usLibs_global_["userScreenName"]];
            if (!myResult) return;
            model = new calcFromRankModel_CalcFromRankModel(model);
            model.updateData(
                myResult.RatedRank,
                model.perfValue,
                model.rateValue
            );
            updateView();
        });
        external_jQuery_("#predictor-input-rank").keyup(function() {
            const inputString = external_jQuery_("#predictor-input-rank").val();
            if (!isFinite(inputString)) return;
            const inputNumber = parseInt(inputString);
            model = new calcFromRankModel_CalcFromRankModel(model);
            model.updateData(inputNumber, 0, 0);
            updateView();
        });
        external_jQuery_("#predictor-input-perf").keyup(function() {
            const inputString = external_jQuery_("#predictor-input-perf").val();
            if (!isFinite(inputString)) return;
            const inputNumber = parseInt(inputString);
            model = new calcFromPerfModel_CalcFromPerfModel(model);
            model.updateData(0, inputNumber, 0);
            updateView();
        });
        external_jQuery_("#predictor-input-rate").keyup(function() {
            const inputString = external_jQuery_("#predictor-input-rate").val();
            if (!isFinite(inputString)) return;
            const inputNumber = parseInt(inputString);
            model = new calcFromRateModel_CalcFromRateModel(model);
            model.updateData(0, 0, inputNumber);
            updateView();
        });
    }

    async function initPredictor() {
        let aPerfs;
        let standings;

        try {
            standings = await Object(external_usLibs_data_["getStandingsData"])(external_usLibs_global_["contestScreenName"]);
        } catch (e) {
            throw new Error("順位表の取得に失敗しました。");
        }

        try {
            const lastUpdated = Object(external_usLibs_global_["getLS"])(aPerfUpdatedTimeKey);
            const now = Date.now();
            aPerfs = await (standings.Fixed ||
            now - lastUpdated <= updateDuration
                ? getAPerfsFromLocalData().catch(() => getAPerfsFromAPI())
                : getAPerfsFromAPI().catch(() => getAPerfsFromLocalData()));
        } catch (e) {
            throw new Error("APerfの取得に失敗しました。");
        }

        async function getAPerfsFromAPI() {
            Object(external_usLibs_global_["setLS"])(aPerfUpdatedTimeKey, Date.now());
            return await Object(external_usLibs_data_["getAPerfsData"])(external_usLibs_global_["contestScreenName"]);
        }
        async function getAPerfsFromLocalData() {
            return await predictorDB.getData("APerfs", external_usLibs_global_["contestScreenName"]);
        }

        await updateData(aPerfs, standings);
        model.setEnable(true);
        model.updateInformation(`最終更新 : ${external_moment_default()().format("HH:mm:ss")}`);

        if (isStandingsPage) {
            external_jQuery_("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>'
            );
            new MutationObserver(addPerfToStandings).observe(
                document.getElementById("standings-tbody"),
                { childList: true }
            );
            new MutationObserver(async mutationRecord => {
                const isDisabled = mutationRecord[0].target.classList.contains(
                    "disabled"
                );
                if (isDisabled) {
                    await updateStandingsFromAPI();
                }
            }).observe(document.getElementById("refresh"), {
                attributes: true,
                attributeFilter: ["class"]
            });
        }
        updateView();
    }

    async function updateStandingsFromAPI() {
        try {
            const shouldEnabled = shouldEnabledPredictor();
            if (!shouldEnabled.verdict) throw new Error(shouldEnabled.message);
            const standings = await Object(external_usLibs_data_["getStandingsData"])(external_usLibs_global_["contestScreenName"]);
            await updateData(contest.aPerfs, standings);
            model.updateInformation(
                `最終更新 : ${external_moment_default()().format("HH:mm:ss")}`
            );
            model.setEnable(true);
        } catch (e) {
            model.updateInformation(e.message);
            model.setEnable(false);
        }
    }

    async function updateData(aperfs, standings) {
        if (Object.keys(aperfs).length === 0) {
            throw new Error("APerfのデータが提供されていません");
        }
        contest = new contest_Contest(
            external_usLibs_global_["contestScreenName"],
            contestInformation,
            standings,
            aperfs
        );
        model.contest = contest;
        await updateResultsData();
    }

    function updateView() {
        const roundedRankValue = isFinite(model.rankValue)
            ? roundValue(model.rankValue, 2)
            : "";
        const roundedPerfValue = isFinite(model.perfValue)
            ? roundValue(model.perfValue, 2)
            : "";
        const roundedRateValue = isFinite(model.rateValue)
            ? roundValue(model.rateValue, 2)
            : "";
        external_jQuery_("#predictor-input-rank").val(roundedRankValue);
        external_jQuery_("#predictor-input-perf").val(roundedPerfValue);
        external_jQuery_("#predictor-input-rate").val(roundedRateValue);

        external_jQuery_("#predictor-alert").html(
            `<h5 class='sidemenu-txt'>${model.information}</h5>`
        );

        if (model.enabled) enabled();
        else disabled();

        if (isStandingsPage) {
            addPerfToStandings();
        }
        function enabled() {
            external_jQuery_("#predictor-reload").button("reset");
            predictorElements.forEach(element => {
                external_jQuery_(`#${element}`).removeAttr("disabled");
            });
        }
        function disabled() {
            external_jQuery_("#predictor-reload").button("reset");
            predictorElements.forEach(element => {
                external_jQuery_(`#${element}`).attr("disabled", true);
            });
        }
    }

    function shouldEnabledPredictor() {
        if (!external_usLibs_global_["startTime"].isBefore())
            return { verdict: false, message: "コンテストは始まっていません" };
        if (external_moment_default()(external_usLibs_global_["startTime"]) < firstContestDate)
            return {
                verdict: false,
                message: "現行レートシステム以前のコンテストです"
            };
        if (contestInformation.RatedRange[0] > contestInformation.RatedRange[1])
            return {
                verdict: false,
                message: "ratedなコンテストではありません"
            };
        return { verdict: true, message: "" };
    }

    //全員の結果データを更新する
    async function updateResultsData() {
        if (contest.standings.Fixed && contest.IsRated) {
            let rawResult = await Object(external_usLibs_data_["getResultsData"])(external_usLibs_global_["contestScreenName"]);
            rawResult.sort((a, b) =>
                a.Place !== b.Place
                    ? a.Place - b.Place
                    : b.OldRating - a.OldRating
            );
            let sortedStandingsData = Array.from(
                contest.standings.StandingsData
            ).filter(x => x.TotalResult.Count !== 0);
            sortedStandingsData.sort((a, b) =>
                a.TotalResult.Count === 0 && b.TotalResult.Count === 0
                    ? 0
                    : a.TotalResult.Count === 0
                    ? 1
                    : b.TotalResult.Count === 0
                    ? -1
                    : a.Rank !== b.Rank
                    ? a.Rank - b.Rank
                    : b.OldRating !== a.OldRating
                    ? b.OldRating - a.OldRating
                    : a.UserIsDeleted
                    ? -1
                    : b.UserIsDeleted
                    ? 1
                    : 0
            );

            let lastPerformance = contest.perfLimit;
            let deletedCount = 0;
            results = new fIxedResults_FixedResults(
                sortedStandingsData.map((data, index) => {
                    let result = rawResult[index - deletedCount];
                    if (!result || data.OldRating !== result.OldRating) {
                        deletedCount++;
                        result = null;
                    }
                    return new Result(
                        result ? result.IsRated : false,
                        data.TotalResult.Count !== 0,
                        data.UserScreenName,
                        data.Rank,
                        -1,
                        data.OldRating,
                        result ? result.NewRating : 0,
                        0,
                        result && result.IsRated
                            ? (lastPerformance = result.Performance)
                            : lastPerformance,
                        result ? result.InnerPerformance : 0
                    );
                })
            );
        } else {
            results = new standingsResults_OnDemandResults(contest, contest.templateResults);
        }
    }

    //結果データを順位表に追加する
    function addPerfToStandings() {
        external_jQuery_(".standings-perf , .standings-rate").remove();

        external_jQuery_("#standings-tbody > tr").each((index, elem) => {
            if (elem.firstElementChild.textContent === "-") {
                let longCell = elem.getElementsByClassName(
                    "standings-result"
                )[0];
                longCell.setAttribute(
                    "colspan",
                    parseInt(longCell.getAttribute("colspan")) + 2
                );
                return;
            }
            const result = results
                ? results.getUserResult(
                      external_jQuery_(".standings-username .username", elem).text()
                  )
                : null;
            const perfElem =
                !result || !result.IsSubmitted
                    ? "-"
                    : getRatingSpan(result.Performance);
            const rateElem = !result
                ? "-"
                : result.IsRated && contest.IsRated
                ? getRatingChangeElem(result.OldRating, result.NewRating)
                : getUnratedElem(result.OldRating);
            external_jQuery_(elem).append(
                `<td class="standings-result standings-perf">${perfElem}</td>`
            );
            external_jQuery_(elem).append(
                `<td class="standings-result standings-rate">${rateElem}</td>`
            );
            function getRatingChangeElem(oldRate, newRate) {
                return `<span class="bold">${getRatingSpan(
                    oldRate
                )}</span> → <span class="bold">${getRatingSpan(
                    newRate
                )}</span> <span class="grey">(${
                    newRate >= oldRate ? "+" : ""
                }${newRate - oldRate})</span>`;
            }
            function getUnratedElem(rate) {
                return `<span class="bold">${getRatingSpan(
                    rate
                )}</span> <span class="grey">(unrated)</span>`;
            }
            function getRatingSpan(rate) {
                return `<span class="user-${Object(external_usLibs_rating_["getColor"])(rate)}">${rate}</span>`;
            }
        });
    }
}

// EXTERNAL MODULE: ./src/elements/estimator/dom.html
var estimator_dom = __webpack_require__(8);
var estimator_dom_default = /*#__PURE__*/__webpack_require__.n(estimator_dom);

// CONCATENATED MODULE: ./src/elements/estimator/model/EstimatorModel.js
class EstimatorModel {
    constructor(inputValue, perfHistory) {
        this.inputDesc = "";
        this.resultDesc = "";
        this.perfHistory = perfHistory;
        this.updateInput(inputValue);
    }

    updateInput(value) {
        this.inputValue = value;
        this.resultValue = this.calcResult(value);
    }

    toggle() {}

    /**
     * @param {Number} [input]
     * @return {Number}
     */
    calcResult(input) {
        return input;
    }
}

// CONCATENATED MODULE: ./src/elements/estimator/model/CalcRatingModel.js




class CalcRatingModel_CalcRatingModel extends EstimatorModel {
    constructor(inputValue, perfHistory) {
        super(inputValue, perfHistory);
        this.inputDesc = "パフォーマンス";
        this.resultDesc = "到達レーティング";
    }

    toggle() {
        return new CalcPerfModel_CalcPerfModel(this.resultValue, this.perfHistory);
    }
    calcResult(input) {
        return Object(external_usLibs_rating_["positivizeRating"])(
            Object(external_usLibs_rating_["calcRatingFromHistory"])([input].concat(this.perfHistory))
        );
    }
}

// CONCATENATED MODULE: ./src/elements/estimator/model/CalcPerfModel.js




class CalcPerfModel_CalcPerfModel extends EstimatorModel {
    constructor(inputValue, perfHistory) {
        super(inputValue, perfHistory);
        this.inputDesc = "目標レーティング";
        this.resultDesc = "必要パフォーマンス";
    }

    toggle() {
        return new CalcRatingModel_CalcRatingModel(this.resultValue, this.perfHistory);
    }
    calcResult(input) {
        return Object(external_usLibs_rating_["calcRequiredPerformance"])(
            Object(external_usLibs_rating_["unpositivizeRating"])(input),
            this.perfHistory
        );
    }
}

// CONCATENATED MODULE: ./src/libs/utils/twitter.js
/**
 *
 * @param {string} [content]
 * @param {string} [url]
 * @return {string}
 */
function GetEmbedTweetLink(content, url) {
    return `https://twitter.com/share?text=${encodeURI(
        content
    )}&url=${encodeURI(url)}`;
}

// CONCATENATED MODULE: ./src/elements/estimator/script.js










let estimator = new external_sidemenu_["SideMenuElement"](
    "estimator",
    "Estimator",
    /atcoder.jp/,
    estimator_dom_default.a,
    script_afterAppend
);

async function script_afterAppend() {
    const estimatorInputSelector = external_jQuery_("#estimator-input");
    const estimatorResultSelector = external_jQuery_("#estimator-res");
    let model = GetModelFromStateCode(
        Object(external_usLibs_global_["getLS"])("sidemenu_estimator_state"),
        Object(external_usLibs_global_["getLS"])("sidemenu_estimator_value"),
        Object(external_usLibs_data_["getPerformanceHistories"])(await Object(external_usLibs_data_["getMyHistoryData"])())
    );
    updateView();

    external_jQuery_("#estimator-toggle").click(function() {
        model = model.toggle();
        updateLocalStorage();
        updateView();
    });
    estimatorInputSelector.keyup(() => {
        updateModel();
        updateLocalStorage();
        updateView();
    });

    /** modelをinputの値に応じて更新 */
    function updateModel() {
        const inputString = estimatorInputSelector.val();
        if (!isFinite(inputString)) return;
        const inputNumber = parseInt(inputString);
        model.updateInput(inputNumber);
    }

    /** modelの状態をLSに保存 */
    function updateLocalStorage() {
        Object(external_usLibs_global_["setLS"])("sidemenu_estimator_value", model.inputValue);
        Object(external_usLibs_global_["setLS"])("sidemenu_estimator_state", model.constructor.name);
    }

    /** modelを元にviewを更新 */
    function updateView() {
        const roundedInput = roundValue(model.inputValue, 2);
        const roundedResult = roundValue(model.resultValue, 2);

        external_jQuery_("#estimator-input-desc").text(model.inputDesc);
        external_jQuery_("#estimator-res-desc").text(model.resultDesc);
        estimatorInputSelector.val(roundedInput);
        estimatorResultSelector.val(roundedResult);

        const tweetStr = `AtCoderのハンドルネーム: ${external_usLibs_global_["userScreenName"]}\n${model.inputDesc}: ${roundedInput}\n${model.resultDesc}: ${roundedResult}\n`;
        external_jQuery_("#estimator-tweet").attr(
            "href",
            GetEmbedTweetLink(
                tweetStr,
                "https://greasyfork.org/ja/scripts/369954-ac-predictor"
            )
        );
    }
}

const models = [CalcPerfModel_CalcPerfModel, CalcRatingModel_CalcRatingModel];

/**
 * LocalStorageに保存されたステートコードから状態を復元します
 * @param {string} [state] ステートを示す文字列(型名)
 * @param {number} [value] 最初に入る値
 * @param {number[]} [history] パフォーマンス履歴(時間降順)
 * @return {EstimatorModel} 構築されたモデル
 */
function GetModelFromStateCode(state, value, history) {
    let model = models.find(model => model.name === state);
    if (!model) model = CalcPerfModel_CalcPerfModel;
    return new model(value, history);
}

// CONCATENATED MODULE: ./src/main.js
// ==UserScript==
// @name        ac-predictor
// @namespace   http://ac-predictor.azurewebsites.net/
// @version     1.2.5
// @description コンテスト中にAtCoderのパフォーマンスを予測します
// @author      keymoon
// @license     MIT
// @require     https://greasyfork.org/scripts/386715-atcoder-sidemenu/code/atcoder-sidemenu.js
// @require     https://greasyfork.org/scripts/386712-atcoder-userscript-libs/code/atcoder-userscript-libs.js
// @supportURL  https://github.com/key-moon/ac-predictor.user.js/issues
// @match       https://atcoder.jp/*
// @exclude     https://atcoder.jp/*/json
// ==/UserScript==





external_sidemenu_["sidemenu"].addElement(predictor);
external_sidemenu_["sidemenu"].addElement(estimator);


/***/ })
/******/ ]);