@xcpcio/core 0.13.0 → 0.14.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 +371 -223
- package/dist/index.d.ts +52 -38
- package/dist/index.mjs +356 -223
- package/package.json +4 -2
- package/src/export/cf.ts +120 -0
- package/src/export/general-excel.ts +153 -0
- package/src/export/index.ts +2 -0
- package/src/index.ts +1 -1
- package/src/problem.ts +8 -0
- package/src/export.ts +0 -116
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,153 @@
|
|
|
1
|
+
import _ from "lodash";
|
|
2
|
+
import * as XLSX from "xlsx-js-style";
|
|
3
|
+
import stringWidth from "string-width";
|
|
4
|
+
|
|
5
|
+
import type { Rank } from "../rank";
|
|
6
|
+
|
|
7
|
+
export class GeneralExcelConverter {
|
|
8
|
+
constructor() {}
|
|
9
|
+
|
|
10
|
+
public convert(rank: Rank): XLSX.WorkBook {
|
|
11
|
+
const workbook = XLSX.utils.book_new();
|
|
12
|
+
const aoa = this.convertToAoa(rank);
|
|
13
|
+
const mainWorksheet = XLSX.utils.aoa_to_sheet(aoa);
|
|
14
|
+
|
|
15
|
+
const cols = [];
|
|
16
|
+
const head = aoa[1];
|
|
17
|
+
for (let j = 0; j < head.length; j++) {
|
|
18
|
+
let wch = 10;
|
|
19
|
+
for (let i = 1; i < aoa.length; i++) {
|
|
20
|
+
wch = Math.max(wch, stringWidth(aoa[i][j]) + 2);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
cols.push({
|
|
24
|
+
wch,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
mainWorksheet["!cols"] = cols;
|
|
29
|
+
|
|
30
|
+
{
|
|
31
|
+
const mergeRange = { s: { r: 0, c: 0 }, e: { r: 0, c: head.length - 1 } };
|
|
32
|
+
const merges = [{ s: mergeRange.s, e: mergeRange.e }];
|
|
33
|
+
mainWorksheet["!merges"] = merges;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const font = {
|
|
37
|
+
name: "Arial Unicode MS",
|
|
38
|
+
bold: false,
|
|
39
|
+
italic: false,
|
|
40
|
+
sz: 12,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const borderStyle = {
|
|
44
|
+
style: "thin",
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const cellStyle = {
|
|
48
|
+
alignment: {
|
|
49
|
+
vertical: "center",
|
|
50
|
+
horizontal: "center",
|
|
51
|
+
},
|
|
52
|
+
border: {
|
|
53
|
+
top: borderStyle,
|
|
54
|
+
bottom: borderStyle,
|
|
55
|
+
left: borderStyle,
|
|
56
|
+
right: borderStyle,
|
|
57
|
+
},
|
|
58
|
+
font,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
for (let i = 1; i < aoa.length; i++) {
|
|
62
|
+
for (let j = 0; j < aoa[i].length; j++) {
|
|
63
|
+
const cellAddress = XLSX.utils.encode_cell({ r: i, c: j });
|
|
64
|
+
const cell = mainWorksheet[cellAddress];
|
|
65
|
+
cell.s = cellStyle;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
{
|
|
70
|
+
const cellAddress = XLSX.utils.encode_cell({ r: 0, c: 0 });
|
|
71
|
+
const cell = mainWorksheet[cellAddress];
|
|
72
|
+
const titleStyle = _.cloneDeep(cellStyle);
|
|
73
|
+
titleStyle.font.sz = 28;
|
|
74
|
+
titleStyle.font.bold = true;
|
|
75
|
+
cell.s = titleStyle;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
XLSX.utils.book_append_sheet(workbook, mainWorksheet, "Scoreboard");
|
|
79
|
+
|
|
80
|
+
return workbook;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
public convertAndWrite(rank: Rank, filename: string): any {
|
|
84
|
+
return XLSX.writeFile(
|
|
85
|
+
this.convert(rank),
|
|
86
|
+
filename,
|
|
87
|
+
{
|
|
88
|
+
compression: true,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
private convertToAoa(rank: Rank): string[][] {
|
|
93
|
+
const aoa: string[][] = [];
|
|
94
|
+
|
|
95
|
+
{
|
|
96
|
+
aoa.push([rank.contest.name]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
{
|
|
100
|
+
const head: string[] = [];
|
|
101
|
+
head.push("Rank");
|
|
102
|
+
|
|
103
|
+
if (rank.contest.organization) {
|
|
104
|
+
head.push(`${rank.contest.organization} Rank`);
|
|
105
|
+
head.push(rank.contest.organization);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
head.push("Name", "Solved", "Penalty", ...rank.contest.problems.map(p => p.label), "Dict");
|
|
109
|
+
aoa.push(head);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const team of rank.teams) {
|
|
113
|
+
const arr: string[] = [];
|
|
114
|
+
|
|
115
|
+
arr.push(team.rank.toString());
|
|
116
|
+
if (team.organization) {
|
|
117
|
+
if (team.organizationRank !== -1) {
|
|
118
|
+
arr.push(team.organizationRank.toString());
|
|
119
|
+
} else {
|
|
120
|
+
arr.push("");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
arr.push(team.organization);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
arr.push(team.name, team.solvedProblemNum.toString(), team.penaltyToMinute.toString());
|
|
127
|
+
|
|
128
|
+
for (const p of team.problemStatistics) {
|
|
129
|
+
if (p.isUnSubmitted) {
|
|
130
|
+
arr.push("-");
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (p.isSolved) {
|
|
134
|
+
arr.push(`+${p.totalCount}(${p.solvedTimestampToMinute})`);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (p.isWrongAnswer) {
|
|
138
|
+
arr.push(`-${p.failedCount}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (p.isPending) {
|
|
142
|
+
arr.push(`? ${p.failedCount} + ${p.pendingCount}`);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
arr.push(`${team.dict}%`);
|
|
147
|
+
|
|
148
|
+
aoa.push(arr);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return aoa;
|
|
152
|
+
}
|
|
153
|
+
}
|
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/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
|
-
}
|