@xcpcio/core 0.35.0 → 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
@@ -15,8 +15,8 @@ const relativeTime = require('dayjs/plugin/relativeTime');
15
15
  const _ = require('lodash');
16
16
  const XLSX = require('xlsx-js-style');
17
17
  const stringWidth = require('string-width');
18
- const colorDiff = require('color-diff');
19
18
  const chroma = require('chroma-js');
19
+ const colorDiff = require('color-diff');
20
20
 
21
21
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e["default"] : e; }
22
22
 
@@ -469,7 +469,7 @@ function calcDirt(attemptedNum, solvedNum) {
469
469
  return Math.floor((attemptedNum - solvedNum) * 100 / attemptedNum);
470
470
  }
471
471
 
472
- function getWhiteOrBlackColor(background) {
472
+ function getWhiteOrBlackColorV1(background) {
473
473
  const [R, G, B] = chroma__default(background).rgb();
474
474
  const color = { R, G, B };
475
475
  const palette = [
@@ -483,6 +483,12 @@ function getWhiteOrBlackColor(background) {
483
483
  return "#fff";
484
484
  }
485
485
  }
486
+ function getWhiteOrBlackColor(background) {
487
+ const [R, G, B] = chroma__default(background).rgb();
488
+ const brightness = (R * 299 + G * 587 + B * 114) / 1e3;
489
+ const threshold = 148;
490
+ return brightness <= threshold ? "#fff" : "#000";
491
+ }
486
492
 
487
493
  class ProblemStatistics {
488
494
  constructor() {
@@ -781,6 +787,8 @@ class Submission {
781
787
  constructor() {
782
788
  this.status = types.SubmissionStatus.UNKNOWN;
783
789
  this.isIgnore = false;
790
+ this.isSolved = false;
791
+ this.isFirstSolved = false;
784
792
  this.id = "";
785
793
  this.teamId = "";
786
794
  this.problemId = "";
@@ -951,59 +959,68 @@ class Contest {
951
959
  this.tag = /* @__PURE__ */ new Map();
952
960
  this.options = new ContestOptions();
953
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
+ }
954
971
  getContestDuration(timeFormat = "HH:mm:ss") {
955
- return dayjs__default.duration(this.endTime.diff(this.startTime)).format(timeFormat);
972
+ return dayjs__default.duration(this.getEndTime().diff(this.getStartTime())).format(timeFormat);
956
973
  }
957
974
  getContestState(nowTime) {
958
975
  const now = createDayJS(nowTime);
959
- if (now.isBefore(this.startTime)) {
976
+ if (now.isBefore(this.getStartTime())) {
960
977
  return types.ContestState.PENDING;
961
978
  }
962
- if (now.isSameOrAfter(this.endTime)) {
979
+ if (now.isSameOrAfter(this.getEndTime())) {
963
980
  return types.ContestState.FINISHED;
964
981
  }
965
- if (now.isSameOrAfter(this.freezeTime)) {
982
+ if (now.isSameOrAfter(this.getFreezeTime())) {
966
983
  return types.ContestState.FROZEN;
967
984
  }
968
985
  return types.ContestState.RUNNING;
969
986
  }
970
987
  getContestPendingTime(nowTime) {
971
988
  let baseTime = createDayJS(nowTime);
972
- if (baseTime.isAfter(this.startTime)) {
973
- baseTime = this.startTime;
989
+ if (baseTime.isAfter(this.getStartTime())) {
990
+ baseTime = this.getStartTime();
974
991
  }
975
- 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()));
976
993
  }
977
994
  getContestElapsedTime(nowTime) {
978
995
  let baseTime = createDayJS(nowTime);
979
- if (baseTime.isAfter(this.endTime)) {
980
- baseTime = this.endTime;
996
+ if (baseTime.isAfter(this.getEndTime())) {
997
+ baseTime = this.getEndTime();
981
998
  }
982
- if (baseTime.isBefore(this.startTime)) {
983
- baseTime = this.startTime;
999
+ if (baseTime.isBefore(this.getStartTime())) {
1000
+ baseTime = this.getStartTime();
984
1001
  }
985
- 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()));
986
1003
  }
987
1004
  getContestRemainingTime(nowTime) {
988
1005
  let baseTime = createDayJS(nowTime);
989
- if (baseTime.isAfter(this.endTime)) {
990
- baseTime = this.endTime;
1006
+ if (baseTime.isAfter(this.getEndTime())) {
1007
+ baseTime = this.getEndTime();
991
1008
  }
992
- if (baseTime.isBefore(this.startTime)) {
993
- baseTime = this.startTime;
1009
+ if (baseTime.isBefore(this.getStartTime())) {
1010
+ baseTime = this.getStartTime();
994
1011
  }
995
- 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()));
996
1013
  }
997
1014
  getContestProgressRatio(nowTime) {
998
1015
  const baseTime = createDayJS(nowTime);
999
- if (this.startTime.isSameOrAfter(baseTime)) {
1016
+ if (this.getStartTime().isSameOrAfter(baseTime)) {
1000
1017
  return 0;
1001
1018
  }
1002
- if (this.endTime.isSameOrBefore(baseTime)) {
1019
+ if (this.getEndTime().isSameOrBefore(baseTime)) {
1003
1020
  return 100;
1004
1021
  }
1005
- const total = this.endTime.diff(this.startTime, "s");
1006
- const pass = baseTime.diff(this.startTime, "s");
1022
+ const total = this.getEndTime().diff(this.getStartTime(), "s");
1023
+ const pass = baseTime.diff(this.getStartTime(), "s");
1007
1024
  return Math.round(pass * 100 / total);
1008
1025
  }
1009
1026
  isEnableAwards(group) {
@@ -1015,6 +1032,26 @@ class Contest {
1015
1032
  }
1016
1033
  return true;
1017
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
+ }
1018
1055
  }
1019
1056
  function createContest(contestJSON) {
1020
1057
  const c = new Contest();
@@ -1227,7 +1264,7 @@ class RankOptions {
1227
1264
  }
1228
1265
  setWidth(width, contest) {
1229
1266
  this.width = width;
1230
- 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);
1231
1268
  this.enableFilterSubmissionsByTimestamp = true;
1232
1269
  }
1233
1270
  disableFilterSubmissionByTimestamp() {
@@ -1376,6 +1413,8 @@ class Rank {
1376
1413
  team.submissions.push(s);
1377
1414
  problem.statistics.submittedNum++;
1378
1415
  if (problemStatistics.isSolved) {
1416
+ s.isSolved = false;
1417
+ s.isFirstSolved = false;
1379
1418
  return;
1380
1419
  }
1381
1420
  if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
@@ -1387,11 +1426,13 @@ class Rank {
1387
1426
  problemStatistics.lastSubmitTimestamp = s.timestampToSecond;
1388
1427
  problemStatistics.totalCount++;
1389
1428
  if (s.isAccepted()) {
1429
+ s.isSolved = true;
1390
1430
  problemStatistics.isSolved = true;
1391
1431
  problemStatistics.solvedTimestamp = s.timestampToSecond;
1392
1432
  problem.statistics.acceptedNum++;
1393
1433
  problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
1394
1434
  if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
1435
+ s.isFirstSolved = true;
1395
1436
  problemStatistics.isFirstSolved = true;
1396
1437
  problem.statistics.firstSolveSubmissions.push(s);
1397
1438
  }
@@ -1517,6 +1558,7 @@ class Rank {
1517
1558
  const gold = new Award();
1518
1559
  const silver = new Award();
1519
1560
  const bronze = new Award();
1561
+ const honorable = new Award();
1520
1562
  {
1521
1563
  gold.medalType = MedalType.GOLD;
1522
1564
  gold.minRank = 1;
@@ -1541,6 +1583,18 @@ class Rank {
1541
1583
  award.push(bronze);
1542
1584
  }
1543
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
+ }
1544
1598
  this.contest.awards.set("official", award);
1545
1599
  }
1546
1600
  }
@@ -1554,12 +1608,22 @@ class Rank {
1554
1608
  return false;
1555
1609
  }
1556
1610
  getSubmissions() {
1557
- if (this.options.enableFilterSubmissionsByTimestamp === false) {
1611
+ if (this.contest.replayContestStartTimestamp === void 0 && this.options.enableFilterSubmissionsByTimestamp === false) {
1558
1612
  return this.submissions;
1559
1613
  }
1560
- return this.submissions.filter(
1561
- (s) => s.timestampToSecond <= this.options.timestamp
1562
- ).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
+ });
1563
1627
  }
1564
1628
  buildBalloons() {
1565
1629
  this.balloons = [];
@@ -1591,6 +1655,9 @@ class Rank {
1591
1655
  })();
1592
1656
  }
1593
1657
  }
1658
+ setReplayTime(replayStartTimestamp) {
1659
+ this.contest.setReplayTime(replayStartTimestamp);
1660
+ }
1594
1661
  }
1595
1662
 
1596
1663
  class ResolverOperation {
@@ -1717,6 +1784,7 @@ exports.getImageSource = getImageSource;
1717
1784
  exports.getTimeDiff = getTimeDiff;
1718
1785
  exports.getTimestamp = getTimestamp;
1719
1786
  exports.getWhiteOrBlackColor = getWhiteOrBlackColor;
1787
+ exports.getWhiteOrBlackColorV1 = getWhiteOrBlackColorV1;
1720
1788
  exports.isAccepted = isAccepted;
1721
1789
  exports.isNotCalculatedPenaltyStatus = isNotCalculatedPenaltyStatus;
1722
1790
  exports.isPending = isPending;
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;
@@ -83,6 +85,7 @@ declare class TeamProblemStatistics {
83
85
 
84
86
  declare function calcDirt(attemptedNum: number, solvedNum: number): number;
85
87
 
88
+ declare function getWhiteOrBlackColorV1(background: string): "#000" | "#fff";
86
89
  declare function getWhiteOrBlackColor(background: string): "#000" | "#fff";
87
90
 
88
91
  declare function createDayJS(time?: Date | string | number | undefined): dayjs.Dayjs;
@@ -125,6 +128,11 @@ declare class Contest {
125
128
  startTime: dayjs.Dayjs;
126
129
  endTime: dayjs.Dayjs;
127
130
  freezeTime: dayjs.Dayjs;
131
+ replayStartTime?: dayjs.Dayjs;
132
+ replayEndTime?: dayjs.Dayjs;
133
+ replayFreezeTime?: dayjs.Dayjs;
134
+ replayNowTime?: dayjs.Dayjs;
135
+ replayContestStartTimestamp?: number;
128
136
  totalDurationTimestamp: number;
129
137
  freezeDurationTimestamp: number;
130
138
  unFreezeDurationTimestamp: number;
@@ -143,6 +151,9 @@ declare class Contest {
143
151
  boardLink?: string;
144
152
  options: ContestOptions;
145
153
  constructor();
154
+ getStartTime(): dayjs.Dayjs;
155
+ getEndTime(): dayjs.Dayjs;
156
+ getFreezeTime(): dayjs.Dayjs;
146
157
  getContestDuration(timeFormat?: string): string;
147
158
  getContestState(nowTime?: Date): ContestState;
148
159
  getContestPendingTime(nowTime?: Date): string;
@@ -150,6 +161,8 @@ declare class Contest {
150
161
  getContestRemainingTime(nowTime?: Date): string;
151
162
  getContestProgressRatio(nowTime?: Date): number;
152
163
  isEnableAwards(group: string): boolean;
164
+ resetReplayTime(): void;
165
+ setReplayTime(replayStartTimestamp: number): void;
153
166
  }
154
167
  declare function createContest(contestJSON: Contest$1): Contest;
155
168
 
@@ -265,6 +278,7 @@ declare class Rank {
265
278
  filterTeamByOrg(team: Team): boolean;
266
279
  getSubmissions(): Submissions;
267
280
  buildBalloons(): void;
281
+ setReplayTime(replayStartTimestamp: number): void;
268
282
  }
269
283
 
270
284
  declare class CodeforcesGymGhostDATConverter {
@@ -327,4 +341,4 @@ declare function isRejected(status: SubmissionStatus): boolean;
327
341
  declare function isPending(status: SubmissionStatus): boolean;
328
342
  declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
329
343
 
330
- export { Award, Awards, Balloon, Balloons, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestIndexList, ContestOptions, GeneralExcelConverter, MedalType, PlaceChartPointData, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, Resolver, SelectOptionItem, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
344
+ export { Award, Awards, Balloon, Balloons, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestIndexList, ContestOptions, GeneralExcelConverter, MedalType, PlaceChartPointData, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, Resolver, SelectOptionItem, Submission, Submissions, Team, TeamProblemStatistics, Teams, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
package/dist/index.mjs CHANGED
@@ -12,8 +12,8 @@ import relativeTime from 'dayjs/plugin/relativeTime';
12
12
  import _ from 'lodash';
13
13
  import * as XLSX from 'xlsx-js-style';
14
14
  import stringWidth from 'string-width';
15
- import { furthest } from 'color-diff';
16
15
  import chroma from 'chroma-js';
16
+ import { furthest } from 'color-diff';
17
17
 
18
18
  function stringToSubmissionStatus(status) {
19
19
  status = status.toUpperCase().replace(" ", "_");
@@ -438,7 +438,7 @@ function calcDirt(attemptedNum, solvedNum) {
438
438
  return Math.floor((attemptedNum - solvedNum) * 100 / attemptedNum);
439
439
  }
440
440
 
441
- function getWhiteOrBlackColor(background) {
441
+ function getWhiteOrBlackColorV1(background) {
442
442
  const [R, G, B] = chroma(background).rgb();
443
443
  const color = { R, G, B };
444
444
  const palette = [
@@ -452,6 +452,12 @@ function getWhiteOrBlackColor(background) {
452
452
  return "#fff";
453
453
  }
454
454
  }
455
+ function getWhiteOrBlackColor(background) {
456
+ const [R, G, B] = chroma(background).rgb();
457
+ const brightness = (R * 299 + G * 587 + B * 114) / 1e3;
458
+ const threshold = 148;
459
+ return brightness <= threshold ? "#fff" : "#000";
460
+ }
455
461
 
456
462
  class ProblemStatistics {
457
463
  constructor() {
@@ -750,6 +756,8 @@ class Submission {
750
756
  constructor() {
751
757
  this.status = SubmissionStatus.UNKNOWN;
752
758
  this.isIgnore = false;
759
+ this.isSolved = false;
760
+ this.isFirstSolved = false;
753
761
  this.id = "";
754
762
  this.teamId = "";
755
763
  this.problemId = "";
@@ -920,59 +928,68 @@ class Contest {
920
928
  this.tag = /* @__PURE__ */ new Map();
921
929
  this.options = new ContestOptions();
922
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
+ }
923
940
  getContestDuration(timeFormat = "HH:mm:ss") {
924
- return dayjs.duration(this.endTime.diff(this.startTime)).format(timeFormat);
941
+ return dayjs.duration(this.getEndTime().diff(this.getStartTime())).format(timeFormat);
925
942
  }
926
943
  getContestState(nowTime) {
927
944
  const now = createDayJS(nowTime);
928
- if (now.isBefore(this.startTime)) {
945
+ if (now.isBefore(this.getStartTime())) {
929
946
  return ContestState.PENDING;
930
947
  }
931
- if (now.isSameOrAfter(this.endTime)) {
948
+ if (now.isSameOrAfter(this.getEndTime())) {
932
949
  return ContestState.FINISHED;
933
950
  }
934
- if (now.isSameOrAfter(this.freezeTime)) {
951
+ if (now.isSameOrAfter(this.getFreezeTime())) {
935
952
  return ContestState.FROZEN;
936
953
  }
937
954
  return ContestState.RUNNING;
938
955
  }
939
956
  getContestPendingTime(nowTime) {
940
957
  let baseTime = createDayJS(nowTime);
941
- if (baseTime.isAfter(this.startTime)) {
942
- baseTime = this.startTime;
958
+ if (baseTime.isAfter(this.getStartTime())) {
959
+ baseTime = this.getStartTime();
943
960
  }
944
- return getTimeDiff(Math.floor(dayjs.duration(this.startTime.diff(baseTime)).asSeconds()));
961
+ return getTimeDiff(Math.floor(dayjs.duration(this.getStartTime().diff(baseTime)).asSeconds()));
945
962
  }
946
963
  getContestElapsedTime(nowTime) {
947
964
  let baseTime = createDayJS(nowTime);
948
- if (baseTime.isAfter(this.endTime)) {
949
- baseTime = this.endTime;
965
+ if (baseTime.isAfter(this.getEndTime())) {
966
+ baseTime = this.getEndTime();
950
967
  }
951
- if (baseTime.isBefore(this.startTime)) {
952
- baseTime = this.startTime;
968
+ if (baseTime.isBefore(this.getStartTime())) {
969
+ baseTime = this.getStartTime();
953
970
  }
954
- return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.startTime)).asSeconds()));
971
+ return getTimeDiff(Math.floor(dayjs.duration(baseTime.diff(this.getStartTime())).asSeconds()));
955
972
  }
956
973
  getContestRemainingTime(nowTime) {
957
974
  let baseTime = createDayJS(nowTime);
958
- if (baseTime.isAfter(this.endTime)) {
959
- baseTime = this.endTime;
975
+ if (baseTime.isAfter(this.getEndTime())) {
976
+ baseTime = this.getEndTime();
960
977
  }
961
- if (baseTime.isBefore(this.startTime)) {
962
- baseTime = this.startTime;
978
+ if (baseTime.isBefore(this.getStartTime())) {
979
+ baseTime = this.getStartTime();
963
980
  }
964
- return getTimeDiff(Math.floor(dayjs.duration(this.endTime.diff(baseTime)).asSeconds()));
981
+ return getTimeDiff(Math.floor(dayjs.duration(this.getEndTime().diff(baseTime)).asSeconds()));
965
982
  }
966
983
  getContestProgressRatio(nowTime) {
967
984
  const baseTime = createDayJS(nowTime);
968
- if (this.startTime.isSameOrAfter(baseTime)) {
985
+ if (this.getStartTime().isSameOrAfter(baseTime)) {
969
986
  return 0;
970
987
  }
971
- if (this.endTime.isSameOrBefore(baseTime)) {
988
+ if (this.getEndTime().isSameOrBefore(baseTime)) {
972
989
  return 100;
973
990
  }
974
- const total = this.endTime.diff(this.startTime, "s");
975
- const pass = baseTime.diff(this.startTime, "s");
991
+ const total = this.getEndTime().diff(this.getStartTime(), "s");
992
+ const pass = baseTime.diff(this.getStartTime(), "s");
976
993
  return Math.round(pass * 100 / total);
977
994
  }
978
995
  isEnableAwards(group) {
@@ -984,6 +1001,26 @@ class Contest {
984
1001
  }
985
1002
  return true;
986
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
+ }
987
1024
  }
988
1025
  function createContest(contestJSON) {
989
1026
  const c = new Contest();
@@ -1196,7 +1233,7 @@ class RankOptions {
1196
1233
  }
1197
1234
  setWidth(width, contest) {
1198
1235
  this.width = width;
1199
- 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);
1200
1237
  this.enableFilterSubmissionsByTimestamp = true;
1201
1238
  }
1202
1239
  disableFilterSubmissionByTimestamp() {
@@ -1345,6 +1382,8 @@ class Rank {
1345
1382
  team.submissions.push(s);
1346
1383
  problem.statistics.submittedNum++;
1347
1384
  if (problemStatistics.isSolved) {
1385
+ s.isSolved = false;
1386
+ s.isFirstSolved = false;
1348
1387
  return;
1349
1388
  }
1350
1389
  if (s.isIgnore || s.isNotCalculatedPenaltyStatus()) {
@@ -1356,11 +1395,13 @@ class Rank {
1356
1395
  problemStatistics.lastSubmitTimestamp = s.timestampToSecond;
1357
1396
  problemStatistics.totalCount++;
1358
1397
  if (s.isAccepted()) {
1398
+ s.isSolved = true;
1359
1399
  problemStatistics.isSolved = true;
1360
1400
  problemStatistics.solvedTimestamp = s.timestampToSecond;
1361
1401
  problem.statistics.acceptedNum++;
1362
1402
  problem.statistics.attemptedNum += problemStatistics.failedCount + 1;
1363
1403
  if (problem.statistics.firstSolveSubmissions.length === 0 || problem.statistics.firstSolveSubmissions[problem.statistics.firstSolveSubmissions.length - 1].timestamp === s.timestamp) {
1404
+ s.isFirstSolved = true;
1364
1405
  problemStatistics.isFirstSolved = true;
1365
1406
  problem.statistics.firstSolveSubmissions.push(s);
1366
1407
  }
@@ -1486,6 +1527,7 @@ class Rank {
1486
1527
  const gold = new Award();
1487
1528
  const silver = new Award();
1488
1529
  const bronze = new Award();
1530
+ const honorable = new Award();
1489
1531
  {
1490
1532
  gold.medalType = MedalType.GOLD;
1491
1533
  gold.minRank = 1;
@@ -1510,6 +1552,18 @@ class Rank {
1510
1552
  award.push(bronze);
1511
1553
  }
1512
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
+ }
1513
1567
  this.contest.awards.set("official", award);
1514
1568
  }
1515
1569
  }
@@ -1523,12 +1577,22 @@ class Rank {
1523
1577
  return false;
1524
1578
  }
1525
1579
  getSubmissions() {
1526
- if (this.options.enableFilterSubmissionsByTimestamp === false) {
1580
+ if (this.contest.replayContestStartTimestamp === void 0 && this.options.enableFilterSubmissionsByTimestamp === false) {
1527
1581
  return this.submissions;
1528
1582
  }
1529
- return this.submissions.filter(
1530
- (s) => s.timestampToSecond <= this.options.timestamp
1531
- ).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
+ });
1532
1596
  }
1533
1597
  buildBalloons() {
1534
1598
  this.balloons = [];
@@ -1560,6 +1624,9 @@ class Rank {
1560
1624
  })();
1561
1625
  }
1562
1626
  }
1627
+ setReplayTime(replayStartTimestamp) {
1628
+ this.contest.setReplayTime(replayStartTimestamp);
1629
+ }
1563
1630
  }
1564
1631
 
1565
1632
  class ResolverOperation {
@@ -1650,4 +1717,4 @@ class Resolver extends Rank {
1650
1717
  }
1651
1718
  }
1652
1719
 
1653
- export { Award, Balloon, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, MedalType, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Resolver, Submission, Team, TeamProblemStatistics, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
1720
+ export { Award, Balloon, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, MedalType, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Resolver, Submission, Team, TeamProblemStatistics, calcDirt, createContest, createContestIndex, createContestIndexList, createDayJS, createProblem, createProblems, createProblemsByProblemIds, createSubmission, createSubmissions, createTeam, createTeams, getImageSource, getTimeDiff, getTimestamp, getWhiteOrBlackColor, getWhiteOrBlackColorV1, isAccepted, isNotCalculatedPenaltyStatus, isPending, isRejected, isValidMedalType, stringToSubmissionStatus };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcpcio/core",
3
- "version": "0.35.0",
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.0"
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 = "";
@@ -1,7 +1,7 @@
1
- import { furthest } from "color-diff";
2
1
  import chroma from "chroma-js";
2
+ import { furthest } from "color-diff";
3
3
 
4
- export function getWhiteOrBlackColor(background: string) {
4
+ export function getWhiteOrBlackColorV1(background: string) {
5
5
  const [R, G, B] = chroma(background).rgb();
6
6
  const color = { R, G, B };
7
7
  const palette = [
@@ -17,3 +17,12 @@ export function getWhiteOrBlackColor(background: string) {
17
17
  return "#fff";
18
18
  }
19
19
  }
20
+
21
+ export function getWhiteOrBlackColor(background: string) {
22
+ const [R, G, B] = chroma(background).rgb();
23
+
24
+ const brightness = (R * 299 + G * 587 + B * 114) / 1000;
25
+ const threshold = 148;
26
+
27
+ return brightness <= threshold ? "#fff" : "#000";
28
+ }