@xcpcio/core 0.4.2 → 0.5.0

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);
@@ -371,6 +379,13 @@ class RankStatistics {
371
379
  }
372
380
  }
373
381
 
382
+ class PlaceChartPointData {
383
+ constructor() {
384
+ this.timePoint = 0;
385
+ this.rank = 0;
386
+ this.lastSolvedProblem = null;
387
+ }
388
+ }
374
389
  class Team {
375
390
  constructor() {
376
391
  this.id = "";
@@ -382,9 +397,26 @@ class Team {
382
397
  this.organizationRank = -1;
383
398
  this.solvedProblemNum = 0;
384
399
  this.attemptedProblemNum = 0;
400
+ this.lastSolvedProblem = null;
401
+ this.lastSolvedProblemTimestamp = 0;
385
402
  this.penalty = 0;
386
403
  this.problemStatistics = [];
387
404
  this.problemStatisticsMap = /* @__PURE__ */ new Map();
405
+ this.submissions = [];
406
+ this.placeChartPoints = [];
407
+ }
408
+ reset() {
409
+ this.rank = 0;
410
+ this.organizationRank = -1;
411
+ this.solvedProblemNum = 0;
412
+ this.attemptedProblemNum = 0;
413
+ this.lastSolvedProblem = null;
414
+ this.lastSolvedProblemTimestamp = 0;
415
+ this.penalty = 0;
416
+ this.problemStatistics = [];
417
+ this.problemStatisticsMap = /* @__PURE__ */ new Map();
418
+ this.submissions = [];
419
+ this.placeChartPoints = [];
388
420
  }
389
421
  get penaltyToMinute() {
390
422
  return Math.floor(this.penalty / 60);
@@ -396,8 +428,8 @@ class Team {
396
428
  }
397
429
  calcSolvedData() {
398
430
  this.solvedProblemNum = 0;
399
- this.penalty = 0;
400
431
  this.attemptedProblemNum = 0;
432
+ this.penalty = 0;
401
433
  for (const p of this.problemStatistics) {
402
434
  if (p.isAccepted) {
403
435
  this.solvedProblemNum++;
@@ -406,6 +438,27 @@ class Team {
406
438
  }
407
439
  }
408
440
  }
441
+ isEqualRank(otherTeam) {
442
+ return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
443
+ }
444
+ postProcessPlaceChartPoints() {
445
+ if (this.placeChartPoints.length === 0) {
446
+ return;
447
+ }
448
+ const res = [];
449
+ res.push(this.placeChartPoints[0]);
450
+ for (let i = 1; i < this.placeChartPoints.length - 1; i++) {
451
+ const p = this.placeChartPoints[i];
452
+ const preP = res[res.length - 1];
453
+ if (p.rank !== preP.rank || p.lastSolvedProblem !== preP.lastSolvedProblem) {
454
+ res.push(p);
455
+ }
456
+ }
457
+ if (this.placeChartPoints.length > 1) {
458
+ res.push(this.placeChartPoints[this.placeChartPoints.length - 1]);
459
+ }
460
+ this.placeChartPoints = res;
461
+ }
409
462
  static compare(lhs, rhs) {
410
463
  if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
411
464
  return rhs.solvedProblemNum - lhs.solvedProblemNum;
@@ -413,6 +466,9 @@ class Team {
413
466
  if (lhs.penalty !== rhs.penalty) {
414
467
  return lhs.penalty - rhs.penalty;
415
468
  }
469
+ if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
470
+ return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
471
+ }
416
472
  if (lhs.name < rhs.name) {
417
473
  return -1;
418
474
  } else if (lhs.name > rhs.name) {
@@ -593,6 +649,9 @@ class Submission {
593
649
  isNotCalculatedPenaltyStatus() {
594
650
  return isNotCalculatedPenaltyStatus(this.status);
595
651
  }
652
+ get timestampToMinute() {
653
+ return Math.floor(this.timestamp / 60);
654
+ }
596
655
  static compare(lhs, rhs) {
597
656
  if (lhs.timestamp !== rhs.timestamp) {
598
657
  return lhs.timestamp - rhs.timestamp;
@@ -629,6 +688,21 @@ function createSubmissions(submissionsJSON) {
629
688
  }
630
689
  }
631
690
 
691
+ class RankOptions {
692
+ constructor() {
693
+ this.enableFilterSubmissionsByTimestamp = false;
694
+ this.width = 0;
695
+ this.timestamp = 0;
696
+ }
697
+ setWidth(width, contest) {
698
+ this.width = width;
699
+ this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
700
+ this.enableFilterSubmissionsByTimestamp = true;
701
+ }
702
+ disableFilterSubmissionByTimestamp() {
703
+ this.enableFilterSubmissionsByTimestamp = false;
704
+ }
705
+ }
632
706
  class Rank {
633
707
  constructor(contest, teams, submissions) {
634
708
  this.contest = contest;
@@ -637,10 +711,12 @@ class Rank {
637
711
  this.submissions = ___default.cloneDeep(submissions).sort(Submission.compare);
638
712
  this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
639
713
  this.rankStatistics = new RankStatistics();
714
+ this.options = new RankOptions();
640
715
  }
641
- buildRank(options) {
716
+ buildRank() {
642
717
  (() => {
643
718
  for (const t of this.teams) {
719
+ t.reset();
644
720
  t.problemStatistics = this.contest.problems.map((p) => {
645
721
  const ps = new TeamProblemStatistics();
646
722
  ps.problem = p;
@@ -652,76 +728,84 @@ class Rank {
652
728
  this.contest.problems.forEach((p) => {
653
729
  p.statistics.reset();
654
730
  });
655
- for (const s of this.submissions) {
731
+ let preSubmissionTimestampToMinute = 0;
732
+ const allSubmissions = this.getSubmissions();
733
+ this.teams.forEach(
734
+ (t) => t.placeChartPoints.push({
735
+ timePoint: 0,
736
+ rank: 1,
737
+ lastSolvedProblem: null
738
+ })
739
+ );
740
+ for (let ix = 0; ix < allSubmissions.length; ix++) {
741
+ const s = allSubmissions[ix];
656
742
  const teamId = s.teamId;
657
743
  const problemId = s.problemId;
658
744
  const team = this.teamsMap.get(teamId);
659
745
  const problem = this.contest.problemsMap.get(problemId);
660
- if (team === void 0 || problem === void 0) {
661
- continue;
662
- }
663
- if (options?.timestamp !== void 0 && options?.timestamp !== null) {
664
- if (s.timestamp > options.timestamp) {
665
- break;
746
+ (() => {
747
+ if (team === void 0 || problem === void 0) {
748
+ return;
666
749
  }
667
- }
668
- const problemStatistics = team.problemStatisticsMap.get(problemId);
669
- const submissions = problemStatistics.submissions;
670
- submissions.push(s);
671
- problem.statistics.submittedNum++;
672
- if (problemStatistics.isSolved) {
673
- continue;
674
- }
675
- if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
676
- problem.statistics.ignoreNum++;
677
- problemStatistics.ignoreCount++;
678
- continue;
679
- }
680
- problemStatistics.isSubmitted = true;
681
- problemStatistics.lastSubmitTimestamp = s.timestamp;
682
- problemStatistics.totalCount++;
683
- if (s.isAccepted()) {
684
- problemStatistics.isSolved = true;
685
- problemStatistics.solvedTimestamp = s.timestamp;
686
- problem.statistics.acceptedNum++;
687
- problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
688
- if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
689
- problemStatistics.isFirstSolved = true;
690
- problem.statistics.firstSolveSubmissions.push(s);
750
+ const problemStatistics = team.problemStatisticsMap.get(problemId);
751
+ const submissions = problemStatistics.submissions;
752
+ submissions.push(s);
753
+ team.submissions.push(s);
754
+ problem.statistics.submittedNum++;
755
+ if (problemStatistics.isSolved) {
756
+ return;
691
757
  }
692
- while (problem.statistics.lastSolveSubmissions.length > 0) {
693
- problem.statistics.lastSolveSubmissions.pop();
758
+ if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
759
+ problem.statistics.ignoreNum++;
760
+ problemStatistics.ignoreCount++;
761
+ return;
694
762
  }
695
- problem.statistics.lastSolveSubmissions.push(s);
696
- }
697
- if (s.isRejected()) {
698
- problemStatistics.failedCount++;
699
- problem.statistics.rejectedNum++;
700
- }
701
- if (s.isPending()) {
702
- problemStatistics.pendingCount++;
703
- problem.statistics.pendingNum++;
704
- }
705
- }
706
- this.teams.forEach((t) => t.calcSolvedData());
707
- this.teams.sort(Team.compare);
708
- {
709
- let rank = 1;
710
- for (const t of this.teams) {
711
- t.rank = rank++;
712
- }
713
- }
714
- if (this.contest.organization) {
715
- let rank = 1;
716
- const se = /* @__PURE__ */ new Set();
717
- for (const t of this.teams) {
718
- if (!se.has(t.organization)) {
719
- se.add(t.organization);
720
- t.organizationRank = rank;
721
- rank++;
763
+ problemStatistics.isSubmitted = true;
764
+ problemStatistics.lastSubmitTimestamp = s.timestamp;
765
+ problemStatistics.totalCount++;
766
+ if (s.isAccepted()) {
767
+ problemStatistics.isSolved = true;
768
+ problemStatistics.solvedTimestamp = s.timestamp;
769
+ problem.statistics.acceptedNum++;
770
+ problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
771
+ if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
772
+ problemStatistics.isFirstSolved = true;
773
+ problem.statistics.firstSolveSubmissions.push(s);
774
+ }
775
+ while (problem.statistics.lastSolveSubmissions.length > 0) {
776
+ problem.statistics.lastSolveSubmissions.pop();
777
+ }
778
+ problem.statistics.lastSolveSubmissions.push(s);
779
+ team.lastSolvedProblem = problem;
780
+ team.lastSolvedProblemTimestamp = s.timestamp;
781
+ }
782
+ if (s.isRejected()) {
783
+ problemStatistics.failedCount++;
784
+ problem.statistics.rejectedNum++;
722
785
  }
786
+ if (s.isPending()) {
787
+ problemStatistics.pendingCount++;
788
+ problem.statistics.pendingNum++;
789
+ }
790
+ })();
791
+ if (s.timestampToMinute > preSubmissionTimestampToMinute || ix === allSubmissions.length - 1) {
792
+ this.teams.forEach((t) => t.calcSolvedData());
793
+ this.teams.sort(Team.compare);
794
+ this.buildTeamRank();
795
+ this.teams.forEach(
796
+ (t) => t.placeChartPoints.push(
797
+ {
798
+ timePoint: s.timestampToMinute,
799
+ rank: t.rank,
800
+ lastSolvedProblem: t.lastSolvedProblem
801
+ }
802
+ )
803
+ );
723
804
  }
805
+ preSubmissionTimestampToMinute = s.timestampToMinute;
724
806
  }
807
+ this.teams.forEach((t) => t.postProcessPlaceChartPoints());
808
+ this.buildOrgRank();
725
809
  })();
726
810
  (() => {
727
811
  this.rankStatistics.reset();
@@ -732,6 +816,47 @@ class Rank {
732
816
  })();
733
817
  return this;
734
818
  }
819
+ buildTeamRank() {
820
+ let rank = 1;
821
+ let preTeam = null;
822
+ for (const t of this.teams) {
823
+ t.rank = rank++;
824
+ if (preTeam !== null) {
825
+ if (t.isEqualRank(preTeam)) {
826
+ t.rank = preTeam.rank;
827
+ }
828
+ }
829
+ preTeam = t;
830
+ }
831
+ }
832
+ buildOrgRank() {
833
+ if (!this.contest.organization) {
834
+ return;
835
+ }
836
+ let rank = 1;
837
+ let preTeam = null;
838
+ const se = /* @__PURE__ */ new Set();
839
+ for (const t of this.teams) {
840
+ const org = t.organization;
841
+ if (se.has(org)) {
842
+ continue;
843
+ }
844
+ se.add(org);
845
+ t.organizationRank = rank++;
846
+ if (preTeam !== null) {
847
+ if (t.isEqualRank(preTeam)) {
848
+ t.organizationRank = preTeam.organizationRank;
849
+ }
850
+ }
851
+ preTeam = t;
852
+ }
853
+ }
854
+ getSubmissions() {
855
+ if (this.options.enableFilterSubmissionsByTimestamp === false) {
856
+ return this.submissions;
857
+ }
858
+ return this.submissions.filter((s) => s.timestamp <= this.options.timestamp).sort(Submission.compare);
859
+ }
735
860
  }
736
861
 
737
862
  class ResolverOperation {
@@ -826,9 +951,11 @@ exports.dayjs = dayjs__default;
826
951
  exports.Contest = Contest;
827
952
  exports.ContestIndex = ContestIndex;
828
953
  exports.ContestIndexConfig = ContestIndexConfig;
954
+ exports.PlaceChartPointData = PlaceChartPointData;
829
955
  exports.Problem = Problem;
830
956
  exports.ProblemStatistics = ProblemStatistics;
831
957
  exports.Rank = Rank;
958
+ exports.RankOptions = RankOptions;
832
959
  exports.RankStatistics = RankStatistics;
833
960
  exports.Resolver = Resolver;
834
961
  exports.Submission = Submission;
package/dist/index.d.ts CHANGED
@@ -14,6 +14,7 @@ declare class Submission {
14
14
  isRejected(): boolean;
15
15
  isPending(): boolean;
16
16
  isNotCalculatedPenaltyStatus(): boolean;
17
+ get timestampToMinute(): number;
17
18
  static compare(lhs: Submission, rhs: Submission): number;
18
19
  }
19
20
  type Submissions = Array<Submission>;
@@ -101,8 +102,8 @@ declare class Contest {
101
102
  getContestDuration(timeFormat?: string): string;
102
103
  getContestState(nowTime?: Date): ContestState;
103
104
  getContestPendingTime(nowTime?: Date): string;
104
- getContestRemainingTime(nowTime?: Date): string;
105
105
  getContestElapsedTime(nowTime?: Date): string;
106
+ getContestRemainingTime(nowTime?: Date): string;
106
107
  getContestProgressRatio(nowTime?: Date): number;
107
108
  }
108
109
  declare function createContest(contestJSON: Contest$1): Contest;
@@ -135,6 +136,12 @@ declare class RankStatistics {
135
136
  reset(): void;
136
137
  }
137
138
 
139
+ declare class PlaceChartPointData {
140
+ timePoint: number;
141
+ rank: number;
142
+ lastSolvedProblem: Problem | null;
143
+ constructor();
144
+ }
138
145
  declare class Team {
139
146
  id: string;
140
147
  name: string;
@@ -148,19 +155,34 @@ declare class Team {
148
155
  organizationRank: number;
149
156
  solvedProblemNum: number;
150
157
  attemptedProblemNum: number;
158
+ lastSolvedProblem: Problem | null;
159
+ lastSolvedProblemTimestamp: number;
151
160
  penalty: number;
152
161
  problemStatistics: Array<TeamProblemStatistics>;
153
162
  problemStatisticsMap: Map<string, TeamProblemStatistics>;
163
+ submissions: Submissions;
164
+ placeChartPoints: Array<PlaceChartPointData>;
154
165
  constructor();
166
+ reset(): void;
155
167
  get penaltyToMinute(): number;
156
168
  get dict(): number;
157
169
  calcSolvedData(): void;
170
+ isEqualRank(otherTeam: Team): boolean;
171
+ postProcessPlaceChartPoints(): void;
158
172
  static compare(lhs: Team, rhs: Team): number;
159
173
  }
160
174
  type Teams = Array<Team>;
161
175
  declare function createTeam(teamJSON: Team$1): Team;
162
176
  declare function createTeams(teamsJSON: Teams$1): Teams;
163
177
 
178
+ declare class RankOptions {
179
+ enableFilterSubmissionsByTimestamp: boolean;
180
+ width: number;
181
+ timestamp: number;
182
+ constructor();
183
+ setWidth(width: number, contest: Contest): void;
184
+ disableFilterSubmissionByTimestamp(): void;
185
+ }
164
186
  declare class Rank {
165
187
  readonly contest: Contest;
166
188
  teams: Teams;
@@ -168,10 +190,12 @@ declare class Rank {
168
190
  submissions: Submissions;
169
191
  submissionsMap: Map<string, Submission>;
170
192
  rankStatistics: RankStatistics;
193
+ options: RankOptions;
171
194
  constructor(contest: Contest, teams: Teams, submissions: Submissions);
172
- buildRank(options?: {
173
- timestamp?: number;
174
- }): this;
195
+ buildRank(): this;
196
+ buildTeamRank(): void;
197
+ buildOrgRank(): void;
198
+ getSubmissions(): Submissions;
175
199
  }
176
200
 
177
201
  declare class ResolverOperation {
@@ -198,4 +222,4 @@ declare function isRejected(status: SubmissionStatus): boolean;
198
222
  declare function isPending(status: SubmissionStatus): boolean;
199
223
  declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
200
224
 
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 };
225
+ export { Contest, ContestIndex, ContestIndexConfig, ContestIndexList, PlaceChartPointData, 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);
@@ -355,6 +363,13 @@ class RankStatistics {
355
363
  }
356
364
  }
357
365
 
366
+ class PlaceChartPointData {
367
+ constructor() {
368
+ this.timePoint = 0;
369
+ this.rank = 0;
370
+ this.lastSolvedProblem = null;
371
+ }
372
+ }
358
373
  class Team {
359
374
  constructor() {
360
375
  this.id = "";
@@ -366,9 +381,26 @@ class Team {
366
381
  this.organizationRank = -1;
367
382
  this.solvedProblemNum = 0;
368
383
  this.attemptedProblemNum = 0;
384
+ this.lastSolvedProblem = null;
385
+ this.lastSolvedProblemTimestamp = 0;
369
386
  this.penalty = 0;
370
387
  this.problemStatistics = [];
371
388
  this.problemStatisticsMap = /* @__PURE__ */ new Map();
389
+ this.submissions = [];
390
+ this.placeChartPoints = [];
391
+ }
392
+ reset() {
393
+ this.rank = 0;
394
+ this.organizationRank = -1;
395
+ this.solvedProblemNum = 0;
396
+ this.attemptedProblemNum = 0;
397
+ this.lastSolvedProblem = null;
398
+ this.lastSolvedProblemTimestamp = 0;
399
+ this.penalty = 0;
400
+ this.problemStatistics = [];
401
+ this.problemStatisticsMap = /* @__PURE__ */ new Map();
402
+ this.submissions = [];
403
+ this.placeChartPoints = [];
372
404
  }
373
405
  get penaltyToMinute() {
374
406
  return Math.floor(this.penalty / 60);
@@ -380,8 +412,8 @@ class Team {
380
412
  }
381
413
  calcSolvedData() {
382
414
  this.solvedProblemNum = 0;
383
- this.penalty = 0;
384
415
  this.attemptedProblemNum = 0;
416
+ this.penalty = 0;
385
417
  for (const p of this.problemStatistics) {
386
418
  if (p.isAccepted) {
387
419
  this.solvedProblemNum++;
@@ -390,6 +422,27 @@ class Team {
390
422
  }
391
423
  }
392
424
  }
425
+ isEqualRank(otherTeam) {
426
+ return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
427
+ }
428
+ postProcessPlaceChartPoints() {
429
+ if (this.placeChartPoints.length === 0) {
430
+ return;
431
+ }
432
+ const res = [];
433
+ res.push(this.placeChartPoints[0]);
434
+ for (let i = 1; i < this.placeChartPoints.length - 1; i++) {
435
+ const p = this.placeChartPoints[i];
436
+ const preP = res[res.length - 1];
437
+ if (p.rank !== preP.rank || p.lastSolvedProblem !== preP.lastSolvedProblem) {
438
+ res.push(p);
439
+ }
440
+ }
441
+ if (this.placeChartPoints.length > 1) {
442
+ res.push(this.placeChartPoints[this.placeChartPoints.length - 1]);
443
+ }
444
+ this.placeChartPoints = res;
445
+ }
393
446
  static compare(lhs, rhs) {
394
447
  if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
395
448
  return rhs.solvedProblemNum - lhs.solvedProblemNum;
@@ -397,6 +450,9 @@ class Team {
397
450
  if (lhs.penalty !== rhs.penalty) {
398
451
  return lhs.penalty - rhs.penalty;
399
452
  }
453
+ if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
454
+ return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
455
+ }
400
456
  if (lhs.name < rhs.name) {
401
457
  return -1;
402
458
  } else if (lhs.name > rhs.name) {
@@ -577,6 +633,9 @@ class Submission {
577
633
  isNotCalculatedPenaltyStatus() {
578
634
  return isNotCalculatedPenaltyStatus(this.status);
579
635
  }
636
+ get timestampToMinute() {
637
+ return Math.floor(this.timestamp / 60);
638
+ }
580
639
  static compare(lhs, rhs) {
581
640
  if (lhs.timestamp !== rhs.timestamp) {
582
641
  return lhs.timestamp - rhs.timestamp;
@@ -613,6 +672,21 @@ function createSubmissions(submissionsJSON) {
613
672
  }
614
673
  }
615
674
 
675
+ class RankOptions {
676
+ constructor() {
677
+ this.enableFilterSubmissionsByTimestamp = false;
678
+ this.width = 0;
679
+ this.timestamp = 0;
680
+ }
681
+ setWidth(width, contest) {
682
+ this.width = width;
683
+ this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
684
+ this.enableFilterSubmissionsByTimestamp = true;
685
+ }
686
+ disableFilterSubmissionByTimestamp() {
687
+ this.enableFilterSubmissionsByTimestamp = false;
688
+ }
689
+ }
616
690
  class Rank {
617
691
  constructor(contest, teams, submissions) {
618
692
  this.contest = contest;
@@ -621,10 +695,12 @@ class Rank {
621
695
  this.submissions = _.cloneDeep(submissions).sort(Submission.compare);
622
696
  this.submissionsMap = new Map(this.submissions.map((s) => [s.id, s]));
623
697
  this.rankStatistics = new RankStatistics();
698
+ this.options = new RankOptions();
624
699
  }
625
- buildRank(options) {
700
+ buildRank() {
626
701
  (() => {
627
702
  for (const t of this.teams) {
703
+ t.reset();
628
704
  t.problemStatistics = this.contest.problems.map((p) => {
629
705
  const ps = new TeamProblemStatistics();
630
706
  ps.problem = p;
@@ -636,76 +712,84 @@ class Rank {
636
712
  this.contest.problems.forEach((p) => {
637
713
  p.statistics.reset();
638
714
  });
639
- for (const s of this.submissions) {
715
+ let preSubmissionTimestampToMinute = 0;
716
+ const allSubmissions = this.getSubmissions();
717
+ this.teams.forEach(
718
+ (t) => t.placeChartPoints.push({
719
+ timePoint: 0,
720
+ rank: 1,
721
+ lastSolvedProblem: null
722
+ })
723
+ );
724
+ for (let ix = 0; ix < allSubmissions.length; ix++) {
725
+ const s = allSubmissions[ix];
640
726
  const teamId = s.teamId;
641
727
  const problemId = s.problemId;
642
728
  const team = this.teamsMap.get(teamId);
643
729
  const problem = this.contest.problemsMap.get(problemId);
644
- if (team === void 0 || problem === void 0) {
645
- continue;
646
- }
647
- if (options?.timestamp !== void 0 && options?.timestamp !== null) {
648
- if (s.timestamp > options.timestamp) {
649
- break;
730
+ (() => {
731
+ if (team === void 0 || problem === void 0) {
732
+ return;
650
733
  }
651
- }
652
- const problemStatistics = team.problemStatisticsMap.get(problemId);
653
- const submissions = problemStatistics.submissions;
654
- submissions.push(s);
655
- problem.statistics.submittedNum++;
656
- if (problemStatistics.isSolved) {
657
- continue;
658
- }
659
- if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
660
- problem.statistics.ignoreNum++;
661
- problemStatistics.ignoreCount++;
662
- continue;
663
- }
664
- problemStatistics.isSubmitted = true;
665
- problemStatistics.lastSubmitTimestamp = s.timestamp;
666
- problemStatistics.totalCount++;
667
- if (s.isAccepted()) {
668
- problemStatistics.isSolved = true;
669
- problemStatistics.solvedTimestamp = s.timestamp;
670
- problem.statistics.acceptedNum++;
671
- problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
672
- if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
673
- problemStatistics.isFirstSolved = true;
674
- problem.statistics.firstSolveSubmissions.push(s);
734
+ const problemStatistics = team.problemStatisticsMap.get(problemId);
735
+ const submissions = problemStatistics.submissions;
736
+ submissions.push(s);
737
+ team.submissions.push(s);
738
+ problem.statistics.submittedNum++;
739
+ if (problemStatistics.isSolved) {
740
+ return;
675
741
  }
676
- while (problem.statistics.lastSolveSubmissions.length > 0) {
677
- problem.statistics.lastSolveSubmissions.pop();
742
+ if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
743
+ problem.statistics.ignoreNum++;
744
+ problemStatistics.ignoreCount++;
745
+ return;
678
746
  }
679
- problem.statistics.lastSolveSubmissions.push(s);
680
- }
681
- if (s.isRejected()) {
682
- problemStatistics.failedCount++;
683
- problem.statistics.rejectedNum++;
684
- }
685
- if (s.isPending()) {
686
- problemStatistics.pendingCount++;
687
- problem.statistics.pendingNum++;
688
- }
689
- }
690
- this.teams.forEach((t) => t.calcSolvedData());
691
- this.teams.sort(Team.compare);
692
- {
693
- let rank = 1;
694
- for (const t of this.teams) {
695
- t.rank = rank++;
696
- }
697
- }
698
- if (this.contest.organization) {
699
- let rank = 1;
700
- const se = /* @__PURE__ */ new Set();
701
- for (const t of this.teams) {
702
- if (!se.has(t.organization)) {
703
- se.add(t.organization);
704
- t.organizationRank = rank;
705
- rank++;
747
+ problemStatistics.isSubmitted = true;
748
+ problemStatistics.lastSubmitTimestamp = s.timestamp;
749
+ problemStatistics.totalCount++;
750
+ if (s.isAccepted()) {
751
+ problemStatistics.isSolved = true;
752
+ problemStatistics.solvedTimestamp = s.timestamp;
753
+ problem.statistics.acceptedNum++;
754
+ problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
755
+ if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
756
+ problemStatistics.isFirstSolved = true;
757
+ problem.statistics.firstSolveSubmissions.push(s);
758
+ }
759
+ while (problem.statistics.lastSolveSubmissions.length > 0) {
760
+ problem.statistics.lastSolveSubmissions.pop();
761
+ }
762
+ problem.statistics.lastSolveSubmissions.push(s);
763
+ team.lastSolvedProblem = problem;
764
+ team.lastSolvedProblemTimestamp = s.timestamp;
765
+ }
766
+ if (s.isRejected()) {
767
+ problemStatistics.failedCount++;
768
+ problem.statistics.rejectedNum++;
706
769
  }
770
+ if (s.isPending()) {
771
+ problemStatistics.pendingCount++;
772
+ problem.statistics.pendingNum++;
773
+ }
774
+ })();
775
+ if (s.timestampToMinute > preSubmissionTimestampToMinute || ix === allSubmissions.length - 1) {
776
+ this.teams.forEach((t) => t.calcSolvedData());
777
+ this.teams.sort(Team.compare);
778
+ this.buildTeamRank();
779
+ this.teams.forEach(
780
+ (t) => t.placeChartPoints.push(
781
+ {
782
+ timePoint: s.timestampToMinute,
783
+ rank: t.rank,
784
+ lastSolvedProblem: t.lastSolvedProblem
785
+ }
786
+ )
787
+ );
707
788
  }
789
+ preSubmissionTimestampToMinute = s.timestampToMinute;
708
790
  }
791
+ this.teams.forEach((t) => t.postProcessPlaceChartPoints());
792
+ this.buildOrgRank();
709
793
  })();
710
794
  (() => {
711
795
  this.rankStatistics.reset();
@@ -716,6 +800,47 @@ class Rank {
716
800
  })();
717
801
  return this;
718
802
  }
803
+ buildTeamRank() {
804
+ let rank = 1;
805
+ let preTeam = null;
806
+ for (const t of this.teams) {
807
+ t.rank = rank++;
808
+ if (preTeam !== null) {
809
+ if (t.isEqualRank(preTeam)) {
810
+ t.rank = preTeam.rank;
811
+ }
812
+ }
813
+ preTeam = t;
814
+ }
815
+ }
816
+ buildOrgRank() {
817
+ if (!this.contest.organization) {
818
+ return;
819
+ }
820
+ let rank = 1;
821
+ let preTeam = null;
822
+ const se = /* @__PURE__ */ new Set();
823
+ for (const t of this.teams) {
824
+ const org = t.organization;
825
+ if (se.has(org)) {
826
+ continue;
827
+ }
828
+ se.add(org);
829
+ t.organizationRank = rank++;
830
+ if (preTeam !== null) {
831
+ if (t.isEqualRank(preTeam)) {
832
+ t.organizationRank = preTeam.organizationRank;
833
+ }
834
+ }
835
+ preTeam = t;
836
+ }
837
+ }
838
+ getSubmissions() {
839
+ if (this.options.enableFilterSubmissionsByTimestamp === false) {
840
+ return this.submissions;
841
+ }
842
+ return this.submissions.filter((s) => s.timestamp <= this.options.timestamp).sort(Submission.compare);
843
+ }
719
844
  }
720
845
 
721
846
  class ResolverOperation {
@@ -806,4 +931,4 @@ class Resolver extends Rank {
806
931
  }
807
932
  }
808
933
 
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 };
934
+ export { Contest, ContestIndex, ContestIndexConfig, PlaceChartPointData, 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.5.0",
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.5.0"
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,104 +77,114 @@ export class Rank {
49
77
  p.statistics.reset();
50
78
  });
51
79
 
52
- for (const s of this.submissions) {
80
+ let preSubmissionTimestampToMinute = 0;
81
+ const allSubmissions = this.getSubmissions();
82
+
83
+ this.teams.forEach(t =>
84
+ t.placeChartPoints.push({
85
+ timePoint: 0,
86
+ rank: 1,
87
+ lastSolvedProblem: null,
88
+ }),
89
+ );
90
+
91
+ for (let ix = 0; ix < allSubmissions.length; ix++) {
92
+ const s = allSubmissions[ix];
93
+
53
94
  const teamId = s.teamId;
54
95
  const problemId = s.problemId;
55
96
  const team = this.teamsMap.get(teamId);
56
97
  const problem = this.contest.problemsMap.get(problemId);
57
98
 
58
- if (team === undefined || problem === undefined) {
59
- continue;
60
- }
99
+ (() => {
100
+ if (team === undefined || problem === undefined) {
101
+ return;
102
+ }
103
+
104
+ const problemStatistics = team.problemStatisticsMap.get(problemId) as TeamProblemStatistics;
105
+ const submissions = problemStatistics.submissions;
61
106
 
62
- if (options?.timestamp !== undefined && options?.timestamp !== null) {
63
- if (s.timestamp > options.timestamp) {
64
- break;
107
+ submissions.push(s);
108
+ team.submissions.push(s);
109
+ problem.statistics.submittedNum++;
110
+
111
+ if (problemStatistics.isSolved) {
112
+ return;
65
113
  }
66
- }
67
114
 
68
- const problemStatistics = team.problemStatisticsMap.get(problemId) as TeamProblemStatistics;
69
- const submissions = problemStatistics.submissions;
115
+ if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
116
+ problem.statistics.ignoreNum++;
117
+ problemStatistics.ignoreCount++;
118
+ return;
119
+ }
70
120
 
71
- submissions.push(s);
72
- problem.statistics.submittedNum++;
121
+ problemStatistics.isSubmitted = true;
122
+ problemStatistics.lastSubmitTimestamp = s.timestamp;
123
+ problemStatistics.totalCount++;
73
124
 
74
- if (problemStatistics.isSolved) {
75
- continue;
76
- }
125
+ if (s.isAccepted()) {
126
+ problemStatistics.isSolved = true;
127
+ problemStatistics.solvedTimestamp = s.timestamp;
77
128
 
78
- if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
79
- problem.statistics.ignoreNum++;
80
- problemStatistics.ignoreCount++;
81
- continue;
82
- }
129
+ problem.statistics.acceptedNum++;
130
+ problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
83
131
 
84
- problemStatistics.isSubmitted = true;
85
- problemStatistics.lastSubmitTimestamp = s.timestamp;
86
- problemStatistics.totalCount++;
132
+ if (
133
+ problem.statistics.firstSolveSubmissions.length === 0
134
+ || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp
135
+ ) {
136
+ problemStatistics.isFirstSolved = true;
137
+ problem.statistics.firstSolveSubmissions.push(s);
138
+ }
87
139
 
88
- if (s.isAccepted()) {
89
- problemStatistics.isSolved = true;
90
- problemStatistics.solvedTimestamp = s.timestamp;
140
+ while (problem.statistics.lastSolveSubmissions.length > 0) {
141
+ problem.statistics.lastSolveSubmissions.pop();
142
+ }
91
143
 
92
- problem.statistics.acceptedNum++;
93
- problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
144
+ problem.statistics.lastSolveSubmissions.push(s);
94
145
 
95
- if (
96
- problem.statistics.firstSolveSubmissions.length === 0
97
- || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp
98
- ) {
99
- problemStatistics.isFirstSolved = true;
100
- problem.statistics.firstSolveSubmissions.push(s);
146
+ team.lastSolvedProblem = problem;
147
+ team.lastSolvedProblemTimestamp = s.timestamp;
101
148
  }
102
149
 
103
- while (problem.statistics.lastSolveSubmissions.length > 0) {
104
- problem.statistics.lastSolveSubmissions.pop();
150
+ if (s.isRejected()) {
151
+ problemStatistics.failedCount++;
152
+ problem.statistics.rejectedNum++;
105
153
  }
106
154
 
107
- problem.statistics.lastSolveSubmissions.push(s);
108
- }
109
-
110
- if (s.isRejected()) {
111
- problemStatistics.failedCount++;
112
- problem.statistics.rejectedNum++;
113
- }
114
-
115
- if (s.isPending()) {
116
- problemStatistics.pendingCount++;
117
- problem.statistics.pendingNum++;
155
+ if (s.isPending()) {
156
+ problemStatistics.pendingCount++;
157
+ problem.statistics.pendingNum++;
158
+ }
159
+ })();
160
+
161
+ if (s.timestampToMinute > preSubmissionTimestampToMinute || ix === allSubmissions.length - 1) {
162
+ this.teams.forEach(t => t.calcSolvedData());
163
+ this.teams.sort(Team.compare);
164
+ this.buildTeamRank();
165
+
166
+ this.teams.forEach(t =>
167
+ t.placeChartPoints.push(
168
+ {
169
+ timePoint: s.timestampToMinute,
170
+ rank: t.rank,
171
+ lastSolvedProblem: t.lastSolvedProblem,
172
+ },
173
+ ),
174
+ );
118
175
  }
119
- }
120
176
 
121
- this.teams.forEach(t => t.calcSolvedData());
122
- this.teams.sort(Team.compare);
123
-
124
- {
125
- let rank = 1;
126
- for (const t of this.teams) {
127
- t.rank = rank++;
128
- }
177
+ preSubmissionTimestampToMinute = s.timestampToMinute;
129
178
  }
130
179
 
131
- if (this.contest.organization) {
132
- let rank = 1;
133
- const se = new Set<string>();
134
-
135
- for (const t of this.teams) {
136
- if (!se.has(t.organization)) {
137
- se.add(t.organization);
138
- t.organizationRank = rank;
139
- rank++;
140
- }
141
- }
142
- }
180
+ this.teams.forEach(t => t.postProcessPlaceChartPoints());
181
+ this.buildOrgRank();
143
182
  })();
144
183
 
145
184
  (() => {
146
185
  this.rankStatistics.reset();
147
186
 
148
187
  this.rankStatistics.teamSolvedNum = Array(this.contest.problems.length + 1).fill(0);
149
-
150
188
  for (const t of this.teams) {
151
189
  this.rankStatistics.teamSolvedNum[t.solvedProblemNum]++;
152
190
  }
@@ -154,4 +192,57 @@ export class Rank {
154
192
 
155
193
  return this;
156
194
  }
195
+
196
+ buildTeamRank() {
197
+ let rank = 1;
198
+ let preTeam = null;
199
+ for (const t of this.teams) {
200
+ t.rank = rank++;
201
+
202
+ if (preTeam !== null) {
203
+ if (t.isEqualRank(preTeam)) {
204
+ t.rank = preTeam.rank;
205
+ }
206
+ }
207
+
208
+ preTeam = t;
209
+ }
210
+ }
211
+
212
+ buildOrgRank() {
213
+ if (!this.contest.organization) {
214
+ return;
215
+ }
216
+
217
+ let rank = 1;
218
+ let preTeam = null;
219
+
220
+ const se = new Set<string>();
221
+
222
+ for (const t of this.teams) {
223
+ const org = t.organization;
224
+ if (se.has(org)) {
225
+ continue;
226
+ }
227
+
228
+ se.add(org);
229
+ t.organizationRank = rank++;
230
+
231
+ if (preTeam !== null) {
232
+ if (t.isEqualRank(preTeam)) {
233
+ t.organizationRank = preTeam.organizationRank;
234
+ }
235
+ }
236
+
237
+ preTeam = t;
238
+ }
239
+ }
240
+
241
+ getSubmissions() {
242
+ if (this.options.enableFilterSubmissionsByTimestamp === false) {
243
+ return this.submissions;
244
+ }
245
+
246
+ return this.submissions.filter(s => s.timestamp <= this.options.timestamp).sort(Submission.compare);
247
+ }
157
248
  }
package/src/submission.ts CHANGED
@@ -41,6 +41,10 @@ export class Submission {
41
41
  return isNotCalculatedPenaltyStatus(this.status);
42
42
  }
43
43
 
44
+ get timestampToMinute() {
45
+ return Math.floor(this.timestamp / 60);
46
+ }
47
+
44
48
  static compare(lhs: Submission, rhs: Submission): number {
45
49
  if (lhs.timestamp !== rhs.timestamp) {
46
50
  return lhs.timestamp - rhs.timestamp;
package/src/team.ts CHANGED
@@ -1,7 +1,20 @@
1
1
  import type { Team as ITeam, Teams as ITeams, Image } from "@xcpcio/types";
2
2
 
3
- import type { TeamProblemStatistics } from "./problem";
3
+ import type { Problem, TeamProblemStatistics } from "./problem";
4
4
  import { calcDict } from "./utils";
5
+ import type { Submissions } from "./submission";
6
+
7
+ export class PlaceChartPointData {
8
+ timePoint: number;
9
+ rank: number;
10
+ lastSolvedProblem: Problem | null;
11
+
12
+ constructor() {
13
+ this.timePoint = 0;
14
+ this.rank = 0;
15
+ this.lastSolvedProblem = null;
16
+ }
17
+ }
5
18
 
6
19
  export class Team {
7
20
  id: string;
@@ -22,11 +35,18 @@ export class Team {
22
35
  solvedProblemNum: number;
23
36
  attemptedProblemNum: number;
24
37
 
38
+ lastSolvedProblem: Problem | null;
39
+ lastSolvedProblemTimestamp: number;
40
+
25
41
  penalty: number;
26
42
 
27
43
  problemStatistics: Array<TeamProblemStatistics>;
28
44
  problemStatisticsMap: Map<string, TeamProblemStatistics>;
29
45
 
46
+ submissions: Submissions;
47
+
48
+ placeChartPoints: Array<PlaceChartPointData>;
49
+
30
50
  constructor() {
31
51
  this.id = "";
32
52
  this.name = "";
@@ -42,10 +62,37 @@ export class Team {
42
62
  this.solvedProblemNum = 0;
43
63
  this.attemptedProblemNum = 0;
44
64
 
65
+ this.lastSolvedProblem = null;
66
+ this.lastSolvedProblemTimestamp = 0;
67
+
68
+ this.penalty = 0;
69
+
70
+ this.problemStatistics = [];
71
+ this.problemStatisticsMap = new Map<string, TeamProblemStatistics>();
72
+
73
+ this.submissions = [];
74
+
75
+ this.placeChartPoints = [];
76
+ }
77
+
78
+ reset() {
79
+ this.rank = 0;
80
+ this.organizationRank = -1;
81
+
82
+ this.solvedProblemNum = 0;
83
+ this.attemptedProblemNum = 0;
84
+
85
+ this.lastSolvedProblem = null;
86
+ this.lastSolvedProblemTimestamp = 0;
87
+
45
88
  this.penalty = 0;
46
89
 
47
90
  this.problemStatistics = [];
48
91
  this.problemStatisticsMap = new Map<string, TeamProblemStatistics>();
92
+
93
+ this.submissions = [];
94
+
95
+ this.placeChartPoints = [];
49
96
  }
50
97
 
51
98
  get penaltyToMinute() {
@@ -61,9 +108,10 @@ export class Team {
61
108
 
62
109
  calcSolvedData() {
63
110
  this.solvedProblemNum = 0;
64
- this.penalty = 0;
65
111
  this.attemptedProblemNum = 0;
66
112
 
113
+ this.penalty = 0;
114
+
67
115
  for (const p of this.problemStatistics) {
68
116
  if (p.isAccepted) {
69
117
  this.solvedProblemNum++;
@@ -74,6 +122,34 @@ export class Team {
74
122
  }
75
123
  }
76
124
 
125
+ isEqualRank(otherTeam: Team) {
126
+ return this.solvedProblemNum === otherTeam.solvedProblemNum && this.penalty === otherTeam.penalty;
127
+ }
128
+
129
+ postProcessPlaceChartPoints() {
130
+ if (this.placeChartPoints.length === 0) {
131
+ return;
132
+ }
133
+
134
+ const res = [];
135
+ res.push(this.placeChartPoints[0]);
136
+
137
+ for (let i = 1; i < this.placeChartPoints.length - 1; i++) {
138
+ const p = this.placeChartPoints[i];
139
+ const preP = res[res.length - 1];
140
+
141
+ if (p.rank !== preP.rank || p.lastSolvedProblem !== preP.lastSolvedProblem) {
142
+ res.push(p);
143
+ }
144
+ }
145
+
146
+ if (this.placeChartPoints.length > 1) {
147
+ res.push(this.placeChartPoints[this.placeChartPoints.length - 1]);
148
+ }
149
+
150
+ this.placeChartPoints = res;
151
+ }
152
+
77
153
  static compare(lhs: Team, rhs: Team): number {
78
154
  if (lhs.solvedProblemNum !== rhs.solvedProblemNum) {
79
155
  return rhs.solvedProblemNum - lhs.solvedProblemNum;
@@ -83,6 +159,10 @@ export class Team {
83
159
  return lhs.penalty - rhs.penalty;
84
160
  }
85
161
 
162
+ if (lhs.lastSolvedProblemTimestamp !== rhs.lastSolvedProblemTimestamp) {
163
+ return lhs.lastSolvedProblemTimestamp - rhs.lastSolvedProblemTimestamp;
164
+ }
165
+
86
166
  if (lhs.name < rhs.name) {
87
167
  return -1;
88
168
  } else if (lhs.name > rhs.name) {