@xcpcio/core 0.4.1 → 0.4.3
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/dist/index.cjs +145 -100
- package/dist/index.d.ts +47 -36
- package/dist/index.mjs +145 -101
- package/package.json +2 -2
- package/src/contest-index.ts +11 -34
- package/src/contest.ts +22 -14
- package/src/problem.ts +2 -0
- package/src/rank.ts +64 -13
- package/src/team.ts +24 -0
package/dist/index.cjs
CHANGED
|
@@ -70,81 +70,6 @@ function getTimeDiff(seconds) {
|
|
|
70
70
|
return [two(h), two(m), two(s)].join(":");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
class ContestIndexConfig {
|
|
74
|
-
constructor() {
|
|
75
|
-
this.contestName = "";
|
|
76
|
-
this.startTime = createDayJS();
|
|
77
|
-
this.endTime = createDayJS();
|
|
78
|
-
this.freezeTime = createDayJS();
|
|
79
|
-
this.totalDurationTimestamp = 0;
|
|
80
|
-
this.freezeDurationTimestamp = 0;
|
|
81
|
-
this.unFreezeDurationTimestamp = 0;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
class ContestIndex {
|
|
85
|
-
constructor() {
|
|
86
|
-
this.config = new ContestIndexConfig();
|
|
87
|
-
this.boardLink = "";
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
function createContestIndex(contestIndexJSON) {
|
|
91
|
-
const c = new ContestIndex();
|
|
92
|
-
const cc = c.config;
|
|
93
|
-
const cjc = contestIndexJSON.config;
|
|
94
|
-
cc.contestName = cjc.contest_name;
|
|
95
|
-
cc.startTime = createDayJS(cjc.start_time);
|
|
96
|
-
cc.endTime = createDayJS(cjc.end_time);
|
|
97
|
-
cc.totalDurationTimestamp = cc.endTime.unix() - cc.startTime.unix();
|
|
98
|
-
{
|
|
99
|
-
cc.freezeTime = cc.endTime;
|
|
100
|
-
cc.freezeDurationTimestamp = 0;
|
|
101
|
-
if (cjc.frozen_time !== void 0 && cjc.frozen_time != null) {
|
|
102
|
-
const frozenTime = Number(cjc.frozen_time);
|
|
103
|
-
cc.freezeTime = createDayJS(cc.endTime.unix() - frozenTime);
|
|
104
|
-
cc.freezeDurationTimestamp = frozenTime;
|
|
105
|
-
}
|
|
106
|
-
cc.unFreezeDurationTimestamp = cc.totalDurationTimestamp - cc.freezeDurationTimestamp;
|
|
107
|
-
}
|
|
108
|
-
cc.logo = cjc.logo;
|
|
109
|
-
c.boardLink = contestIndexJSON.board_link;
|
|
110
|
-
return c;
|
|
111
|
-
}
|
|
112
|
-
function createContestIndexList(contestListJSON) {
|
|
113
|
-
const contestIndexList = [];
|
|
114
|
-
const dfs = (contestList) => {
|
|
115
|
-
if (Object.prototype.hasOwnProperty.call(contestList, "config")) {
|
|
116
|
-
contestIndexList.push(createContestIndex(contestList));
|
|
117
|
-
} else {
|
|
118
|
-
for (const k in contestList) {
|
|
119
|
-
dfs(contestList[k]);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
};
|
|
123
|
-
dfs(contestListJSON);
|
|
124
|
-
contestIndexList.sort((a, b) => {
|
|
125
|
-
if (a.config.startTime.isBefore(b.config.startTime)) {
|
|
126
|
-
return 1;
|
|
127
|
-
}
|
|
128
|
-
if (a.config.startTime.isAfter(b.config.startTime)) {
|
|
129
|
-
return -1;
|
|
130
|
-
}
|
|
131
|
-
if (a.config.endTime.isBefore(b.config.endTime)) {
|
|
132
|
-
return 1;
|
|
133
|
-
}
|
|
134
|
-
if (a.config.endTime.isAfter(b.config.endTime)) {
|
|
135
|
-
return -1;
|
|
136
|
-
}
|
|
137
|
-
if (a.config.contestName < b.config.contestName) {
|
|
138
|
-
return 1;
|
|
139
|
-
}
|
|
140
|
-
if (a.config.contestName > b.config.contestName) {
|
|
141
|
-
return -1;
|
|
142
|
-
}
|
|
143
|
-
return 0;
|
|
144
|
-
});
|
|
145
|
-
return contestIndexList;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
73
|
class ProblemStatistics {
|
|
149
74
|
constructor() {
|
|
150
75
|
this.acceptedNum = 0;
|
|
@@ -161,6 +86,8 @@ class ProblemStatistics {
|
|
|
161
86
|
this.rejectedNum = 0;
|
|
162
87
|
this.pendingNum = 0;
|
|
163
88
|
this.submittedNum = 0;
|
|
89
|
+
this.attemptedNum = 0;
|
|
90
|
+
this.ignoreNum = 0;
|
|
164
91
|
this.firstSolveSubmissions = [];
|
|
165
92
|
this.lastSolveSubmissions = [];
|
|
166
93
|
}
|
|
@@ -272,8 +199,8 @@ class Contest {
|
|
|
272
199
|
getContestDuration(timeFormat = "HH:mm:ss") {
|
|
273
200
|
return dayjs__default.duration(this.endTime.diff(this.startTime)).format(timeFormat);
|
|
274
201
|
}
|
|
275
|
-
getContestState() {
|
|
276
|
-
const now = createDayJS();
|
|
202
|
+
getContestState(nowTime) {
|
|
203
|
+
const now = createDayJS(nowTime);
|
|
277
204
|
if (now.isBefore(this.startTime)) {
|
|
278
205
|
return types.ContestState.PENDING;
|
|
279
206
|
}
|
|
@@ -285,29 +212,35 @@ class Contest {
|
|
|
285
212
|
}
|
|
286
213
|
return types.ContestState.RUNNING;
|
|
287
214
|
}
|
|
288
|
-
getContestPendingTime() {
|
|
289
|
-
let baseTime = createDayJS();
|
|
215
|
+
getContestPendingTime(nowTime) {
|
|
216
|
+
let baseTime = createDayJS(nowTime);
|
|
290
217
|
if (baseTime.isAfter(this.startTime)) {
|
|
291
218
|
baseTime = this.startTime;
|
|
292
219
|
}
|
|
293
220
|
return getTimeDiff(Math.floor(dayjs__default.duration(this.startTime.diff(baseTime)).asSeconds()));
|
|
294
221
|
}
|
|
295
|
-
|
|
296
|
-
let baseTime =
|
|
297
|
-
if (baseTime.isAfter(endTime)) {
|
|
298
|
-
baseTime = endTime;
|
|
222
|
+
getContestElapsedTime(nowTime) {
|
|
223
|
+
let baseTime = createDayJS(nowTime);
|
|
224
|
+
if (baseTime.isAfter(this.endTime)) {
|
|
225
|
+
baseTime = this.endTime;
|
|
299
226
|
}
|
|
300
|
-
|
|
227
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
228
|
+
baseTime = this.startTime;
|
|
229
|
+
}
|
|
230
|
+
return getTimeDiff(Math.floor(dayjs__default.duration(baseTime.diff(this.startTime)).asSeconds()));
|
|
301
231
|
}
|
|
302
|
-
|
|
303
|
-
let baseTime =
|
|
232
|
+
getContestRemainingTime(nowTime) {
|
|
233
|
+
let baseTime = createDayJS(nowTime);
|
|
304
234
|
if (baseTime.isAfter(this.endTime)) {
|
|
305
235
|
baseTime = this.endTime;
|
|
306
236
|
}
|
|
307
|
-
|
|
237
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
238
|
+
baseTime = this.startTime;
|
|
239
|
+
}
|
|
240
|
+
return getTimeDiff(Math.floor(dayjs__default.duration(this.endTime.diff(baseTime)).asSeconds()));
|
|
308
241
|
}
|
|
309
|
-
getContestProgressRatio() {
|
|
310
|
-
const baseTime =
|
|
242
|
+
getContestProgressRatio(nowTime) {
|
|
243
|
+
const baseTime = createDayJS(nowTime);
|
|
311
244
|
if (this.startTime.isSameOrAfter(baseTime)) {
|
|
312
245
|
return 0;
|
|
313
246
|
}
|
|
@@ -367,6 +300,66 @@ function createContest(contestJSON) {
|
|
|
367
300
|
return c;
|
|
368
301
|
}
|
|
369
302
|
|
|
303
|
+
class ContestIndexConfig {
|
|
304
|
+
constructor() {
|
|
305
|
+
this.contestName = "";
|
|
306
|
+
this.startTime = createDayJS();
|
|
307
|
+
this.endTime = createDayJS();
|
|
308
|
+
this.freezeTime = createDayJS();
|
|
309
|
+
this.totalDurationTimestamp = 0;
|
|
310
|
+
this.freezeDurationTimestamp = 0;
|
|
311
|
+
this.unFreezeDurationTimestamp = 0;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
class ContestIndex {
|
|
315
|
+
constructor() {
|
|
316
|
+
this.contest = new Contest();
|
|
317
|
+
this.boardLink = "";
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
function createContestIndex(contestIndexJSON) {
|
|
321
|
+
const c = new ContestIndex();
|
|
322
|
+
const cjc = contestIndexJSON.config;
|
|
323
|
+
c.contest = createContest(cjc);
|
|
324
|
+
c.boardLink = contestIndexJSON.board_link;
|
|
325
|
+
return c;
|
|
326
|
+
}
|
|
327
|
+
function createContestIndexList(contestListJSON) {
|
|
328
|
+
const contestIndexList = [];
|
|
329
|
+
const dfs = (contestList) => {
|
|
330
|
+
if (Object.prototype.hasOwnProperty.call(contestList, "config")) {
|
|
331
|
+
contestIndexList.push(createContestIndex(contestList));
|
|
332
|
+
} else {
|
|
333
|
+
for (const k in contestList) {
|
|
334
|
+
dfs(contestList[k]);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
};
|
|
338
|
+
dfs(contestListJSON);
|
|
339
|
+
contestIndexList.sort((a, b) => {
|
|
340
|
+
if (a.contest.startTime.isBefore(b.contest.startTime)) {
|
|
341
|
+
return 1;
|
|
342
|
+
}
|
|
343
|
+
if (a.contest.startTime.isAfter(b.contest.startTime)) {
|
|
344
|
+
return -1;
|
|
345
|
+
}
|
|
346
|
+
if (a.contest.endTime.isBefore(b.contest.endTime)) {
|
|
347
|
+
return 1;
|
|
348
|
+
}
|
|
349
|
+
if (a.contest.endTime.isAfter(b.contest.endTime)) {
|
|
350
|
+
return -1;
|
|
351
|
+
}
|
|
352
|
+
if (a.contest.name < b.contest.name) {
|
|
353
|
+
return 1;
|
|
354
|
+
}
|
|
355
|
+
if (a.contest.name > b.contest.name) {
|
|
356
|
+
return -1;
|
|
357
|
+
}
|
|
358
|
+
return 0;
|
|
359
|
+
});
|
|
360
|
+
return contestIndexList;
|
|
361
|
+
}
|
|
362
|
+
|
|
370
363
|
function getImageSource(image) {
|
|
371
364
|
if (image?.url) {
|
|
372
365
|
return image.url;
|
|
@@ -397,6 +390,17 @@ class Team {
|
|
|
397
390
|
this.organizationRank = -1;
|
|
398
391
|
this.solvedProblemNum = 0;
|
|
399
392
|
this.attemptedProblemNum = 0;
|
|
393
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
394
|
+
this.penalty = 0;
|
|
395
|
+
this.problemStatistics = [];
|
|
396
|
+
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
397
|
+
}
|
|
398
|
+
reset() {
|
|
399
|
+
this.rank = 0;
|
|
400
|
+
this.organizationRank = -1;
|
|
401
|
+
this.solvedProblemNum = 0;
|
|
402
|
+
this.attemptedProblemNum = 0;
|
|
403
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
400
404
|
this.penalty = 0;
|
|
401
405
|
this.problemStatistics = [];
|
|
402
406
|
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
@@ -421,6 +425,9 @@ class Team {
|
|
|
421
425
|
}
|
|
422
426
|
}
|
|
423
427
|
}
|
|
428
|
+
isEqualRank(otherTeam) {
|
|
429
|
+
return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
|
|
430
|
+
}
|
|
424
431
|
static compare(lhs, rhs) {
|
|
425
432
|
if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
|
|
426
433
|
return rhs.solvedProblemNum - lhs.solvedProblemNum;
|
|
@@ -428,6 +435,9 @@ class Team {
|
|
|
428
435
|
if (lhs.penalty !== rhs.penalty) {
|
|
429
436
|
return lhs.penalty - rhs.penalty;
|
|
430
437
|
}
|
|
438
|
+
if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
|
|
439
|
+
return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
|
|
440
|
+
}
|
|
431
441
|
if (lhs.name < rhs.name) {
|
|
432
442
|
return -1;
|
|
433
443
|
} else if (lhs.name > rhs.name) {
|
|
@@ -644,6 +654,21 @@ function createSubmissions(submissionsJSON) {
|
|
|
644
654
|
}
|
|
645
655
|
}
|
|
646
656
|
|
|
657
|
+
class RankOptions {
|
|
658
|
+
constructor() {
|
|
659
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
660
|
+
this.width = 0;
|
|
661
|
+
this.timestamp = 0;
|
|
662
|
+
}
|
|
663
|
+
setWidth(width, contest) {
|
|
664
|
+
this.width = width;
|
|
665
|
+
this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
|
|
666
|
+
this.enableFilterSubmissionsByTimestamp = true;
|
|
667
|
+
}
|
|
668
|
+
disableFilterSubmissionByTimestamp() {
|
|
669
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
670
|
+
}
|
|
671
|
+
}
|
|
647
672
|
class Rank {
|
|
648
673
|
constructor(contest, teams, submissions) {
|
|
649
674
|
this.contest = contest;
|
|
@@ -652,10 +677,12 @@ class Rank {
|
|
|
652
677
|
this.submissions = ___default.cloneDeep(submissions).sort(Submission.compare);
|
|
653
678
|
this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
|
|
654
679
|
this.rankStatistics = new RankStatistics();
|
|
680
|
+
this.options = new RankOptions();
|
|
655
681
|
}
|
|
656
|
-
buildRank(
|
|
682
|
+
buildRank() {
|
|
657
683
|
(() => {
|
|
658
684
|
for (const t of this.teams) {
|
|
685
|
+
t.reset();
|
|
659
686
|
t.problemStatistics = this.contest.problems.map((p) => {
|
|
660
687
|
const ps = new TeamProblemStatistics();
|
|
661
688
|
ps.problem = p;
|
|
@@ -667,7 +694,7 @@ class Rank {
|
|
|
667
694
|
this.contest.problems.forEach((p) => {
|
|
668
695
|
p.statistics.reset();
|
|
669
696
|
});
|
|
670
|
-
for (const s of this.
|
|
697
|
+
for (const s of this.getSubmissions()) {
|
|
671
698
|
const teamId = s.teamId;
|
|
672
699
|
const problemId = s.problemId;
|
|
673
700
|
const team = this.teamsMap.get(teamId);
|
|
@@ -675,11 +702,6 @@ class Rank {
|
|
|
675
702
|
if (team === void 0 || problem === void 0) {
|
|
676
703
|
continue;
|
|
677
704
|
}
|
|
678
|
-
if (options?.timestamp !== void 0 && options?.timestamp !== null) {
|
|
679
|
-
if (s.timestamp > options.timestamp) {
|
|
680
|
-
break;
|
|
681
|
-
}
|
|
682
|
-
}
|
|
683
705
|
const problemStatistics = team.problemStatisticsMap.get(problemId);
|
|
684
706
|
const submissions = problemStatistics.submissions;
|
|
685
707
|
submissions.push(s);
|
|
@@ -708,6 +730,7 @@ class Rank {
|
|
|
708
730
|
problem.statistics.lastSolveSubmissions.pop();
|
|
709
731
|
}
|
|
710
732
|
problem.statistics.lastSolveSubmissions.push(s);
|
|
733
|
+
team.lastSolvedProblemTimestamp = s.timestamp;
|
|
711
734
|
}
|
|
712
735
|
if (s.isRejected()) {
|
|
713
736
|
problemStatistics.failedCount++;
|
|
@@ -722,19 +745,34 @@ class Rank {
|
|
|
722
745
|
this.teams.sort(Team.compare);
|
|
723
746
|
{
|
|
724
747
|
let rank = 1;
|
|
748
|
+
let preTeam = null;
|
|
725
749
|
for (const t of this.teams) {
|
|
726
750
|
t.rank = rank++;
|
|
751
|
+
if (preTeam !== null) {
|
|
752
|
+
if (t.isEqualRank(preTeam)) {
|
|
753
|
+
t.rank = preTeam.rank;
|
|
754
|
+
}
|
|
755
|
+
}
|
|
756
|
+
preTeam = t;
|
|
727
757
|
}
|
|
728
758
|
}
|
|
729
759
|
if (this.contest.organization) {
|
|
730
760
|
let rank = 1;
|
|
761
|
+
let preTeam = null;
|
|
731
762
|
const se = /* @__PURE__ */ new Set();
|
|
732
763
|
for (const t of this.teams) {
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
764
|
+
const org = t.organization;
|
|
765
|
+
if (se.has(org)) {
|
|
766
|
+
continue;
|
|
767
|
+
}
|
|
768
|
+
se.add(org);
|
|
769
|
+
t.organizationRank = rank++;
|
|
770
|
+
if (preTeam !== null) {
|
|
771
|
+
if (t.isEqualRank(preTeam)) {
|
|
772
|
+
t.organizationRank = preTeam.organizationRank;
|
|
773
|
+
}
|
|
737
774
|
}
|
|
775
|
+
preTeam = t;
|
|
738
776
|
}
|
|
739
777
|
}
|
|
740
778
|
})();
|
|
@@ -747,6 +785,12 @@ class Rank {
|
|
|
747
785
|
})();
|
|
748
786
|
return this;
|
|
749
787
|
}
|
|
788
|
+
getSubmissions() {
|
|
789
|
+
if (this.options.enableFilterSubmissionsByTimestamp === false) {
|
|
790
|
+
return this.submissions;
|
|
791
|
+
}
|
|
792
|
+
return this.submissions.filter((s) => s.timestamp <= this.options.timestamp).sort(Submission.compare);
|
|
793
|
+
}
|
|
750
794
|
}
|
|
751
795
|
|
|
752
796
|
class ResolverOperation {
|
|
@@ -844,6 +888,7 @@ exports.ContestIndexConfig = ContestIndexConfig;
|
|
|
844
888
|
exports.Problem = Problem;
|
|
845
889
|
exports.ProblemStatistics = ProblemStatistics;
|
|
846
890
|
exports.Rank = Rank;
|
|
891
|
+
exports.RankOptions = RankOptions;
|
|
847
892
|
exports.RankStatistics = RankStatistics;
|
|
848
893
|
exports.Resolver = Resolver;
|
|
849
894
|
exports.Submission = Submission;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,32 +1,6 @@
|
|
|
1
1
|
import dayjs from 'dayjs';
|
|
2
2
|
export { default as dayjs } from 'dayjs';
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
declare class ContestIndexConfig {
|
|
6
|
-
contestName: string;
|
|
7
|
-
startTime: dayjs.Dayjs;
|
|
8
|
-
endTime: dayjs.Dayjs;
|
|
9
|
-
freezeTime: dayjs.Dayjs;
|
|
10
|
-
totalDurationTimestamp: number;
|
|
11
|
-
freezeDurationTimestamp: number;
|
|
12
|
-
unFreezeDurationTimestamp: number;
|
|
13
|
-
logo?: Image;
|
|
14
|
-
constructor();
|
|
15
|
-
}
|
|
16
|
-
declare class ContestIndex {
|
|
17
|
-
config: ContestIndexConfig;
|
|
18
|
-
boardLink: string;
|
|
19
|
-
constructor();
|
|
20
|
-
}
|
|
21
|
-
type ContestIndexList = Array<ContestIndex>;
|
|
22
|
-
declare function createContestIndex(contestIndexJSON: ContestIndex$1): ContestIndex;
|
|
23
|
-
declare function createContestIndexList(contestListJSON: any): ContestIndexList;
|
|
24
|
-
|
|
25
|
-
declare function calcDict(attemptedNum: number, solvedNum: number): number;
|
|
26
|
-
|
|
27
|
-
declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
|
|
28
|
-
declare function getTimestamp(time: number | dayjs.Dayjs): number;
|
|
29
|
-
declare function getTimeDiff(seconds: number): string;
|
|
3
|
+
import { SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, StatusTimeDisplay, Image, ContestState, Contest as Contest$1, ContestIndex as ContestIndex$1, Team as Team$1, Teams as Teams$1 } from '@xcpcio/types';
|
|
30
4
|
|
|
31
5
|
declare class Submission {
|
|
32
6
|
id: string;
|
|
@@ -96,6 +70,12 @@ declare class TeamProblemStatistics {
|
|
|
96
70
|
get penalty(): number;
|
|
97
71
|
}
|
|
98
72
|
|
|
73
|
+
declare function calcDict(attemptedNum: number, solvedNum: number): number;
|
|
74
|
+
|
|
75
|
+
declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
|
|
76
|
+
declare function getTimestamp(time: number | dayjs.Dayjs): number;
|
|
77
|
+
declare function getTimeDiff(seconds: number): string;
|
|
78
|
+
|
|
99
79
|
declare class Contest {
|
|
100
80
|
name: string;
|
|
101
81
|
startTime: dayjs.Dayjs;
|
|
@@ -119,14 +99,34 @@ declare class Contest {
|
|
|
119
99
|
version: string;
|
|
120
100
|
constructor();
|
|
121
101
|
getContestDuration(timeFormat?: string): string;
|
|
122
|
-
getContestState(): ContestState;
|
|
123
|
-
getContestPendingTime(): string;
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
getContestProgressRatio(): number;
|
|
102
|
+
getContestState(nowTime?: Date): ContestState;
|
|
103
|
+
getContestPendingTime(nowTime?: Date): string;
|
|
104
|
+
getContestElapsedTime(nowTime?: Date): string;
|
|
105
|
+
getContestRemainingTime(nowTime?: Date): string;
|
|
106
|
+
getContestProgressRatio(nowTime?: Date): number;
|
|
127
107
|
}
|
|
128
108
|
declare function createContest(contestJSON: Contest$1): Contest;
|
|
129
109
|
|
|
110
|
+
declare class ContestIndexConfig {
|
|
111
|
+
contestName: string;
|
|
112
|
+
startTime: dayjs.Dayjs;
|
|
113
|
+
endTime: dayjs.Dayjs;
|
|
114
|
+
freezeTime: dayjs.Dayjs;
|
|
115
|
+
totalDurationTimestamp: number;
|
|
116
|
+
freezeDurationTimestamp: number;
|
|
117
|
+
unFreezeDurationTimestamp: number;
|
|
118
|
+
logo?: Image;
|
|
119
|
+
constructor();
|
|
120
|
+
}
|
|
121
|
+
declare class ContestIndex {
|
|
122
|
+
contest: Contest;
|
|
123
|
+
boardLink: string;
|
|
124
|
+
constructor();
|
|
125
|
+
}
|
|
126
|
+
type ContestIndexList = Array<ContestIndex>;
|
|
127
|
+
declare function createContestIndex(contestIndexJSON: ContestIndex$1): ContestIndex;
|
|
128
|
+
declare function createContestIndexList(contestListJSON: any): ContestIndexList;
|
|
129
|
+
|
|
130
130
|
declare function getImageSource(image: Image): string;
|
|
131
131
|
|
|
132
132
|
declare class RankStatistics {
|
|
@@ -148,19 +148,30 @@ declare class Team {
|
|
|
148
148
|
organizationRank: number;
|
|
149
149
|
solvedProblemNum: number;
|
|
150
150
|
attemptedProblemNum: number;
|
|
151
|
+
lastSolvedProblemTimestamp: number;
|
|
151
152
|
penalty: number;
|
|
152
153
|
problemStatistics: Array<TeamProblemStatistics>;
|
|
153
154
|
problemStatisticsMap: Map<string, TeamProblemStatistics>;
|
|
154
155
|
constructor();
|
|
156
|
+
reset(): void;
|
|
155
157
|
get penaltyToMinute(): number;
|
|
156
158
|
get dict(): number;
|
|
157
159
|
calcSolvedData(): void;
|
|
160
|
+
isEqualRank(otherTeam: Team): boolean;
|
|
158
161
|
static compare(lhs: Team, rhs: Team): number;
|
|
159
162
|
}
|
|
160
163
|
type Teams = Array<Team>;
|
|
161
164
|
declare function createTeam(teamJSON: Team$1): Team;
|
|
162
165
|
declare function createTeams(teamsJSON: Teams$1): Teams;
|
|
163
166
|
|
|
167
|
+
declare class RankOptions {
|
|
168
|
+
enableFilterSubmissionsByTimestamp: boolean;
|
|
169
|
+
width: number;
|
|
170
|
+
timestamp: number;
|
|
171
|
+
constructor();
|
|
172
|
+
setWidth(width: number, contest: Contest): void;
|
|
173
|
+
disableFilterSubmissionByTimestamp(): void;
|
|
174
|
+
}
|
|
164
175
|
declare class Rank {
|
|
165
176
|
readonly contest: Contest;
|
|
166
177
|
teams: Teams;
|
|
@@ -168,10 +179,10 @@ declare class Rank {
|
|
|
168
179
|
submissions: Submissions;
|
|
169
180
|
submissionsMap: Map<string, Submission>;
|
|
170
181
|
rankStatistics: RankStatistics;
|
|
182
|
+
options: RankOptions;
|
|
171
183
|
constructor(contest: Contest, teams: Teams, submissions: Submissions);
|
|
172
|
-
buildRank(
|
|
173
|
-
|
|
174
|
-
}): this;
|
|
184
|
+
buildRank(): this;
|
|
185
|
+
getSubmissions(): Submissions;
|
|
175
186
|
}
|
|
176
187
|
|
|
177
188
|
declare class ResolverOperation {
|
|
@@ -198,4 +209,4 @@ declare function isRejected(status: SubmissionStatus): boolean;
|
|
|
198
209
|
declare function isPending(status: SubmissionStatus): boolean;
|
|
199
210
|
declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
|
|
200
211
|
|
|
201
|
-
export { Contest, ContestIndex, ContestIndexConfig, ContestIndexList, Problem, ProblemStatistics, Problems, Rank, RankStatistics, Resolver, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
|
212
|
+
export { Contest, ContestIndex, ContestIndexConfig, ContestIndexList, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, Resolver, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
package/dist/index.mjs
CHANGED
|
@@ -54,81 +54,6 @@ function getTimeDiff(seconds) {
|
|
|
54
54
|
return [two(h), two(m), two(s)].join(":");
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
class ContestIndexConfig {
|
|
58
|
-
constructor() {
|
|
59
|
-
this.contestName = "";
|
|
60
|
-
this.startTime = createDayJS();
|
|
61
|
-
this.endTime = createDayJS();
|
|
62
|
-
this.freezeTime = createDayJS();
|
|
63
|
-
this.totalDurationTimestamp = 0;
|
|
64
|
-
this.freezeDurationTimestamp = 0;
|
|
65
|
-
this.unFreezeDurationTimestamp = 0;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
class ContestIndex {
|
|
69
|
-
constructor() {
|
|
70
|
-
this.config = new ContestIndexConfig();
|
|
71
|
-
this.boardLink = "";
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
function createContestIndex(contestIndexJSON) {
|
|
75
|
-
const c = new ContestIndex();
|
|
76
|
-
const cc = c.config;
|
|
77
|
-
const cjc = contestIndexJSON.config;
|
|
78
|
-
cc.contestName = cjc.contest_name;
|
|
79
|
-
cc.startTime = createDayJS(cjc.start_time);
|
|
80
|
-
cc.endTime = createDayJS(cjc.end_time);
|
|
81
|
-
cc.totalDurationTimestamp = cc.endTime.unix() - cc.startTime.unix();
|
|
82
|
-
{
|
|
83
|
-
cc.freezeTime = cc.endTime;
|
|
84
|
-
cc.freezeDurationTimestamp = 0;
|
|
85
|
-
if (cjc.frozen_time !== void 0 && cjc.frozen_time != null) {
|
|
86
|
-
const frozenTime = Number(cjc.frozen_time);
|
|
87
|
-
cc.freezeTime = createDayJS(cc.endTime.unix() - frozenTime);
|
|
88
|
-
cc.freezeDurationTimestamp = frozenTime;
|
|
89
|
-
}
|
|
90
|
-
cc.unFreezeDurationTimestamp = cc.totalDurationTimestamp - cc.freezeDurationTimestamp;
|
|
91
|
-
}
|
|
92
|
-
cc.logo = cjc.logo;
|
|
93
|
-
c.boardLink = contestIndexJSON.board_link;
|
|
94
|
-
return c;
|
|
95
|
-
}
|
|
96
|
-
function createContestIndexList(contestListJSON) {
|
|
97
|
-
const contestIndexList = [];
|
|
98
|
-
const dfs = (contestList) => {
|
|
99
|
-
if (Object.prototype.hasOwnProperty.call(contestList, "config")) {
|
|
100
|
-
contestIndexList.push(createContestIndex(contestList));
|
|
101
|
-
} else {
|
|
102
|
-
for (const k in contestList) {
|
|
103
|
-
dfs(contestList[k]);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
};
|
|
107
|
-
dfs(contestListJSON);
|
|
108
|
-
contestIndexList.sort((a, b) => {
|
|
109
|
-
if (a.config.startTime.isBefore(b.config.startTime)) {
|
|
110
|
-
return 1;
|
|
111
|
-
}
|
|
112
|
-
if (a.config.startTime.isAfter(b.config.startTime)) {
|
|
113
|
-
return -1;
|
|
114
|
-
}
|
|
115
|
-
if (a.config.endTime.isBefore(b.config.endTime)) {
|
|
116
|
-
return 1;
|
|
117
|
-
}
|
|
118
|
-
if (a.config.endTime.isAfter(b.config.endTime)) {
|
|
119
|
-
return -1;
|
|
120
|
-
}
|
|
121
|
-
if (a.config.contestName < b.config.contestName) {
|
|
122
|
-
return 1;
|
|
123
|
-
}
|
|
124
|
-
if (a.config.contestName > b.config.contestName) {
|
|
125
|
-
return -1;
|
|
126
|
-
}
|
|
127
|
-
return 0;
|
|
128
|
-
});
|
|
129
|
-
return contestIndexList;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
57
|
class ProblemStatistics {
|
|
133
58
|
constructor() {
|
|
134
59
|
this.acceptedNum = 0;
|
|
@@ -145,6 +70,8 @@ class ProblemStatistics {
|
|
|
145
70
|
this.rejectedNum = 0;
|
|
146
71
|
this.pendingNum = 0;
|
|
147
72
|
this.submittedNum = 0;
|
|
73
|
+
this.attemptedNum = 0;
|
|
74
|
+
this.ignoreNum = 0;
|
|
148
75
|
this.firstSolveSubmissions = [];
|
|
149
76
|
this.lastSolveSubmissions = [];
|
|
150
77
|
}
|
|
@@ -256,8 +183,8 @@ class Contest {
|
|
|
256
183
|
getContestDuration(timeFormat = "HH:mm:ss") {
|
|
257
184
|
return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
|
|
258
185
|
}
|
|
259
|
-
getContestState() {
|
|
260
|
-
const now = createDayJS();
|
|
186
|
+
getContestState(nowTime) {
|
|
187
|
+
const now = createDayJS(nowTime);
|
|
261
188
|
if (now.isBefore(this.startTime)) {
|
|
262
189
|
return ContestState.PENDING;
|
|
263
190
|
}
|
|
@@ -269,29 +196,35 @@ class Contest {
|
|
|
269
196
|
}
|
|
270
197
|
return ContestState.RUNNING;
|
|
271
198
|
}
|
|
272
|
-
getContestPendingTime() {
|
|
273
|
-
let baseTime = createDayJS();
|
|
199
|
+
getContestPendingTime(nowTime) {
|
|
200
|
+
let baseTime = createDayJS(nowTime);
|
|
274
201
|
if (baseTime.isAfter(this.startTime)) {
|
|
275
202
|
baseTime = this.startTime;
|
|
276
203
|
}
|
|
277
204
|
return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
|
|
278
205
|
}
|
|
279
|
-
|
|
280
|
-
let baseTime =
|
|
281
|
-
if (baseTime.isAfter(endTime)) {
|
|
282
|
-
baseTime = endTime;
|
|
206
|
+
getContestElapsedTime(nowTime) {
|
|
207
|
+
let baseTime = createDayJS(nowTime);
|
|
208
|
+
if (baseTime.isAfter(this.endTime)) {
|
|
209
|
+
baseTime = this.endTime;
|
|
283
210
|
}
|
|
284
|
-
|
|
211
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
212
|
+
baseTime = this.startTime;
|
|
213
|
+
}
|
|
214
|
+
return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
|
|
285
215
|
}
|
|
286
|
-
|
|
287
|
-
let baseTime =
|
|
216
|
+
getContestRemainingTime(nowTime) {
|
|
217
|
+
let baseTime = createDayJS(nowTime);
|
|
288
218
|
if (baseTime.isAfter(this.endTime)) {
|
|
289
219
|
baseTime = this.endTime;
|
|
290
220
|
}
|
|
291
|
-
|
|
221
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
222
|
+
baseTime = this.startTime;
|
|
223
|
+
}
|
|
224
|
+
return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
|
|
292
225
|
}
|
|
293
|
-
getContestProgressRatio() {
|
|
294
|
-
const baseTime =
|
|
226
|
+
getContestProgressRatio(nowTime) {
|
|
227
|
+
const baseTime = createDayJS(nowTime);
|
|
295
228
|
if (this.startTime.isSameOrAfter(baseTime)) {
|
|
296
229
|
return 0;
|
|
297
230
|
}
|
|
@@ -351,6 +284,66 @@ function createContest(contestJSON) {
|
|
|
351
284
|
return c;
|
|
352
285
|
}
|
|
353
286
|
|
|
287
|
+
class ContestIndexConfig {
|
|
288
|
+
constructor() {
|
|
289
|
+
this.contestName = "";
|
|
290
|
+
this.startTime = createDayJS();
|
|
291
|
+
this.endTime = createDayJS();
|
|
292
|
+
this.freezeTime = createDayJS();
|
|
293
|
+
this.totalDurationTimestamp = 0;
|
|
294
|
+
this.freezeDurationTimestamp = 0;
|
|
295
|
+
this.unFreezeDurationTimestamp = 0;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
class ContestIndex {
|
|
299
|
+
constructor() {
|
|
300
|
+
this.contest = new Contest();
|
|
301
|
+
this.boardLink = "";
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
function createContestIndex(contestIndexJSON) {
|
|
305
|
+
const c = new ContestIndex();
|
|
306
|
+
const cjc = contestIndexJSON.config;
|
|
307
|
+
c.contest = createContest(cjc);
|
|
308
|
+
c.boardLink = contestIndexJSON.board_link;
|
|
309
|
+
return c;
|
|
310
|
+
}
|
|
311
|
+
function createContestIndexList(contestListJSON) {
|
|
312
|
+
const contestIndexList = [];
|
|
313
|
+
const dfs = (contestList) => {
|
|
314
|
+
if (Object.prototype.hasOwnProperty.call(contestList, "config")) {
|
|
315
|
+
contestIndexList.push(createContestIndex(contestList));
|
|
316
|
+
} else {
|
|
317
|
+
for (const k in contestList) {
|
|
318
|
+
dfs(contestList[k]);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
dfs(contestListJSON);
|
|
323
|
+
contestIndexList.sort((a, b) => {
|
|
324
|
+
if (a.contest.startTime.isBefore(b.contest.startTime)) {
|
|
325
|
+
return 1;
|
|
326
|
+
}
|
|
327
|
+
if (a.contest.startTime.isAfter(b.contest.startTime)) {
|
|
328
|
+
return -1;
|
|
329
|
+
}
|
|
330
|
+
if (a.contest.endTime.isBefore(b.contest.endTime)) {
|
|
331
|
+
return 1;
|
|
332
|
+
}
|
|
333
|
+
if (a.contest.endTime.isAfter(b.contest.endTime)) {
|
|
334
|
+
return -1;
|
|
335
|
+
}
|
|
336
|
+
if (a.contest.name < b.contest.name) {
|
|
337
|
+
return 1;
|
|
338
|
+
}
|
|
339
|
+
if (a.contest.name > b.contest.name) {
|
|
340
|
+
return -1;
|
|
341
|
+
}
|
|
342
|
+
return 0;
|
|
343
|
+
});
|
|
344
|
+
return contestIndexList;
|
|
345
|
+
}
|
|
346
|
+
|
|
354
347
|
function getImageSource(image) {
|
|
355
348
|
if (image?.url) {
|
|
356
349
|
return image.url;
|
|
@@ -381,6 +374,17 @@ class Team {
|
|
|
381
374
|
this.organizationRank = -1;
|
|
382
375
|
this.solvedProblemNum = 0;
|
|
383
376
|
this.attemptedProblemNum = 0;
|
|
377
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
378
|
+
this.penalty = 0;
|
|
379
|
+
this.problemStatistics = [];
|
|
380
|
+
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
381
|
+
}
|
|
382
|
+
reset() {
|
|
383
|
+
this.rank = 0;
|
|
384
|
+
this.organizationRank = -1;
|
|
385
|
+
this.solvedProblemNum = 0;
|
|
386
|
+
this.attemptedProblemNum = 0;
|
|
387
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
384
388
|
this.penalty = 0;
|
|
385
389
|
this.problemStatistics = [];
|
|
386
390
|
this.problemStatisticsMap = /* @__PURE__ */ new Map();
|
|
@@ -405,6 +409,9 @@ class Team {
|
|
|
405
409
|
}
|
|
406
410
|
}
|
|
407
411
|
}
|
|
412
|
+
isEqualRank(otherTeam) {
|
|
413
|
+
return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
|
|
414
|
+
}
|
|
408
415
|
static compare(lhs, rhs) {
|
|
409
416
|
if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
|
|
410
417
|
return rhs.solvedProblemNum - lhs.solvedProblemNum;
|
|
@@ -412,6 +419,9 @@ class Team {
|
|
|
412
419
|
if (lhs.penalty !== rhs.penalty) {
|
|
413
420
|
return lhs.penalty - rhs.penalty;
|
|
414
421
|
}
|
|
422
|
+
if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
|
|
423
|
+
return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
|
|
424
|
+
}
|
|
415
425
|
if (lhs.name < rhs.name) {
|
|
416
426
|
return -1;
|
|
417
427
|
} else if (lhs.name > rhs.name) {
|
|
@@ -628,6 +638,21 @@ function createSubmissions(submissionsJSON) {
|
|
|
628
638
|
}
|
|
629
639
|
}
|
|
630
640
|
|
|
641
|
+
class RankOptions {
|
|
642
|
+
constructor() {
|
|
643
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
644
|
+
this.width = 0;
|
|
645
|
+
this.timestamp = 0;
|
|
646
|
+
}
|
|
647
|
+
setWidth(width, contest) {
|
|
648
|
+
this.width = width;
|
|
649
|
+
this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
|
|
650
|
+
this.enableFilterSubmissionsByTimestamp = true;
|
|
651
|
+
}
|
|
652
|
+
disableFilterSubmissionByTimestamp() {
|
|
653
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
654
|
+
}
|
|
655
|
+
}
|
|
631
656
|
class Rank {
|
|
632
657
|
constructor(contest, teams, submissions) {
|
|
633
658
|
this.contest = contest;
|
|
@@ -636,10 +661,12 @@ class Rank {
|
|
|
636
661
|
this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
|
|
637
662
|
this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
|
|
638
663
|
this.rankStatistics = new RankStatistics();
|
|
664
|
+
this.options = new RankOptions();
|
|
639
665
|
}
|
|
640
|
-
buildRank(
|
|
666
|
+
buildRank() {
|
|
641
667
|
(() => {
|
|
642
668
|
for (const t of this.teams) {
|
|
669
|
+
t.reset();
|
|
643
670
|
t.problemStatistics = this.contest.problems.map((p) => {
|
|
644
671
|
const ps = new TeamProblemStatistics();
|
|
645
672
|
ps.problem = p;
|
|
@@ -651,7 +678,7 @@ class Rank {
|
|
|
651
678
|
this.contest.problems.forEach((p) => {
|
|
652
679
|
p.statistics.reset();
|
|
653
680
|
});
|
|
654
|
-
for (const s of this.
|
|
681
|
+
for (const s of this.getSubmissions()) {
|
|
655
682
|
const teamId = s.teamId;
|
|
656
683
|
const problemId = s.problemId;
|
|
657
684
|
const team = this.teamsMap.get(teamId);
|
|
@@ -659,11 +686,6 @@ class Rank {
|
|
|
659
686
|
if (team === void 0 || problem === void 0) {
|
|
660
687
|
continue;
|
|
661
688
|
}
|
|
662
|
-
if (options?.timestamp !== void 0 && options?.timestamp !== null) {
|
|
663
|
-
if (s.timestamp > options.timestamp) {
|
|
664
|
-
break;
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
689
|
const problemStatistics = team.problemStatisticsMap.get(problemId);
|
|
668
690
|
const submissions = problemStatistics.submissions;
|
|
669
691
|
submissions.push(s);
|
|
@@ -692,6 +714,7 @@ class Rank {
|
|
|
692
714
|
problem.statistics.lastSolveSubmissions.pop();
|
|
693
715
|
}
|
|
694
716
|
problem.statistics.lastSolveSubmissions.push(s);
|
|
717
|
+
team.lastSolvedProblemTimestamp = s.timestamp;
|
|
695
718
|
}
|
|
696
719
|
if (s.isRejected()) {
|
|
697
720
|
problemStatistics.failedCount++;
|
|
@@ -706,19 +729,34 @@ class Rank {
|
|
|
706
729
|
this.teams.sort(Team.compare);
|
|
707
730
|
{
|
|
708
731
|
let rank = 1;
|
|
732
|
+
let preTeam = null;
|
|
709
733
|
for (const t of this.teams) {
|
|
710
734
|
t.rank = rank++;
|
|
735
|
+
if (preTeam !== null) {
|
|
736
|
+
if (t.isEqualRank(preTeam)) {
|
|
737
|
+
t.rank = preTeam.rank;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
preTeam = t;
|
|
711
741
|
}
|
|
712
742
|
}
|
|
713
743
|
if (this.contest.organization) {
|
|
714
744
|
let rank = 1;
|
|
745
|
+
let preTeam = null;
|
|
715
746
|
const se = /* @__PURE__ */ new Set();
|
|
716
747
|
for (const t of this.teams) {
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
748
|
+
const org = t.organization;
|
|
749
|
+
if (se.has(org)) {
|
|
750
|
+
continue;
|
|
751
|
+
}
|
|
752
|
+
se.add(org);
|
|
753
|
+
t.organizationRank = rank++;
|
|
754
|
+
if (preTeam !== null) {
|
|
755
|
+
if (t.isEqualRank(preTeam)) {
|
|
756
|
+
t.organizationRank = preTeam.organizationRank;
|
|
757
|
+
}
|
|
721
758
|
}
|
|
759
|
+
preTeam = t;
|
|
722
760
|
}
|
|
723
761
|
}
|
|
724
762
|
})();
|
|
@@ -731,6 +769,12 @@ class Rank {
|
|
|
731
769
|
})();
|
|
732
770
|
return this;
|
|
733
771
|
}
|
|
772
|
+
getSubmissions() {
|
|
773
|
+
if (this.options.enableFilterSubmissionsByTimestamp === false) {
|
|
774
|
+
return this.submissions;
|
|
775
|
+
}
|
|
776
|
+
return this.submissions.filter((s) => s.timestamp <= this.options.timestamp).sort(Submission.compare);
|
|
777
|
+
}
|
|
734
778
|
}
|
|
735
779
|
|
|
736
780
|
class ResolverOperation {
|
|
@@ -821,4 +865,4 @@ class Resolver extends Rank {
|
|
|
821
865
|
}
|
|
822
866
|
}
|
|
823
867
|
|
|
824
|
-
export { Contest, ContestIndex, ContestIndexConfig, Problem, ProblemStatistics, Rank, RankStatistics, Resolver, Submission, Team, TeamProblemStatistics, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
|
868
|
+
export { Contest, ContestIndex, ContestIndexConfig, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Resolver, Submission, Team, TeamProblemStatistics, calcDict, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, stringToSubmissionStatus };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xcpcio/core",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "XCPCIO Core",
|
|
5
5
|
"author": "Dup4 <lyuzhi.pan@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"dayjs": "^1.11.8",
|
|
44
44
|
"lodash": "^4.17.21",
|
|
45
|
-
"@xcpcio/types": "0.4.
|
|
45
|
+
"@xcpcio/types": "0.4.3"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@babel/types": "^7.22.4",
|
package/src/contest-index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type dayjs from "dayjs";
|
|
2
2
|
|
|
3
|
-
import type { ContestIndex as IContestIndex, Image } from "@xcpcio/types";
|
|
3
|
+
import type { Contest as IContest, ContestIndex as IContestIndex, Image } from "@xcpcio/types";
|
|
4
4
|
|
|
5
5
|
import { createDayJS } from "./utils";
|
|
6
|
+
import { Contest, createContest } from "./contest";
|
|
6
7
|
|
|
7
8
|
export class ContestIndexConfig {
|
|
8
9
|
contestName: string;
|
|
@@ -31,11 +32,11 @@ export class ContestIndexConfig {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
export class ContestIndex {
|
|
34
|
-
|
|
35
|
+
contest: Contest;
|
|
35
36
|
boardLink: string;
|
|
36
37
|
|
|
37
38
|
constructor() {
|
|
38
|
-
this.
|
|
39
|
+
this.contest = new Contest();
|
|
39
40
|
this.boardLink = "";
|
|
40
41
|
}
|
|
41
42
|
}
|
|
@@ -44,33 +45,9 @@ export type ContestIndexList = Array<ContestIndex>;
|
|
|
44
45
|
|
|
45
46
|
export function createContestIndex(contestIndexJSON: IContestIndex): ContestIndex {
|
|
46
47
|
const c = new ContestIndex();
|
|
47
|
-
const cc = c.config;
|
|
48
48
|
const cjc = contestIndexJSON.config;
|
|
49
49
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
cc.startTime = createDayJS(cjc.start_time);
|
|
53
|
-
cc.endTime = createDayJS(cjc.end_time);
|
|
54
|
-
|
|
55
|
-
cc.totalDurationTimestamp = cc.endTime.unix() - cc.startTime.unix();
|
|
56
|
-
|
|
57
|
-
{
|
|
58
|
-
// default value
|
|
59
|
-
cc.freezeTime = cc.endTime;
|
|
60
|
-
cc.freezeDurationTimestamp = 0;
|
|
61
|
-
|
|
62
|
-
if (cjc.frozen_time !== undefined && cjc.frozen_time != null) {
|
|
63
|
-
const frozenTime = Number(cjc.frozen_time);
|
|
64
|
-
|
|
65
|
-
cc.freezeTime = createDayJS(cc.endTime.unix() - frozenTime);
|
|
66
|
-
cc.freezeDurationTimestamp = frozenTime;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
cc.unFreezeDurationTimestamp = cc.totalDurationTimestamp - cc.freezeDurationTimestamp;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
cc.logo = cjc.logo;
|
|
73
|
-
|
|
50
|
+
c.contest = createContest(cjc as IContest);
|
|
74
51
|
c.boardLink = contestIndexJSON.board_link;
|
|
75
52
|
|
|
76
53
|
return c;
|
|
@@ -92,27 +69,27 @@ export function createContestIndexList(contestListJSON: any): ContestIndexList {
|
|
|
92
69
|
dfs(contestListJSON);
|
|
93
70
|
|
|
94
71
|
contestIndexList.sort((a: ContestIndex, b: ContestIndex) => {
|
|
95
|
-
if (a.
|
|
72
|
+
if (a.contest.startTime.isBefore(b.contest.startTime)) {
|
|
96
73
|
return 1;
|
|
97
74
|
}
|
|
98
75
|
|
|
99
|
-
if (a.
|
|
76
|
+
if (a.contest.startTime.isAfter(b.contest.startTime)) {
|
|
100
77
|
return -1;
|
|
101
78
|
}
|
|
102
79
|
|
|
103
|
-
if (a.
|
|
80
|
+
if (a.contest.endTime.isBefore(b.contest.endTime)) {
|
|
104
81
|
return 1;
|
|
105
82
|
}
|
|
106
83
|
|
|
107
|
-
if (a.
|
|
84
|
+
if (a.contest.endTime.isAfter(b.contest.endTime)) {
|
|
108
85
|
return -1;
|
|
109
86
|
}
|
|
110
87
|
|
|
111
|
-
if (a.
|
|
88
|
+
if (a.contest.name < b.contest.name) {
|
|
112
89
|
return 1;
|
|
113
90
|
}
|
|
114
91
|
|
|
115
|
-
if (a.
|
|
92
|
+
if (a.contest.name > b.contest.name) {
|
|
116
93
|
return -1;
|
|
117
94
|
}
|
|
118
95
|
|
package/src/contest.ts
CHANGED
|
@@ -62,8 +62,8 @@ export class Contest {
|
|
|
62
62
|
return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
getContestState(): ContestState {
|
|
66
|
-
const now = createDayJS();
|
|
65
|
+
getContestState(nowTime?: Date): ContestState {
|
|
66
|
+
const now = createDayJS(nowTime);
|
|
67
67
|
|
|
68
68
|
if (now.isBefore(this.startTime)) {
|
|
69
69
|
return ContestState.PENDING;
|
|
@@ -80,8 +80,8 @@ export class Contest {
|
|
|
80
80
|
return ContestState.RUNNING;
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
getContestPendingTime(): string {
|
|
84
|
-
let baseTime = createDayJS();
|
|
83
|
+
getContestPendingTime(nowTime?: Date): string {
|
|
84
|
+
let baseTime = createDayJS(nowTime);
|
|
85
85
|
if (baseTime.isAfter(this.startTime)) {
|
|
86
86
|
baseTime = this.startTime;
|
|
87
87
|
}
|
|
@@ -89,26 +89,34 @@ export class Contest {
|
|
|
89
89
|
return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
|
|
90
90
|
}
|
|
91
91
|
|
|
92
|
-
|
|
93
|
-
let baseTime =
|
|
94
|
-
if (baseTime.isAfter(endTime)) {
|
|
95
|
-
baseTime = endTime;
|
|
92
|
+
getContestElapsedTime(nowTime?: Date): string {
|
|
93
|
+
let baseTime = createDayJS(nowTime);
|
|
94
|
+
if (baseTime.isAfter(this.endTime)) {
|
|
95
|
+
baseTime = this.endTime;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
99
|
+
baseTime = this.startTime;
|
|
96
100
|
}
|
|
97
101
|
|
|
98
|
-
return getTimeDiff(Math.floor(dayjs.duration(
|
|
102
|
+
return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
|
|
99
103
|
}
|
|
100
104
|
|
|
101
|
-
|
|
102
|
-
let baseTime =
|
|
105
|
+
getContestRemainingTime(nowTime?: Date): string {
|
|
106
|
+
let baseTime = createDayJS(nowTime);
|
|
103
107
|
if (baseTime.isAfter(this.endTime)) {
|
|
104
108
|
baseTime = this.endTime;
|
|
105
109
|
}
|
|
106
110
|
|
|
107
|
-
|
|
111
|
+
if (baseTime.isBefore(this.startTime)) {
|
|
112
|
+
baseTime = this.startTime;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
|
|
108
116
|
}
|
|
109
117
|
|
|
110
|
-
getContestProgressRatio(): number {
|
|
111
|
-
const baseTime =
|
|
118
|
+
getContestProgressRatio(nowTime?: Date): number {
|
|
119
|
+
const baseTime = createDayJS(nowTime);
|
|
112
120
|
|
|
113
121
|
if (this.startTime.isSameOrAfter(baseTime)) {
|
|
114
122
|
return 0;
|
package/src/problem.ts
CHANGED
package/src/rank.ts
CHANGED
|
@@ -8,6 +8,28 @@ import { Submission } from "./submission";
|
|
|
8
8
|
import { TeamProblemStatistics } from "./problem";
|
|
9
9
|
import { RankStatistics } from "./rank-statistics";
|
|
10
10
|
|
|
11
|
+
export class RankOptions {
|
|
12
|
+
enableFilterSubmissionsByTimestamp: boolean;
|
|
13
|
+
width: number;
|
|
14
|
+
timestamp: number;
|
|
15
|
+
|
|
16
|
+
constructor() {
|
|
17
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
18
|
+
this.width = 0;
|
|
19
|
+
this.timestamp = 0;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
setWidth(width: number, contest: Contest) {
|
|
23
|
+
this.width = width;
|
|
24
|
+
this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 0.0001);
|
|
25
|
+
this.enableFilterSubmissionsByTimestamp = true;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
disableFilterSubmissionByTimestamp() {
|
|
29
|
+
this.enableFilterSubmissionsByTimestamp = false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
11
33
|
export class Rank {
|
|
12
34
|
readonly contest: Contest;
|
|
13
35
|
|
|
@@ -19,6 +41,8 @@ export class Rank {
|
|
|
19
41
|
|
|
20
42
|
rankStatistics: RankStatistics;
|
|
21
43
|
|
|
44
|
+
options: RankOptions;
|
|
45
|
+
|
|
22
46
|
constructor(contest: Contest, teams: Teams, submissions: Submissions) {
|
|
23
47
|
this.contest = contest;
|
|
24
48
|
|
|
@@ -29,11 +53,15 @@ export class Rank {
|
|
|
29
53
|
this.submissionsMap = new Map(this.submissions.map(s => [s.id, s]));
|
|
30
54
|
|
|
31
55
|
this.rankStatistics = new RankStatistics();
|
|
56
|
+
|
|
57
|
+
this.options = new RankOptions();
|
|
32
58
|
}
|
|
33
59
|
|
|
34
|
-
buildRank(
|
|
60
|
+
buildRank() {
|
|
35
61
|
(() => {
|
|
36
62
|
for (const t of this.teams) {
|
|
63
|
+
t.reset();
|
|
64
|
+
|
|
37
65
|
t.problemStatistics = this.contest.problems.map((p) => {
|
|
38
66
|
const ps = new TeamProblemStatistics();
|
|
39
67
|
ps.problem = p;
|
|
@@ -49,7 +77,7 @@ export class Rank {
|
|
|
49
77
|
p.statistics.reset();
|
|
50
78
|
});
|
|
51
79
|
|
|
52
|
-
for (const s of this.
|
|
80
|
+
for (const s of this.getSubmissions()) {
|
|
53
81
|
const teamId = s.teamId;
|
|
54
82
|
const problemId = s.problemId;
|
|
55
83
|
const team = this.teamsMap.get(teamId);
|
|
@@ -59,12 +87,6 @@ export class Rank {
|
|
|
59
87
|
continue;
|
|
60
88
|
}
|
|
61
89
|
|
|
62
|
-
if (options?.timestamp !== undefined && options?.timestamp !== null) {
|
|
63
|
-
if (s.timestamp > options.timestamp) {
|
|
64
|
-
break;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
90
|
const problemStatistics = team.problemStatisticsMap.get(problemId) as TeamProblemStatistics;
|
|
69
91
|
const submissions = problemStatistics.submissions;
|
|
70
92
|
|
|
@@ -105,6 +127,7 @@ export class Rank {
|
|
|
105
127
|
}
|
|
106
128
|
|
|
107
129
|
problem.statistics.lastSolveSubmissions.push(s);
|
|
130
|
+
team.lastSolvedProblemTimestamp = s.timestamp;
|
|
108
131
|
}
|
|
109
132
|
|
|
110
133
|
if (s.isRejected()) {
|
|
@@ -123,21 +146,42 @@ export class Rank {
|
|
|
123
146
|
|
|
124
147
|
{
|
|
125
148
|
let rank = 1;
|
|
149
|
+
let preTeam = null;
|
|
126
150
|
for (const t of this.teams) {
|
|
127
151
|
t.rank = rank++;
|
|
152
|
+
|
|
153
|
+
if (preTeam !== null) {
|
|
154
|
+
if (t.isEqualRank(preTeam)) {
|
|
155
|
+
t.rank = preTeam.rank;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
preTeam = t;
|
|
128
160
|
}
|
|
129
161
|
}
|
|
130
162
|
|
|
131
163
|
if (this.contest.organization) {
|
|
132
164
|
let rank = 1;
|
|
165
|
+
let preTeam = null;
|
|
166
|
+
|
|
133
167
|
const se = new Set<string>();
|
|
134
168
|
|
|
135
169
|
for (const t of this.teams) {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
170
|
+
const org = t.organization;
|
|
171
|
+
if (se.has(org)) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
se.add(org);
|
|
176
|
+
t.organizationRank = rank++;
|
|
177
|
+
|
|
178
|
+
if (preTeam !== null) {
|
|
179
|
+
if (t.isEqualRank(preTeam)) {
|
|
180
|
+
t.organizationRank = preTeam.organizationRank;
|
|
181
|
+
}
|
|
140
182
|
}
|
|
183
|
+
|
|
184
|
+
preTeam = t;
|
|
141
185
|
}
|
|
142
186
|
}
|
|
143
187
|
})();
|
|
@@ -146,7 +190,6 @@ export class Rank {
|
|
|
146
190
|
this.rankStatistics.reset();
|
|
147
191
|
|
|
148
192
|
this.rankStatistics.teamSolvedNum = Array(this.contest.problems.length + 1).fill(0);
|
|
149
|
-
|
|
150
193
|
for (const t of this.teams) {
|
|
151
194
|
this.rankStatistics.teamSolvedNum[t.solvedProblemNum]++;
|
|
152
195
|
}
|
|
@@ -154,4 +197,12 @@ export class Rank {
|
|
|
154
197
|
|
|
155
198
|
return this;
|
|
156
199
|
}
|
|
200
|
+
|
|
201
|
+
getSubmissions() {
|
|
202
|
+
if (this.options.enableFilterSubmissionsByTimestamp === false) {
|
|
203
|
+
return this.submissions;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return this.submissions.filter(s => s.timestamp <= this.options.timestamp).sort(Submission.compare);
|
|
207
|
+
}
|
|
157
208
|
}
|
package/src/team.ts
CHANGED
|
@@ -21,6 +21,7 @@ export class Team {
|
|
|
21
21
|
|
|
22
22
|
solvedProblemNum: number;
|
|
23
23
|
attemptedProblemNum: number;
|
|
24
|
+
lastSolvedProblemTimestamp: number;
|
|
24
25
|
|
|
25
26
|
penalty: number;
|
|
26
27
|
|
|
@@ -41,6 +42,21 @@ export class Team {
|
|
|
41
42
|
|
|
42
43
|
this.solvedProblemNum = 0;
|
|
43
44
|
this.attemptedProblemNum = 0;
|
|
45
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
46
|
+
|
|
47
|
+
this.penalty = 0;
|
|
48
|
+
|
|
49
|
+
this.problemStatistics = [];
|
|
50
|
+
this.problemStatisticsMap = new Map<string, TeamProblemStatistics>();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
reset() {
|
|
54
|
+
this.rank = 0;
|
|
55
|
+
this.organizationRank = -1;
|
|
56
|
+
|
|
57
|
+
this.solvedProblemNum = 0;
|
|
58
|
+
this.attemptedProblemNum = 0;
|
|
59
|
+
this.lastSolvedProblemTimestamp = 0;
|
|
44
60
|
|
|
45
61
|
this.penalty = 0;
|
|
46
62
|
|
|
@@ -74,6 +90,10 @@ export class Team {
|
|
|
74
90
|
}
|
|
75
91
|
}
|
|
76
92
|
|
|
93
|
+
isEqualRank(otherTeam: Team) {
|
|
94
|
+
return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
|
|
95
|
+
}
|
|
96
|
+
|
|
77
97
|
static compare(lhs: Team, rhs: Team): number {
|
|
78
98
|
if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
|
|
79
99
|
return rhs.solvedProblemNum - lhs.solvedProblemNum;
|
|
@@ -83,6 +103,10 @@ export class Team {
|
|
|
83
103
|
return lhs.penalty - rhs.penalty;
|
|
84
104
|
}
|
|
85
105
|
|
|
106
|
+
if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
|
|
107
|
+
return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
|
|
108
|
+
}
|
|
109
|
+
|
|
86
110
|
if (lhs.name < rhs.name) {
|
|
87
111
|
return -1;
|
|
88
112
|
} else if (lhs.name > rhs.name) {
|