oicontest
Version:
OI Contest Management Tool
342 lines (341 loc) • 17.4 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.addProblemCommand = void 0;
const commander_1 = require("commander");
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const inquirer_1 = __importDefault(require("inquirer"));
const utils_1 = require("../utils/utils");
const config_1 = require("../lib/config");
const chalk_1 = __importDefault(require("chalk"));
const mkdirp_1 = require("mkdirp");
exports.addProblemCommand = new commander_1.Command('addproblem')
.description('Add a new problem to the contest')
.action(() => __awaiter(void 0, void 0, void 0, function* () {
const contestDir = process.cwd();
try {
const config = yield (0, config_1.loadConfig)(contestDir);
console.log(chalk_1.default.bold.blue('\n➕ Add New Problem\n'));
const answers = yield inquirer_1.default.prompt([
{
type: 'input',
name: 'id',
message: 'Problem ID:',
validate: input => input.trim() !== '' || 'ID cannot be empty'
},
{
type: 'input',
name: 'title',
message: 'Problem title:',
default: 'Untitled Problem'
},
{
type: 'input',
name: 'timeLimit',
message: 'Time limit (ms):',
default: '1000',
validate: input => !isNaN(Number(input)) || 'Please enter a valid number'
},
{
type: 'input',
name: 'memoryLimit',
message: 'Memory limit (MB):',
default: '256',
validate: input => !isNaN(Number(input)) || 'Please enter a valid number'
},
{
type: "checkbox",
name: "tags",
message: "请选择题目标签:",
choices: [
{ name: "入门", value: "入门" },
{ name: "普及-", value: "普及-" },
{ name: "普及/提高-", value: "普及/提高-" },
{ name: "普及+/提高", value: "普及+/提高" },
{ name: "提高+/省选-", value: "提高+/省选-" },
{ name: "省选/NOI-", value: "省选/NOI-" },
{ name: "NOI/NOI+/CTSC", value: "NOI/NOI+/CTSC" },
new inquirer_1.default.Separator(),
{ name: "动态规划", value: "动态规划" },
{ name: "图论", value: "图论" },
{ name: "数据结构", value: "数据结构" },
{ name: "数学", value: "数学" },
{ name: "基础算法", value: "基础算法" },
{ name: "字符串", value: "字符串" },
{ name: "其他", value: "其他" },
],
default: ["入门"],
},
{
type: "input",
name: "othertag",
message: "其他分类标签(用空格或,分割多个):",
default: "",
},
{
type: 'input',
name: 'maxScore',
message: 'Maximum score:',
default: '100',
validate: input => !isNaN(Number(input)) || 'Please enter a valid number'
}
]);
// Check if problem exists
if (config.problems.some(p => p.id === answers.id)) {
console.error(chalk_1.default.red(`\nError: Problem "${answers.id}" already exists`));
process.exit(1);
}
const problemDir = path.join(contestDir, 'problem', answers.id);
// Create problem directories
// fs.ensureDirSync(path.join(problemDir, 'testdata')); // 测试数据
// fs.ensureDirSync(path.join(problemDir, 'src')); // 源文件,包括std等
// fs.ensureDirSync(path.join(problemDir, 'sample')); // 样例文件,包括大样例
// fs.ensureDirSync(path.join(problemDir, 'solution')); // 题解
// // Create default files
// fs.writeFileSync(
// path.join(problemDir, 'problem.md'),
// `# ${answers.title}\n\n## Problem Description\n\n`
// );
// 选择checker类型
//
const checkerType = yield inquirer_1.default.prompt([
{
type: "list",
name: "selectedChecker",
message: "请选择需要的Checker 类型:",
choices: [
{
name: "ncmp (标准比较器:逐字节对比用户输出和标准答案,但会忽略行末空格和文件末尾的多余换行)",
value: "ncmp.cpp",
},
{
name: "wcmp (比较两个单词序列,按顺序逐个比较单词,若某对单词不同或序列长度不同,则判定为答案错误;否则判定为答案正确。)",
value: "wcmp.cpp",
},
{
name: "fcmp (将文件按行作为字符串序列进行比较,若某行内容不同,则判定为答案错误;否则判定为答案正确。)",
value: "fcmp.cpp",
},
{
name: "rcmp (比较两个双精度浮点数,允许最大绝对误差为 1.5E-6。若误差超过该值,判定为答案错误;否则判定为答案正确。)",
value: "rcmp.cpp",
},
{
name: "rcmp4 (比较两个双精度浮点数序列,允许最大绝对或相对误差为 1E-4。若某对元素误差超过该值,判定为答案错误;否则判定为答案正确。)",
value: "rcmp4.cpp",
},
{
name: "rcmp6 (比较两个双精度浮点数序列,允许最大绝对或相对误差为 1E-6。若某对元素误差超过该值,判定为答案错误;否则判定为答案正确。)",
value: "rcmp6.cpp",
},
{
name: "rcmp9 (比较两个双精度浮点数序列,允许最大绝对或相对误差为 1E-9。若某对元素误差超过该值,判定为答案错误;否则判定为答案正确。)",
value: "rcmp9.cpp",
},
{
name: "rncmp (比较两个双精度浮点数序列,允许最大绝对误差为 1.5E-5。若某对元素误差超过该值,判定为答案错误;否则判定为答案正确。)",
value: "rncmp.cpp",
},
{
name: "uncmp (比较两个无序的有符号长整型序列,会先排序再比较,若序列长度或元素不同,则判定为错误。)",
value: "uncmp.cpp",
},
{
name: 'yesno (检查输入是否为 "YES" 或 "NO" (大小写不敏感),若输入不符合要求或与答案不一致,则判定为错误。)',
value: "yesno.cpp",
},
{
name: "acmp (比较两个双精度浮点数,允许最大绝对误差为 1.5E-6。若误差超过该值,判定为答案错误;否则判定为答案正确。)",
value: "acmp.cpp",
},
{
name: "caseicmp (带有测试用例支持的单 int64 检查器,用于比较两个有序的 int64 序列,若序列长度不同或对应元素不同,则判定为错误。)",
value: "caseicmp.cpp",
},
{
name: 'casencmp (用于比较输出和答案的格式为 "Case X: <number> <number> ..." 的情况,按测试用例逐行比较长整型序列。)',
value: "casencmp.cpp",
},
{
name: 'casewcmp (用于比较输出和答案的格式为 "Case X: <token> <token> ..." 的情况,按测试用例逐行比较字符串序列。)',
value: "casewcmp.cpp",
},
{
name: "dcmp (比较两个双精度浮点数,允许最大绝对或相对误差为 1E-6。若误差超过该值,判定为答案错误;否则判定为答案正确。)",
value: "dcmp.cpp",
},
{
name: "hcmp (比较两个有符号的大整数,会先检查输入是否为有效的整数格式,若格式错误或数值不同,则判定为错误。)",
value: "hcmp.cpp",
},
{
name: "icmp (比较两个有符号的整数,若两个整数不相等,则判定为答案错误;否则判定为答案正确。)",
value: "icmp.cpp",
},
{
name: "lcmp (将文件按行拆分为单词序列进行比较,若某行的单词序列不同,则判定为答案错误;否则判定为答案正确。)",
value: "lcmp.cpp",
},
{
name: "ncmp (比较两个有序的有符号长整型序列,会检查序列长度和对应元素是否相同,若不同则判定为错误。)",
value: "ncmp.cpp",
},
{
name: 'nyesno (用于检查多个 "YES" 或 "NO" (大小写不敏感)的输入,会统计 "YES" 和 "NO" 的数量,若输入不符合要求或与答案不一致,则判定为错误。)',
value: "nyesno.cpp",
},
{
name: "pointscmp (示例得分检查器,通过比较两个双精度浮点数的差值来给出得分。)",
value: "pointscmp.cpp",
},
{
name: "pointsinfo (示例带有 points_info 的检查器,读取两个双精度浮点数,记录相关信息并退出。)",
value: "pointsinfo.cpp",
},
],
default: 0,
},
]);
// 创建目录结构
const dirs = ["testdata", "src", "sample", "solution", "additional_file"];
for (const dir of dirs) {
yield (0, mkdirp_1.mkdirp)(path.join(problemDir, dir));
}
// 生成题目状态文件
const problemStatus = {
dir: { desc: "目录完整", status: false },
isvalidated: { desc: "验证输入数据", status: false },
isgenerated: { desc: "评测数据", status: false },
ischecked: { desc: "是否检查完整", status: false }
};
try {
const statusFileName = path.resolve(problemDir, "status.json");
console.log(statusFileName);
fs.writeFileSync(statusFileName, JSON.stringify(problemStatus, null, 2), "utf-8");
}
catch (e) {
console.error(chalk_1.default.red("写入题目状态文件失败:"), e.message);
process.exit(1);
}
// 写入模板文件
const templates = {
"src/std.cpp": "std.cpp",
"problem_zh.md": "problem_zh.md",
"src/generator.cpp": "generator.cpp",
"src/validator.cpp": "validator.cpp",
"src/testlib.h": "testlib.h",
"solution/stdsol.md": "solution/stdsol.md",
"src/checker.cpp": "checker.cpp",
};
// 根据用户选择的checker
if (checkerType.value)
templates["src/checker.cpp"] = `checkers/${checkerType.value}`;
else
templates["src/checker.cpp"] = "checkers/wcmp.cpp";
// 根据用户选择过滤模板文件
const finalTemplates = Object.entries(templates).filter(([filePath]) => {
if (filePath.includes("checker") && false)
return false;
if (filePath.includes("generator") && false)
return false;
if (filePath.includes("validator") && false)
return false;
return true;
});
const templateDir = path.resolve(__dirname, "../templates");
for (const [filePath, templateName] of finalTemplates) {
(0, utils_1.writeFileWithDir)(path.join(problemDir, filePath), (0, utils_1.getTemplate)(templateDir, templateName));
}
// 生成 problem.yaml
answers.tags.filter((item) => item !== "");
if (answers.othertag)
answers["othertag"].split(/[\s,,;]+/).forEach((el) => {
answers.tags.push(el);
});
const problemYaml = {
tag: [...answers.tags],
title: answers.title,
};
(0, utils_1.writeYaml)(path.join(problemDir, "problem.yaml"), problemYaml);
// 生成 testdata/config.yaml
const configYaml = {
memory: answers.memoryLimit,
time: answers.timeLimit,
};
(0, utils_1.writeYaml)(path.join(problemDir, "testdata", "config.yaml"), configYaml);
const problemName = answers.title;
console.log(chalk_1.default.green(`\n题目 "${problemName}" 初始化完成!`));
console.log(chalk_1.default.blue(`目录结构已创建在: ${problemDir}`));
//查找目前有几道题目
const indexs = config.problems.length;
const problemConfig = {
id: answers.id,
index: indexs,
title: answers.title,
timeLimit: parseInt(answers.timeLimit),
memoryLimit: parseInt(answers.memoryLimit),
maxScore: parseInt(answers.maxScore),
};
// Save problem config
fs.writeJsonSync(path.join(problemDir, 'config.json'), problemConfig, { spaces: 2 });
// Update contest config
config.problems.push(problemConfig);
config.status.problemsAdded = true;
yield (0, config_1.saveConfig)(process.cwd(), config);
console.log(chalk_1.default.green.bold('\n✅ Problem added successfully!'));
console.log(chalk_1.default.cyan(` ID: ${answers.id}`));
console.log(chalk_1.default.cyan(` Title: ${answers.title}`));
console.log(chalk_1.default.cyan(` Time Limit: ${answers.timeLimit}ms`));
console.log(chalk_1.default.cyan(` Memory Limit: ${answers.memoryLimit}MB`));
console.log(chalk_1.default.cyan(` Directory: ${problemDir}`));
}
catch (err) {
console.error(chalk_1.default.red(`Error adding problem: ${err.message}`));
process.exit(1);
}
}));