@xcpcio/core 0.13.0 → 0.15.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/src/award.ts CHANGED
@@ -1,9 +1,9 @@
1
1
  export enum MedalType {
2
- UNKNOWN,
3
- GOLD,
4
- SILVER,
5
- BRONZE,
6
- HONORABLE,
2
+ UNKNOWN = "Unknown",
3
+ GOLD = "Gold",
4
+ SILVER = "Silver",
5
+ BRONZE = "Bronze",
6
+ HONORABLE = "Honorable",
7
7
  }
8
8
 
9
9
  export class Award {
@@ -18,4 +18,15 @@ export class Award {
18
18
  }
19
19
  }
20
20
 
21
+ export function isValidMedalType(medal: MedalType): boolean {
22
+ const validMedalType = [
23
+ MedalType.GOLD,
24
+ MedalType.SILVER,
25
+ MedalType.BRONZE,
26
+ MedalType.HONORABLE,
27
+ ];
28
+
29
+ return validMedalType.includes(medal);
30
+ }
31
+
21
32
  export type Awards = Map<string, Award[]>;
package/src/contest.ts CHANGED
@@ -136,6 +136,18 @@ export class Contest {
136
136
 
137
137
  return Math.round((pass * 100) / total);
138
138
  }
139
+
140
+ isEnableAwards(group: string): boolean {
141
+ if (!this.awards) {
142
+ return false;
143
+ }
144
+
145
+ if (!this.awards.has(group)) {
146
+ return false;
147
+ }
148
+
149
+ return true;
150
+ }
139
151
  }
140
152
 
141
153
  export function createContest(contestJSON: IContest): Contest {
@@ -0,0 +1,120 @@
1
+ import { SubmissionStatus } from "@xcpcio/types";
2
+
3
+ import type { Rank } from "../rank";
4
+
5
+ import {
6
+ isAccepted,
7
+ isNotCalculatedPenaltyStatus,
8
+ isPending,
9
+ } from "../submission-status";
10
+
11
+ import dayjs from "../utils/dayjs";
12
+
13
+ export class CodeforcesGymGhostDATConverter {
14
+ constructor() {}
15
+
16
+ public convert(rank: Rank): string {
17
+ let res = "";
18
+
19
+ res += `@contest "${rank.contest.name}"
20
+ @contlen ${Math.floor(dayjs.duration(rank.contest.endTime.diff(rank.contest.startTime)).asMinutes())}
21
+ @problems ${rank.contest.problems.length}
22
+ @teams ${rank.teams.length + 100}
23
+ @submissions ${rank.submissions.length}
24
+ `;
25
+
26
+ rank.contest.problems.forEach((p) => {
27
+ res += `@p ${p.label},${p.label},20,0\n`;
28
+ });
29
+
30
+ let teamIndex = 1;
31
+ const teamIdMap = new Map<string, number>();
32
+ const submissionsIdMap = new Map<string, Map<string, number>>();
33
+
34
+ rank.teams.forEach((team) => {
35
+ let name = team.name;
36
+
37
+ if (team.organization) {
38
+ name = `${team.organization} - ${name}`;
39
+ }
40
+
41
+ if (team.members) {
42
+ name = `${name} - ${team.membersToString}`;
43
+ }
44
+
45
+ res += `@t ${teamIndex},0,1,${name}\n`;
46
+ teamIdMap.set(team.id, teamIndex);
47
+ teamIndex++;
48
+
49
+ {
50
+ const mp = new Map<string, number>();
51
+ rank.contest.problems.forEach((p) => {
52
+ mp.set(p.id, 0);
53
+ });
54
+ submissionsIdMap.set(team.id, mp);
55
+ }
56
+ });
57
+
58
+ for (let i = 0; i < 100; i++) {
59
+ res += `@t ${teamIndex},0,1,Пополнить команду\n`;
60
+ teamIndex++;
61
+ }
62
+
63
+ rank.getSubmissions().forEach((submission) => {
64
+ const teamId = submission.teamId;
65
+ const problemId = submission.problemId;
66
+ const problem = rank.contest.problemsMap.get(problemId);
67
+
68
+ if (!problem) {
69
+ return;
70
+ }
71
+
72
+ const status = this.submissionStatusToCodeforcesGymDatStatus(submission.status);
73
+ submissionsIdMap.get(teamId)!.set(problemId, submissionsIdMap.get(teamId)!.get(problemId)! + 1);
74
+
75
+ res += `@s ${teamIdMap.get(teamId)},${problem.label},${submissionsIdMap.get(teamId)?.get(problemId)},${submission.timestamp},${status}\n`;
76
+ });
77
+
78
+ return res;
79
+ }
80
+
81
+ private submissionStatusToCodeforcesGymDatStatus(status: SubmissionStatus): string {
82
+ if (isAccepted(status)) {
83
+ return "OK";
84
+ }
85
+
86
+ if (status === SubmissionStatus.WRONG_ANSWER) {
87
+ return "WA";
88
+ }
89
+
90
+ if (status === SubmissionStatus.TIME_LIMIT_EXCEEDED) {
91
+ return "TL";
92
+ }
93
+
94
+ if (status === SubmissionStatus.MEMORY_LIMIT_EXCEEDED) {
95
+ return "ML";
96
+ }
97
+
98
+ if (status === SubmissionStatus.OUTPUT_LIMIT_EXCEEDED) {
99
+ return "IL";
100
+ }
101
+
102
+ if (status === SubmissionStatus.PRESENTATION_ERROR) {
103
+ return "PE";
104
+ }
105
+
106
+ if (status === SubmissionStatus.RUNTIME_ERROR) {
107
+ return "RT";
108
+ }
109
+
110
+ if (status === SubmissionStatus.COMPILATION_ERROR || isNotCalculatedPenaltyStatus(status)) {
111
+ return "CE";
112
+ }
113
+
114
+ if (isPending(status)) {
115
+ return "PD";
116
+ }
117
+
118
+ return "RJ";
119
+ }
120
+ }
@@ -0,0 +1,184 @@
1
+ import _ from "lodash";
2
+ import * as XLSX from "xlsx-js-style";
3
+ import stringWidth from "string-width";
4
+
5
+ import { isValidMedalType } from "../award";
6
+ import type { Rank } from "../rank";
7
+
8
+ export class GeneralExcelConverter {
9
+ constructor() {}
10
+
11
+ public convert(oriRank: Rank): XLSX.WorkBook {
12
+ const rank = _.cloneDeep(oriRank);
13
+
14
+ rank.options.disableFilterTeamsByGroup();
15
+ rank.options.disableFilterSubmissionByTimestamp();
16
+
17
+ const workbook = XLSX.utils.book_new();
18
+
19
+ for (const [k, v] of rank.contest.group) {
20
+ rank.options.setGroup(k);
21
+ rank.buildRank();
22
+
23
+ const sheet = this.convertToSheet(rank);
24
+ XLSX.utils.book_append_sheet(workbook, sheet, v.names.get(v.defaultLang) as string);
25
+ }
26
+
27
+ return workbook;
28
+ }
29
+
30
+ public convertAndWrite(rank: Rank, filename: string): any {
31
+ return XLSX.writeFile(
32
+ this.convert(rank),
33
+ filename,
34
+ {
35
+ compression: true,
36
+ });
37
+ }
38
+
39
+ private convertToSheet(rank: Rank): XLSX.WorkSheet {
40
+ const aoa = this.convertToAoa(rank);
41
+ const sheet = XLSX.utils.aoa_to_sheet(aoa);
42
+
43
+ const cols = [];
44
+ const head = aoa[1];
45
+ for (let j = 0; j < head.length; j++) {
46
+ let wch = 10;
47
+ for (let i = 1; i < aoa.length; i++) {
48
+ wch = Math.max(wch, stringWidth(aoa[i][j]) + 2);
49
+ }
50
+
51
+ cols.push({
52
+ wch,
53
+ });
54
+ }
55
+
56
+ sheet["!cols"] = cols;
57
+
58
+ {
59
+ const mergeRange = { s: { r: 0, c: 0 }, e: { r: 0, c: head.length - 1 } };
60
+ const merges = [{ s: mergeRange.s, e: mergeRange.e }];
61
+ sheet["!merges"] = merges;
62
+ }
63
+
64
+ const font = {
65
+ name: "Arial Unicode MS",
66
+ bold: false,
67
+ italic: false,
68
+ sz: 12,
69
+ };
70
+
71
+ const borderStyle = {
72
+ style: "thin",
73
+ };
74
+
75
+ const cellStyle = {
76
+ alignment: {
77
+ vertical: "center",
78
+ horizontal: "center",
79
+ },
80
+ border: {
81
+ top: borderStyle,
82
+ bottom: borderStyle,
83
+ left: borderStyle,
84
+ right: borderStyle,
85
+ },
86
+ font,
87
+ };
88
+
89
+ for (let i = 1; i < aoa.length; i++) {
90
+ for (let j = 0; j < aoa[i].length; j++) {
91
+ const cellAddress = XLSX.utils.encode_cell({ r: i, c: j });
92
+ const cell = sheet[cellAddress];
93
+ cell.s = cellStyle;
94
+ }
95
+ }
96
+
97
+ {
98
+ const cellAddress = XLSX.utils.encode_cell({ r: 0, c: 0 });
99
+ const cell = sheet[cellAddress];
100
+ const titleStyle = _.cloneDeep(cellStyle);
101
+ titleStyle.font.sz = 28;
102
+ titleStyle.font.bold = true;
103
+ cell.s = titleStyle;
104
+ }
105
+
106
+ return sheet;
107
+ }
108
+
109
+ private convertToAoa(rank: Rank): string[][] {
110
+ const aoa: string[][] = [];
111
+
112
+ const enableAwards = rank.contest.isEnableAwards(rank.options.group);
113
+
114
+ {
115
+ aoa.push([rank.contest.name]);
116
+ }
117
+
118
+ {
119
+ const head: string[] = [];
120
+ head.push("Rank");
121
+
122
+ if (rank.contest.organization) {
123
+ head.push(`${rank.contest.organization} Rank`);
124
+ head.push(rank.contest.organization);
125
+ }
126
+
127
+ head.push("Name", "Solved", "Penalty", ...rank.contest.problems.map(p => p.label), "Dict");
128
+
129
+ if (enableAwards) {
130
+ head.push("Medal");
131
+ }
132
+
133
+ aoa.push(head);
134
+ }
135
+
136
+ for (const team of rank.teams) {
137
+ const arr: string[] = [];
138
+
139
+ arr.push(team.rank.toString());
140
+ if (team.organization) {
141
+ if (team.organizationRank !== -1) {
142
+ arr.push(team.organizationRank.toString());
143
+ } else {
144
+ arr.push("");
145
+ }
146
+
147
+ arr.push(team.organization);
148
+ }
149
+
150
+ arr.push(team.name, team.solvedProblemNum.toString(), team.penaltyToMinute.toString());
151
+
152
+ for (const p of team.problemStatistics) {
153
+ if (p.isUnSubmitted) {
154
+ arr.push("-");
155
+ }
156
+
157
+ if (p.isSolved) {
158
+ arr.push(`+${p.totalCount}(${p.solvedTimestampToMinute})`);
159
+ }
160
+
161
+ if (p.isWrongAnswer) {
162
+ arr.push(`-${p.failedCount}`);
163
+ }
164
+
165
+ if (p.isPending) {
166
+ arr.push(`? ${p.failedCount} + ${p.pendingCount}`);
167
+ }
168
+ }
169
+
170
+ arr.push(`${team.dict}%`);
171
+
172
+ if (enableAwards) {
173
+ const medals = team.awards
174
+ .filter(a => isValidMedalType(a))
175
+ .map(a => a.toString());
176
+ arr.push(medals.join(", "));
177
+ }
178
+
179
+ aoa.push(arr);
180
+ }
181
+
182
+ return aoa;
183
+ }
184
+ }
@@ -0,0 +1,2 @@
1
+ export * from "./cf";
2
+ export * from "./general-excel";
package/src/index.ts CHANGED
@@ -1,8 +1,8 @@
1
+ export * from "./export";
1
2
  export * from "./award";
2
3
  export * from "./contest-index";
3
4
  export * from "./utils";
4
5
  export * from "./contest";
5
- export * from "./export";
6
6
  export * from "./image";
7
7
  export * from "./problem";
8
8
  export * from "./rank-statistics";
package/src/problem.ts CHANGED
@@ -189,4 +189,12 @@ export class TeamProblemStatistics {
189
189
 
190
190
  return Math.floor(this.solvedTimestamp / 60) * 60 + this.failedCount * this.contestPenalty;
191
191
  }
192
+
193
+ get penaltyToMinute() {
194
+ return Math.floor(this.penalty / 60);
195
+ }
196
+
197
+ get solvedTimestampToMinute() {
198
+ return Math.floor(this.solvedTimestamp / 60);
199
+ }
192
200
  }
package/src/team.ts CHANGED
@@ -101,6 +101,8 @@ export class Team {
101
101
  this.submissions = [];
102
102
 
103
103
  this.placeChartPoints = [];
104
+
105
+ this.awards = [];
104
106
  }
105
107
 
106
108
  get penaltyToMinute() {
package/src/export.ts DELETED
@@ -1,116 +0,0 @@
1
- import { SubmissionStatus } from "@xcpcio/types";
2
-
3
- import type { Rank } from "./rank";
4
-
5
- import {
6
- isAccepted,
7
- isNotCalculatedPenaltyStatus,
8
- isPending,
9
- } from "./submission-status";
10
-
11
- import dayjs from "./utils/dayjs";
12
-
13
- function submissionStatusToCodeforcesGymDatStatus(status: SubmissionStatus): string {
14
- if (isAccepted(status)) {
15
- return "OK";
16
- }
17
-
18
- if (status === SubmissionStatus.WRONG_ANSWER) {
19
- return "WA";
20
- }
21
-
22
- if (status === SubmissionStatus.TIME_LIMIT_EXCEEDED) {
23
- return "TL";
24
- }
25
-
26
- if (status === SubmissionStatus.MEMORY_LIMIT_EXCEEDED) {
27
- return "ML";
28
- }
29
-
30
- if (status === SubmissionStatus.OUTPUT_LIMIT_EXCEEDED) {
31
- return "IL";
32
- }
33
-
34
- if (status === SubmissionStatus.PRESENTATION_ERROR) {
35
- return "PE";
36
- }
37
-
38
- if (status === SubmissionStatus.RUNTIME_ERROR) {
39
- return "RT";
40
- }
41
-
42
- if (status === SubmissionStatus.COMPILATION_ERROR || isNotCalculatedPenaltyStatus(status)) {
43
- return "CE";
44
- }
45
-
46
- if (isPending(status)) {
47
- return "PD";
48
- }
49
-
50
- return "RJ";
51
- }
52
-
53
- export function rankToCodeforcesGymDAT(rank: Rank) {
54
- let res = "";
55
-
56
- res += `@contest "${rank.contest.name}"
57
- @contlen ${Math.floor(dayjs.duration(rank.contest.endTime.diff(rank.contest.startTime)).asMinutes())}
58
- @problems ${rank.contest.problems.length}
59
- @teams ${rank.teams.length + 100}
60
- @submissions ${rank.submissions.length}
61
- `;
62
-
63
- rank.contest.problems.forEach((p) => {
64
- res += `@p ${p.label},${p.label},20,0\n`;
65
- });
66
-
67
- let teamIndex = 1;
68
- const teamIdMap = new Map<string, number>();
69
- const submissionsIdMap = new Map<string, Map<string, number>>();
70
-
71
- rank.teams.forEach((team) => {
72
- let name = team.name;
73
-
74
- if (team.organization) {
75
- name = `${team.organization} - ${name}`;
76
- }
77
-
78
- if (team.members) {
79
- name = `${name} - ${team.membersToString}`;
80
- }
81
-
82
- res += `@t ${teamIndex},0,1,${name}\n`;
83
- teamIdMap.set(team.id, teamIndex);
84
- teamIndex++;
85
-
86
- {
87
- const mp = new Map<string, number>();
88
- rank.contest.problems.forEach((p) => {
89
- mp.set(p.id, 0);
90
- });
91
- submissionsIdMap.set(team.id, mp);
92
- }
93
- });
94
-
95
- for (let i = 0; i < 100; i++) {
96
- res += `@t ${teamIndex},0,1,Пополнить команду\n`;
97
- teamIndex++;
98
- }
99
-
100
- rank.getSubmissions().forEach((submission) => {
101
- const teamId = submission.teamId;
102
- const problemId = submission.problemId;
103
- const problem = rank.contest.problemsMap.get(problemId);
104
-
105
- if (!problem) {
106
- return;
107
- }
108
-
109
- const status = submissionStatusToCodeforcesGymDatStatus(submission.status);
110
- submissionsIdMap.get(teamId)!.set(problemId, submissionsIdMap.get(teamId)!.get(problemId)! + 1);
111
-
112
- res += `@s ${teamIdMap.get(teamId)},${problem.label},${submissionsIdMap.get(teamId)?.get(problemId)},${submission.timestamp},${status}\n`;
113
- });
114
-
115
- return res;
116
- }