@xcpcio/core 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +7 -0
- package/dist/index.cjs +716 -0
- package/dist/index.d.ts +162 -0
- package/dist/index.mjs +676 -0
- package/package.json +73 -0
- package/src/contest.ts +191 -0
- package/src/image.ts +13 -0
- package/src/index.ts +9 -0
- package/src/problem.ts +157 -0
- package/src/rank.ts +122 -0
- package/src/resolver-operation.ts +22 -0
- package/src/resolver.ts +120 -0
- package/src/submission-status.ts +151 -0
- package/src/submission.ts +87 -0
- package/src/team.ts +116 -0
- package/src/utils/dayjs.ts +61 -0
- package/src/utils/index.ts +1 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,676 @@
|
|
|
1
|
+
import dayjs from 'dayjs';
|
|
2
|
+
export { default as dayjs } from 'dayjs';
|
|
3
|
+
import duration from 'dayjs/plugin/duration';
|
|
4
|
+
import utc from 'dayjs/plugin/utc';
|
|
5
|
+
import timezone from 'dayjs/plugin/timezone';
|
|
6
|
+
import advancedFormat from 'dayjs/plugin/advancedFormat';
|
|
7
|
+
import isSameOrBefore from 'dayjs/plugin/isSameOrBefore';
|
|
8
|
+
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter';
|
|
9
|
+
import minMax from 'dayjs/plugin/minMax';
|
|
10
|
+
import relativeTime from 'dayjs/plugin/relativeTime';
|
|
11
|
+
import { VERSION, ContestState, SubmissionStatus } from '@xcpcio/types';
|
|
12
|
+
import _ from 'lodash';
|
|
13
|
+
|
|
14
|
+
dayjs.extend(duration);
|
|
15
|
+
dayjs.extend(utc);
|
|
16
|
+
dayjs.extend(timezone);
|
|
17
|
+
dayjs.extend(advancedFormat);
|
|
18
|
+
dayjs.extend(isSameOrBefore);
|
|
19
|
+
dayjs.extend(isSameOrAfter);
|
|
20
|
+
dayjs.extend(minMax);
|
|
21
|
+
dayjs.extend(relativeTime);
|
|
22
|
+
function createDayJS(time = void 0) {
|
|
23
|
+
if (time === void 0) {
|
|
24
|
+
return dayjs();
|
|
25
|
+
}
|
|
26
|
+
if (typeof time == "number" && String(time).length === 10) {
|
|
27
|
+
return dayjs.unix(time);
|
|
28
|
+
}
|
|
29
|
+
return dayjs(time);
|
|
30
|
+
}
|
|
31
|
+
function getTimestamp(time) {
|
|
32
|
+
if (typeof time === "number") {
|
|
33
|
+
return time;
|
|
34
|
+
}
|
|
35
|
+
return time.unix();
|
|
36
|
+
}
|
|
37
|
+
function getTimeDiff(seconds) {
|
|
38
|
+
const two = (a) => {
|
|
39
|
+
if (a < 10)
|
|
40
|
+
return "0" + a;
|
|
41
|
+
return String(a);
|
|
42
|
+
};
|
|
43
|
+
const h = Math.floor(seconds / 3600);
|
|
44
|
+
const m = Math.floor(seconds % 3600 / 60);
|
|
45
|
+
const s = seconds % 60;
|
|
46
|
+
return [two(h), two(m), two(s)].join(":");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
class Problem {
|
|
50
|
+
constructor() {
|
|
51
|
+
this.id = "";
|
|
52
|
+
this.label = "";
|
|
53
|
+
this.name = "";
|
|
54
|
+
this.statistics = {
|
|
55
|
+
acceptedNum: 0,
|
|
56
|
+
rejectedNum: 0,
|
|
57
|
+
pendingNum: 0,
|
|
58
|
+
submittedNum: 0
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function createProblem(problemJSON) {
|
|
63
|
+
const p = new Problem();
|
|
64
|
+
p.id = String(problemJSON.id);
|
|
65
|
+
p.label = problemJSON.label;
|
|
66
|
+
p.name = problemJSON.name ?? "";
|
|
67
|
+
p.timeLimit = problemJSON.time_limit;
|
|
68
|
+
p.memoryLimit = problemJSON.memory_limit;
|
|
69
|
+
p.balloonColor = problemJSON.balloon_color;
|
|
70
|
+
return p;
|
|
71
|
+
}
|
|
72
|
+
function createProblems(problemsJSON) {
|
|
73
|
+
return problemsJSON.map((pJSON) => {
|
|
74
|
+
const p = new Problem();
|
|
75
|
+
p.id = pJSON.id;
|
|
76
|
+
p.label = pJSON.label;
|
|
77
|
+
p.name = pJSON.name ?? "";
|
|
78
|
+
p.timeLimit = pJSON.time_limit;
|
|
79
|
+
p.memoryLimit = pJSON.memory_limit;
|
|
80
|
+
p.balloonColor = pJSON.balloon_color;
|
|
81
|
+
return p;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
function createProblemsByProblemIds(problemIds, balloonColors) {
|
|
85
|
+
const problems = problemIds.map((label, index) => {
|
|
86
|
+
const p = new Problem();
|
|
87
|
+
p.id = String(index);
|
|
88
|
+
p.label = label;
|
|
89
|
+
return p;
|
|
90
|
+
});
|
|
91
|
+
if (balloonColors !== void 0 && balloonColors !== null) {
|
|
92
|
+
for (const index in balloonColors) {
|
|
93
|
+
problems[index].balloonColor = balloonColors[index];
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
return problems;
|
|
97
|
+
}
|
|
98
|
+
class TeamProblemStatistics {
|
|
99
|
+
constructor(options) {
|
|
100
|
+
this.isFirstSolved = options?.teamProblemStatistics?.isFirstSolved ?? false;
|
|
101
|
+
this.isSolved = options?.teamProblemStatistics?.isSolved ?? false;
|
|
102
|
+
this.solvedTimestamp = options?.teamProblemStatistics?.solvedTimestamp ?? 0;
|
|
103
|
+
this.isSubmitted = options?.teamProblemStatistics?.isSubmitted ?? false;
|
|
104
|
+
this.lastSubmitTimestamp = options?.teamProblemStatistics?.lastSubmitTimestamp ?? 0;
|
|
105
|
+
this.failedCount = options?.teamProblemStatistics?.failedCount ?? 0;
|
|
106
|
+
this.pendingCount = options?.teamProblemStatistics?.pendingCount ?? 0;
|
|
107
|
+
this.ignoreCount = options?.teamProblemStatistics?.ignoreCount ?? 0;
|
|
108
|
+
this.totalCount = options?.teamProblemStatistics?.totalCount ?? 0;
|
|
109
|
+
this.submissions = options?.teamProblemStatistics?.submissions ?? [];
|
|
110
|
+
this.problem = options?.teamProblemStatistics?.problem ?? new Problem();
|
|
111
|
+
this.contestPenalty = options?.teamProblemStatistics?.contestPenalty ?? 20 * 60;
|
|
112
|
+
}
|
|
113
|
+
get isAccepted() {
|
|
114
|
+
return this.isSolved;
|
|
115
|
+
}
|
|
116
|
+
get isWrongAnswer() {
|
|
117
|
+
return !this.isSolved && this.pendingCount === 0 && this.failedCount > 0;
|
|
118
|
+
}
|
|
119
|
+
get isPending() {
|
|
120
|
+
return !this.isSolved && this.pendingCount > 0;
|
|
121
|
+
}
|
|
122
|
+
get isUnSubmitted() {
|
|
123
|
+
return this.totalCount === 0;
|
|
124
|
+
}
|
|
125
|
+
get penalty() {
|
|
126
|
+
if (this.isSolved === false) {
|
|
127
|
+
return 0;
|
|
128
|
+
}
|
|
129
|
+
return Math.floor(this.solvedTimestamp / 60) * 60 + this.failedCount * this.contestPenalty;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
class Contest {
|
|
134
|
+
constructor() {
|
|
135
|
+
this.name = "";
|
|
136
|
+
this.version = VERSION;
|
|
137
|
+
this.startTime = createDayJS();
|
|
138
|
+
this.endTime = createDayJS();
|
|
139
|
+
this.freezeTime = createDayJS();
|
|
140
|
+
this.totalDurationTimestamp = 0;
|
|
141
|
+
this.freezeDurationTimestamp = 0;
|
|
142
|
+
this.unFreezeDurationTimestamp = 0;
|
|
143
|
+
this.penalty = 20 * 60;
|
|
144
|
+
this.problems = [];
|
|
145
|
+
this.problemsMap = /* @__PURE__ */ new Map();
|
|
146
|
+
this.statusTimeDisplay = {
|
|
147
|
+
correct: true,
|
|
148
|
+
incorrect: true,
|
|
149
|
+
pending: true
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
getContestDuration(timeFormat = "HH:mm:ss") {
|
|
153
|
+
return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
|
|
154
|
+
}
|
|
155
|
+
getContestState() {
|
|
156
|
+
const now = createDayJS();
|
|
157
|
+
if (now.isBefore(this.startTime)) {
|
|
158
|
+
return ContestState.PENDING;
|
|
159
|
+
}
|
|
160
|
+
if (now.isSameOrAfter(this.endTime)) {
|
|
161
|
+
return ContestState.FINISHED;
|
|
162
|
+
}
|
|
163
|
+
if (now.isSameOrAfter(this.freezeTime)) {
|
|
164
|
+
return ContestState.FROZEN;
|
|
165
|
+
}
|
|
166
|
+
return ContestState.RUNNING;
|
|
167
|
+
}
|
|
168
|
+
getContestPendingTime() {
|
|
169
|
+
let baseTime = createDayJS();
|
|
170
|
+
if (baseTime.isAfter(this.startTime)) {
|
|
171
|
+
baseTime = this.startTime;
|
|
172
|
+
}
|
|
173
|
+
return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
|
|
174
|
+
}
|
|
175
|
+
getContestRemainingTime(endTime) {
|
|
176
|
+
let baseTime = dayjs();
|
|
177
|
+
if (baseTime.isAfter(endTime)) {
|
|
178
|
+
baseTime = endTime;
|
|
179
|
+
}
|
|
180
|
+
return getTimeDiff(Math.floor(dayjs.duration(endTime.diff(baseTime)).asSeconds()));
|
|
181
|
+
}
|
|
182
|
+
getContestElapsedTime() {
|
|
183
|
+
let baseTime = dayjs();
|
|
184
|
+
if (baseTime.isAfter(this.endTime)) {
|
|
185
|
+
baseTime = this.endTime;
|
|
186
|
+
}
|
|
187
|
+
return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
|
|
188
|
+
}
|
|
189
|
+
getContestProgressRatio() {
|
|
190
|
+
const baseTime = dayjs();
|
|
191
|
+
if (this.startTime.isSameOrAfter(baseTime)) {
|
|
192
|
+
return 0;
|
|
193
|
+
}
|
|
194
|
+
if (this.endTime.isSameOrBefore(baseTime)) {
|
|
195
|
+
return 100;
|
|
196
|
+
}
|
|
197
|
+
const total = this.endTime.diff(this.startTime, "s");
|
|
198
|
+
const pass = baseTime.diff(this.startTime, "s");
|
|
199
|
+
return Math.round(pass * 100 / total);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
function createContest(contestJSON) {
|
|
203
|
+
const c = new Contest();
|
|
204
|
+
c.name = contestJSON.contest_name;
|
|
205
|
+
c.startTime = createDayJS(contestJSON.start_time);
|
|
206
|
+
c.endTime = createDayJS(contestJSON.end_time);
|
|
207
|
+
c.totalDurationTimestamp = c.endTime.unix() - c.startTime.unix();
|
|
208
|
+
{
|
|
209
|
+
c.freezeTime = c.endTime;
|
|
210
|
+
c.freezeDurationTimestamp = 0;
|
|
211
|
+
if (contestJSON.frozen_time !== void 0 && contestJSON.frozen_time != null) {
|
|
212
|
+
const frozenTime = Number(contestJSON.frozen_time);
|
|
213
|
+
c.freezeTime = createDayJS(c.endTime.unix() - frozenTime);
|
|
214
|
+
c.freezeDurationTimestamp = frozenTime;
|
|
215
|
+
}
|
|
216
|
+
if (contestJSON.freeze_time !== void 0 && contestJSON.freeze_time !== null) {
|
|
217
|
+
c.freezeTime = createDayJS(contestJSON.freeze_time);
|
|
218
|
+
c.freezeDurationTimestamp = c.endTime.unix() - c.freezeTime.unix();
|
|
219
|
+
}
|
|
220
|
+
c.unFreezeDurationTimestamp = c.totalDurationTimestamp - c.freezeDurationTimestamp;
|
|
221
|
+
}
|
|
222
|
+
c.penalty = contestJSON.penalty;
|
|
223
|
+
{
|
|
224
|
+
if (contestJSON.problem_id !== void 0 && contestJSON.problem_id !== null) {
|
|
225
|
+
c.problems = createProblemsByProblemIds(contestJSON.problem_id, contestJSON.balloon_color);
|
|
226
|
+
}
|
|
227
|
+
if (contestJSON.problems !== void 0 && contestJSON.problems !== null) {
|
|
228
|
+
c.problems = createProblems(contestJSON.problems);
|
|
229
|
+
}
|
|
230
|
+
c.problemsMap = new Map(c.problems.map((p) => [p.id, p]));
|
|
231
|
+
}
|
|
232
|
+
if (contestJSON.status_time_display !== void 0 && contestJSON.status_time_display !== null) {
|
|
233
|
+
c.statusTimeDisplay = {
|
|
234
|
+
correct: Boolean(contestJSON.status_time_display.correct ?? false),
|
|
235
|
+
incorrect: Boolean(contestJSON.status_time_display.incorrect ?? false),
|
|
236
|
+
pending: Boolean(contestJSON.status_time_display.pending ?? false)
|
|
237
|
+
};
|
|
238
|
+
}
|
|
239
|
+
c.badge = contestJSON.badge;
|
|
240
|
+
c.medal = contestJSON.medal;
|
|
241
|
+
c.organization = contestJSON.organization;
|
|
242
|
+
c.group = contestJSON.group;
|
|
243
|
+
c.tag = contestJSON.tag;
|
|
244
|
+
c.banner = contestJSON.banner;
|
|
245
|
+
c.logo = contestJSON.logo;
|
|
246
|
+
c.boardLink = contestJSON.board_link;
|
|
247
|
+
return c;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function getImageSource(image) {
|
|
251
|
+
if (image?.url) {
|
|
252
|
+
return image.url;
|
|
253
|
+
}
|
|
254
|
+
if (image?.base64) {
|
|
255
|
+
return `data:image/${image.type};base64,${image.base64}`;
|
|
256
|
+
}
|
|
257
|
+
return "";
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
class Team {
|
|
261
|
+
constructor() {
|
|
262
|
+
this.id = "";
|
|
263
|
+
this.name = "";
|
|
264
|
+
this.organization = "";
|
|
265
|
+
this.group = [];
|
|
266
|
+
this.tag = [];
|
|
267
|
+
this.rank = 0;
|
|
268
|
+
this.solvedProblemNum = 0;
|
|
269
|
+
this.penalty = 0;
|
|
270
|
+
this.problemStatistics = [];
|
|
271
|
+
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
272
|
+
}
|
|
273
|
+
penaltyToMinute() {
|
|
274
|
+
return Math.floor(this.penalty / 60);
|
|
275
|
+
}
|
|
276
|
+
calcSolvedData() {
|
|
277
|
+
this.solvedProblemNum = 0;
|
|
278
|
+
this.penalty = 0;
|
|
279
|
+
for (const p of this.problemStatistics) {
|
|
280
|
+
if (p.isAccepted) {
|
|
281
|
+
this.solvedProblemNum++;
|
|
282
|
+
this.penalty += p.penalty;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
static compare(lhs, rhs) {
|
|
287
|
+
if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
|
|
288
|
+
return rhs.solvedProblemNum - lhs.solvedProblemNum;
|
|
289
|
+
}
|
|
290
|
+
if (lhs.penalty !== rhs.penalty) {
|
|
291
|
+
return lhs.penalty - rhs.penalty;
|
|
292
|
+
}
|
|
293
|
+
if (lhs.name < rhs.name) {
|
|
294
|
+
return -1;
|
|
295
|
+
} else if (lhs.name > rhs.name) {
|
|
296
|
+
return 1;
|
|
297
|
+
}
|
|
298
|
+
return 0;
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function createTeam(teamJSON) {
|
|
302
|
+
const t = new Team();
|
|
303
|
+
t.id = teamJSON.id ?? teamJSON.team_id ?? "";
|
|
304
|
+
t.name = teamJSON.name ?? teamJSON.team_name ?? "";
|
|
305
|
+
t.organization = teamJSON.organization ?? "";
|
|
306
|
+
t.group = teamJSON.group ?? [];
|
|
307
|
+
t.tag = teamJSON.group ?? [];
|
|
308
|
+
t.coach = teamJSON.coach;
|
|
309
|
+
t.members = teamJSON.members;
|
|
310
|
+
if (teamJSON.official === true) {
|
|
311
|
+
t.group.push("official");
|
|
312
|
+
}
|
|
313
|
+
if (teamJSON.unofficial === true) {
|
|
314
|
+
t.group.push("unofficial");
|
|
315
|
+
}
|
|
316
|
+
if (teamJSON.girl === true) {
|
|
317
|
+
t.group.push("girl");
|
|
318
|
+
}
|
|
319
|
+
t.group = [...new Set(t.group)];
|
|
320
|
+
t.group.sort();
|
|
321
|
+
return t;
|
|
322
|
+
}
|
|
323
|
+
function createTeams(teamsJSON) {
|
|
324
|
+
if (Array.isArray(teamsJSON)) {
|
|
325
|
+
return teamsJSON.map((t) => createTeam(t));
|
|
326
|
+
} else {
|
|
327
|
+
const teams = Object.entries(teamsJSON).map(
|
|
328
|
+
([teamId, team]) => createTeam({ ...team, team_id: team.team_id ?? teamId })
|
|
329
|
+
);
|
|
330
|
+
return teams;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function stringToSubmissionStatus(status) {
|
|
335
|
+
status = status.toUpperCase().replace(" ", "_");
|
|
336
|
+
if (["OK", "AC", SubmissionStatus.ACCEPTED.toString()].includes(status)) {
|
|
337
|
+
return SubmissionStatus.ACCEPTED;
|
|
338
|
+
}
|
|
339
|
+
if ([SubmissionStatus.CORRECT.toString()].includes(status)) {
|
|
340
|
+
return SubmissionStatus.CORRECT;
|
|
341
|
+
}
|
|
342
|
+
if ([SubmissionStatus.PARTIALLY_CORRECT.toString()].includes(status)) {
|
|
343
|
+
return SubmissionStatus.PARTIALLY_CORRECT;
|
|
344
|
+
}
|
|
345
|
+
if (["WA", SubmissionStatus.WRONG_ANSWER.toString()].includes(status)) {
|
|
346
|
+
return SubmissionStatus.WRONG_ANSWER;
|
|
347
|
+
}
|
|
348
|
+
if (["RJ", "INCORRECT", SubmissionStatus.REJECTED.toString()].includes(status)) {
|
|
349
|
+
return SubmissionStatus.REJECTED;
|
|
350
|
+
}
|
|
351
|
+
if (["PD", SubmissionStatus.PENDING.toString()].includes(status)) {
|
|
352
|
+
return SubmissionStatus.PENDING;
|
|
353
|
+
}
|
|
354
|
+
if ([SubmissionStatus.WAITING.toString()].includes(status)) {
|
|
355
|
+
return SubmissionStatus.WAITING;
|
|
356
|
+
}
|
|
357
|
+
if ([SubmissionStatus.JUDGING.toString()].includes(status)) {
|
|
358
|
+
return SubmissionStatus.JUDGING;
|
|
359
|
+
}
|
|
360
|
+
if ([SubmissionStatus.FROZEN.toString()].includes(status)) {
|
|
361
|
+
return SubmissionStatus.FROZEN;
|
|
362
|
+
}
|
|
363
|
+
if (["CE", SubmissionStatus.COMPILE_ERROR.toString()].includes(status)) {
|
|
364
|
+
return SubmissionStatus.COMPILE_ERROR;
|
|
365
|
+
}
|
|
366
|
+
if (["PE", SubmissionStatus.PRESENTATION_ERROR.toString()].includes(status)) {
|
|
367
|
+
return SubmissionStatus.PRESENTATION_ERROR;
|
|
368
|
+
}
|
|
369
|
+
if (["TL", "TLE", SubmissionStatus.TIME_LIMIT_EXCEEDED.toString()].includes(status)) {
|
|
370
|
+
return SubmissionStatus.TIME_LIMIT_EXCEEDED;
|
|
371
|
+
}
|
|
372
|
+
if (["ML", "MLE", SubmissionStatus.MEMORY_LIMIT_EXCEEDED.toString()].includes(status)) {
|
|
373
|
+
return SubmissionStatus.MEMORY_LIMIT_EXCEEDED;
|
|
374
|
+
}
|
|
375
|
+
if (["OL", "OLE", SubmissionStatus.OUTPUT_LIMIT_EXCEEDED.toString()].includes(status)) {
|
|
376
|
+
return SubmissionStatus.OUTPUT_LIMIT_EXCEEDED;
|
|
377
|
+
}
|
|
378
|
+
if (["IL", "ILE", SubmissionStatus.IDLENESS_LIMIT_EXCEEDED.toString()].includes(status)) {
|
|
379
|
+
return SubmissionStatus.IDLENESS_LIMIT_EXCEEDED;
|
|
380
|
+
}
|
|
381
|
+
if (["RT", "RE", "RTE", SubmissionStatus.RUNTIME_ERROR.toString()].includes(status)) {
|
|
382
|
+
return SubmissionStatus.RUNTIME_ERROR;
|
|
383
|
+
}
|
|
384
|
+
if (["JE", SubmissionStatus.JUDGEMENT_FAILED.toString()].includes(status)) {
|
|
385
|
+
return SubmissionStatus.JUDGEMENT_FAILED;
|
|
386
|
+
}
|
|
387
|
+
if (["SE", SubmissionStatus.SYSTEM_ERROR.toString()].includes(status)) {
|
|
388
|
+
return SubmissionStatus.SYSTEM_ERROR;
|
|
389
|
+
}
|
|
390
|
+
if ([SubmissionStatus.HACKED.toString()].includes(status)) {
|
|
391
|
+
return SubmissionStatus.HACKED;
|
|
392
|
+
}
|
|
393
|
+
if ([SubmissionStatus.CONFIGURATION_ERROR.toString()].includes(status)) {
|
|
394
|
+
return SubmissionStatus.CONFIGURATION_ERROR;
|
|
395
|
+
}
|
|
396
|
+
if ([SubmissionStatus.CANCELED.toString()].includes(status)) {
|
|
397
|
+
return SubmissionStatus.CANCELED;
|
|
398
|
+
}
|
|
399
|
+
if ([SubmissionStatus.SKIPPED.toString()].includes(status)) {
|
|
400
|
+
return SubmissionStatus.SKIPPED;
|
|
401
|
+
}
|
|
402
|
+
if ([SubmissionStatus.SECURITY_VIOLATED.toString()].includes(status)) {
|
|
403
|
+
return SubmissionStatus.SECURITY_VIOLATED;
|
|
404
|
+
}
|
|
405
|
+
if ([SubmissionStatus.DENIAL_OF_JUDGEMENT.toString()].includes(status)) {
|
|
406
|
+
return SubmissionStatus.DENIAL_OF_JUDGEMENT;
|
|
407
|
+
}
|
|
408
|
+
return SubmissionStatus.UNKNOWN;
|
|
409
|
+
}
|
|
410
|
+
function isAccepted(status) {
|
|
411
|
+
const acceptedArray = [SubmissionStatus.ACCEPTED, SubmissionStatus.CORRECT];
|
|
412
|
+
return acceptedArray.includes(status);
|
|
413
|
+
}
|
|
414
|
+
function isRejected(status) {
|
|
415
|
+
const rejectArray = [
|
|
416
|
+
SubmissionStatus.RUNTIME_ERROR,
|
|
417
|
+
SubmissionStatus.TIME_LIMIT_EXCEEDED,
|
|
418
|
+
SubmissionStatus.MEMORY_LIMIT_EXCEEDED,
|
|
419
|
+
SubmissionStatus.OUTPUT_LIMIT_EXCEEDED,
|
|
420
|
+
SubmissionStatus.IDLENESS_LIMIT_EXCEEDED,
|
|
421
|
+
SubmissionStatus.WRONG_ANSWER,
|
|
422
|
+
SubmissionStatus.REJECTED,
|
|
423
|
+
SubmissionStatus.JUDGEMENT_FAILED,
|
|
424
|
+
SubmissionStatus.HACKED
|
|
425
|
+
];
|
|
426
|
+
return rejectArray.includes(status);
|
|
427
|
+
}
|
|
428
|
+
function isPending(status) {
|
|
429
|
+
const pendingArray = [
|
|
430
|
+
SubmissionStatus.PENDING,
|
|
431
|
+
SubmissionStatus.WAITING,
|
|
432
|
+
SubmissionStatus.JUDGING,
|
|
433
|
+
SubmissionStatus.FROZEN
|
|
434
|
+
];
|
|
435
|
+
return pendingArray.includes(status);
|
|
436
|
+
}
|
|
437
|
+
function isNotCalculatedPenaltyStatus(status) {
|
|
438
|
+
const isNotCalculatedPenaltyArray = [
|
|
439
|
+
SubmissionStatus.COMPILE_ERROR,
|
|
440
|
+
SubmissionStatus.PRESENTATION_ERROR,
|
|
441
|
+
SubmissionStatus.CONFIGURATION_ERROR,
|
|
442
|
+
SubmissionStatus.SYSTEM_ERROR,
|
|
443
|
+
SubmissionStatus.CANCELED,
|
|
444
|
+
SubmissionStatus.SKIPPED,
|
|
445
|
+
SubmissionStatus.UNKNOWN,
|
|
446
|
+
SubmissionStatus.UNDEFINED
|
|
447
|
+
];
|
|
448
|
+
return isNotCalculatedPenaltyArray.includes(status);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
class Submission {
|
|
452
|
+
constructor() {
|
|
453
|
+
this.status = SubmissionStatus.UNKNOWN;
|
|
454
|
+
this.isIgnore = false;
|
|
455
|
+
this.id = "";
|
|
456
|
+
this.teamId = "";
|
|
457
|
+
this.problemId = "";
|
|
458
|
+
this.timestamp = 0;
|
|
459
|
+
}
|
|
460
|
+
isAccepted() {
|
|
461
|
+
return isAccepted(this.status);
|
|
462
|
+
}
|
|
463
|
+
isRejected() {
|
|
464
|
+
return isRejected(this.status);
|
|
465
|
+
}
|
|
466
|
+
isPending() {
|
|
467
|
+
return isPending(this.status);
|
|
468
|
+
}
|
|
469
|
+
isNotCalculatedPenaltyStatus() {
|
|
470
|
+
return isNotCalculatedPenaltyStatus(this.status);
|
|
471
|
+
}
|
|
472
|
+
static compare(lhs, rhs) {
|
|
473
|
+
if (lhs.timestamp !== rhs.timestamp) {
|
|
474
|
+
return lhs.timestamp - rhs.timestamp;
|
|
475
|
+
}
|
|
476
|
+
if (lhs.teamId === rhs.teamId) {
|
|
477
|
+
if (lhs.isAccepted() && !rhs.isAccepted()) {
|
|
478
|
+
return -1;
|
|
479
|
+
}
|
|
480
|
+
if (!lhs.isAccepted() && rhs.isAccepted()) {
|
|
481
|
+
return 1;
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
return 0;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
function createSubmission(submissionJSON) {
|
|
488
|
+
const s = new Submission();
|
|
489
|
+
s.id = String(submissionJSON.id ?? submissionJSON.submission_id ?? "");
|
|
490
|
+
s.teamId = String(submissionJSON.team_id);
|
|
491
|
+
s.problemId = String(submissionJSON.problem_id);
|
|
492
|
+
s.timestamp = submissionJSON.timestamp;
|
|
493
|
+
s.status = stringToSubmissionStatus(submissionJSON.status);
|
|
494
|
+
s.isIgnore = submissionJSON.is_ignore ?? false;
|
|
495
|
+
return s;
|
|
496
|
+
}
|
|
497
|
+
function createSubmissions(submissionsJSON) {
|
|
498
|
+
if (Array.isArray(submissionsJSON)) {
|
|
499
|
+
return submissionsJSON.map((s, index) => createSubmission({ ...s, id: s.submission_id ?? String(index) }));
|
|
500
|
+
} else {
|
|
501
|
+
const submissions = Object.entries(submissionsJSON).map(
|
|
502
|
+
([submissionId, s]) => createSubmission({ ...s, id: s.submission_id ?? submissionId })
|
|
503
|
+
);
|
|
504
|
+
return submissions;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
class Rank {
|
|
509
|
+
constructor(contest, teams, submissions) {
|
|
510
|
+
this.contest = contest;
|
|
511
|
+
this.teams = _.cloneDeep(teams);
|
|
512
|
+
this.teamsMap = new Map(this.teams.map((t) => [t.id, t]));
|
|
513
|
+
this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
|
|
514
|
+
this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
|
|
515
|
+
this.firstSolvedSubmissions = new Map(this.contest.problems.map((p) => [p.id, []]));
|
|
516
|
+
}
|
|
517
|
+
buildRank(options) {
|
|
518
|
+
(() => {
|
|
519
|
+
for (const t of this.teams) {
|
|
520
|
+
t.problemStatistics = this.contest.problems.map((p) => {
|
|
521
|
+
const ps = new TeamProblemStatistics();
|
|
522
|
+
ps.problem = p;
|
|
523
|
+
ps.contestPenalty = this.contest.penalty;
|
|
524
|
+
return ps;
|
|
525
|
+
});
|
|
526
|
+
t.problemStatisticsMap = new Map(t.problemStatistics.map((ps) => [ps.problem.id, ps]));
|
|
527
|
+
}
|
|
528
|
+
this.firstSolvedSubmissions = new Map(this.contest.problems.map((p) => [p.id, []]));
|
|
529
|
+
for (const s of this.submissions) {
|
|
530
|
+
const teamId = s.teamId;
|
|
531
|
+
const problemId = s.problemId;
|
|
532
|
+
const team = this.teamsMap.get(teamId);
|
|
533
|
+
const problem = this.contest.problemsMap.get(problemId);
|
|
534
|
+
if (team === void 0 || problem === void 0) {
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
if (options?.timestamp !== void 0 && options?.timestamp !== null) {
|
|
538
|
+
if (s.timestamp > options.timestamp) {
|
|
539
|
+
break;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
const problemStatistics = team.problemStatisticsMap.get(problemId);
|
|
543
|
+
const submissions = problemStatistics.submissions;
|
|
544
|
+
const firstSolvedSubmissions = this.firstSolvedSubmissions.get(problemId);
|
|
545
|
+
submissions.push(s);
|
|
546
|
+
problem.statistics.submittedNum++;
|
|
547
|
+
if (problemStatistics.isSolved) {
|
|
548
|
+
continue;
|
|
549
|
+
}
|
|
550
|
+
if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
|
|
551
|
+
problemStatistics.ignoreCount++;
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
problemStatistics.isSubmitted = true;
|
|
555
|
+
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
556
|
+
problemStatistics.totalCount++;
|
|
557
|
+
if (s.isAccepted()) {
|
|
558
|
+
problemStatistics.isSolved = true;
|
|
559
|
+
problemStatistics.solvedTimestamp = s.timestamp;
|
|
560
|
+
problem.statistics.acceptedNum++;
|
|
561
|
+
if (firstSolvedSubmissions.length === 0 || firstSolvedSubmissions[firstSolvedSubmissions.length - 1].timestamp === s.timestamp) {
|
|
562
|
+
problemStatistics.isFirstSolved = true;
|
|
563
|
+
firstSolvedSubmissions.push(s);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
if (s.isRejected()) {
|
|
567
|
+
problemStatistics.failedCount++;
|
|
568
|
+
problem.statistics.rejectedNum++;
|
|
569
|
+
}
|
|
570
|
+
if (s.isPending()) {
|
|
571
|
+
problemStatistics.pendingCount++;
|
|
572
|
+
problem.statistics.pendingNum++;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
this.teams.forEach((t) => t.calcSolvedData());
|
|
576
|
+
this.teams.sort(Team.compare);
|
|
577
|
+
{
|
|
578
|
+
let rank = 1;
|
|
579
|
+
for (const t of this.teams) {
|
|
580
|
+
t.rank = rank++;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
})();
|
|
584
|
+
return this;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
class ResolverOperation {
|
|
589
|
+
constructor() {
|
|
590
|
+
this.id = 0;
|
|
591
|
+
this.team = new Team();
|
|
592
|
+
this.problemIx = 0;
|
|
593
|
+
this.beforeTeamProblemStatistics = new TeamProblemStatistics();
|
|
594
|
+
this.afterTeamProblemStatistics = new TeamProblemStatistics();
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
class Resolver extends Rank {
|
|
599
|
+
constructor(contest, teams, submissions) {
|
|
600
|
+
submissions.sort(Submission.compare);
|
|
601
|
+
let beforeFreezeSubmissions = submissions;
|
|
602
|
+
let afterFreezeSubmissions = submissions;
|
|
603
|
+
{
|
|
604
|
+
const ix = _.sortedIndex(
|
|
605
|
+
submissions.map((s) => s.timestamp),
|
|
606
|
+
contest.unFreezeDurationTimestamp
|
|
607
|
+
);
|
|
608
|
+
beforeFreezeSubmissions = submissions.slice(0, ix + 1);
|
|
609
|
+
afterFreezeSubmissions = submissions.slice(ix, -1);
|
|
610
|
+
}
|
|
611
|
+
super(contest, teams, beforeFreezeSubmissions);
|
|
612
|
+
this.finalRank = new Rank(contest, teams, submissions);
|
|
613
|
+
this.operations = [];
|
|
614
|
+
this.beforeFreezeSubmissions = beforeFreezeSubmissions;
|
|
615
|
+
this.afterFreezeSubmissions = afterFreezeSubmissions;
|
|
616
|
+
}
|
|
617
|
+
buildResolver() {
|
|
618
|
+
this.buildRank();
|
|
619
|
+
this.finalRank.buildRank();
|
|
620
|
+
for (const s of this.afterFreezeSubmissions) {
|
|
621
|
+
const teamId = s.teamId;
|
|
622
|
+
const problemId = s.problemId;
|
|
623
|
+
const team = this.teamsMap.get(teamId);
|
|
624
|
+
const problem = this.contest.problemsMap.get(problemId);
|
|
625
|
+
if (team === void 0 || problem === void 0) {
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
const problemStatistics = team.problemStatisticsMap.get(problemId);
|
|
629
|
+
problemStatistics.pendingCount++;
|
|
630
|
+
problemStatistics.totalCount++;
|
|
631
|
+
if (!problemStatistics.isAccepted) {
|
|
632
|
+
problemStatistics.lastSubmitTimestamp = s.timestamp;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
{
|
|
636
|
+
const teams_ = _.cloneDeep(this.teams);
|
|
637
|
+
for (let i = this.teams.length - 1; i >= 0; ) {
|
|
638
|
+
const team = teams_[i];
|
|
639
|
+
const teamId = team.id;
|
|
640
|
+
let handleCnt = 0;
|
|
641
|
+
let problemIx = -1;
|
|
642
|
+
for (const p of team.problemStatistics) {
|
|
643
|
+
problemIx++;
|
|
644
|
+
if (!p.isPending) {
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
handleCnt++;
|
|
648
|
+
const beforeTeamProblemStatistics = this.teamsMap.get(teamId)?.problemStatistics[problemIx];
|
|
649
|
+
const afterTeamProblemStatistics = this.finalRank.teamsMap.get(teamId)?.problemStatistics[problemIx];
|
|
650
|
+
const op = new ResolverOperation();
|
|
651
|
+
op.id = this.operations.length;
|
|
652
|
+
op.team = this.teamsMap.get(teamId);
|
|
653
|
+
op.problemIx = problemIx;
|
|
654
|
+
op.beforeTeamProblemStatistics = beforeTeamProblemStatistics;
|
|
655
|
+
op.afterTeamProblemStatistics = afterTeamProblemStatistics;
|
|
656
|
+
this.operations.push(op);
|
|
657
|
+
team.problemStatistics[problemIx] = afterTeamProblemStatistics;
|
|
658
|
+
team.calcSolvedData();
|
|
659
|
+
break;
|
|
660
|
+
}
|
|
661
|
+
{
|
|
662
|
+
let j = i;
|
|
663
|
+
while (j > 0 && Team.compare(teams_[j], teams_[j - 1]) < 0) {
|
|
664
|
+
[teams_[j], teams_[j - 1]] = [teams_[j - 1], teams_[j]];
|
|
665
|
+
j--;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
if (handleCnt === 0) {
|
|
669
|
+
i--;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
export { Contest, Problem, Rank, Resolver, Submission, Team, TeamProblemStatistics, createContest, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|