@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/dist/index.cjs +410 -223
- package/dist/index.d.ts +55 -38
- package/dist/index.mjs +394 -223
- package/package.json +4 -2
- package/src/award.ts +16 -5
- package/src/contest.ts +12 -0
- package/src/export/cf.ts +120 -0
- package/src/export/general-excel.ts +184 -0
- package/src/export/index.ts +2 -0
- package/src/index.ts +1 -1
- package/src/problem.ts +8 -0
- package/src/team.ts +2 -0
- package/src/export.ts +0 -116
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 {
|
package/src/export/cf.ts
ADDED
|
@@ -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
|
+
}
|
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
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
|
-
}
|