@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 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
- getContestRemainingTime(endTime) {
296
- let baseTime = dayjs__default();
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
- return getTimeDiff(Math.floor(dayjs__default.duration(endTime.diff(baseTime)).asSeconds()));
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
- getContestElapsedTime() {
303
- let baseTime = dayjs__default();
232
+ getContestRemainingTime(nowTime) {
233
+ let baseTime = createDayJS(nowTime);
304
234
  if (baseTime.isAfter(this.endTime)) {
305
235
  baseTime = this.endTime;
306
236
  }
307
- return getTimeDiff(Math.floor(dayjs__default.duration(baseTime.diff(this.startTime)).asSeconds()));
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 = dayjs__default();
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(options) {
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.submissions) {
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
- if (!se.has(t.organization)) {
734
- se.add(t.organization);
735
- t.organizationRank = rank;
736
- rank++;
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 { Image, ContestIndex as ContestIndex$1, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, StatusTimeDisplay, ContestState, Contest as Contest$1, Team as Team$1, Teams as Teams$1 } from '@xcpcio/types';
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
- getContestRemainingTime(endTime: dayjs.Dayjs): string;
125
- getContestElapsedTime(): string;
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(options?: {
173
- timestamp?: number;
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
- getContestRemainingTime(endTime) {
280
- let baseTime = dayjs();
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
- return getTimeDiff(Math.floor(dayjs.duration(endTime.diff(baseTime)).asSeconds()));
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
- getContestElapsedTime() {
287
- let baseTime = dayjs();
216
+ getContestRemainingTime(nowTime) {
217
+ let baseTime = createDayJS(nowTime);
288
218
  if (baseTime.isAfter(this.endTime)) {
289
219
  baseTime = this.endTime;
290
220
  }
291
- return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
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 = dayjs();
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(options) {
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.submissions) {
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
- if (!se.has(t.organization)) {
718
- se.add(t.organization);
719
- t.organizationRank = rank;
720
- rank++;
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.1",
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.1"
45
+ "@xcpcio/types": "0.4.3"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@babel/types": "^7.22.4",
@@ -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
- config: ContestIndexConfig;
35
+ contest: Contest;
35
36
  boardLink: string;
36
37
 
37
38
  constructor() {
38
- this.config = new ContestIndexConfig();
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
- cc.contestName = cjc.contest_name;
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.config.startTime.isBefore(b.config.startTime)) {
72
+ if (a.contest.startTime.isBefore(b.contest.startTime)) {
96
73
  return 1;
97
74
  }
98
75
 
99
- if (a.config.startTime.isAfter(b.config.startTime)) {
76
+ if (a.contest.startTime.isAfter(b.contest.startTime)) {
100
77
  return -1;
101
78
  }
102
79
 
103
- if (a.config.endTime.isBefore(b.config.endTime)) {
80
+ if (a.contest.endTime.isBefore(b.contest.endTime)) {
104
81
  return 1;
105
82
  }
106
83
 
107
- if (a.config.endTime.isAfter(b.config.endTime)) {
84
+ if (a.contest.endTime.isAfter(b.contest.endTime)) {
108
85
  return -1;
109
86
  }
110
87
 
111
- if (a.config.contestName < b.config.contestName) {
88
+ if (a.contest.name < b.contest.name) {
112
89
  return 1;
113
90
  }
114
91
 
115
- if (a.config.contestName > b.config.contestName) {
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
- getContestRemainingTime(endTime: dayjs.Dayjs): string {
93
- let baseTime = dayjs();
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(endTime.diff(baseTime)).asSeconds()));
102
+ return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
99
103
  }
100
104
 
101
- getContestElapsedTime(): string {
102
- let baseTime = dayjs();
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
- return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
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 = dayjs();
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
@@ -34,6 +34,8 @@ export class ProblemStatistics {
34
34
  this.pendingNum = 0;
35
35
 
36
36
  this.submittedNum = 0;
37
+ this.attemptedNum = 0;
38
+ this.ignoreNum = 0;
37
39
 
38
40
  this.firstSolveSubmissions = [];
39
41
  this.lastSolveSubmissions = [];
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(options?: { timestamp?: number }) {
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.submissions) {
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
- if (!se.has(t.organization)) {
137
- se.add(t.organization);
138
- t.organizationRank = rank;
139
- rank++;
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) {