@xcpcio/core 0.35.1 → 0.36.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
@@ -787,6 +787,8 @@ class Submission {
787
787
  constructor() {
788
788
  this.status = types.SubmissionStatus.UNKNOWN;
789
789
  this.isIgnore = false;
790
+ this.isSolved = false;
791
+ this.isFirstSolved = false;
790
792
  this.id = "";
791
793
  this.teamId = "";
792
794
  this.problemId = "";
@@ -957,59 +959,68 @@ class Contest {
957
959
  this.tag = /* @__PURE__ */ new Map();
958
960
  this.options = new ContestOptions();
959
961
  }
962
+ getStartTime() {
963
+ return this.replayStartTime ?? this.startTime;
964
+ }
965
+ getEndTime() {
966
+ return this.replayEndTime ?? this.endTime;
967
+ }
968
+ getFreezeTime() {
969
+ return this.replayFreezeTime ?? this.freezeTime;
970
+ }
960
971
  getContestDuration(timeFormat = "HH:mm:ss") {
961
- return dayjs__default.duration(this.endTime.diff(this.startTime)).format(timeFormat);
972
+ return dayjs__default.duration(this.getEndTime().diff(this.getStartTime())).format(timeFormat);
962
973
  }
963
974
  getContestState(nowTime) {
964
975
  const now = createDayJS(nowTime);
965
- if (now.isBefore(this.startTime)) {
976
+ if (now.isBefore(this.getStartTime())) {
966
977
  return types.ContestState.PENDING;
967
978
  }
968
- if (now.isSameOrAfter(this.endTime)) {
979
+ if (now.isSameOrAfter(this.getEndTime())) {
969
980
  return types.ContestState.FINISHED;
970
981
  }
971
- if (now.isSameOrAfter(this.freezeTime)) {
982
+ if (now.isSameOrAfter(this.getFreezeTime())) {
972
983
  return types.ContestState.FROZEN;
973
984
  }
974
985
  return types.ContestState.RUNNING;
975
986
  }
976
987
  getContestPendingTime(nowTime) {
977
988
  let baseTime = createDayJS(nowTime);
978
- if (baseTime.isAfter(this.startTime)) {
979
- baseTime = this.startTime;
989
+ if (baseTime.isAfter(this.getStartTime())) {
990
+ baseTime = this.getStartTime();
980
991
  }
981
- return getTimeDiff(Math.floor(dayjs__default.duration(this.startTime.diff(baseTime)).asSeconds()));
992
+ return getTimeDiff(Math.floor(dayjs__default.duration(this.getStartTime().diff(baseTime)).asSeconds()));
982
993
  }
983
994
  getContestElapsedTime(nowTime) {
984
995
  let baseTime = createDayJS(nowTime);
985
- if (baseTime.isAfter(this.endTime)) {
986
- baseTime = this.endTime;
996
+ if (baseTime.isAfter(this.getEndTime())) {
997
+ baseTime = this.getEndTime();
987
998
  }
988
- if (baseTime.isBefore(this.startTime)) {
989
- baseTime = this.startTime;
999
+ if (baseTime.isBefore(this.getStartTime())) {
1000
+ baseTime = this.getStartTime();
990
1001
  }
991
- return getTimeDiff(Math.floor(dayjs__default.duration(baseTime.diff(this.startTime)).asSeconds()));
1002
+ return getTimeDiff(Math.floor(dayjs__default.duration(baseTime.diff(this.getStartTime())).asSeconds()));
992
1003
  }
993
1004
  getContestRemainingTime(nowTime) {
994
1005
  let baseTime = createDayJS(nowTime);
995
- if (baseTime.isAfter(this.endTime)) {
996
- baseTime = this.endTime;
1006
+ if (baseTime.isAfter(this.getEndTime())) {
1007
+ baseTime = this.getEndTime();
997
1008
  }
998
- if (baseTime.isBefore(this.startTime)) {
999
- baseTime = this.startTime;
1009
+ if (baseTime.isBefore(this.getStartTime())) {
1010
+ baseTime = this.getStartTime();
1000
1011
  }
1001
- return getTimeDiff(Math.floor(dayjs__default.duration(this.endTime.diff(baseTime)).asSeconds()));
1012
+ return getTimeDiff(Math.floor(dayjs__default.duration(this.getEndTime().diff(baseTime)).asSeconds()));
1002
1013
  }
1003
1014
  getContestProgressRatio(nowTime) {
1004
1015
  const baseTime = createDayJS(nowTime);
1005
- if (this.startTime.isSameOrAfter(baseTime)) {
1016
+ if (this.getStartTime().isSameOrAfter(baseTime)) {
1006
1017
  return 0;
1007
1018
  }
1008
- if (this.endTime.isSameOrBefore(baseTime)) {
1019
+ if (this.getEndTime().isSameOrBefore(baseTime)) {
1009
1020
  return 100;
1010
1021
  }
1011
- const total = this.endTime.diff(this.startTime, "s");
1012
- const pass = baseTime.diff(this.startTime, "s");
1022
+ const total = this.getEndTime().diff(this.getStartTime(), "s");
1023
+ const pass = baseTime.diff(this.getStartTime(), "s");
1013
1024
  return Math.round(pass * 100 / total);
1014
1025
  }
1015
1026
  isEnableAwards(group) {
@@ -1021,6 +1032,26 @@ class Contest {
1021
1032
  }
1022
1033
  return true;
1023
1034
  }
1035
+ resetReplayTime() {
1036
+ this.replayStartTime = void 0;
1037
+ this.replayEndTime = void 0;
1038
+ this.replayFreezeTime = void 0;
1039
+ this.replayNowTime = void 0;
1040
+ this.replayContestStartTimestamp = void 0;
1041
+ }
1042
+ setReplayTime(replayStartTimestamp) {
1043
+ if (replayStartTimestamp === 0) {
1044
+ this.resetReplayTime();
1045
+ return;
1046
+ }
1047
+ const replayStartTime = createDayJS(replayStartTimestamp);
1048
+ const diff = replayStartTime.diff(this.startTime, "s");
1049
+ this.replayStartTime = this.startTime.add(diff, "s");
1050
+ this.replayEndTime = this.endTime.add(diff, "s");
1051
+ this.replayFreezeTime = this.freezeTime.add(diff, "s");
1052
+ this.replayNowTime = createDayJS();
1053
+ this.replayContestStartTimestamp = this.replayNowTime.diff(this.replayStartTime, "s");
1054
+ }
1024
1055
  }
1025
1056
  function createContest(contestJSON) {
1026
1057
  const c = new Contest();
@@ -1233,7 +1264,7 @@ class RankOptions {
1233
1264
  }
1234
1265
  setWidth(width, contest) {
1235
1266
  this.width = width;
1236
- this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
1267
+ this.timestamp = Math.floor((contest.getEndTime().unix() - contest.getStartTime().unix()) * this.width * 1e-4);
1237
1268
  this.enableFilterSubmissionsByTimestamp = true;
1238
1269
  }
1239
1270
  disableFilterSubmissionByTimestamp() {
@@ -1382,6 +1413,8 @@ class Rank {
1382
1413
  team.submissions.push(s);
1383
1414
  problem.statistics.submittedNum++;
1384
1415
  if (problemStatistics.isSolved) {
1416
+ s.isSolved = false;
1417
+ s.isFirstSolved = false;
1385
1418
  return;
1386
1419
  }
1387
1420
  if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
@@ -1393,11 +1426,13 @@ class Rank {
1393
1426
  problemStatistics.lastSubmitTimestamp = s.timestampToSecond;
1394
1427
  problemStatistics.totalCount++;
1395
1428
  if (s.isAccepted()) {
1429
+ s.isSolved = true;
1396
1430
  problemStatistics.isSolved = true;
1397
1431
  problemStatistics.solvedTimestamp = s.timestampToSecond;
1398
1432
  problem.statistics.acceptedNum++;
1399
1433
  problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
1400
1434
  if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
1435
+ s.isFirstSolved = true;
1401
1436
  problemStatistics.isFirstSolved = true;
1402
1437
  problem.statistics.firstSolveSubmissions.push(s);
1403
1438
  }
@@ -1523,6 +1558,7 @@ class Rank {
1523
1558
  const gold = new Award();
1524
1559
  const silver = new Award();
1525
1560
  const bronze = new Award();
1561
+ const honorable = new Award();
1526
1562
  {
1527
1563
  gold.medalType = MedalType.GOLD;
1528
1564
  gold.minRank = 1;
@@ -1547,6 +1583,18 @@ class Rank {
1547
1583
  award.push(bronze);
1548
1584
  }
1549
1585
  }
1586
+ {
1587
+ honorable.medalType = MedalType.HONORABLE;
1588
+ honorable.minRank = bronze.maxRank + 1;
1589
+ this.teams.forEach((t) => {
1590
+ if (t.solvedProblemNum > 0) {
1591
+ honorable.maxRank = Math.max(honorable.maxRank, t.rank);
1592
+ }
1593
+ });
1594
+ if (honorable.maxRank >= honorable.minRank) {
1595
+ award.push(honorable);
1596
+ }
1597
+ }
1550
1598
  this.contest.awards.set("official", award);
1551
1599
  }
1552
1600
  }
@@ -1560,12 +1608,22 @@ class Rank {
1560
1608
  return false;
1561
1609
  }
1562
1610
  getSubmissions() {
1563
- if (this.options.enableFilterSubmissionsByTimestamp === false) {
1611
+ if (this.contest.replayContestStartTimestamp === void 0 && this.options.enableFilterSubmissionsByTimestamp === false) {
1564
1612
  return this.submissions;
1565
1613
  }
1566
- return this.submissions.filter(
1567
- (s) => s.timestampToSecond <= this.options.timestamp
1568
- ).sort(Submission.compare);
1614
+ return this.submissions.filter((s) => {
1615
+ if (this.contest.replayContestStartTimestamp !== void 0) {
1616
+ if (s.timestampToSecond > this.contest.replayContestStartTimestamp) {
1617
+ return false;
1618
+ }
1619
+ }
1620
+ if (this.options.enableFilterSubmissionsByTimestamp) {
1621
+ if (s.timestampToSecond > this.options.timestamp) {
1622
+ return false;
1623
+ }
1624
+ }
1625
+ return true;
1626
+ });
1569
1627
  }
1570
1628
  buildBalloons() {
1571
1629
  this.balloons = [];
@@ -1597,6 +1655,9 @@ class Rank {
1597
1655
  })();
1598
1656
  }
1599
1657
  }
1658
+ setReplayTime(replayStartTimestamp) {
1659
+ this.contest.setReplayTime(replayStartTimestamp);
1660
+ }
1600
1661
  }
1601
1662
 
1602
1663
  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
 
@@ -266,6 +278,7 @@ declare class Rank {
266
278
  filterTeamByOrg(team: Team): boolean;
267
279
  getSubmissions(): Submissions;
268
280
  buildBalloons(): void;
281
+ setReplayTime(replayStartTimestamp: number): void;
269
282
  }
270
283
 
271
284
  declare class CodeforcesGymGhostDATConverter {
package/dist/index.mjs CHANGED
@@ -756,6 +756,8 @@ class Submission {
756
756
  constructor() {
757
757
  this.status = SubmissionStatus.UNKNOWN;
758
758
  this.isIgnore = false;
759
+ this.isSolved = false;
760
+ this.isFirstSolved = false;
759
761
  this.id = "";
760
762
  this.teamId = "";
761
763
  this.problemId = "";
@@ -926,59 +928,68 @@ class Contest {
926
928
  this.tag = /* @__PURE__ */ new Map();
927
929
  this.options = new ContestOptions();
928
930
  }
931
+ getStartTime() {
932
+ return this.replayStartTime ?? this.startTime;
933
+ }
934
+ getEndTime() {
935
+ return this.replayEndTime ?? this.endTime;
936
+ }
937
+ getFreezeTime() {
938
+ return this.replayFreezeTime ?? this.freezeTime;
939
+ }
929
940
  getContestDuration(timeFormat = "HH:mm:ss") {
930
- return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
941
+ return dayjs.duration(this.getEndTime().diff(this.getStartTime())).format(timeFormat);
931
942
  }
932
943
  getContestState(nowTime) {
933
944
  const now = createDayJS(nowTime);
934
- if (now.isBefore(this.startTime)) {
945
+ if (now.isBefore(this.getStartTime())) {
935
946
  return ContestState.PENDING;
936
947
  }
937
- if (now.isSameOrAfter(this.endTime)) {
948
+ if (now.isSameOrAfter(this.getEndTime())) {
938
949
  return ContestState.FINISHED;
939
950
  }
940
- if (now.isSameOrAfter(this.freezeTime)) {
951
+ if (now.isSameOrAfter(this.getFreezeTime())) {
941
952
  return ContestState.FROZEN;
942
953
  }
943
954
  return ContestState.RUNNING;
944
955
  }
945
956
  getContestPendingTime(nowTime) {
946
957
  let baseTime = createDayJS(nowTime);
947
- if (baseTime.isAfter(this.startTime)) {
948
- baseTime = this.startTime;
958
+ if (baseTime.isAfter(this.getStartTime())) {
959
+ baseTime = this.getStartTime();
949
960
  }
950
- return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
961
+ return getTimeDiff(Math.floor(dayjs.duration(this.getStartTime().diff(baseTime)).asSeconds()));
951
962
  }
952
963
  getContestElapsedTime(nowTime) {
953
964
  let baseTime = createDayJS(nowTime);
954
- if (baseTime.isAfter(this.endTime)) {
955
- baseTime = this.endTime;
965
+ if (baseTime.isAfter(this.getEndTime())) {
966
+ baseTime = this.getEndTime();
956
967
  }
957
- if (baseTime.isBefore(this.startTime)) {
958
- baseTime = this.startTime;
968
+ if (baseTime.isBefore(this.getStartTime())) {
969
+ baseTime = this.getStartTime();
959
970
  }
960
- return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
971
+ return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.getStartTime())).asSeconds()));
961
972
  }
962
973
  getContestRemainingTime(nowTime) {
963
974
  let baseTime = createDayJS(nowTime);
964
- if (baseTime.isAfter(this.endTime)) {
965
- baseTime = this.endTime;
975
+ if (baseTime.isAfter(this.getEndTime())) {
976
+ baseTime = this.getEndTime();
966
977
  }
967
- if (baseTime.isBefore(this.startTime)) {
968
- baseTime = this.startTime;
978
+ if (baseTime.isBefore(this.getStartTime())) {
979
+ baseTime = this.getStartTime();
969
980
  }
970
- return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
981
+ return getTimeDiff(Math.floor(dayjs.duration(this.getEndTime().diff(baseTime)).asSeconds()));
971
982
  }
972
983
  getContestProgressRatio(nowTime) {
973
984
  const baseTime = createDayJS(nowTime);
974
- if (this.startTime.isSameOrAfter(baseTime)) {
985
+ if (this.getStartTime().isSameOrAfter(baseTime)) {
975
986
  return 0;
976
987
  }
977
- if (this.endTime.isSameOrBefore(baseTime)) {
988
+ if (this.getEndTime().isSameOrBefore(baseTime)) {
978
989
  return 100;
979
990
  }
980
- const total = this.endTime.diff(this.startTime, "s");
981
- const pass = baseTime.diff(this.startTime, "s");
991
+ const total = this.getEndTime().diff(this.getStartTime(), "s");
992
+ const pass = baseTime.diff(this.getStartTime(), "s");
982
993
  return Math.round(pass * 100 / total);
983
994
  }
984
995
  isEnableAwards(group) {
@@ -990,6 +1001,26 @@ class Contest {
990
1001
  }
991
1002
  return true;
992
1003
  }
1004
+ resetReplayTime() {
1005
+ this.replayStartTime = void 0;
1006
+ this.replayEndTime = void 0;
1007
+ this.replayFreezeTime = void 0;
1008
+ this.replayNowTime = void 0;
1009
+ this.replayContestStartTimestamp = void 0;
1010
+ }
1011
+ setReplayTime(replayStartTimestamp) {
1012
+ if (replayStartTimestamp === 0) {
1013
+ this.resetReplayTime();
1014
+ return;
1015
+ }
1016
+ const replayStartTime = createDayJS(replayStartTimestamp);
1017
+ const diff = replayStartTime.diff(this.startTime, "s");
1018
+ this.replayStartTime = this.startTime.add(diff, "s");
1019
+ this.replayEndTime = this.endTime.add(diff, "s");
1020
+ this.replayFreezeTime = this.freezeTime.add(diff, "s");
1021
+ this.replayNowTime = createDayJS();
1022
+ this.replayContestStartTimestamp = this.replayNowTime.diff(this.replayStartTime, "s");
1023
+ }
993
1024
  }
994
1025
  function createContest(contestJSON) {
995
1026
  const c = new Contest();
@@ -1202,7 +1233,7 @@ class RankOptions {
1202
1233
  }
1203
1234
  setWidth(width, contest) {
1204
1235
  this.width = width;
1205
- this.timestamp = Math.floor((contest.endTime.unix() - contest.startTime.unix()) * this.width * 1e-4);
1236
+ this.timestamp = Math.floor((contest.getEndTime().unix() - contest.getStartTime().unix()) * this.width * 1e-4);
1206
1237
  this.enableFilterSubmissionsByTimestamp = true;
1207
1238
  }
1208
1239
  disableFilterSubmissionByTimestamp() {
@@ -1351,6 +1382,8 @@ class Rank {
1351
1382
  team.submissions.push(s);
1352
1383
  problem.statistics.submittedNum++;
1353
1384
  if (problemStatistics.isSolved) {
1385
+ s.isSolved = false;
1386
+ s.isFirstSolved = false;
1354
1387
  return;
1355
1388
  }
1356
1389
  if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
@@ -1362,11 +1395,13 @@ class Rank {
1362
1395
  problemStatistics.lastSubmitTimestamp = s.timestampToSecond;
1363
1396
  problemStatistics.totalCount++;
1364
1397
  if (s.isAccepted()) {
1398
+ s.isSolved = true;
1365
1399
  problemStatistics.isSolved = true;
1366
1400
  problemStatistics.solvedTimestamp = s.timestampToSecond;
1367
1401
  problem.statistics.acceptedNum++;
1368
1402
  problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
1369
1403
  if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
1404
+ s.isFirstSolved = true;
1370
1405
  problemStatistics.isFirstSolved = true;
1371
1406
  problem.statistics.firstSolveSubmissions.push(s);
1372
1407
  }
@@ -1492,6 +1527,7 @@ class Rank {
1492
1527
  const gold = new Award();
1493
1528
  const silver = new Award();
1494
1529
  const bronze = new Award();
1530
+ const honorable = new Award();
1495
1531
  {
1496
1532
  gold.medalType = MedalType.GOLD;
1497
1533
  gold.minRank = 1;
@@ -1516,6 +1552,18 @@ class Rank {
1516
1552
  award.push(bronze);
1517
1553
  }
1518
1554
  }
1555
+ {
1556
+ honorable.medalType = MedalType.HONORABLE;
1557
+ honorable.minRank = bronze.maxRank + 1;
1558
+ this.teams.forEach((t) => {
1559
+ if (t.solvedProblemNum > 0) {
1560
+ honorable.maxRank = Math.max(honorable.maxRank, t.rank);
1561
+ }
1562
+ });
1563
+ if (honorable.maxRank >= honorable.minRank) {
1564
+ award.push(honorable);
1565
+ }
1566
+ }
1519
1567
  this.contest.awards.set("official", award);
1520
1568
  }
1521
1569
  }
@@ -1529,12 +1577,22 @@ class Rank {
1529
1577
  return false;
1530
1578
  }
1531
1579
  getSubmissions() {
1532
- if (this.options.enableFilterSubmissionsByTimestamp === false) {
1580
+ if (this.contest.replayContestStartTimestamp === void 0 && this.options.enableFilterSubmissionsByTimestamp === false) {
1533
1581
  return this.submissions;
1534
1582
  }
1535
- return this.submissions.filter(
1536
- (s) => s.timestampToSecond <= this.options.timestamp
1537
- ).sort(Submission.compare);
1583
+ return this.submissions.filter((s) => {
1584
+ if (this.contest.replayContestStartTimestamp !== void 0) {
1585
+ if (s.timestampToSecond > this.contest.replayContestStartTimestamp) {
1586
+ return false;
1587
+ }
1588
+ }
1589
+ if (this.options.enableFilterSubmissionsByTimestamp) {
1590
+ if (s.timestampToSecond > this.options.timestamp) {
1591
+ return false;
1592
+ }
1593
+ }
1594
+ return true;
1595
+ });
1538
1596
  }
1539
1597
  buildBalloons() {
1540
1598
  this.balloons = [];
@@ -1566,6 +1624,9 @@ class Rank {
1566
1624
  })();
1567
1625
  }
1568
1626
  }
1627
+ setReplayTime(replayStartTimestamp) {
1628
+ this.contest.setReplayTime(replayStartTimestamp);
1629
+ }
1569
1630
  }
1570
1631
 
1571
1632
  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.36.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.36.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 {
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 = "";