mirror of
https://gitcode.com/gh_mirrors/hy/Hydro.git
synced 2026-10-20 01:38:19 +08:00
core: contest.getRawStatus API
This commit is contained in:
1 parent
2cc9c6a06a
commit
68286607e9
5 files changed
+46
-27
No files matched your search
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hydrooj",
|
"name": "hydrooj",
|
||||||
"version": "2.14.4",
|
"version": "2.14.5",
|
||||||
"bin": "bin/hydrooj.js",
|
"bin": "bin/hydrooj.js",
|
||||||
"main": "dist/loader.js",
|
"main": "dist/loader.js",
|
||||||
"typings": "dist/loader.d.ts",
|
"typings": "dist/loader.d.ts",
|
||||||
|
|||||||
@@ -143,6 +143,13 @@ class ContestScoreboardHandler extends ContestHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ContestScoreboardRawHandler extends ContestHandler {
|
||||||
|
@param('tid', Types.ObjectID)
|
||||||
|
async get(domainId: string, tid: ObjectID) {
|
||||||
|
this.response.body = await this.getRawStatus(domainId, tid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
class ContestScoreboardDownloadHandler extends ContestHandler {
|
class ContestScoreboardDownloadHandler extends ContestHandler {
|
||||||
@param('tid', Types.ObjectID)
|
@param('tid', Types.ObjectID)
|
||||||
@param('ext', Types.String)
|
@param('ext', Types.String)
|
||||||
@@ -414,6 +421,7 @@ export async function apply() {
|
|||||||
Route('contest_boardcast', '/contest/:tid/boardcast', ContestBoardcastHandler);
|
Route('contest_boardcast', '/contest/:tid/boardcast', ContestBoardcastHandler);
|
||||||
Route('contest_edit', '/contest/:tid/edit', ContestEditHandler, PERM.PERM_VIEW_CONTEST);
|
Route('contest_edit', '/contest/:tid/edit', ContestEditHandler, PERM.PERM_VIEW_CONTEST);
|
||||||
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST);
|
Route('contest_scoreboard', '/contest/:tid/scoreboard', ContestScoreboardHandler, PERM.PERM_VIEW_CONTEST);
|
||||||
|
Route('contest_scoreboard_raw', '/contest/:tid/scoreboard/raw', ContestScoreboardRawHandler, PERM.PERM_VIEW_CONTEST);
|
||||||
Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardDownloadHandler, PERM.PERM_VIEW_CONTEST);
|
Route('contest_scoreboard_download', '/contest/:tid/export/:ext', ContestScoreboardDownloadHandler, PERM.PERM_VIEW_CONTEST);
|
||||||
Route('contest_detail_problem', '/contest/:tid/p/:pid', ContestProblemHandler, PERM.PERM_VIEW_CONTEST);
|
Route('contest_detail_problem', '/contest/:tid/p/:pid', ContestProblemHandler, PERM.PERM_VIEW_CONTEST);
|
||||||
Route('contest_detail_problem_submit', '/contest/:tid/p/:pid/submit', ContestProblemSubmitHandler, PERM.PERM_VIEW_CONTEST);
|
Route('contest_detail_problem_submit', '/contest/:tid/p/:pid/submit', ContestProblemSubmitHandler, PERM.PERM_VIEW_CONTEST);
|
||||||
|
|||||||
@@ -282,6 +282,7 @@ export interface ScoreboardNode {
|
|||||||
value: string,
|
value: string,
|
||||||
raw?: any,
|
raw?: any,
|
||||||
}
|
}
|
||||||
|
export type ScoreboardRow = ScoreboardNode[] & { raw?: any };
|
||||||
|
|
||||||
export type PenaltyRules = Dictionary<number>;
|
export type PenaltyRules = Dictionary<number>;
|
||||||
|
|
||||||
@@ -423,9 +424,9 @@ export interface ContestRule {
|
|||||||
stat: (tdoc: Tdoc, journal: any[]) => ContestStat,
|
stat: (tdoc: Tdoc, journal: any[]) => ContestStat,
|
||||||
scoreboard: (
|
scoreboard: (
|
||||||
isExport: boolean, _: (s: string) => string,
|
isExport: boolean, _: (s: string) => string,
|
||||||
tdoc: Tdoc, rankedTsdocs: any[] | Generator<any>, udict: Udict, pdict: Pdict
|
tdoc: Tdoc, rankedTsdocs: any[], udict: Udict, pdict: Pdict
|
||||||
) => ScoreboardNode[][],
|
) => ScoreboardRow[],
|
||||||
rank: (tsdocs: any[]) => any[] | Generator<any>,
|
rank: (tsdocs: any[]) => any[],
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ContestRules = Dictionary<ContestRule>;
|
export type ContestRules = Dictionary<ContestRule>;
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
function* ranked<T>(diter: T[], equ = (a: T, b: T) => a === b): Generator<[number, T], void> {
|
function ranked<T>(diter: T[], equ = (a: T, b: T) => a === b): [number, T][] {
|
||||||
let last = null;
|
let last = null;
|
||||||
let r = 0;
|
let r = 0;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
const results: [number, T][] = [];
|
||||||
for (const doc of diter) {
|
for (const doc of diter) {
|
||||||
count++;
|
count++;
|
||||||
if (count === 1 || !equ(last, doc)) r = count;
|
if (count === 1 || !equ(last, doc)) r = count;
|
||||||
last = doc;
|
last = doc;
|
||||||
yield [r, doc];
|
results.push([r, doc]);
|
||||||
}
|
}
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
global.Hydro.lib.rank = ranked;
|
global.Hydro.lib.rank = ranked;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { ObjectID, FilterQuery } from 'mongodb';
|
import { ObjectID, FilterQuery } from 'mongodb';
|
||||||
|
import { filter, sortBy } from 'lodash';
|
||||||
import * as user from './user';
|
import * as user from './user';
|
||||||
import * as problem from './problem';
|
import * as problem from './problem';
|
||||||
import * as document from './document';
|
import * as document from './document';
|
||||||
@@ -8,7 +9,7 @@ import {
|
|||||||
ContestNotAttendedError, ContestScoreboardHiddenError,
|
ContestNotAttendedError, ContestScoreboardHiddenError,
|
||||||
} from '../error';
|
} from '../error';
|
||||||
import {
|
import {
|
||||||
ContestRule, ContestRules, ScoreboardNode, Tdoc, Udict,
|
ContestRule, ContestRules, ScoreboardNode, ScoreboardRow, Tdoc, Udict,
|
||||||
} from '../interface';
|
} from '../interface';
|
||||||
import * as misc from '../lib/misc';
|
import * as misc from '../lib/misc';
|
||||||
import ranked from '../lib/rank';
|
import ranked from '../lib/rank';
|
||||||
@@ -34,25 +35,24 @@ const acm: ContestRule = {
|
|||||||
if (!j.accept) naccept[j.pid] = (naccept[j.pid] || 0) + 1;
|
if (!j.accept) naccept[j.pid] = (naccept[j.pid] || 0) + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function _time(jdoc) {
|
|
||||||
const real = jdoc.rid.generationTime - Math.floor(tdoc.beginAt.getTime() / 1000);
|
|
||||||
const penalty = 20 * 60 * naccept[jdoc.pid];
|
|
||||||
return real + penalty;
|
|
||||||
}
|
|
||||||
for (const key in effective) {
|
for (const key in effective) {
|
||||||
const j = effective[key];
|
const j = effective[key];
|
||||||
|
const real = j.rid.generationTime - Math.floor(tdoc.beginAt.getTime() / 1000);
|
||||||
|
const penalty = 20 * 60 * naccept[j.pid];
|
||||||
detail.push({
|
detail.push({
|
||||||
...j, naccept: naccept[j.pid], time: _time(j),
|
...j, naccept: naccept[j.pid], time: real + penalty, real, penalty,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (const d of detail) {
|
for (const d of detail) {
|
||||||
accept += d.accept;
|
if (d.accept) {
|
||||||
if (d.accept) time += d.time;
|
accept += d.accept;
|
||||||
|
time += d.time;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return { accept, time, detail };
|
return { accept, time, detail };
|
||||||
},
|
},
|
||||||
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
scoreboard(isExport, _, tdoc, rankedTsdocs, udict, pdict) {
|
||||||
const columns: ScoreboardNode[] = [
|
const columns: ScoreboardRow = [
|
||||||
{ type: 'rank', value: _('Rank') },
|
{ type: 'rank', value: _('Rank') },
|
||||||
{ type: 'user', value: _('User') },
|
{ type: 'user', value: _('User') },
|
||||||
{ type: 'solved_problems', value: _('Solved Problems') },
|
{ type: 'solved_problems', value: _('Solved Problems') },
|
||||||
@@ -88,13 +88,11 @@ const acm: ContestRule = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const rows = [columns];
|
const rows: ScoreboardRow[] = [columns];
|
||||||
for (const [rank, tsdoc] of rankedTsdocs) {
|
for (const [rank, tsdoc] of rankedTsdocs) {
|
||||||
const tsddict = {};
|
const tsddict = {};
|
||||||
if (tsdoc.detail) {
|
for (const item of tsdoc.detail || []) tsddict[item.pid] = item;
|
||||||
for (const item of tsdoc.detail) tsddict[item.pid] = item;
|
const row: ScoreboardRow = [];
|
||||||
}
|
|
||||||
const row = [];
|
|
||||||
row.push(
|
row.push(
|
||||||
{ type: 'string', value: rank },
|
{ type: 'string', value: rank },
|
||||||
{ type: 'user', value: udict[tsdoc.uid].uname, raw: tsdoc.uid },
|
{ type: 'user', value: udict[tsdoc.uid].uname, raw: tsdoc.uid },
|
||||||
@@ -134,6 +132,7 @@ const acm: ContestRule = {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
row.raw = tsdoc;
|
||||||
rows.push(row);
|
rows.push(row);
|
||||||
}
|
}
|
||||||
return rows;
|
return rows;
|
||||||
@@ -233,7 +232,7 @@ const homework: ContestRule = {
|
|||||||
if (exceedSeconds < 0) return score;
|
if (exceedSeconds < 0) return score;
|
||||||
let coefficient = 1;
|
let coefficient = 1;
|
||||||
const keys = Object.keys(tdoc.penaltyRules)
|
const keys = Object.keys(tdoc.penaltyRules)
|
||||||
.map((k) => parseFloat(k)).sort((a, b) => a - b);
|
.map(parseFloat).sort((a, b) => a - b);
|
||||||
for (const i of keys) {
|
for (const i of keys) {
|
||||||
if (i * 3600 <= exceedSeconds) coefficient = tdoc.penaltyRules[i];
|
if (i * 3600 <= exceedSeconds) coefficient = tdoc.penaltyRules[i];
|
||||||
else break;
|
else break;
|
||||||
@@ -498,6 +497,17 @@ export const ContestHandlerMixin = (c) => class extends c {
|
|||||||
domainId: string, tid: ObjectID,
|
domainId: string, tid: ObjectID,
|
||||||
isExport = false, docType: 30 | 60 = document.TYPE_CONTEST,
|
isExport = false, docType: 30 | 60 = document.TYPE_CONTEST,
|
||||||
): Promise<[Tdoc, ScoreboardNode[][], Udict]> {
|
): Promise<[Tdoc, ScoreboardNode[][], Udict]> {
|
||||||
|
const {
|
||||||
|
tdoc, udict, pdict, rankedTsdocs,
|
||||||
|
} = await this.getRawStatus(domainId, tid, docType);
|
||||||
|
const rows = RULES[tdoc.rule].scoreboard(
|
||||||
|
isExport, this.translate.bind(this),
|
||||||
|
tdoc, rankedTsdocs, udict, pdict,
|
||||||
|
);
|
||||||
|
return [tdoc, rows, udict];
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRawStatus(domainId: string, tid: ObjectID, docType: 30 | 60 = document.TYPE_CONTEST) {
|
||||||
const tdoc = await get(domainId, tid, docType);
|
const tdoc = await get(domainId, tid, docType);
|
||||||
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
if (!this.canShowScoreboard(tdoc)) throw new ContestScoreboardHiddenError(tid);
|
||||||
const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort).toArray();
|
const tsdocs = await getMultiStatus(domainId, { docId: tid }, docType).sort(RULES[tdoc.rule].statusSort).toArray();
|
||||||
@@ -507,11 +517,9 @@ export const ContestHandlerMixin = (c) => class extends c {
|
|||||||
problem.getList(domainId, tdoc.pids, true),
|
problem.getList(domainId, tdoc.pids, true),
|
||||||
]);
|
]);
|
||||||
const rankedTsdocs = RULES[tdoc.rule].rank(tsdocs);
|
const rankedTsdocs = RULES[tdoc.rule].rank(tsdocs);
|
||||||
const rows = RULES[tdoc.rule].scoreboard(
|
return {
|
||||||
isExport, this.translate.bind(this),
|
tdoc, tsdocs, udict, pdict, rankedTsdocs,
|
||||||
tdoc, rankedTsdocs, udict, pdict,
|
};
|
||||||
);
|
|
||||||
return [tdoc, rows, udict];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// eslint-disable-next-line class-methods-use-this
|
// eslint-disable-next-line class-methods-use-this
|
||||||
|
|||||||
Reference in new issue
Block a user