From 4aa1c9fe1f169a28abf16ec5b62e1377a76a77b1 Mon Sep 17 00:00:00 2001 From: masnn Date: Sun, 7 Jun 2020 15:38:38 +0800 Subject: [PATCH] =?UTF-8?q?rating=E7=B3=BB=E7=BB=9F=EF=BC=88=E6=B5=8B?= =?UTF-8?q?=E8=AF=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hydro/development.js | 11 ++++++ hydro/handler/manage.js | 1 - hydro/handler/record.js | 1 - hydro/lib/rating.js | 88 +++++++++++++++++++++++++++++++++++++++++ hydro/loader.js | 10 ++++- hydro/model/contest.js | 3 +- hydro/script/rating.js | 22 +++++++++++ 7 files changed, 131 insertions(+), 5 deletions(-) create mode 100644 hydro/lib/rating.js create mode 100644 hydro/script/rating.js diff --git a/hydro/development.js b/hydro/development.js index 815948057..814409941 100644 --- a/hydro/development.js +++ b/hydro/development.js @@ -8,6 +8,17 @@ process.stdin.on('data', async (input) => { console.warn(e); } }); +process.on('unhandledRejection', (e) => console.log(e)); + +global.onDestory = []; +async function terminate() { + for (const task of global.onDestory) { + // eslint-disable-next-line no-await-in-loop + await task(); + } + process.exit(0); +} +process.on('SIGINT', terminate); require('./loader').load().catch((e) => { console.error(e); diff --git a/hydro/handler/manage.js b/hydro/handler/manage.js index 467c6bc92..7e73d73cd 100644 --- a/hydro/handler/manage.js +++ b/hydro/handler/manage.js @@ -81,7 +81,6 @@ class SystemSettingHandler extends SystemHandler { const s = setting.SYSTEM_SETTINGS_BY_KEY[`${key}.${sub}`]; if (s) { if (s.ui === 'number') args[key][sub] = Number(args[key][sub]); - console.log(s); subtasks.push(system.set(`${key}.${sub}`, args[key][sub])); } } diff --git a/hydro/handler/record.js b/hydro/handler/record.js index a516e473b..43df34f36 100644 --- a/hydro/handler/record.js +++ b/hydro/handler/record.js @@ -25,7 +25,6 @@ class RecordListHandler extends RecordHandler { ]; } const rdocs = await record.getMany(domainId, q, { _id: -1 }, page, await system.get('RECORD_PER_PAGE')); - console.log(rdocs); const [udict, pdict] = await Promise.all([ user.getList(domainId, rdocs.map((rdoc) => rdoc.uid)), problem.getList(domainId, rdocs.map((rdoc) => rdoc.pid), false), diff --git a/hydro/lib/rating.js b/hydro/lib/rating.js new file mode 100644 index 000000000..585b997d7 --- /dev/null +++ b/hydro/lib/rating.js @@ -0,0 +1,88 @@ +class User { + constructor(rank, old, handle = 0) { + this.rank = rank; + this.old = old; + this.seed = 1; + this.handle = handle.toString(); + } +} + +class RatingCalculator { + constructor(users) { + this.users = []; + for (const user of users) { + this.users.push(new User(user.rank, user.old, user.uid)); + } + } + + // eslint-disable-next-line class-methods-use-this + calP(a, b) { + return 1 / (1 + 10 ** ((b.old - a.old) / 400)); + } + + getExSeed(rating, ownUser) { + const exUser = new User(0.0, rating); + let result = 1; + for (const user of this.users) { + if (user !== ownUser) result += this.calP(user, exUser); + } + return result; + } + + calRating(rank, user) { + let left = 1; + let right = 8000; + while (right - left > 1) { + const mid = Math.floor((left + right) / 2); + if (this.getExSeed(mid, user) < rank) right = mid; + else left = mid; + } + return left; + } + + calculate() { + // Calculate seed + for (let i = 0; i < this.users.length; i++) { + this.users[i].seed = 1; + for (let j = 0; j < this.users.length; j++) { + if (i !== j) { + this.users[i].seed += this.calP(this.users[j], this.users[i]); + } + } + } + // Calculate initial delta and sum_delta + let sumDelta = 0; + for (const user of this.users) { + user.delta = (this.calRating(Math.sqrt(user.rank * user.seed), user) - user.old) / 2; + sumDelta += user.delta; + } + // Calculate first inc + let inc = Math.floor(-sumDelta / this.users.length) - 1; + for (const user of this.users) user.delta += inc; + // Calculate second inc + this.users = this.users.sort((a, b) => b.old - a.old); + const s = Math.min( + this.users.length, + Math.floor(4 * Math.round(Math.sqrt(this.users.length))), + ); + let sumS = 0; + for (let i = 0; i < s; i++) { + sumS += this.users[i].delta; + } + inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0); + // Calculate new rating + for (const user of this.users) { + user.delta += inc; + user.new = user.old + user.delta; + } + this.users = this.users.sort((a, b) => a.rank - b.rank); + return this.users; + } +} + +function calculate(users) { + const calculator = new RatingCalculator(users); + return calculator.calculate(); +} + +module.exports = calculate; diff --git a/hydro/loader.js b/hydro/loader.js index 19edfdd2d..8e33a5aeb 100644 --- a/hydro/loader.js +++ b/hydro/loader.js @@ -263,10 +263,16 @@ async function load() { const notfound = require('./handler/notfound'); await notfound.apply(); for (const i in global.Hydro.service) { - if (global.Hydro.service[i].postInit) await global.Hydro.service[i].postInit(); + if (global.Hydro.service[i].postInit) { + try { + await global.Hydro.service[i].postInit(); + } catch (e) { + console.error(e); + } + } } const builtinScript = [ - 'install', 'uninstall', + 'install', 'uninstall', 'rating', ]; for (const i of builtinScript) require(`./script/${i}`); await script(); diff --git a/hydro/model/contest.js b/hydro/model/contest.js index 4576a2b14..bd12e37a7 100644 --- a/hydro/model/contest.js +++ b/hydro/model/contest.js @@ -271,7 +271,6 @@ function getStatus(domainId, tid, uid, type = document.TYPE_CONTEST) { async function getListStatus(domainId, uid, tids, type = document.TYPE_CONTEST) { const r = {}; - console.log(tids); // eslint-disable-next-line no-await-in-loop for (const tid of tids) r[tid] = await getStatus(domainId, tid, uid, type); return r; @@ -392,11 +391,13 @@ async function recalcStatus(domainId, tid, type) { } return await Promise.all(tasks); } + global.Hydro.model.contest = module.exports = { RULES, ContestHandlerMixin, add, getListStatus, + getMultiStatus, attend, edit, get, diff --git a/hydro/script/rating.js b/hydro/script/rating.js new file mode 100644 index 000000000..37ed8de9e --- /dev/null +++ b/hydro/script/rating.js @@ -0,0 +1,22 @@ +const { ObjectID } = require('bson'); +const contest = require('../model/contest'); +const user = require('../model/user'); +const rating = require('../lib/rating'); + +async function run({ domainId, contestId }) { + contestId = new ObjectID(contestId); + const tdoc = await contest.get(domainId, contestId); + const tsdocs = await contest.getMultiStatus(domainId, { docId: contestId }) + .sort(contest.RULES[tdoc.rule].statusSort).toArray(); + const udict = await user.getList(domainId, tsdocs.map((tsdoc) => tsdoc.uid)); + const rankedTsdocs = contest.RULES[tdoc.rule].rank(tsdocs); + const users = []; + for (const result of rankedTsdocs) { + users.push({ ...result[1], rank: result[0], old: udict[result[1].uid].rating || 1500 }); + } + console.log(users); + const rated = rating(users); + console.log(rated); +} + +global.Hydro.script.rating = module.exports = { run };