@xcpcio/core 0.4.2 → 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
@@ -86,6 +86,8 @@ class ProblemStatistics {
86
86
  this.rejectedNum = 0;
87
87
  this.pendingNum = 0;
88
88
  this.submittedNum = 0;
89
+ this.attemptedNum = 0;
90
+ this.ignoreNum = 0;
89
91
  this.firstSolveSubmissions = [];
90
92
  this.lastSolveSubmissions = [];
91
93
  }
@@ -217,19 +219,25 @@ class Contest {
217
219
  }
218
220
  return getTimeDiff(Math.floor(dayjs__default.duration(this.startTime.diff(baseTime)).asSeconds()));
219
221
  }
220
- getContestRemainingTime(nowTime) {
222
+ getContestElapsedTime(nowTime) {
221
223
  let baseTime = createDayJS(nowTime);
222
224
  if (baseTime.isAfter(this.endTime)) {
223
225
  baseTime = this.endTime;
224
226
  }
225
- return getTimeDiff(Math.floor(dayjs__default.duration(this.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()));
226
231
  }
227
- getContestElapsedTime(nowTime) {
232
+ getContestRemainingTime(nowTime) {
228
233
  let baseTime = createDayJS(nowTime);
229
234
  if (baseTime.isAfter(this.endTime)) {
230
235
  baseTime = this.endTime;
231
236
  }
232
- 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()));
233
241
  }
234
242
  getContestProgressRatio(nowTime) {
235
243
  const baseTime = createDayJS(nowTime);
@@ -382,6 +390,17 @@ class Team {
382
390
  this.organizationRank = -1;
383
391
  this.solvedProblemNum = 0;
384
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;
385
404
  this.penalty = 0;
386
405
  this.problemStatistics = [];
387
406
  this.problemStatisticsMap = /* @__PURE__ */ new Map();
@@ -406,6 +425,9 @@ class Team {
406
425
  }
407
426
  }
408
427
  }
428
+ isEqualRank(otherTeam) {
429
+ return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
430
+ }
409
431
  static compare(lhs, rhs) {
410
432
  if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
411
433
  return rhs.solvedProblemNum - lhs.solvedProblemNum;
@@ -413,6 +435,9 @@ class Team {
413
435
  if (lhs.penalty !== rhs.penalty) {
414
436
  return lhs.penalty - rhs.penalty;
415
437
  }
438
+ if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
439
+ return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
440
+ }
416
441
  if (lhs.name < rhs.name) {
417
442
  return -1;
418
443
  } else if (lhs.name > rhs.name) {
@@ -629,6 +654,21 @@ function createSubmissions(submissionsJSON) {
629
654
  }
630
655
  }
631
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
+ }
632
672
  class Rank {
633
673
  constructor(contest, teams, submissions) {
634
674
  this.contest = contest;
@@ -637,10 +677,12 @@ class Rank {
637
677
  this.submissions = ___default.cloneDeep(submissions).sort(Submission.compare);
638
678
  this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
639
679
  this.rankStatistics = new RankStatistics();
680
+ this.options = new RankOptions();
640
681
  }
641
- buildRank(options) {
682
+ buildRank() {
642
683
  (() => {
643
684
  for (const t of this.teams) {
685
+ t.reset();
644
686
  t.problemStatistics = this.contest.problems.map((p) => {
645
687
  const ps = new TeamProblemStatistics();
646
688
  ps.problem = p;
@@ -652,7 +694,7 @@ class Rank {
652
694
  this.contest.problems.forEach((p) => {
653
695
  p.statistics.reset();
654
696
  });
655
- for (const s of this.submissions) {
697
+ for (const s of this.getSubmissions()) {
656
698
  const teamId = s.teamId;
657
699
  const problemId = s.problemId;
658
700
  const team = this.teamsMap.get(teamId);
@@ -660,11 +702,6 @@ class Rank {
660
702
  if (team === void 0 || problem === void 0) {
661
703
  continue;
662
704
  }
663
- if (options?.timestamp !== void 0 && options?.timestamp !== null) {
664
- if (s.timestamp > options.timestamp) {
665
- break;
666
- }
667
- }
668
705
  const problemStatistics = team.problemStatisticsMap.get(problemId);
669
706
  const submissions = problemStatistics.submissions;
670
707
  submissions.push(s);
@@ -693,6 +730,7 @@ class Rank {
693
730
  problem.statistics.lastSolveSubmissions.pop();
694
731
  }
695
732
  problem.statistics.lastSolveSubmissions.push(s);
733
+ team.lastSolvedProblemTimestamp = s.timestamp;
696
734
  }
697
735
  if (s.isRejected()) {
698
736
  problemStatistics.failedCount++;
@@ -707,19 +745,34 @@ class Rank {
707
745
  this.teams.sort(Team.compare);
708
746
  {
709
747
  let rank = 1;
748
+ let preTeam = null;
710
749
  for (const t of this.teams) {
711
750
  t.rank = rank++;
751
+ if (preTeam !== null) {
752
+ if (t.isEqualRank(preTeam)) {
753
+ t.rank = preTeam.rank;
754
+ }
755
+ }
756
+ preTeam = t;
712
757
  }
713
758
  }
714
759
  if (this.contest.organization) {
715
760
  let rank = 1;
761
+ let preTeam = null;
716
762
  const se = /* @__PURE__ */ new Set();
717
763
  for (const t of this.teams) {
718
- if (!se.has(t.organization)) {
719
- se.add(t.organization);
720
- t.organizationRank = rank;
721
- 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
+ }
722
774
  }
775
+ preTeam = t;
723
776
  }
724
777
  }
725
778
  })();
@@ -732,6 +785,12 @@ class Rank {
732
785
  })();
733
786
  return this;
734
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
+ }
735
794
  }
736
795
 
737
796
  class ResolverOperation {
@@ -829,6 +888,7 @@ exports.ContestIndexConfig = ContestIndexConfig;
829
888
  exports.Problem = Problem;
830
889
  exports.ProblemStatistics = ProblemStatistics;
831
890
  exports.Rank = Rank;
891
+ exports.RankOptions = RankOptions;
832
892
  exports.RankStatistics = RankStatistics;
833
893
  exports.Resolver = Resolver;
834
894
  exports.Submission = Submission;
package/dist/index.d.ts CHANGED
@@ -101,8 +101,8 @@ declare class Contest {
101
101
  getContestDuration(timeFormat?: string): string;
102
102
  getContestState(nowTime?: Date): ContestState;
103
103
  getContestPendingTime(nowTime?: Date): string;
104
- getContestRemainingTime(nowTime?: Date): string;
105
104
  getContestElapsedTime(nowTime?: Date): string;
105
+ getContestRemainingTime(nowTime?: Date): string;
106
106
  getContestProgressRatio(nowTime?: Date): number;
107
107
  }
108
108
  declare function createContest(contestJSON: Contest$1): Contest;
@@ -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
@@ -70,6 +70,8 @@ class ProblemStatistics {
70
70
  this.rejectedNum = 0;
71
71
  this.pendingNum = 0;
72
72
  this.submittedNum = 0;
73
+ this.attemptedNum = 0;
74
+ this.ignoreNum = 0;
73
75
  this.firstSolveSubmissions = [];
74
76
  this.lastSolveSubmissions = [];
75
77
  }
@@ -201,19 +203,25 @@ class Contest {
201
203
  }
202
204
  return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
203
205
  }
204
- getContestRemainingTime(nowTime) {
206
+ getContestElapsedTime(nowTime) {
205
207
  let baseTime = createDayJS(nowTime);
206
208
  if (baseTime.isAfter(this.endTime)) {
207
209
  baseTime = this.endTime;
208
210
  }
209
- return getTimeDiff(Math.floor(dayjs.duration(this.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()));
210
215
  }
211
- getContestElapsedTime(nowTime) {
216
+ getContestRemainingTime(nowTime) {
212
217
  let baseTime = createDayJS(nowTime);
213
218
  if (baseTime.isAfter(this.endTime)) {
214
219
  baseTime = this.endTime;
215
220
  }
216
- 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()));
217
225
  }
218
226
  getContestProgressRatio(nowTime) {
219
227
  const baseTime = createDayJS(nowTime);
@@ -366,6 +374,17 @@ class Team {
366
374
  this.organizationRank = -1;
367
375
  this.solvedProblemNum = 0;
368
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;
369
388
  this.penalty = 0;
370
389
  this.problemStatistics = [];
371
390
  this.problemStatisticsMap = /* @__PURE__ */ new Map();
@@ -390,6 +409,9 @@ class Team {
390
409
  }
391
410
  }
392
411
  }
412
+ isEqualRank(otherTeam) {
413
+ return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
414
+ }
393
415
  static compare(lhs, rhs) {
394
416
  if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
395
417
  return rhs.solvedProblemNum - lhs.solvedProblemNum;
@@ -397,6 +419,9 @@ class Team {
397
419
  if (lhs.penalty !== rhs.penalty) {
398
420
  return lhs.penalty - rhs.penalty;
399
421
  }
422
+ if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
423
+ return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
424
+ }
400
425
  if (lhs.name < rhs.name) {
401
426
  return -1;
402
427
  } else if (lhs.name > rhs.name) {
@@ -613,6 +638,21 @@ function createSubmissions(submissionsJSON) {
613
638
  }
614
639
  }
615
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
+ }
616
656
  class Rank {
617
657
  constructor(contest, teams, submissions) {
618
658
  this.contest = contest;
@@ -621,10 +661,12 @@ class Rank {
621
661
  this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
622
662
  this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
623
663
  this.rankStatistics = new RankStatistics();
664
+ this.options = new RankOptions();
624
665
  }
625
- buildRank(options) {
666
+ buildRank() {
626
667
  (() => {
627
668
  for (const t of this.teams) {
669
+ t.reset();
628
670
  t.problemStatistics = this.contest.problems.map((p) => {
629
671
  const ps = new TeamProblemStatistics();
630
672
  ps.problem = p;
@@ -636,7 +678,7 @@ class Rank {
636
678
  this.contest.problems.forEach((p) => {
637
679
  p.statistics.reset();
638
680
  });
639
- for (const s of this.submissions) {
681
+ for (const s of this.getSubmissions()) {
640
682
  const teamId = s.teamId;
641
683
  const problemId = s.problemId;
642
684
  const team = this.teamsMap.get(teamId);
@@ -644,11 +686,6 @@ class Rank {
644
686
  if (team === void 0 || problem === void 0) {
645
687
  continue;
646
688
  }
647
- if (options?.timestamp !== void 0 && options?.timestamp !== null) {
648
- if (s.timestamp > options.timestamp) {
649
- break;
650
- }
651
- }
652
689
  const problemStatistics = team.problemStatisticsMap.get(problemId);
653
690
  const submissions = problemStatistics.submissions;
654
691
  submissions.push(s);
@@ -677,6 +714,7 @@ class Rank {
677
714
  problem.statistics.lastSolveSubmissions.pop();
678
715
  }
679
716
  problem.statistics.lastSolveSubmissions.push(s);
717
+ team.lastSolvedProblemTimestamp = s.timestamp;
680
718
  }
681
719
  if (s.isRejected()) {
682
720
  problemStatistics.failedCount++;
@@ -691,19 +729,34 @@ class Rank {
691
729
  this.teams.sort(Team.compare);
692
730
  {
693
731
  let rank = 1;
732
+ let preTeam = null;
694
733
  for (const t of this.teams) {
695
734
  t.rank = rank++;
735
+ if (preTeam !== null) {
736
+ if (t.isEqualRank(preTeam)) {
737
+ t.rank = preTeam.rank;
738
+ }
739
+ }
740
+ preTeam = t;
696
741
  }
697
742
  }
698
743
  if (this.contest.organization) {
699
744
  let rank = 1;
745
+ let preTeam = null;
700
746
  const se = /* @__PURE__ */ new Set();
701
747
  for (const t of this.teams) {
702
- if (!se.has(t.organization)) {
703
- se.add(t.organization);
704
- t.organizationRank = rank;
705
- 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
+ }
706
758
  }
759
+ preTeam = t;
707
760
  }
708
761
  }
709
762
  })();
@@ -716,6 +769,12 @@ class Rank {
716
769
  })();
717
770
  return this;
718
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
+ }
719
778
  }
720
779
 
721
780
  class ResolverOperation {
@@ -806,4 +865,4 @@ class Resolver extends Rank {
806
865
  }
807
866
  }
808
867
 
809
- 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.2",
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.2"
45
+ "@xcpcio/types": "0.4.3"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@babel/types": "^7.22.4",
package/src/contest.ts CHANGED
@@ -89,22 +89,30 @@ export class Contest {
89
89
  return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
90
90
  }
91
91
 
92
- getContestRemainingTime(nowTime?: Date): string {
92
+ getContestElapsedTime(nowTime?: Date): string {
93
93
  let baseTime = createDayJS(nowTime);
94
94
  if (baseTime.isAfter(this.endTime)) {
95
95
  baseTime = this.endTime;
96
96
  }
97
97
 
98
- return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
98
+ if (baseTime.isBefore(this.startTime)) {
99
+ baseTime = this.startTime;
100
+ }
101
+
102
+ return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
99
103
  }
100
104
 
101
- getContestElapsedTime(nowTime?: Date): string {
105
+ getContestRemainingTime(nowTime?: Date): string {
102
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
118
  getContestProgressRatio(nowTime?: Date): number {
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) {