@xcpcio/core 0.45.0 → 0.46.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
@@ -235,7 +235,7 @@ class CodeforcesGymGhostDATConverter {
235
235
  if (team.members) {
236
236
  name = `${name} - ${team.membersToString}`;
237
237
  }
238
- res += `@t ${teamIndex},0,1,${name}
238
+ res += `@t ${teamIndex},0,1,"${name}"
239
239
  `;
240
240
  teamIdMap.set(team.id, teamIndex);
241
241
  teamIndex++;
@@ -248,7 +248,7 @@ class CodeforcesGymGhostDATConverter {
248
248
  }
249
249
  });
250
250
  for (let i = 0; i < 100; i++) {
251
- res += `@t ${teamIndex},0,1,\u041F\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443
251
+ res += `@t ${teamIndex},0,1,"\u041F\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443"
252
252
  `;
253
253
  teamIndex++;
254
254
  }
@@ -576,6 +576,233 @@ function getWhiteOrBlackColor(background) {
576
576
  return brightness <= threshold ? "#fff" : "#000";
577
577
  }
578
578
 
579
+ class RatingHistory {
580
+ constructor() {
581
+ this.rank = 0;
582
+ this.rating = 0;
583
+ this.contestID = "";
584
+ this.contestName = "";
585
+ this.contestLink = "";
586
+ this.contestTime = createDayJS();
587
+ }
588
+ toJSON() {
589
+ return {
590
+ rank: this.rank,
591
+ rating: this.rating,
592
+ contestID: this.contestID,
593
+ contestName: this.contestName,
594
+ contestLink: this.contestLink,
595
+ contestTime: this.contestTime.toDate()
596
+ };
597
+ }
598
+ static fromJSON(iRatingHistory) {
599
+ if (typeof iRatingHistory === "string") {
600
+ iRatingHistory = JSON.parse(iRatingHistory);
601
+ }
602
+ const ratingHistory = new RatingHistory();
603
+ ratingHistory.rank = iRatingHistory.rank;
604
+ ratingHistory.rating = iRatingHistory.rating;
605
+ ratingHistory.contestID = iRatingHistory.contestID;
606
+ ratingHistory.contestName = iRatingHistory.contestName;
607
+ ratingHistory.contestLink = iRatingHistory.contestLink;
608
+ ratingHistory.contestTime = createDayJS(iRatingHistory.contestTime);
609
+ return ratingHistory;
610
+ }
611
+ }
612
+
613
+ class RatingUser {
614
+ constructor() {
615
+ this.id = "";
616
+ this.name = "";
617
+ this.rating = 0;
618
+ this.minRating = 1061109567;
619
+ this.maxRating = -1061109567;
620
+ this.rank = 0;
621
+ this.oldRating = 0;
622
+ this.seed = 1;
623
+ this.delta = 0;
624
+ this.ratingHistories = [];
625
+ }
626
+ UpdateRating(rating) {
627
+ this.rating = rating;
628
+ this.minRating = Math.min(this.minRating, rating);
629
+ this.maxRating = Math.max(this.maxRating, rating);
630
+ }
631
+ toJSON() {
632
+ return {
633
+ id: this.id,
634
+ name: this.name,
635
+ rating: this.rating,
636
+ minRating: this.minRating,
637
+ maxRating: this.maxRating,
638
+ ratingHistories: this.ratingHistories.map((ratingHistory) => ratingHistory.toJSON())
639
+ };
640
+ }
641
+ static fromJSON(iRatingUser) {
642
+ if (typeof iRatingUser === "string") {
643
+ iRatingUser = JSON.parse(iRatingUser);
644
+ }
645
+ const ratingUser = new RatingUser();
646
+ ratingUser.id = iRatingUser.id;
647
+ ratingUser.name = iRatingUser.name;
648
+ ratingUser.rating = iRatingUser.rating;
649
+ ratingUser.minRating = iRatingUser.minRating;
650
+ ratingUser.maxRating = iRatingUser.maxRating;
651
+ for (const iRatingHistory of iRatingUser.ratingHistories) {
652
+ ratingUser.ratingHistories.push(RatingHistory.fromJSON(iRatingHistory));
653
+ }
654
+ return ratingUser;
655
+ }
656
+ }
657
+
658
+ class RatingCalculator {
659
+ constructor() {
660
+ this.users = [];
661
+ }
662
+ calculate() {
663
+ this.calculateInternal();
664
+ }
665
+ calcP(userA, userB) {
666
+ return 1 / (1 + 10 ** ((userB.oldRating - userA.oldRating) / 400));
667
+ }
668
+ getExSeed(users, rating, ownUser) {
669
+ const exUser = new RatingUser();
670
+ exUser.oldRating = rating;
671
+ let res = 0;
672
+ users.forEach((user) => {
673
+ if (user.id !== ownUser.id) {
674
+ res += this.calcP(user, exUser);
675
+ }
676
+ });
677
+ return res;
678
+ }
679
+ calcRating(users, rank, user) {
680
+ let left = 1;
681
+ let right = 8e3;
682
+ while (right - left > 1) {
683
+ const mid = Math.floor((left + right) / 2);
684
+ if (this.getExSeed(users, mid, user) < rank) {
685
+ right = mid;
686
+ } else {
687
+ left = mid;
688
+ }
689
+ }
690
+ return left;
691
+ }
692
+ calculateInternal() {
693
+ for (let i = 0; i < this.users.length; i++) {
694
+ const u = this.users[i];
695
+ u.seed = 1;
696
+ for (let j = 0; j < this.users.length; j++) {
697
+ if (i !== j) {
698
+ const otherUser = this.users[j];
699
+ u.seed += this.calcP(otherUser, u);
700
+ }
701
+ }
702
+ }
703
+ let sumDelta = 0;
704
+ for (let i = 0; i < this.users.length; i++) {
705
+ const u = this.users[i];
706
+ u.delta = Math.floor(
707
+ (this.calcRating(this.users, Math.sqrt(u.rank * u.seed), u) - u.oldRating) / 2
708
+ );
709
+ sumDelta += u.delta;
710
+ }
711
+ let inc = Math.floor(-sumDelta / this.users.length) - 1;
712
+ for (let i = 0; i < this.users.length; i++) {
713
+ const u = this.users[i];
714
+ u.delta += inc;
715
+ }
716
+ this.users = this.users.sort((a, b) => b.oldRating - a.oldRating);
717
+ const s = Math.min(this.users.length, Math.floor(4 * Math.round(Math.sqrt(this.users.length))));
718
+ let sumS = 0;
719
+ for (let i = 0; i < s; i++) {
720
+ sumS += this.users[i].delta;
721
+ }
722
+ inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);
723
+ this.users.forEach((u) => {
724
+ u.delta += inc;
725
+ u.UpdateRating(u.oldRating + u.delta);
726
+ });
727
+ this.users = this.users.sort((a, b) => a.rank - b.rank);
728
+ }
729
+ }
730
+
731
+ class Rating {
732
+ constructor() {
733
+ this.id = "";
734
+ this.name = "";
735
+ this.baseRating = 1500;
736
+ this.contestIDs = [];
737
+ this.users = [];
738
+ this.ranks = [];
739
+ this.userMap = /* @__PURE__ */ new Map();
740
+ }
741
+ buildRating() {
742
+ for (const rank of this.ranks) {
743
+ rank.buildRank();
744
+ for (const u of this.users) {
745
+ u.oldRating = u.rating;
746
+ }
747
+ for (const t of rank.teams) {
748
+ const id = this.generateTeamId(t);
749
+ if (!this.userMap.has(id)) {
750
+ const u = new RatingUser();
751
+ u.id = id;
752
+ u.name = t.name;
753
+ u.rank = t.rank;
754
+ u.oldRating = this.baseRating;
755
+ u.UpdateRating(this.baseRating);
756
+ this.userMap.set(id, u);
757
+ this.users.push(u);
758
+ }
759
+ }
760
+ const ratingCalculator = new RatingCalculator();
761
+ ratingCalculator.users = this.users;
762
+ ratingCalculator.calculate();
763
+ for (const u of this.users) {
764
+ const h = new RatingHistory();
765
+ h.rank = u.rank;
766
+ h.rating = u.rating;
767
+ h.contestID = rank.contest.id;
768
+ h.contestLink = h.contestID;
769
+ h.contestName = rank.contest.name;
770
+ h.contestTime = rank.contest.startTime;
771
+ u.ratingHistories.push(h);
772
+ }
773
+ }
774
+ }
775
+ generateTeamId(t) {
776
+ if (Array.isArray(t.members) && t.members.length > 0) {
777
+ return t.members.join("|");
778
+ }
779
+ return `${t.organization}-${t.name}`;
780
+ }
781
+ toJSON() {
782
+ return {
783
+ id: this.id,
784
+ name: this.name,
785
+ baseRating: this.baseRating,
786
+ contestIDs: this.contestIDs,
787
+ users: this.users.map((ratingUser) => ratingUser.toJSON())
788
+ };
789
+ }
790
+ static fromJSON(iRating) {
791
+ if (typeof iRating === "string") {
792
+ iRating = JSON.parse(iRating);
793
+ }
794
+ const rating = new Rating();
795
+ rating.id = iRating.id;
796
+ rating.name = iRating.name;
797
+ rating.baseRating = iRating.baseRating;
798
+ rating.contestIDs = iRating.contestIDs;
799
+ for (const iUser of iRating.users) {
800
+ rating.users.push(RatingUser.fromJSON(iUser));
801
+ }
802
+ return rating;
803
+ }
804
+ }
805
+
579
806
  class ProblemStatistics {
580
807
  constructor() {
581
808
  this.acceptedNum = 0;
@@ -1183,6 +1410,7 @@ function createContestOptions(contestOptionsJSON = {}) {
1183
1410
 
1184
1411
  class Contest {
1185
1412
  constructor() {
1413
+ this.id = "";
1186
1414
  this.name = "";
1187
1415
  this.startTime = createDayJS();
1188
1416
  this.endTime = createDayJS();
@@ -2042,6 +2270,10 @@ exports.ProblemStatistics = ProblemStatistics;
2042
2270
  exports.Rank = Rank;
2043
2271
  exports.RankOptions = RankOptions;
2044
2272
  exports.RankStatistics = RankStatistics;
2273
+ exports.Rating = Rating;
2274
+ exports.RatingCalculator = RatingCalculator;
2275
+ exports.RatingHistory = RatingHistory;
2276
+ exports.RatingUser = RatingUser;
2045
2277
  exports.Resolver = Resolver;
2046
2278
  exports.Submission = Submission;
2047
2279
  exports.Team = Team;
package/dist/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
- import { TimeUnit, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, Lang, CalculationOfPenalty, StatusTimeDisplay, MedalPreset, Image, BannerMode, ContestState, Contest as Contest$1, Team as Team$1, Teams as Teams$1, ContestIndex as ContestIndex$1 } from '@xcpcio/types';
1
+ import { TimeUnit, SubmissionStatus, Submission as Submission$1, Submissions as Submissions$1, BalloonColor, Problem as Problem$1, Problems as Problems$1, Lang, CalculationOfPenalty, StatusTimeDisplay, MedalPreset, Image, BannerMode, ContestState, Contest as Contest$1, Team as Team$1, Teams as Teams$1, IRatingHistory, ContestIndex as ContestIndex$1 } from '@xcpcio/types';
2
2
  import dayjs from 'dayjs';
3
3
  export { default as dayjs } from 'dayjs';
4
4
  import * as XLSX from 'xlsx-js-style';
5
+ import { IRatingUser, IRating } from '@xcpcio/types/index';
5
6
 
6
7
  declare class Submission {
7
8
  id: string;
@@ -126,6 +127,7 @@ declare class ContestOptions {
126
127
  }
127
128
 
128
129
  declare class Contest {
130
+ id: string;
129
131
  name: string;
130
132
  startTime: dayjs.Dayjs;
131
133
  endTime: dayjs.Dayjs;
@@ -331,6 +333,7 @@ declare class Rank {
331
333
  buildBalloons(): void;
332
334
  setReplayTime(replayStartTimestamp: number): void;
333
335
  }
336
+ type Ranks = Array<Rank>;
334
337
 
335
338
  declare class CodeforcesGymGhostDATConverter {
336
339
  constructor();
@@ -352,6 +355,63 @@ declare class ICPCStandingsCsvConverter {
352
355
  private getMedalCitation;
353
356
  }
354
357
 
358
+ declare class RatingHistory {
359
+ rank: number;
360
+ rating: number;
361
+ contestID: string;
362
+ contestName: string;
363
+ contestLink: string;
364
+ contestTime: dayjs.Dayjs;
365
+ constructor();
366
+ toJSON(): IRatingHistory;
367
+ static fromJSON(iRatingHistory: IRatingHistory | string): RatingHistory;
368
+ }
369
+ type RatingHistories = Array<RatingHistory>;
370
+
371
+ declare class RatingUser {
372
+ id: string;
373
+ name: string;
374
+ rating: number;
375
+ minRating: number;
376
+ maxRating: number;
377
+ rank: number;
378
+ oldRating: number;
379
+ seed: number;
380
+ delta: number;
381
+ ratingHistories: RatingHistories;
382
+ constructor();
383
+ UpdateRating(rating: number): void;
384
+ toJSON(): IRatingUser;
385
+ static fromJSON(iRatingUser: IRatingUser | string): RatingUser;
386
+ }
387
+ type RatingUsers = Array<RatingUser>;
388
+ type RatingUserMap = Map<string, RatingUser>;
389
+
390
+ declare class RatingCalculator {
391
+ users: RatingUsers;
392
+ constructor();
393
+ calculate(): void;
394
+ private calcP;
395
+ private getExSeed;
396
+ private calcRating;
397
+ private calculateInternal;
398
+ }
399
+
400
+ declare class Rating {
401
+ id: string;
402
+ name: string;
403
+ baseRating: number;
404
+ contestIDs: string[];
405
+ users: RatingUsers;
406
+ ranks: Ranks;
407
+ userMap: RatingUserMap;
408
+ constructor();
409
+ buildRating(): void;
410
+ generateTeamId(t: Team): string;
411
+ toJSON(): IRating;
412
+ static fromJSON(iRating: IRating | string): Rating;
413
+ }
414
+
355
415
  declare class ContestIndexConfig {
356
416
  contestName: string;
357
417
  startTime: dayjs.Dayjs;
@@ -398,4 +458,4 @@ declare function isRejected(status: SubmissionStatus): boolean;
398
458
  declare function isPending(status: SubmissionStatus): boolean;
399
459
  declare function isNotCalculatedPenaltyStatus(status: SubmissionStatus): boolean;
400
460
 
401
- export { Award, Awards, Balloon, Balloons, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestIndexList, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, 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 };
461
+ export { Award, Awards, Balloon, Balloons, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestIndexList, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, PlaceChartPointData, Problem, ProblemStatistics, Problems, Rank, RankOptions, RankStatistics, Ranks, Rating, RatingCalculator, RatingHistories, RatingHistory, RatingUser, RatingUserMap, RatingUsers, 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
@@ -202,7 +202,7 @@ class CodeforcesGymGhostDATConverter {
202
202
  if (team.members) {
203
203
  name = `${name} - ${team.membersToString}`;
204
204
  }
205
- res += `@t ${teamIndex},0,1,${name}
205
+ res += `@t ${teamIndex},0,1,"${name}"
206
206
  `;
207
207
  teamIdMap.set(team.id, teamIndex);
208
208
  teamIndex++;
@@ -215,7 +215,7 @@ class CodeforcesGymGhostDATConverter {
215
215
  }
216
216
  });
217
217
  for (let i = 0; i < 100; i++) {
218
- res += `@t ${teamIndex},0,1,\u041F\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443
218
+ res += `@t ${teamIndex},0,1,"\u041F\u043E\u043F\u043E\u043B\u043D\u0438\u0442\u044C \u043A\u043E\u043C\u0430\u043D\u0434\u0443"
219
219
  `;
220
220
  teamIndex++;
221
221
  }
@@ -543,6 +543,233 @@ function getWhiteOrBlackColor(background) {
543
543
  return brightness <= threshold ? "#fff" : "#000";
544
544
  }
545
545
 
546
+ class RatingHistory {
547
+ constructor() {
548
+ this.rank = 0;
549
+ this.rating = 0;
550
+ this.contestID = "";
551
+ this.contestName = "";
552
+ this.contestLink = "";
553
+ this.contestTime = createDayJS();
554
+ }
555
+ toJSON() {
556
+ return {
557
+ rank: this.rank,
558
+ rating: this.rating,
559
+ contestID: this.contestID,
560
+ contestName: this.contestName,
561
+ contestLink: this.contestLink,
562
+ contestTime: this.contestTime.toDate()
563
+ };
564
+ }
565
+ static fromJSON(iRatingHistory) {
566
+ if (typeof iRatingHistory === "string") {
567
+ iRatingHistory = JSON.parse(iRatingHistory);
568
+ }
569
+ const ratingHistory = new RatingHistory();
570
+ ratingHistory.rank = iRatingHistory.rank;
571
+ ratingHistory.rating = iRatingHistory.rating;
572
+ ratingHistory.contestID = iRatingHistory.contestID;
573
+ ratingHistory.contestName = iRatingHistory.contestName;
574
+ ratingHistory.contestLink = iRatingHistory.contestLink;
575
+ ratingHistory.contestTime = createDayJS(iRatingHistory.contestTime);
576
+ return ratingHistory;
577
+ }
578
+ }
579
+
580
+ class RatingUser {
581
+ constructor() {
582
+ this.id = "";
583
+ this.name = "";
584
+ this.rating = 0;
585
+ this.minRating = 1061109567;
586
+ this.maxRating = -1061109567;
587
+ this.rank = 0;
588
+ this.oldRating = 0;
589
+ this.seed = 1;
590
+ this.delta = 0;
591
+ this.ratingHistories = [];
592
+ }
593
+ UpdateRating(rating) {
594
+ this.rating = rating;
595
+ this.minRating = Math.min(this.minRating, rating);
596
+ this.maxRating = Math.max(this.maxRating, rating);
597
+ }
598
+ toJSON() {
599
+ return {
600
+ id: this.id,
601
+ name: this.name,
602
+ rating: this.rating,
603
+ minRating: this.minRating,
604
+ maxRating: this.maxRating,
605
+ ratingHistories: this.ratingHistories.map((ratingHistory) => ratingHistory.toJSON())
606
+ };
607
+ }
608
+ static fromJSON(iRatingUser) {
609
+ if (typeof iRatingUser === "string") {
610
+ iRatingUser = JSON.parse(iRatingUser);
611
+ }
612
+ const ratingUser = new RatingUser();
613
+ ratingUser.id = iRatingUser.id;
614
+ ratingUser.name = iRatingUser.name;
615
+ ratingUser.rating = iRatingUser.rating;
616
+ ratingUser.minRating = iRatingUser.minRating;
617
+ ratingUser.maxRating = iRatingUser.maxRating;
618
+ for (const iRatingHistory of iRatingUser.ratingHistories) {
619
+ ratingUser.ratingHistories.push(RatingHistory.fromJSON(iRatingHistory));
620
+ }
621
+ return ratingUser;
622
+ }
623
+ }
624
+
625
+ class RatingCalculator {
626
+ constructor() {
627
+ this.users = [];
628
+ }
629
+ calculate() {
630
+ this.calculateInternal();
631
+ }
632
+ calcP(userA, userB) {
633
+ return 1 / (1 + 10 ** ((userB.oldRating - userA.oldRating) / 400));
634
+ }
635
+ getExSeed(users, rating, ownUser) {
636
+ const exUser = new RatingUser();
637
+ exUser.oldRating = rating;
638
+ let res = 0;
639
+ users.forEach((user) => {
640
+ if (user.id !== ownUser.id) {
641
+ res += this.calcP(user, exUser);
642
+ }
643
+ });
644
+ return res;
645
+ }
646
+ calcRating(users, rank, user) {
647
+ let left = 1;
648
+ let right = 8e3;
649
+ while (right - left > 1) {
650
+ const mid = Math.floor((left + right) / 2);
651
+ if (this.getExSeed(users, mid, user) < rank) {
652
+ right = mid;
653
+ } else {
654
+ left = mid;
655
+ }
656
+ }
657
+ return left;
658
+ }
659
+ calculateInternal() {
660
+ for (let i = 0; i < this.users.length; i++) {
661
+ const u = this.users[i];
662
+ u.seed = 1;
663
+ for (let j = 0; j < this.users.length; j++) {
664
+ if (i !== j) {
665
+ const otherUser = this.users[j];
666
+ u.seed += this.calcP(otherUser, u);
667
+ }
668
+ }
669
+ }
670
+ let sumDelta = 0;
671
+ for (let i = 0; i < this.users.length; i++) {
672
+ const u = this.users[i];
673
+ u.delta = Math.floor(
674
+ (this.calcRating(this.users, Math.sqrt(u.rank * u.seed), u) - u.oldRating) / 2
675
+ );
676
+ sumDelta += u.delta;
677
+ }
678
+ let inc = Math.floor(-sumDelta / this.users.length) - 1;
679
+ for (let i = 0; i < this.users.length; i++) {
680
+ const u = this.users[i];
681
+ u.delta += inc;
682
+ }
683
+ this.users = this.users.sort((a, b) => b.oldRating - a.oldRating);
684
+ const s = Math.min(this.users.length, Math.floor(4 * Math.round(Math.sqrt(this.users.length))));
685
+ let sumS = 0;
686
+ for (let i = 0; i < s; i++) {
687
+ sumS += this.users[i].delta;
688
+ }
689
+ inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);
690
+ this.users.forEach((u) => {
691
+ u.delta += inc;
692
+ u.UpdateRating(u.oldRating + u.delta);
693
+ });
694
+ this.users = this.users.sort((a, b) => a.rank - b.rank);
695
+ }
696
+ }
697
+
698
+ class Rating {
699
+ constructor() {
700
+ this.id = "";
701
+ this.name = "";
702
+ this.baseRating = 1500;
703
+ this.contestIDs = [];
704
+ this.users = [];
705
+ this.ranks = [];
706
+ this.userMap = /* @__PURE__ */ new Map();
707
+ }
708
+ buildRating() {
709
+ for (const rank of this.ranks) {
710
+ rank.buildRank();
711
+ for (const u of this.users) {
712
+ u.oldRating = u.rating;
713
+ }
714
+ for (const t of rank.teams) {
715
+ const id = this.generateTeamId(t);
716
+ if (!this.userMap.has(id)) {
717
+ const u = new RatingUser();
718
+ u.id = id;
719
+ u.name = t.name;
720
+ u.rank = t.rank;
721
+ u.oldRating = this.baseRating;
722
+ u.UpdateRating(this.baseRating);
723
+ this.userMap.set(id, u);
724
+ this.users.push(u);
725
+ }
726
+ }
727
+ const ratingCalculator = new RatingCalculator();
728
+ ratingCalculator.users = this.users;
729
+ ratingCalculator.calculate();
730
+ for (const u of this.users) {
731
+ const h = new RatingHistory();
732
+ h.rank = u.rank;
733
+ h.rating = u.rating;
734
+ h.contestID = rank.contest.id;
735
+ h.contestLink = h.contestID;
736
+ h.contestName = rank.contest.name;
737
+ h.contestTime = rank.contest.startTime;
738
+ u.ratingHistories.push(h);
739
+ }
740
+ }
741
+ }
742
+ generateTeamId(t) {
743
+ if (Array.isArray(t.members) && t.members.length > 0) {
744
+ return t.members.join("|");
745
+ }
746
+ return `${t.organization}-${t.name}`;
747
+ }
748
+ toJSON() {
749
+ return {
750
+ id: this.id,
751
+ name: this.name,
752
+ baseRating: this.baseRating,
753
+ contestIDs: this.contestIDs,
754
+ users: this.users.map((ratingUser) => ratingUser.toJSON())
755
+ };
756
+ }
757
+ static fromJSON(iRating) {
758
+ if (typeof iRating === "string") {
759
+ iRating = JSON.parse(iRating);
760
+ }
761
+ const rating = new Rating();
762
+ rating.id = iRating.id;
763
+ rating.name = iRating.name;
764
+ rating.baseRating = iRating.baseRating;
765
+ rating.contestIDs = iRating.contestIDs;
766
+ for (const iUser of iRating.users) {
767
+ rating.users.push(RatingUser.fromJSON(iUser));
768
+ }
769
+ return rating;
770
+ }
771
+ }
772
+
546
773
  class ProblemStatistics {
547
774
  constructor() {
548
775
  this.acceptedNum = 0;
@@ -1150,6 +1377,7 @@ function createContestOptions(contestOptionsJSON = {}) {
1150
1377
 
1151
1378
  class Contest {
1152
1379
  constructor() {
1380
+ this.id = "";
1153
1381
  this.name = "";
1154
1382
  this.startTime = createDayJS();
1155
1383
  this.endTime = createDayJS();
@@ -1989,4 +2217,4 @@ class Resolver extends Rank {
1989
2217
  }
1990
2218
  }
1991
2219
 
1992
- export { Award, Balloon, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, 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 };
2220
+ export { Award, Balloon, BattleOfGiants, CodeforcesGymGhostDATConverter, Contest, ContestIndex, ContestIndexConfig, ContestOptions, GeneralExcelConverter, Giants, GiantsType, ICPCStandingsCsvConverter, MedalType, PlaceChartPointData, Problem, ProblemStatistics, Rank, RankOptions, RankStatistics, Rating, RatingCalculator, RatingHistory, RatingUser, 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.45.0",
3
+ "version": "0.46.0",
4
4
  "description": "XCPCIO Core",
5
5
  "author": "Dup4 <lyuzhi.pan@gmail.com>",
6
6
  "license": "MIT",
@@ -49,7 +49,7 @@
49
49
  "papaparse": "^5.4.1",
50
50
  "string-width": "^6.1.0",
51
51
  "xlsx-js-style": "^1.2.0",
52
- "@xcpcio/types": "0.45.0"
52
+ "@xcpcio/types": "0.46.0"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@babel/types": "^7.22.4",
package/src/contest.ts CHANGED
@@ -10,6 +10,7 @@ import { type Awards, MedalType } from "./award";
10
10
  import { ContestOptions, createContestOptions } from "./contest-options";
11
11
 
12
12
  export class Contest {
13
+ id = "";
13
14
  name = "";
14
15
 
15
16
  startTime: dayjs.Dayjs;
package/src/export/cf.ts CHANGED
@@ -42,7 +42,7 @@ export class CodeforcesGymGhostDATConverter {
42
42
  name = `${name} - ${team.membersToString}`;
43
43
  }
44
44
 
45
- res += `@t ${teamIndex},0,1,${name}\n`;
45
+ res += `@t ${teamIndex},0,1,"${name}"\n`;
46
46
  teamIdMap.set(team.id, teamIndex);
47
47
  teamIndex++;
48
48
 
@@ -56,7 +56,7 @@ export class CodeforcesGymGhostDATConverter {
56
56
  });
57
57
 
58
58
  for (let i = 0; i < 100; i++) {
59
- res += `@t ${teamIndex},0,1,Пополнить команду\n`;
59
+ res += `@t ${teamIndex},0,1,"Пополнить команду"\n`;
60
60
  teamIndex++;
61
61
  }
62
62
 
package/src/index.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  export * from "./export";
2
+ export * from "./rating";
2
3
  export * from "./utils";
3
4
 
4
5
  export * from "./award";
package/src/rank.ts CHANGED
@@ -608,3 +608,5 @@ export class Rank {
608
608
  this.contest.setReplayTime(replayStartTimestamp);
609
609
  }
610
610
  }
611
+
612
+ export type Ranks = Array<Rank>;
@@ -0,0 +1,4 @@
1
+ export * from "./rating-calculator";
2
+ export * from "./rating-history";
3
+ export * from "./rating-user";
4
+ export * from "./rating";
@@ -0,0 +1,100 @@
1
+ import type { RatingUsers } from "./rating-user";
2
+ import { RatingUser } from "./rating-user";
3
+
4
+ // https://www.wikiwand.com/en/Elo_rating_system
5
+ export class RatingCalculator {
6
+ users: RatingUsers;
7
+
8
+ constructor() {
9
+ this.users = [];
10
+ }
11
+
12
+ calculate() {
13
+ this.calculateInternal();
14
+ }
15
+
16
+ private calcP(userA: RatingUser, userB: RatingUser) {
17
+ return 1.0 / (1.0 + 10 ** ((userB.oldRating - userA.oldRating) / 400.0));
18
+ }
19
+
20
+ private getExSeed(users: RatingUsers, rating: number, ownUser: RatingUser) {
21
+ const exUser = new RatingUser();
22
+ exUser.oldRating = rating;
23
+
24
+ let res = 0;
25
+
26
+ users.forEach((user) => {
27
+ if (user.id !== ownUser.id) {
28
+ res += this.calcP(user, exUser);
29
+ }
30
+ });
31
+
32
+ return res;
33
+ }
34
+
35
+ private calcRating(users: RatingUsers, rank: number, user: RatingUser) {
36
+ let left = 1;
37
+ let right = 8000;
38
+
39
+ while (right - left > 1) {
40
+ const mid = Math.floor((left + right) / 2);
41
+ if (this.getExSeed(users, mid, user) < rank) {
42
+ right = mid;
43
+ } else {
44
+ left = mid;
45
+ }
46
+ }
47
+
48
+ return left;
49
+ }
50
+
51
+ private calculateInternal() {
52
+ // Calculate seed
53
+ for (let i = 0; i < this.users.length; i++) {
54
+ const u = this.users[i];
55
+ u.seed = 1.0;
56
+ for (let j = 0; j < this.users.length; j++) {
57
+ if (i !== j) {
58
+ const otherUser = this.users[j];
59
+ u.seed += this.calcP(otherUser, u);
60
+ }
61
+ }
62
+ }
63
+
64
+ // Calculate initial delta and sumDelta
65
+ let sumDelta = 0;
66
+ for (let i = 0; i < this.users.length; i++) {
67
+ const u = this.users[i];
68
+ u.delta = Math.floor(
69
+ (this.calcRating(this.users, Math.sqrt(u.rank * u.seed), u)
70
+ - u.oldRating)
71
+ / 2,
72
+ );
73
+ sumDelta += u.delta;
74
+ }
75
+
76
+ // Calculate first inc
77
+ let inc = Math.floor(-sumDelta / this.users.length) - 1;
78
+ for (let i = 0; i < this.users.length; i++) {
79
+ const u = this.users[i];
80
+ u.delta += inc;
81
+ }
82
+
83
+ // Calculate second inc
84
+ this.users = this.users.sort((a, b) => b.oldRating - a.oldRating);
85
+ const s = Math.min(this.users.length, Math.floor(4 * Math.round(Math.sqrt(this.users.length))));
86
+ let sumS = 0;
87
+ for (let i = 0; i < s; i++) {
88
+ sumS += this.users[i].delta;
89
+ }
90
+ inc = Math.min(Math.max(Math.floor(-sumS / s), -10), 0);
91
+
92
+ // Calculate new rating
93
+ this.users.forEach((u) => {
94
+ u.delta += inc;
95
+ u.UpdateRating(u.oldRating + u.delta);
96
+ });
97
+
98
+ this.users = this.users.sort((a, b) => a.rank - b.rank);
99
+ }
100
+ }
@@ -0,0 +1,55 @@
1
+ import type { IRatingHistory } from "@xcpcio/types";
2
+
3
+ import type { dayjs } from "../utils";
4
+ import { createDayJS } from "../utils";
5
+
6
+ export class RatingHistory {
7
+ rank: number;
8
+ rating: number;
9
+
10
+ contestID: string;
11
+ contestName: string;
12
+ contestLink: string;
13
+ contestTime: dayjs.Dayjs;
14
+
15
+ constructor() {
16
+ this.rank = 0;
17
+ this.rating = 0;
18
+
19
+ this.contestID = "";
20
+ this.contestName = "";
21
+ this.contestLink = "";
22
+ this.contestTime = createDayJS();
23
+ }
24
+
25
+ toJSON(): IRatingHistory {
26
+ return {
27
+ rank: this.rank,
28
+ rating: this.rating,
29
+
30
+ contestID: this.contestID,
31
+ contestName: this.contestName,
32
+ contestLink: this.contestLink,
33
+ contestTime: this.contestTime.toDate(),
34
+ };
35
+ }
36
+
37
+ static fromJSON(iRatingHistory: IRatingHistory | string): RatingHistory {
38
+ if (typeof iRatingHistory === "string") {
39
+ iRatingHistory = JSON.parse(iRatingHistory) as IRatingHistory;
40
+ }
41
+
42
+ const ratingHistory = new RatingHistory();
43
+ ratingHistory.rank = iRatingHistory.rank;
44
+ ratingHistory.rating = iRatingHistory.rating;
45
+
46
+ ratingHistory.contestID = iRatingHistory.contestID;
47
+ ratingHistory.contestName = iRatingHistory.contestName;
48
+ ratingHistory.contestLink = iRatingHistory.contestLink;
49
+ ratingHistory.contestTime = createDayJS(iRatingHistory.contestTime);
50
+
51
+ return ratingHistory;
52
+ }
53
+ }
54
+
55
+ export type RatingHistories = Array<RatingHistory>;
@@ -0,0 +1,79 @@
1
+ import type { IRatingUser } from "@xcpcio/types/index";
2
+ import { type RatingHistories, RatingHistory } from "./rating-history";
3
+
4
+ export class RatingUser {
5
+ id: string;
6
+ name: string;
7
+
8
+ rating: number;
9
+ minRating: number;
10
+ maxRating: number;
11
+
12
+ rank: number;
13
+ oldRating: number;
14
+
15
+ seed: number;
16
+ delta: number;
17
+
18
+ ratingHistories: RatingHistories;
19
+
20
+ constructor() {
21
+ this.id = "";
22
+ this.name = "";
23
+
24
+ this.rating = 0;
25
+ this.minRating = 0x3F3F3F3F;
26
+ this.maxRating = -0x3F3F3F3F;
27
+
28
+ this.rank = 0;
29
+ this.oldRating = 0;
30
+
31
+ this.seed = 1.0;
32
+ this.delta = 0;
33
+
34
+ this.ratingHistories = [];
35
+ }
36
+
37
+ UpdateRating(rating: number) {
38
+ this.rating = rating;
39
+ this.minRating = Math.min(this.minRating, rating);
40
+ this.maxRating = Math.max(this.maxRating, rating);
41
+ }
42
+
43
+ toJSON(): IRatingUser {
44
+ return {
45
+ id: this.id,
46
+ name: this.name,
47
+
48
+ rating: this.rating,
49
+ minRating: this.minRating,
50
+ maxRating: this.maxRating,
51
+
52
+ ratingHistories: this.ratingHistories.map(ratingHistory => ratingHistory.toJSON()),
53
+ };
54
+ }
55
+
56
+ static fromJSON(iRatingUser: IRatingUser | string): RatingUser {
57
+ if (typeof iRatingUser === "string") {
58
+ iRatingUser = JSON.parse(iRatingUser) as IRatingUser;
59
+ }
60
+
61
+ const ratingUser = new RatingUser();
62
+
63
+ ratingUser.id = iRatingUser.id;
64
+ ratingUser.name = iRatingUser.name;
65
+
66
+ ratingUser.rating = iRatingUser.rating;
67
+ ratingUser.minRating = iRatingUser.minRating;
68
+ ratingUser.maxRating = iRatingUser.maxRating;
69
+
70
+ for (const iRatingHistory of iRatingUser.ratingHistories) {
71
+ ratingUser.ratingHistories.push(RatingHistory.fromJSON(iRatingHistory));
72
+ }
73
+
74
+ return ratingUser;
75
+ }
76
+ }
77
+
78
+ export type RatingUsers = Array<RatingUser>;
79
+ export type RatingUserMap = Map<string, RatingUser>;
@@ -0,0 +1,114 @@
1
+ import type { IRating } from "@xcpcio/types/index";
2
+ import type { Ranks } from "../rank";
3
+ import type { Team } from "../team";
4
+
5
+ import { RatingCalculator } from "./rating-calculator";
6
+ import { RatingHistory } from "./rating-history";
7
+ import type { RatingUserMap, RatingUsers } from "./rating-user";
8
+ import { RatingUser } from "./rating-user";
9
+
10
+ export class Rating {
11
+ id: string;
12
+ name: string;
13
+ baseRating: number;
14
+
15
+ contestIDs: string[];
16
+ users: RatingUsers;
17
+
18
+ ranks: Ranks;
19
+ userMap: RatingUserMap;
20
+
21
+ constructor() {
22
+ this.id = "";
23
+ this.name = "";
24
+ this.baseRating = 1500;
25
+
26
+ this.contestIDs = [];
27
+ this.users = [];
28
+
29
+ this.ranks = [];
30
+ this.userMap = new Map<string, RatingUser>();
31
+ }
32
+
33
+ buildRating() {
34
+ for (const rank of this.ranks) {
35
+ rank.buildRank();
36
+
37
+ for (const u of this.users) {
38
+ u.oldRating = u.rating;
39
+ }
40
+
41
+ for (const t of rank.teams) {
42
+ const id = this.generateTeamId(t);
43
+
44
+ if (!this.userMap.has(id)) {
45
+ const u = new RatingUser();
46
+ u.id = id;
47
+ u.name = t.name;
48
+ u.rank = t.rank;
49
+ u.oldRating = this.baseRating;
50
+ u.UpdateRating(this.baseRating);
51
+
52
+ this.userMap.set(id, u);
53
+ this.users.push(u);
54
+ }
55
+ }
56
+
57
+ const ratingCalculator = new RatingCalculator();
58
+ ratingCalculator.users = this.users;
59
+ ratingCalculator.calculate();
60
+
61
+ for (const u of this.users) {
62
+ const h = new RatingHistory();
63
+ h.rank = u.rank;
64
+ h.rating = u.rating;
65
+
66
+ h.contestID = rank.contest.id;
67
+ h.contestLink = h.contestID;
68
+ h.contestName = rank.contest.name;
69
+ h.contestTime = rank.contest.startTime;
70
+
71
+ u.ratingHistories.push(h);
72
+ }
73
+ }
74
+ }
75
+
76
+ generateTeamId(t: Team) {
77
+ if (Array.isArray(t.members) && t.members.length > 0) {
78
+ return t.members.join("|");
79
+ }
80
+
81
+ return `${t.organization}-${t.name}`;
82
+ }
83
+
84
+ toJSON(): IRating {
85
+ return {
86
+ id: this.id,
87
+ name: this.name,
88
+ baseRating: this.baseRating,
89
+
90
+ contestIDs: this.contestIDs,
91
+ users: this.users.map(ratingUser => ratingUser.toJSON()),
92
+ };
93
+ }
94
+
95
+ static fromJSON(iRating: IRating | string): Rating {
96
+ if (typeof iRating === "string") {
97
+ iRating = JSON.parse(iRating) as IRating;
98
+ }
99
+
100
+ const rating = new Rating();
101
+
102
+ rating.id = iRating.id;
103
+ rating.name = iRating.name;
104
+ rating.baseRating = iRating.baseRating;
105
+
106
+ rating.contestIDs = iRating.contestIDs;
107
+
108
+ for (const iUser of iRating.users) {
109
+ rating.users.push(RatingUser.fromJSON(iUser));
110
+ }
111
+
112
+ return rating;
113
+ }
114
+ }