@xcpcio/core 0.35.1 → 0.37.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
@@ -409,6 +409,8 @@ class GeneralExcelConverter {
409
409
  convertToAoa(rank) {
410
410
  const aoa = [];
411
411
  const enableAwards = rank.contest.isEnableAwards(rank.options.group);
412
+ const enableMembers = (Array.isArray(rank.teams) && rank.teams[0]?.members) ?? false;
413
+ const enableCoach = rank.teams[0]?.coach ?? false;
412
414
  {
413
415
  aoa.push([rank.contest.name]);
414
416
  }
@@ -419,10 +421,18 @@ class GeneralExcelConverter {
419
421
  head.push(`${rank.contest.organization} Rank`);
420
422
  head.push(rank.contest.organization);
421
423
  }
422
- head.push("Name", "Solved", "Penalty", ...rank.contest.problems.map((p) => p.label), "Dirt");
424
+ head.push("Team", "Solved", "Penalty", ...rank.contest.problems.map((p) => p.label), "Dirt");
423
425
  if (enableAwards) {
424
426
  head.push("Medal");
425
427
  }
428
+ if (enableMembers) {
429
+ head.push("Member1", "Member2", "Member3");
430
+ }
431
+ if (enableCoach) {
432
+ head.push("Coach");
433
+ }
434
+ head.push("Unofficial");
435
+ head.push("Girl");
426
436
  aoa.push(head);
427
437
  }
428
438
  for (const team of rank.teams) {
@@ -456,6 +466,25 @@ class GeneralExcelConverter {
456
466
  const medals = team.awards.filter((a) => isValidMedalType(a)).map((a) => a.toString());
457
467
  arr.push(medals.join(", "));
458
468
  }
469
+ if (enableMembers) {
470
+ const members = team.members;
471
+ if (Array.isArray(members)) {
472
+ arr.push(members[0] ?? "");
473
+ arr.push(members[1] ?? "");
474
+ arr.push(members[2] ?? "");
475
+ } else {
476
+ arr.push("", "", "");
477
+ }
478
+ }
479
+ if (enableCoach) {
480
+ if (typeof team.coach === "string") {
481
+ arr.push(team.coach ?? "");
482
+ } else {
483
+ arr.push("");
484
+ }
485
+ }
486
+ arr.push(team.isUnofficial ? "Y" : "N");
487
+ arr.push(team.isGirl ? "Y" : "N");
459
488
  aoa.push(arr);
460
489
  }
461
490
  return aoa;
@@ -661,6 +690,26 @@ class Team {
661
690
  const solvedNum = this.solvedProblemNum;
662
691
  return calcDirt(attemptedNum, solvedNum);
663
692
  }
693
+ get isUnofficial() {
694
+ return this.group.includes("unofficial");
695
+ }
696
+ get isGirl() {
697
+ return this.group.includes("girl");
698
+ }
699
+ get membersToArray() {
700
+ if (Array.isArray(this.members)) {
701
+ return this.members;
702
+ }
703
+ if (typeof this.members === "string") {
704
+ if (this.members.includes(", ")) {
705
+ return this.members.split(", ");
706
+ }
707
+ if (this.members.includes("\u3001")) {
708
+ return this.members.split("\u3001");
709
+ }
710
+ }
711
+ return [];
712
+ }
664
713
  get membersToString() {
665
714
  if (typeof this.members === "string") {
666
715
  return this.members;
@@ -787,6 +836,8 @@ class Submission {
787
836
  constructor() {
788
837
  this.status = types.SubmissionStatus.UNKNOWN;
789
838
  this.isIgnore = false;
839
+ this.isSolved = false;
840
+ this.isFirstSolved = false;
790
841
  this.id = "";
791
842
  this.teamId = "";
792
843
  this.problemId = "";
@@ -957,59 +1008,68 @@ class Contest {
957
1008
  this.tag = /* @__PURE__ */ new Map();
958
1009
  this.options = new ContestOptions();
959
1010
  }
1011
+ getStartTime() {
1012
+ return this.replayStartTime ?? this.startTime;
1013
+ }
1014
+ getEndTime() {
1015
+ return this.replayEndTime ?? this.endTime;
1016
+ }
1017
+ getFreezeTime() {
1018
+ return this.replayFreezeTime ?? this.freezeTime;
1019
+ }
960
1020
  getContestDuration(timeFormat = "HH:mm:ss") {
961
- return dayjs__default.duration(this.endTime.diff(this.startTime)).format(timeFormat);
1021
+ return dayjs__default.duration(this.getEndTime().diff(this.getStartTime())).format(timeFormat);
962
1022
  }
963
1023
  getContestState(nowTime) {
964
1024
  const now = createDayJS(nowTime);
965
- if (now.isBefore(this.startTime)) {
1025
+ if (now.isBefore(this.getStartTime())) {
966
1026
  return types.ContestState.PENDING;
967
1027
  }
968
- if (now.isSameOrAfter(this.endTime)) {
1028
+ if (now.isSameOrAfter(this.getEndTime())) {
969
1029
  return types.ContestState.FINISHED;
970
1030
  }
971
- if (now.isSameOrAfter(this.freezeTime)) {
1031
+ if (now.isSameOrAfter(this.getFreezeTime())) {
972
1032
  return types.ContestState.FROZEN;
973
1033
  }
974
1034
  return types.ContestState.RUNNING;
975
1035
  }
976
1036
  getContestPendingTime(nowTime) {
977
1037
  let baseTime = createDayJS(nowTime);
978
- if (baseTime.isAfter(this.startTime)) {
979
- baseTime = this.startTime;
1038
+ if (baseTime.isAfter(this.getStartTime())) {
1039
+ baseTime = this.getStartTime();
980
1040
  }
981
- return getTimeDiff(Math.floor(dayjs__default.duration(this.startTime.diff(baseTime)).asSeconds()));
1041
+ return getTimeDiff(Math.floor(dayjs__default.duration(this.getStartTime().diff(baseTime)).asSeconds()));
982
1042
  }
983
1043
  getContestElapsedTime(nowTime) {
984
1044
  let baseTime = createDayJS(nowTime);
985
- if (baseTime.isAfter(this.endTime)) {
986
- baseTime = this.endTime;
1045
+ if (baseTime.isAfter(this.getEndTime())) {
1046
+ baseTime = this.getEndTime();
987
1047
  }
988
- if (baseTime.isBefore(this.startTime)) {
989
- baseTime = this.startTime;
1048
+ if (baseTime.isBefore(this.getStartTime())) {
1049
+ baseTime = this.getStartTime();
990
1050
  }
991
- return getTimeDiff(Math.floor(dayjs__default.duration(baseTime.diff(this.startTime)).asSeconds()));
1051
+ return getTimeDiff(Math.floor(dayjs__default.duration(baseTime.diff(this.getStartTime())).asSeconds()));
992
1052
  }
993
1053
  getContestRemainingTime(nowTime) {
994
1054
  let baseTime = createDayJS(nowTime);
995
- if (baseTime.isAfter(this.endTime)) {
996
- baseTime = this.endTime;
1055
+ if (baseTime.isAfter(this.getEndTime())) {
1056
+ baseTime = this.getEndTime();
997
1057
  }
998
- if (baseTime.isBefore(this.startTime)) {
999
- baseTime = this.startTime;
1058
+ if (baseTime.isBefore(this.getStartTime())) {
1059
+ baseTime = this.getStartTime();
1000
1060
  }
1001
- return getTimeDiff(Math.floor(dayjs__default.duration(this.endTime.diff(baseTime)).asSeconds()));
1061
+ return getTimeDiff(Math.floor(dayjs__default.duration(this.getEndTime().diff(baseTime)).asSeconds()));
1002
1062
  }
1003
1063
  getContestProgressRatio(nowTime) {
1004
1064
  const baseTime = createDayJS(nowTime);
1005
- if (this.startTime.isSameOrAfter(baseTime)) {
1065
+ if (this.getStartTime().isSameOrAfter(baseTime)) {
1006
1066
  return 0;
1007
1067
  }
1008
- if (this.endTime.isSameOrBefore(baseTime)) {
1068
+ if (this.getEndTime().isSameOrBefore(baseTime)) {
1009
1069
  return 100;
1010
1070
  }
1011
- const total = this.endTime.diff(this.startTime, "s");
1012
- const pass = baseTime.diff(this.startTime, "s");
1071
+ const total = this.getEndTime().diff(this.getStartTime(), "s");
1072
+ const pass = baseTime.diff(this.getStartTime(), "s");
1013
1073
  return Math.round(pass * 100 / total);
1014
1074
  }
1015
1075
  isEnableAwards(group) {
@@ -1021,6 +1081,26 @@ class Contest {
1021
1081
  }
1022
1082
  return true;
1023
1083
  }
1084
+ resetReplayTime() {
1085
+ this.replayStartTime = void 0;
1086
+ this.replayEndTime = void 0;
1087
+ this.replayFreezeTime = void 0;
1088
+ this.replayNowTime = void 0;
1089
+ this.replayContestStartTimestamp = void 0;
1090
+ }
1091
+ setReplayTime(replayStartTimestamp) {
1092
+ if (replayStartTimestamp === 0) {
1093
+ this.resetReplayTime();
1094
+ return;
1095
+ }
1096
+ const replayStartTime = createDayJS(replayStartTimestamp);
1097
+ const diff = replayStartTime.diff(this.startTime, "s");
1098
+ this.replayStartTime = this.startTime.add(diff, "s");
1099
+ this.replayEndTime = this.endTime.add(diff, "s");
1100
+ this.replayFreezeTime = this.freezeTime.add(diff, "s");
1101
+ this.replayNowTime = createDayJS();
1102
+ this.replayContestStartTimestamp = this.replayNowTime.diff(this.replayStartTime, "s");
1103
+ }
1024
1104
  }
1025
1105
  function createContest(contestJSON) {
1026
1106
  const c = new Contest();
@@ -1233,7 +1313,7 @@ class RankOptions {
1233
1313
  }
1234
1314
  setWidth(width, contest) {
1235
1315
  this.width = width;
1236
- this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
1316
+ this.timestamp = Math.floor((contest.getEndTime().unix() - contest.getStartTime().unix()) * this.width * 1e-4);
1237
1317
  this.enableFilterSubmissionsByTimestamp = true;
1238
1318
  }
1239
1319
  disableFilterSubmissionByTimestamp() {
@@ -1382,6 +1462,8 @@ class Rank {
1382
1462
  team.submissions.push(s);
1383
1463
  problem.statistics.submittedNum++;
1384
1464
  if (problemStatistics.isSolved) {
1465
+ s.isSolved = false;
1466
+ s.isFirstSolved = false;
1385
1467
  return;
1386
1468
  }
1387
1469
  if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
@@ -1393,11 +1475,13 @@ class Rank {
1393
1475
  problemStatistics.lastSubmitTimestamp = s.timestampToSecond;
1394
1476
  problemStatistics.totalCount++;
1395
1477
  if (s.isAccepted()) {
1478
+ s.isSolved = true;
1396
1479
  problemStatistics.isSolved = true;
1397
1480
  problemStatistics.solvedTimestamp = s.timestampToSecond;
1398
1481
  problem.statistics.acceptedNum++;
1399
1482
  problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
1400
1483
  if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
1484
+ s.isFirstSolved = true;
1401
1485
  problemStatistics.isFirstSolved = true;
1402
1486
  problem.statistics.firstSolveSubmissions.push(s);
1403
1487
  }
@@ -1523,6 +1607,7 @@ class Rank {
1523
1607
  const gold = new Award();
1524
1608
  const silver = new Award();
1525
1609
  const bronze = new Award();
1610
+ const honorable = new Award();
1526
1611
  {
1527
1612
  gold.medalType = MedalType.GOLD;
1528
1613
  gold.minRank = 1;
@@ -1547,6 +1632,18 @@ class Rank {
1547
1632
  award.push(bronze);
1548
1633
  }
1549
1634
  }
1635
+ {
1636
+ honorable.medalType = MedalType.HONORABLE;
1637
+ honorable.minRank = bronze.maxRank + 1;
1638
+ this.teams.forEach((t) => {
1639
+ if (t.solvedProblemNum > 0) {
1640
+ honorable.maxRank = Math.max(honorable.maxRank, t.rank);
1641
+ }
1642
+ });
1643
+ if (honorable.maxRank >= honorable.minRank) {
1644
+ award.push(honorable);
1645
+ }
1646
+ }
1550
1647
  this.contest.awards.set("official", award);
1551
1648
  }
1552
1649
  }
@@ -1560,12 +1657,22 @@ class Rank {
1560
1657
  return false;
1561
1658
  }
1562
1659
  getSubmissions() {
1563
- if (this.options.enableFilterSubmissionsByTimestamp === false) {
1660
+ if (this.contest.replayContestStartTimestamp === void 0 && this.options.enableFilterSubmissionsByTimestamp === false) {
1564
1661
  return this.submissions;
1565
1662
  }
1566
- return this.submissions.filter(
1567
- (s) => s.timestampToSecond <= this.options.timestamp
1568
- ).sort(Submission.compare);
1663
+ return this.submissions.filter((s) => {
1664
+ if (this.contest.replayContestStartTimestamp !== void 0) {
1665
+ if (s.timestampToSecond > this.contest.replayContestStartTimestamp) {
1666
+ return false;
1667
+ }
1668
+ }
1669
+ if (this.options.enableFilterSubmissionsByTimestamp) {
1670
+ if (s.timestampToSecond > this.options.timestamp) {
1671
+ return false;
1672
+ }
1673
+ }
1674
+ return true;
1675
+ });
1569
1676
  }
1570
1677
  buildBalloons() {
1571
1678
  this.balloons = [];
@@ -1597,6 +1704,9 @@ class Rank {
1597
1704
  })();
1598
1705
  }
1599
1706
  }
1707
+ setReplayTime(replayStartTimestamp) {
1708
+ this.contest.setReplayTime(replayStartTimestamp);
1709
+ }
1600
1710
  }
1601
1711
 
1602
1712
  class ResolverOperation {
package/dist/index.d.ts CHANGED
@@ -13,6 +13,8 @@ declare class Submission {
13
13
  language?: string;
14
14
  status: SubmissionStatus;
15
15
  isIgnore: boolean;
16
+ isSolved: boolean;
17
+ isFirstSolved: boolean;
16
18
  constructor();
17
19
  isAccepted(): boolean;
18
20
  isRejected(): boolean;
@@ -126,6 +128,11 @@ declare class Contest {
126
128
  startTime: dayjs.Dayjs;
127
129
  endTime: dayjs.Dayjs;
128
130
  freezeTime: dayjs.Dayjs;
131
+ replayStartTime?: dayjs.Dayjs;
132
+ replayEndTime?: dayjs.Dayjs;
133
+ replayFreezeTime?: dayjs.Dayjs;
134
+ replayNowTime?: dayjs.Dayjs;
135
+ replayContestStartTimestamp?: number;
129
136
  totalDurationTimestamp: number;
130
137
  freezeDurationTimestamp: number;
131
138
  unFreezeDurationTimestamp: number;
@@ -144,6 +151,9 @@ declare class Contest {
144
151
  boardLink?: string;
145
152
  options: ContestOptions;
146
153
  constructor();
154
+ getStartTime(): dayjs.Dayjs;
155
+ getEndTime(): dayjs.Dayjs;
156
+ getFreezeTime(): dayjs.Dayjs;
147
157
  getContestDuration(timeFormat?: string): string;
148
158
  getContestState(nowTime?: Date): ContestState;
149
159
  getContestPendingTime(nowTime?: Date): string;
@@ -151,6 +161,8 @@ declare class Contest {
151
161
  getContestRemainingTime(nowTime?: Date): string;
152
162
  getContestProgressRatio(nowTime?: Date): number;
153
163
  isEnableAwards(group: string): boolean;
164
+ resetReplayTime(): void;
165
+ setReplayTime(replayStartTimestamp: number): void;
154
166
  }
155
167
  declare function createContest(contestJSON: Contest$1): Contest;
156
168
 
@@ -187,6 +199,9 @@ declare class Team {
187
199
  reset(): void;
188
200
  get penaltyToMinute(): number;
189
201
  get dirt(): number;
202
+ get isUnofficial(): boolean;
203
+ get isGirl(): boolean;
204
+ get membersToArray(): string[];
190
205
  get membersToString(): string | undefined;
191
206
  get isEffectiveTeam(): boolean;
192
207
  calcSolvedData(options: ContestOptions): void;
@@ -266,6 +281,7 @@ declare class Rank {
266
281
  filterTeamByOrg(team: Team): boolean;
267
282
  getSubmissions(): Submissions;
268
283
  buildBalloons(): void;
284
+ setReplayTime(replayStartTimestamp: number): void;
269
285
  }
270
286
 
271
287
  declare class CodeforcesGymGhostDATConverter {
package/dist/index.mjs CHANGED
@@ -378,6 +378,8 @@ class GeneralExcelConverter {
378
378
  convertToAoa(rank) {
379
379
  const aoa = [];
380
380
  const enableAwards = rank.contest.isEnableAwards(rank.options.group);
381
+ const enableMembers = (Array.isArray(rank.teams) && rank.teams[0]?.members) ?? false;
382
+ const enableCoach = rank.teams[0]?.coach ?? false;
381
383
  {
382
384
  aoa.push([rank.contest.name]);
383
385
  }
@@ -388,10 +390,18 @@ class GeneralExcelConverter {
388
390
  head.push(`${rank.contest.organization} Rank`);
389
391
  head.push(rank.contest.organization);
390
392
  }
391
- head.push("Name", "Solved", "Penalty", ...rank.contest.problems.map((p) => p.label), "Dirt");
393
+ head.push("Team", "Solved", "Penalty", ...rank.contest.problems.map((p) => p.label), "Dirt");
392
394
  if (enableAwards) {
393
395
  head.push("Medal");
394
396
  }
397
+ if (enableMembers) {
398
+ head.push("Member1", "Member2", "Member3");
399
+ }
400
+ if (enableCoach) {
401
+ head.push("Coach");
402
+ }
403
+ head.push("Unofficial");
404
+ head.push("Girl");
395
405
  aoa.push(head);
396
406
  }
397
407
  for (const team of rank.teams) {
@@ -425,6 +435,25 @@ class GeneralExcelConverter {
425
435
  const medals = team.awards.filter((a) => isValidMedalType(a)).map((a) => a.toString());
426
436
  arr.push(medals.join(", "));
427
437
  }
438
+ if (enableMembers) {
439
+ const members = team.members;
440
+ if (Array.isArray(members)) {
441
+ arr.push(members[0] ?? "");
442
+ arr.push(members[1] ?? "");
443
+ arr.push(members[2] ?? "");
444
+ } else {
445
+ arr.push("", "", "");
446
+ }
447
+ }
448
+ if (enableCoach) {
449
+ if (typeof team.coach === "string") {
450
+ arr.push(team.coach ?? "");
451
+ } else {
452
+ arr.push("");
453
+ }
454
+ }
455
+ arr.push(team.isUnofficial ? "Y" : "N");
456
+ arr.push(team.isGirl ? "Y" : "N");
428
457
  aoa.push(arr);
429
458
  }
430
459
  return aoa;
@@ -630,6 +659,26 @@ class Team {
630
659
  const solvedNum = this.solvedProblemNum;
631
660
  return calcDirt(attemptedNum, solvedNum);
632
661
  }
662
+ get isUnofficial() {
663
+ return this.group.includes("unofficial");
664
+ }
665
+ get isGirl() {
666
+ return this.group.includes("girl");
667
+ }
668
+ get membersToArray() {
669
+ if (Array.isArray(this.members)) {
670
+ return this.members;
671
+ }
672
+ if (typeof this.members === "string") {
673
+ if (this.members.includes(", ")) {
674
+ return this.members.split(", ");
675
+ }
676
+ if (this.members.includes("\u3001")) {
677
+ return this.members.split("\u3001");
678
+ }
679
+ }
680
+ return [];
681
+ }
633
682
  get membersToString() {
634
683
  if (typeof this.members === "string") {
635
684
  return this.members;
@@ -756,6 +805,8 @@ class Submission {
756
805
  constructor() {
757
806
  this.status = SubmissionStatus.UNKNOWN;
758
807
  this.isIgnore = false;
808
+ this.isSolved = false;
809
+ this.isFirstSolved = false;
759
810
  this.id = "";
760
811
  this.teamId = "";
761
812
  this.problemId = "";
@@ -926,59 +977,68 @@ class Contest {
926
977
  this.tag = /* @__PURE__ */ new Map();
927
978
  this.options = new ContestOptions();
928
979
  }
980
+ getStartTime() {
981
+ return this.replayStartTime ?? this.startTime;
982
+ }
983
+ getEndTime() {
984
+ return this.replayEndTime ?? this.endTime;
985
+ }
986
+ getFreezeTime() {
987
+ return this.replayFreezeTime ?? this.freezeTime;
988
+ }
929
989
  getContestDuration(timeFormat = "HH:mm:ss") {
930
- return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
990
+ return dayjs.duration(this.getEndTime().diff(this.getStartTime())).format(timeFormat);
931
991
  }
932
992
  getContestState(nowTime) {
933
993
  const now = createDayJS(nowTime);
934
- if (now.isBefore(this.startTime)) {
994
+ if (now.isBefore(this.getStartTime())) {
935
995
  return ContestState.PENDING;
936
996
  }
937
- if (now.isSameOrAfter(this.endTime)) {
997
+ if (now.isSameOrAfter(this.getEndTime())) {
938
998
  return ContestState.FINISHED;
939
999
  }
940
- if (now.isSameOrAfter(this.freezeTime)) {
1000
+ if (now.isSameOrAfter(this.getFreezeTime())) {
941
1001
  return ContestState.FROZEN;
942
1002
  }
943
1003
  return ContestState.RUNNING;
944
1004
  }
945
1005
  getContestPendingTime(nowTime) {
946
1006
  let baseTime = createDayJS(nowTime);
947
- if (baseTime.isAfter(this.startTime)) {
948
- baseTime = this.startTime;
1007
+ if (baseTime.isAfter(this.getStartTime())) {
1008
+ baseTime = this.getStartTime();
949
1009
  }
950
- return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
1010
+ return getTimeDiff(Math.floor(dayjs.duration(this.getStartTime().diff(baseTime)).asSeconds()));
951
1011
  }
952
1012
  getContestElapsedTime(nowTime) {
953
1013
  let baseTime = createDayJS(nowTime);
954
- if (baseTime.isAfter(this.endTime)) {
955
- baseTime = this.endTime;
1014
+ if (baseTime.isAfter(this.getEndTime())) {
1015
+ baseTime = this.getEndTime();
956
1016
  }
957
- if (baseTime.isBefore(this.startTime)) {
958
- baseTime = this.startTime;
1017
+ if (baseTime.isBefore(this.getStartTime())) {
1018
+ baseTime = this.getStartTime();
959
1019
  }
960
- return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
1020
+ return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.getStartTime())).asSeconds()));
961
1021
  }
962
1022
  getContestRemainingTime(nowTime) {
963
1023
  let baseTime = createDayJS(nowTime);
964
- if (baseTime.isAfter(this.endTime)) {
965
- baseTime = this.endTime;
1024
+ if (baseTime.isAfter(this.getEndTime())) {
1025
+ baseTime = this.getEndTime();
966
1026
  }
967
- if (baseTime.isBefore(this.startTime)) {
968
- baseTime = this.startTime;
1027
+ if (baseTime.isBefore(this.getStartTime())) {
1028
+ baseTime = this.getStartTime();
969
1029
  }
970
- return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
1030
+ return getTimeDiff(Math.floor(dayjs.duration(this.getEndTime().diff(baseTime)).asSeconds()));
971
1031
  }
972
1032
  getContestProgressRatio(nowTime) {
973
1033
  const baseTime = createDayJS(nowTime);
974
- if (this.startTime.isSameOrAfter(baseTime)) {
1034
+ if (this.getStartTime().isSameOrAfter(baseTime)) {
975
1035
  return 0;
976
1036
  }
977
- if (this.endTime.isSameOrBefore(baseTime)) {
1037
+ if (this.getEndTime().isSameOrBefore(baseTime)) {
978
1038
  return 100;
979
1039
  }
980
- const total = this.endTime.diff(this.startTime, "s");
981
- const pass = baseTime.diff(this.startTime, "s");
1040
+ const total = this.getEndTime().diff(this.getStartTime(), "s");
1041
+ const pass = baseTime.diff(this.getStartTime(), "s");
982
1042
  return Math.round(pass * 100 / total);
983
1043
  }
984
1044
  isEnableAwards(group) {
@@ -990,6 +1050,26 @@ class Contest {
990
1050
  }
991
1051
  return true;
992
1052
  }
1053
+ resetReplayTime() {
1054
+ this.replayStartTime = void 0;
1055
+ this.replayEndTime = void 0;
1056
+ this.replayFreezeTime = void 0;
1057
+ this.replayNowTime = void 0;
1058
+ this.replayContestStartTimestamp = void 0;
1059
+ }
1060
+ setReplayTime(replayStartTimestamp) {
1061
+ if (replayStartTimestamp === 0) {
1062
+ this.resetReplayTime();
1063
+ return;
1064
+ }
1065
+ const replayStartTime = createDayJS(replayStartTimestamp);
1066
+ const diff = replayStartTime.diff(this.startTime, "s");
1067
+ this.replayStartTime = this.startTime.add(diff, "s");
1068
+ this.replayEndTime = this.endTime.add(diff, "s");
1069
+ this.replayFreezeTime = this.freezeTime.add(diff, "s");
1070
+ this.replayNowTime = createDayJS();
1071
+ this.replayContestStartTimestamp = this.replayNowTime.diff(this.replayStartTime, "s");
1072
+ }
993
1073
  }
994
1074
  function createContest(contestJSON) {
995
1075
  const c = new Contest();
@@ -1202,7 +1282,7 @@ class RankOptions {
1202
1282
  }
1203
1283
  setWidth(width, contest) {
1204
1284
  this.width = width;
1205
- this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
1285
+ this.timestamp = Math.floor((contest.getEndTime().unix() - contest.getStartTime().unix()) * this.width * 1e-4);
1206
1286
  this.enableFilterSubmissionsByTimestamp = true;
1207
1287
  }
1208
1288
  disableFilterSubmissionByTimestamp() {
@@ -1351,6 +1431,8 @@ class Rank {
1351
1431
  team.submissions.push(s);
1352
1432
  problem.statistics.submittedNum++;
1353
1433
  if (problemStatistics.isSolved) {
1434
+ s.isSolved = false;
1435
+ s.isFirstSolved = false;
1354
1436
  return;
1355
1437
  }
1356
1438
  if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
@@ -1362,11 +1444,13 @@ class Rank {
1362
1444
  problemStatistics.lastSubmitTimestamp = s.timestampToSecond;
1363
1445
  problemStatistics.totalCount++;
1364
1446
  if (s.isAccepted()) {
1447
+ s.isSolved = true;
1365
1448
  problemStatistics.isSolved = true;
1366
1449
  problemStatistics.solvedTimestamp = s.timestampToSecond;
1367
1450
  problem.statistics.acceptedNum++;
1368
1451
  problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
1369
1452
  if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
1453
+ s.isFirstSolved = true;
1370
1454
  problemStatistics.isFirstSolved = true;
1371
1455
  problem.statistics.firstSolveSubmissions.push(s);
1372
1456
  }
@@ -1492,6 +1576,7 @@ class Rank {
1492
1576
  const gold = new Award();
1493
1577
  const silver = new Award();
1494
1578
  const bronze = new Award();
1579
+ const honorable = new Award();
1495
1580
  {
1496
1581
  gold.medalType = MedalType.GOLD;
1497
1582
  gold.minRank = 1;
@@ -1516,6 +1601,18 @@ class Rank {
1516
1601
  award.push(bronze);
1517
1602
  }
1518
1603
  }
1604
+ {
1605
+ honorable.medalType = MedalType.HONORABLE;
1606
+ honorable.minRank = bronze.maxRank + 1;
1607
+ this.teams.forEach((t) => {
1608
+ if (t.solvedProblemNum > 0) {
1609
+ honorable.maxRank = Math.max(honorable.maxRank, t.rank);
1610
+ }
1611
+ });
1612
+ if (honorable.maxRank >= honorable.minRank) {
1613
+ award.push(honorable);
1614
+ }
1615
+ }
1519
1616
  this.contest.awards.set("official", award);
1520
1617
  }
1521
1618
  }
@@ -1529,12 +1626,22 @@ class Rank {
1529
1626
  return false;
1530
1627
  }
1531
1628
  getSubmissions() {
1532
- if (this.options.enableFilterSubmissionsByTimestamp === false) {
1629
+ if (this.contest.replayContestStartTimestamp === void 0 && this.options.enableFilterSubmissionsByTimestamp === false) {
1533
1630
  return this.submissions;
1534
1631
  }
1535
- return this.submissions.filter(
1536
- (s) => s.timestampToSecond <= this.options.timestamp
1537
- ).sort(Submission.compare);
1632
+ return this.submissions.filter((s) => {
1633
+ if (this.contest.replayContestStartTimestamp !== void 0) {
1634
+ if (s.timestampToSecond > this.contest.replayContestStartTimestamp) {
1635
+ return false;
1636
+ }
1637
+ }
1638
+ if (this.options.enableFilterSubmissionsByTimestamp) {
1639
+ if (s.timestampToSecond > this.options.timestamp) {
1640
+ return false;
1641
+ }
1642
+ }
1643
+ return true;
1644
+ });
1538
1645
  }
1539
1646
  buildBalloons() {
1540
1647
  this.balloons = [];
@@ -1566,6 +1673,9 @@ class Rank {
1566
1673
  })();
1567
1674
  }
1568
1675
  }
1676
+ setReplayTime(replayStartTimestamp) {
1677
+ this.contest.setReplayTime(replayStartTimestamp);
1678
+ }
1569
1679
  }
1570
1680
 
1571
1681
  class ResolverOperation {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcpcio/core",
3
- "version": "0.35.1",
3
+ "version": "0.37.0",
4
4
  "description": "XCPCIO Core",
5
5
  "author": "Dup4 <lyuzhi.pan@gmail.com>",
6
6
  "license": "MIT",
@@ -46,7 +46,7 @@
46
46
  "lodash": "^4.17.21",
47
47
  "string-width": "^6.1.0",
48
48
  "xlsx-js-style": "^1.2.0",
49
- "@xcpcio/types": "0.35.1"
49
+ "@xcpcio/types": "0.37.0"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@babel/types": "^7.22.4",
package/src/contest.ts CHANGED
@@ -16,6 +16,12 @@ export class Contest {
16
16
  endTime: dayjs.Dayjs;
17
17
  freezeTime: dayjs.Dayjs;
18
18
 
19
+ replayStartTime?: dayjs.Dayjs;
20
+ replayEndTime?: dayjs.Dayjs;
21
+ replayFreezeTime?: dayjs.Dayjs;
22
+ replayNowTime?: dayjs.Dayjs;
23
+ replayContestStartTimestamp?: number;
24
+
19
25
  totalDurationTimestamp: number;
20
26
  freezeDurationTimestamp: number;
21
27
  unFreezeDurationTimestamp: number;
@@ -68,22 +74,34 @@ export class Contest {
68
74
  this.options = new ContestOptions();
69
75
  }
70
76
 
77
+ getStartTime() {
78
+ return this.replayStartTime ?? this.startTime;
79
+ }
80
+
81
+ getEndTime() {
82
+ return this.replayEndTime ?? this.endTime;
83
+ }
84
+
85
+ getFreezeTime() {
86
+ return this.replayFreezeTime ?? this.freezeTime;
87
+ }
88
+
71
89
  getContestDuration(timeFormat = "HH:mm:ss"): string {
72
- return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
90
+ return dayjs.duration(this.getEndTime().diff(this.getStartTime())).format(timeFormat);
73
91
  }
74
92
 
75
93
  getContestState(nowTime?: Date): ContestState {
76
94
  const now = createDayJS(nowTime);
77
95
 
78
- if (now.isBefore(this.startTime)) {
96
+ if (now.isBefore(this.getStartTime())) {
79
97
  return ContestState.PENDING;
80
98
  }
81
99
 
82
- if (now.isSameOrAfter(this.endTime)) {
100
+ if (now.isSameOrAfter(this.getEndTime())) {
83
101
  return ContestState.FINISHED;
84
102
  }
85
103
 
86
- if (now.isSameOrAfter(this.freezeTime)) {
104
+ if (now.isSameOrAfter(this.getFreezeTime())) {
87
105
  return ContestState.FROZEN;
88
106
  }
89
107
 
@@ -92,52 +110,52 @@ export class Contest {
92
110
 
93
111
  getContestPendingTime(nowTime?: Date): string {
94
112
  let baseTime = createDayJS(nowTime);
95
- if (baseTime.isAfter(this.startTime)) {
96
- baseTime = this.startTime;
113
+ if (baseTime.isAfter(this.getStartTime())) {
114
+ baseTime = this.getStartTime();
97
115
  }
98
116
 
99
- return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
117
+ return getTimeDiff(Math.floor(dayjs.duration(this.getStartTime().diff(baseTime)).asSeconds()));
100
118
  }
101
119
 
102
120
  getContestElapsedTime(nowTime?: Date): string {
103
121
  let baseTime = createDayJS(nowTime);
104
- if (baseTime.isAfter(this.endTime)) {
105
- baseTime = this.endTime;
122
+ if (baseTime.isAfter(this.getEndTime())) {
123
+ baseTime = this.getEndTime();
106
124
  }
107
125
 
108
- if (baseTime.isBefore(this.startTime)) {
109
- baseTime = this.startTime;
126
+ if (baseTime.isBefore(this.getStartTime())) {
127
+ baseTime = this.getStartTime();
110
128
  }
111
129
 
112
- return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
130
+ return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.getStartTime())).asSeconds()));
113
131
  }
114
132
 
115
133
  getContestRemainingTime(nowTime?: Date): string {
116
134
  let baseTime = createDayJS(nowTime);
117
- if (baseTime.isAfter(this.endTime)) {
118
- baseTime = this.endTime;
135
+ if (baseTime.isAfter(this.getEndTime())) {
136
+ baseTime = this.getEndTime();
119
137
  }
120
138
 
121
- if (baseTime.isBefore(this.startTime)) {
122
- baseTime = this.startTime;
139
+ if (baseTime.isBefore(this.getStartTime())) {
140
+ baseTime = this.getStartTime();
123
141
  }
124
142
 
125
- return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
143
+ return getTimeDiff(Math.floor(dayjs.duration(this.getEndTime().diff(baseTime)).asSeconds()));
126
144
  }
127
145
 
128
146
  getContestProgressRatio(nowTime?: Date): number {
129
147
  const baseTime = createDayJS(nowTime);
130
148
 
131
- if (this.startTime.isSameOrAfter(baseTime)) {
149
+ if (this.getStartTime().isSameOrAfter(baseTime)) {
132
150
  return 0;
133
151
  }
134
152
 
135
- if (this.endTime.isSameOrBefore(baseTime)) {
153
+ if (this.getEndTime().isSameOrBefore(baseTime)) {
136
154
  return 100;
137
155
  }
138
156
 
139
- const total = this.endTime.diff(this.startTime, "s");
140
- const pass = baseTime.diff(this.startTime, "s");
157
+ const total = this.getEndTime().diff(this.getStartTime(), "s");
158
+ const pass = baseTime.diff(this.getStartTime(), "s");
141
159
 
142
160
  return Math.round((pass * 100) / total);
143
161
  }
@@ -153,6 +171,30 @@ export class Contest {
153
171
 
154
172
  return true;
155
173
  }
174
+
175
+ resetReplayTime() {
176
+ this.replayStartTime = undefined;
177
+ this.replayEndTime = undefined;
178
+ this.replayFreezeTime = undefined;
179
+ this.replayNowTime = undefined;
180
+ this.replayContestStartTimestamp = undefined;
181
+ }
182
+
183
+ setReplayTime(replayStartTimestamp: number) {
184
+ if (replayStartTimestamp === 0) {
185
+ this.resetReplayTime();
186
+ return;
187
+ }
188
+
189
+ const replayStartTime = createDayJS(replayStartTimestamp);
190
+ const diff = replayStartTime.diff(this.startTime, "s");
191
+
192
+ this.replayStartTime = this.startTime.add(diff, "s");
193
+ this.replayEndTime = this.endTime.add(diff, "s");
194
+ this.replayFreezeTime = this.freezeTime.add(diff, "s");
195
+ this.replayNowTime = createDayJS();
196
+ this.replayContestStartTimestamp = this.replayNowTime.diff(this.replayStartTime, "s");
197
+ }
156
198
  }
157
199
 
158
200
  export function createContest(contestJSON: IContest): Contest {
@@ -110,6 +110,8 @@ export class GeneralExcelConverter {
110
110
  const aoa: string[][] = [];
111
111
 
112
112
  const enableAwards = rank.contest.isEnableAwards(rank.options.group);
113
+ const enableMembers = (Array.isArray(rank.teams) && rank.teams[0]?.members) ?? false;
114
+ const enableCoach = rank.teams[0]?.coach ?? false;
113
115
 
114
116
  {
115
117
  aoa.push([rank.contest.name]);
@@ -124,12 +126,23 @@ export class GeneralExcelConverter {
124
126
  head.push(rank.contest.organization);
125
127
  }
126
128
 
127
- head.push("Name", "Solved", "Penalty", ...rank.contest.problems.map(p => p.label), "Dirt");
129
+ head.push("Team", "Solved", "Penalty", ...rank.contest.problems.map(p => p.label), "Dirt");
128
130
 
129
131
  if (enableAwards) {
130
132
  head.push("Medal");
131
133
  }
132
134
 
135
+ if (enableMembers) {
136
+ head.push("Member1", "Member2", "Member3");
137
+ }
138
+
139
+ if (enableCoach) {
140
+ head.push("Coach");
141
+ }
142
+
143
+ head.push("Unofficial");
144
+ head.push("Girl");
145
+
133
146
  aoa.push(head);
134
147
  }
135
148
 
@@ -176,6 +189,28 @@ export class GeneralExcelConverter {
176
189
  arr.push(medals.join(", "));
177
190
  }
178
191
 
192
+ if (enableMembers) {
193
+ const members = team.members;
194
+ if (Array.isArray(members)) {
195
+ arr.push(members[0] ?? "");
196
+ arr.push(members[1] ?? "");
197
+ arr.push(members[2] ?? "");
198
+ } else {
199
+ arr.push("", "", "");
200
+ }
201
+ }
202
+
203
+ if (enableCoach) {
204
+ if (typeof team.coach === "string") {
205
+ arr.push(team.coach ?? "");
206
+ } else {
207
+ arr.push("");
208
+ }
209
+ }
210
+
211
+ arr.push(team.isUnofficial ? "Y" : "N");
212
+ arr.push(team.isGirl ? "Y" : "N");
213
+
179
214
  aoa.push(arr);
180
215
  }
181
216
 
package/src/rank.ts CHANGED
@@ -51,7 +51,7 @@ export class RankOptions {
51
51
 
52
52
  setWidth(width: number, contest: Contest) {
53
53
  this.width = width;
54
- this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 0.0001);
54
+ this.timestamp = Math.floor((contest.getEndTime().unix() - contest.getStartTime().unix()) * this.width * 0.0001);
55
55
  this.enableFilterSubmissionsByTimestamp = true;
56
56
  }
57
57
 
@@ -270,6 +270,8 @@ export class Rank {
270
270
  problem.statistics.submittedNum++;
271
271
 
272
272
  if (problemStatistics.isSolved) {
273
+ s.isSolved = false;
274
+ s.isFirstSolved = false;
273
275
  return;
274
276
  }
275
277
 
@@ -284,6 +286,8 @@ export class Rank {
284
286
  problemStatistics.totalCount++;
285
287
 
286
288
  if (s.isAccepted()) {
289
+ s.isSolved = true;
290
+
287
291
  problemStatistics.isSolved = true;
288
292
  problemStatistics.solvedTimestamp = s.timestampToSecond;
289
293
 
@@ -294,6 +298,7 @@ export class Rank {
294
298
  problem.statistics.firstSolveSubmissions.length === 0
295
299
  || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp
296
300
  ) {
301
+ s.isFirstSolved = true;
297
302
  problemStatistics.isFirstSolved = true;
298
303
  problem.statistics.firstSolveSubmissions.push(s);
299
304
  }
@@ -454,6 +459,7 @@ export class Rank {
454
459
  const gold = new Award();
455
460
  const silver = new Award();
456
461
  const bronze = new Award();
462
+ const honorable = new Award();
457
463
 
458
464
  {
459
465
  gold.medalType = MedalType.GOLD;
@@ -482,6 +488,19 @@ export class Rank {
482
488
  }
483
489
  }
484
490
 
491
+ {
492
+ honorable.medalType = MedalType.HONORABLE;
493
+ honorable.minRank = bronze.maxRank + 1;
494
+ this.teams.forEach((t) => {
495
+ if (t.solvedProblemNum > 0) {
496
+ honorable.maxRank = Math.max(honorable.maxRank, t.rank);
497
+ }
498
+ });
499
+ if (honorable.maxRank >= honorable.minRank) {
500
+ award.push(honorable);
501
+ }
502
+ }
503
+
485
504
  this.contest.awards.set("official", award);
486
505
  }
487
506
  }
@@ -499,13 +518,25 @@ export class Rank {
499
518
  }
500
519
 
501
520
  getSubmissions() {
502
- if (this.options.enableFilterSubmissionsByTimestamp === false) {
521
+ if (this.contest.replayContestStartTimestamp === undefined && this.options.enableFilterSubmissionsByTimestamp === false) {
503
522
  return this.submissions;
504
523
  }
505
524
 
506
- return this.submissions.filter(s =>
507
- s.timestampToSecond <= this.options.timestamp,
508
- ).sort(Submission.compare);
525
+ return this.submissions.filter((s) => {
526
+ if (this.contest.replayContestStartTimestamp !== undefined) {
527
+ if (s.timestampToSecond > this.contest.replayContestStartTimestamp) {
528
+ return false;
529
+ }
530
+ }
531
+
532
+ if (this.options.enableFilterSubmissionsByTimestamp) {
533
+ if (s.timestampToSecond > this.options.timestamp) {
534
+ return false;
535
+ }
536
+ }
537
+
538
+ return true;
539
+ });
509
540
  }
510
541
 
511
542
  buildBalloons() {
@@ -547,4 +578,8 @@ export class Rank {
547
578
  })();
548
579
  }
549
580
  }
581
+
582
+ setReplayTime(replayStartTimestamp: number) {
583
+ this.contest.setReplayTime(replayStartTimestamp);
584
+ }
550
585
  }
package/src/submission.ts CHANGED
@@ -21,6 +21,8 @@ export class Submission {
21
21
 
22
22
  status = SubmissionStatus.UNKNOWN;
23
23
  isIgnore = false;
24
+ isSolved = false;
25
+ isFirstSolved = false;
24
26
 
25
27
  constructor() {
26
28
  this.id = "";
package/src/team.ts CHANGED
@@ -119,6 +119,32 @@ export class Team {
119
119
  return calcDirt(attemptedNum, solvedNum);
120
120
  }
121
121
 
122
+ get isUnofficial() {
123
+ return this.group.includes("unofficial");
124
+ }
125
+
126
+ get isGirl() {
127
+ return this.group.includes("girl");
128
+ }
129
+
130
+ get membersToArray() {
131
+ if (Array.isArray(this.members)) {
132
+ return this.members;
133
+ }
134
+
135
+ if (typeof this.members === "string") {
136
+ if (this.members.includes(", ")) {
137
+ return this.members.split(", ");
138
+ }
139
+
140
+ if (this.members.includes("、")) {
141
+ return this.members.split("、");
142
+ }
143
+ }
144
+
145
+ return [];
146
+ }
147
+
122
148
  get membersToString() {
123
149
  if (typeof this.members === "string") {
124
150
  return this.members;