judgeExtraFiles;
+
+ /**
+ * 普通评测的命令配置
+ */
+ Constants.RunConfig runConfig;
+
+ /**
+ * 特殊判题的命令配置
+ */
+ Constants.RunConfig spjRunConfig;
+
+ /**
+ * 交互判题的命令配置
+ */
+ Constants.RunConfig interactiveRunConfig;
+
+ /**
+ * 是否需要生成用户程序输出的文件
+ */
+ private Boolean needUserOutputFile;
+
+ /**
+ * 是否需要自动移除评测数据的行末空格
+ */
+ private Boolean removeEOLBlank;
+
+}
\ No newline at end of file
diff --git a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/entity/SandBoxRes.java b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/entity/SandBoxRes.java
new file mode 100644
index 000000000..24bfb0d40
--- /dev/null
+++ b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/entity/SandBoxRes.java
@@ -0,0 +1,49 @@
+package top.hcode.hoj.judge.entity;
+
+import lombok.Builder;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import lombok.experimental.Accessors;
+
+/**
+ * @Author: Himit_ZH
+ * @Date: 2022/1/3 15:27
+ * @Description: 单个测评结果实体类
+ */
+@Data
+@EqualsAndHashCode(callSuper = false)
+@Accessors(chain = true)
+@Builder
+public class SandBoxRes {
+ /**
+ * 单个程序的状态码
+ */
+ private Integer status;
+
+ /**
+ * 单个程序的退出码
+ */
+ private Integer exitCode;
+
+ /**
+ * 单个程序的运行所耗空间 kb
+ */
+ private Long memory;
+
+ /**
+ * 单个程序的运行所耗时间 ms
+ */
+ private Long time;
+
+ /**
+ * 单个程序的标准输出
+ */
+ private String stdout;
+
+ /**
+ * 单个程序的错误信息
+ */
+ private String stderr;
+
+}
\ No newline at end of file
diff --git a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/DefaultJudge.java b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/DefaultJudge.java
new file mode 100644
index 000000000..550c947e3
--- /dev/null
+++ b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/DefaultJudge.java
@@ -0,0 +1,143 @@
+package top.hcode.hoj.judge.task;
+
+import cn.hutool.core.io.file.FileWriter;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import org.springframework.stereotype.Component;
+import org.springframework.util.DigestUtils;
+import org.springframework.util.StringUtils;
+import top.hcode.hoj.common.exception.SystemError;
+import top.hcode.hoj.judge.AbstractJudge;
+import top.hcode.hoj.judge.SandboxRun;
+import top.hcode.hoj.judge.entity.JudgeDTO;
+import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
+import top.hcode.hoj.judge.entity.SandBoxRes;
+import top.hcode.hoj.util.Constants;
+
+/**
+ * @Author: Himit_ZH
+ * @Date: 2022/1/2 21:18
+ * @Description: 普通评测
+ */
+@Component
+public class DefaultJudge extends AbstractJudge {
+ @Override
+ public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
+ Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
+ // 调用安全沙箱使用测试点对程序进行测试
+ return SandboxRun.testCase(
+ parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
+ runConfig.getEnvs(),
+ judgeDTO.getTestCaseInputPath(),
+ judgeGlobalDTO.getTestTime(),
+ judgeDTO.getMaxOutputSize(),
+ judgeGlobalDTO.getMaxStack(),
+ runConfig.getExeName(),
+ judgeGlobalDTO.getUserFileId());
+ }
+
+ @Override
+ public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
+
+ JSONObject result = new JSONObject();
+
+ StringBuilder errMsg = new StringBuilder();
+ // 如果测试跑题无异常
+ if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
+
+ // 对结果的时间损耗和空间损耗与题目限制做比较,判断是否mle和tle
+ if (sandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
+ result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
+ } else if (sandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) {
+ result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
+ } else {
+ // 与原测试数据输出的md5进行对比 AC或者是WA
+ JSONObject testcaseInfo = (JSONObject) ((JSONArray) judgeGlobalDTO.getTestCaseInfo().get("testCases")).get(judgeDTO.getTestCaseId() - 1);
+ result.set("status", compareOutput(sandBoxRes.getStdout(), judgeGlobalDTO.getRemoveEOLBlank(), testcaseInfo));
+ }
+ } else if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
+ result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
+ } else if (sandBoxRes.getExitCode() != 0) {
+ result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
+ if (sandBoxRes.getExitCode() < 32) {
+ errMsg.append(String.format("Your program return ExitCode: %s (%s)\n", sandBoxRes.getExitCode(), SandboxRun.signals.get(sandBoxRes.getExitCode())));
+ } else {
+ errMsg.append(String.format("Your program return ExitCode: %s\n", sandBoxRes.getExitCode()));
+ }
+ } else {
+ result.set("status", sandBoxRes.getStatus());
+ }
+
+ // b
+ result.set("memory", sandBoxRes.getMemory());
+ // ns->ms
+ result.set("time", sandBoxRes.getTime());
+
+ if (!StringUtils.isEmpty(sandBoxRes.getStdout())) {
+ // 对于当前测试样例,用户程序的输出对应生成的文件
+ FileWriter stdWriter = new FileWriter(judgeGlobalDTO.getRunDir() + "/" + judgeDTO.getTestCaseId() + ".out");
+ stdWriter.write(sandBoxRes.getStdout());
+ }
+
+ if (!StringUtils.isEmpty(sandBoxRes.getStderr())) {
+ // 对于当前测试样例,用户的错误提示生成对应文件
+ FileWriter errWriter = new FileWriter(judgeGlobalDTO.getRunDir() + "/" + judgeDTO.getTestCaseId() + ".err");
+ errWriter.write(sandBoxRes.getStderr());
+ // 同时记录错误信息
+ errMsg.append(sandBoxRes.getStderr());
+ }
+
+ // 记录该测试点的错误信息
+ if (!StringUtils.isEmpty(errMsg.toString())) {
+ result.set("errMsg", errMsg.toString());
+ }
+
+ if (judgeGlobalDTO.getNeedUserOutputFile()) { // 如果需要获取用户对于该题目的输出
+ result.set("output", sandBoxRes.getStdout());
+ }
+
+ return result;
+ }
+
+ @Override
+ public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
+ return null;
+ }
+
+ // 根据评测结果与用户程序输出的字符串MD5进行对比
+ private Integer compareOutput(String userOutput, Boolean isRemoveEOLBlank, JSONObject testcaseInfo) {
+
+ // 如果当前题目选择默认去掉字符串末位空格
+ if (isRemoveEOLBlank) {
+ String userOutputMd5 = DigestUtils.md5DigestAsHex(rtrim(userOutput).getBytes());
+ if (userOutputMd5.equals(testcaseInfo.getStr("EOFStrippedOutputMd5"))) {
+ return Constants.Judge.STATUS_ACCEPTED.getStatus();
+ }
+ } else { // 不选择默认去掉文末空格 与原数据进行对比
+ String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.getBytes());
+ if (userOutputMd5.equals(testcaseInfo.getStr("outputMd5"))) {
+ return Constants.Judge.STATUS_ACCEPTED.getStatus();
+ }
+ }
+ // 如果不AC,进行PE判断,否则为WA
+ String userOutputMd5 = DigestUtils.md5DigestAsHex(userOutput.replaceAll("\\s+", "").getBytes());
+ if (userOutputMd5.equals(testcaseInfo.getStr("allStrippedOutputMd5"))) {
+ return Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus();
+ } else {
+ return Constants.Judge.STATUS_WRONG_ANSWER.getStatus();
+ }
+ }
+
+
+ // 去除行末尾空白符
+ private String rtrim(String value) {
+ if (value == null) return null;
+ StringBuilder sb = new StringBuilder();
+ String[] strArr = value.split("\n");
+ for (String str : strArr) {
+ sb.append(str.replaceAll("\\s+$", "")).append("\n");
+ }
+ return sb.toString().replaceAll("\\s+$", "");
+ }
+
+}
\ No newline at end of file
diff --git a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/InteractiveJudge.java b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/InteractiveJudge.java
new file mode 100644
index 000000000..3abdf4f3f
--- /dev/null
+++ b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/InteractiveJudge.java
@@ -0,0 +1,165 @@
+package top.hcode.hoj.judge.task;
+
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import top.hcode.hoj.common.exception.SystemError;
+import top.hcode.hoj.judge.AbstractJudge;
+import top.hcode.hoj.judge.SandboxRun;
+import top.hcode.hoj.judge.entity.JudgeDTO;
+import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
+import top.hcode.hoj.judge.entity.SandBoxRes;
+import top.hcode.hoj.util.Constants;
+
+import java.io.File;
+
+/**
+ * @Author: Himit_ZH
+ * @Date: 2022/1/2 23:24
+ * @Description: 交互评测
+ */
+@Component
+public class InteractiveJudge extends AbstractJudge {
+
+ @Override
+ public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
+
+ Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
+ Constants.RunConfig interactiveRunConfig = judgeGlobalDTO.getInteractiveRunConfig();
+
+ // 交互程序的路径
+ String interactiveExeSrc = Constants.JudgeDir.INTERACTIVE_WORKPLACE_DIR.getContent()
+ + File.separator + judgeGlobalDTO.getProblemId() + File.separator + interactiveRunConfig.getExeName();
+
+ String testCaseInputFileName = judgeGlobalDTO.getProblemId() + "_input";
+ String testCaseOutputFileName = judgeGlobalDTO.getProblemId() + "_output";
+
+ // 其实交互题不存在用户输出文件的,为了统一格式造了个名字
+ String userOutputFileName = judgeGlobalDTO.getProblemId() + "_user_output";
+
+ JSONArray jsonArray = SandboxRun.interactTestCase(
+ parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
+ runConfig.getEnvs(),
+ runConfig.getExeName(),
+ judgeGlobalDTO.getUserFileId(),
+ judgeGlobalDTO.getTestTime(),
+ judgeGlobalDTO.getMaxStack(),
+ judgeDTO.getTestCaseInputPath(),
+ testCaseInputFileName,
+ judgeDTO.getTestCaseOutputPath(),
+ testCaseOutputFileName,
+ parseRunCommand(interactiveRunConfig.getCommand(), interactiveRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),
+ interactiveRunConfig.getEnvs(),
+ interactiveExeSrc,
+ interactiveRunConfig.getExeName());
+ return jsonArray;
+ }
+
+ @Override
+ public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
+ return null;
+ }
+
+ @Override
+ public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
+
+ JSONObject result = new JSONObject();
+
+ // 记录错误信息
+ StringBuilder errMsg = new StringBuilder();
+
+ int userExitCode = userSandBoxRes.getExitCode();
+
+ if (userSandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
+ // 如果运行超过题目限制时间,直接TLE
+ if (userSandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
+ result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
+ } else if (userSandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) { // 如果运行超过题目限制空间,直接MLE
+ result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
+ } else {
+ // 根据交互程序的退出状态码及输出进行判断
+ JSONObject interactiveCheckRes = checkInteractiveRes(interactiveSandBoxRes);
+ int code = interactiveCheckRes.getInt("code");
+ if (code == SPJ_WA) {
+ result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
+ } else if (code == SPJ_AC) {
+ result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
+ } else if (code == SPJ_PE) {
+ result.set("status", Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus());
+ } else {
+ result.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
+ }
+
+ String spjErrMsg = interactiveCheckRes.getStr("errMsg");
+ if (!StringUtils.isEmpty(spjErrMsg)) {
+ errMsg.append(spjErrMsg).append(" ");
+ }
+ }
+ } else if (userSandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
+ result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
+ } else if ((userExitCode != 0 && userExitCode != 13) || (userExitCode == 13 && interactiveSandBoxRes.getExitCode() == 0)) {
+ // Broken Pipe
+ result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
+ if (userExitCode < 32) {
+ errMsg.append(String.format("Your program return exitCode: %s (%s)\n", userExitCode, SandboxRun.signals.get(userExitCode)));
+ } else {
+ errMsg.append(String.format("Your program return exitCode: %s\n", userExitCode));
+ }
+ } else {
+ result.set("status", interactiveSandBoxRes.getStatus());
+ errMsg.append(interactiveSandBoxRes.getStderr()).append(" ");
+ if (interactiveSandBoxRes.getExitCode() !=0 && !StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
+ errMsg.append(String.format("Interactive program exited with code: %s",interactiveSandBoxRes.getExitCode()));
+ }
+ }
+ // kb
+ result.set("memory", userSandBoxRes.getMemory());
+ // ms
+ result.set("time", userSandBoxRes.getTime());
+
+ // 记录该测试点的错误信息
+ if (!StringUtils.isEmpty(errMsg.toString())) {
+ result.set("errMsg", errMsg.toString());
+ }
+
+ return result;
+ }
+
+
+ private JSONObject checkInteractiveRes(SandBoxRes interactiveSandBoxRes) {
+
+ JSONObject result = new JSONObject();
+
+ int exitCode = interactiveSandBoxRes.getExitCode();
+
+ // 获取跑题用户输出或错误输出
+ if (!StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
+ result.set("errMsg", interactiveSandBoxRes.getStderr());
+ }
+
+ // 如果程序无异常
+ if (interactiveSandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
+ if (exitCode == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
+ result.set("code", SPJ_AC);
+ } else {
+ result.set("code", exitCode);
+ }
+ } else if (interactiveSandBoxRes.getStatus().equals(Constants.Judge.STATUS_RUNTIME_ERROR.getStatus())) {
+ if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {
+ result.set("code", exitCode);
+ } else {
+ if (!StringUtils.isEmpty(interactiveSandBoxRes.getStderr())) {
+ // 适配testlib.h 根据错误信息前缀判断
+ return parseTestLibErr(interactiveSandBoxRes.getStderr());
+ } else {
+ result.set("code", SPJ_ERROR);
+ }
+ }
+ } else {
+ result.set("code", SPJ_ERROR);
+ }
+ return result;
+ }
+
+}
\ No newline at end of file
diff --git a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/SpecialJudge.java b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/SpecialJudge.java
new file mode 100644
index 000000000..99ff07430
--- /dev/null
+++ b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/judge/task/SpecialJudge.java
@@ -0,0 +1,196 @@
+package top.hcode.hoj.judge.task;
+
+import cn.hutool.core.io.file.FileWriter;
+import cn.hutool.json.JSONArray;
+import cn.hutool.json.JSONObject;
+import org.springframework.stereotype.Component;
+import org.springframework.util.StringUtils;
+import top.hcode.hoj.common.exception.SystemError;
+import top.hcode.hoj.judge.AbstractJudge;
+import top.hcode.hoj.judge.SandboxRun;
+import top.hcode.hoj.judge.entity.JudgeDTO;
+import top.hcode.hoj.judge.entity.JudgeGlobalDTO;
+import top.hcode.hoj.judge.entity.SandBoxRes;
+import top.hcode.hoj.util.Constants;
+
+import java.io.File;
+
+/**
+ * @Author: Himit_ZH
+ * @Date: 2022/1/2 22:23
+ * @Description: 特殊判题 支持testlib
+ */
+
+@Component
+public class SpecialJudge extends AbstractJudge {
+ @Override
+ public JSONArray judgeCase(JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
+ Constants.RunConfig runConfig = judgeGlobalDTO.getRunConfig();
+ // 调用安全沙箱使用测试点对程序进行测试
+ return SandboxRun.testCase(
+ parseRunCommand(runConfig.getCommand(), runConfig, null, null, null),
+ runConfig.getEnvs(),
+ judgeDTO.getTestCaseInputPath(),
+ judgeGlobalDTO.getTestTime(),
+ judgeDTO.getMaxOutputSize(),
+ judgeGlobalDTO.getMaxStack(),
+ runConfig.getExeName(),
+ judgeGlobalDTO.getUserFileId());
+ }
+
+
+ @Override
+ public JSONObject checkMultipleResult(SandBoxRes userSandBoxRes, SandBoxRes interactiveSandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) {
+ return null;
+ }
+
+ @Override
+ public JSONObject checkResult(SandBoxRes sandBoxRes, JudgeDTO judgeDTO, JudgeGlobalDTO judgeGlobalDTO) throws SystemError {
+
+ JSONObject result = new JSONObject();
+ StringBuilder errMsg = new StringBuilder();
+ // 如果测试跑题无异常
+ if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_ACCEPTED.getStatus())) {
+
+ // 对结果的时间损耗和空间损耗与题目限制做比较,判断是否mle和tle
+ if (sandBoxRes.getTime() >= judgeGlobalDTO.getMaxTime()) {
+ result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
+ } else if (sandBoxRes.getMemory() >= judgeGlobalDTO.getMaxMemory()) {
+ result.set("status", Constants.Judge.STATUS_MEMORY_LIMIT_EXCEEDED.getStatus());
+ } else {
+
+ // 对于当前测试样例,用户程序的输出对应生成的文件
+ String userOutputFilePath = judgeGlobalDTO.getRunDir() + File.separator + judgeDTO.getTestCaseId() + ".out";
+ FileWriter stdWriter = new FileWriter(userOutputFilePath);
+ stdWriter.write(sandBoxRes.getStdout());
+
+ Constants.RunConfig spjRunConfig = judgeGlobalDTO.getSpjRunConfig();
+
+ // 特判程序的路径
+ String spjExeSrc = Constants.JudgeDir.SPJ_WORKPLACE_DIR.getContent() + File.separator
+ + judgeGlobalDTO.getProblemId() + File.separator + spjRunConfig.getExeName();
+
+ String userOutputFileName = judgeGlobalDTO.getProblemId() + "_user_output";
+ String testCaseInputFileName = judgeGlobalDTO.getProblemId() + "_input";
+ String testCaseOutputFileName = judgeGlobalDTO.getProblemId() + "_output";
+ // 进行spj程序运行比对
+ JSONObject spjResult = spjRunAndCheckResult(userOutputFilePath,
+ userOutputFileName,
+ judgeDTO.getTestCaseInputPath(),
+ testCaseInputFileName,
+ judgeDTO.getTestCaseOutputPath(),
+ testCaseOutputFileName,
+ spjExeSrc,
+ spjRunConfig);
+ int code = spjResult.getInt("code");
+ if (code == SPJ_WA) {
+ result.set("status", Constants.Judge.STATUS_WRONG_ANSWER.getStatus());
+ } else if (code == SPJ_AC) {
+ result.set("status", Constants.Judge.STATUS_ACCEPTED.getStatus());
+ } else if (code == SPJ_PE) {
+ result.set("status", Constants.Judge.STATUS_PRESENTATION_ERROR.getStatus());
+ } else {
+ result.set("status", Constants.Judge.STATUS_SYSTEM_ERROR.getStatus());
+ }
+
+ String spjErrMsg = spjResult.getStr("errMsg");
+ if (!StringUtils.isEmpty(spjErrMsg)) {
+ errMsg.append(spjErrMsg).append(" ");
+ }
+
+ }
+ } else if (sandBoxRes.getStatus().equals(Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus())) {
+ result.set("status", Constants.Judge.STATUS_TIME_LIMIT_EXCEEDED.getStatus());
+ } else if (sandBoxRes.getExitCode() != 0) {
+ result.set("status", Constants.Judge.STATUS_RUNTIME_ERROR.getStatus());
+ if (sandBoxRes.getExitCode() < 32) {
+ errMsg.append(String.format("Your program return ExitCode: %s (%s)\n", sandBoxRes.getExitCode(), SandboxRun.signals.get(sandBoxRes.getExitCode())));
+ } else {
+ errMsg.append(String.format("Your program return ExitCode: %s\n", sandBoxRes.getExitCode()));
+ }
+ } else {
+ result.set("status", sandBoxRes.getStatus());
+ }
+
+ // b
+ result.set("memory", sandBoxRes.getMemory());
+ // ns->ms
+ result.set("time", sandBoxRes.getTime());
+
+ // 记录该测试点的错误信息
+ if (!StringUtils.isEmpty(errMsg.toString())) {
+ result.set("errMsg", errMsg.toString());
+ }
+
+ if (!StringUtils.isEmpty(sandBoxRes.getStderr())) {
+ // 同时记录错误信息
+ errMsg.append(sandBoxRes.getStderr());
+ // 对于当前测试样例,用户的错误提示生成对应文件
+ FileWriter errWriter = new FileWriter(judgeGlobalDTO.getRunDir() + File.separator + judgeDTO.getTestCaseId() + ".err");
+ errWriter.write(sandBoxRes.getStderr());
+ }
+
+ return result;
+ }
+
+
+ private JSONObject spjRunAndCheckResult(String userOutputFilePath,
+ String userOutputFileName,
+ String testCaseInputFilePath,
+ String testCaseInputFileName,
+ String testCaseOutputFilePath,
+ String testCaseOutputFileName,
+ String spjExeSrc,
+ Constants.RunConfig spjRunConfig) throws SystemError {
+
+ // 调用安全沙箱运行spj程序
+ JSONArray spjJudgeResultList = SandboxRun.spjCheckResult(
+ parseRunCommand(spjRunConfig.getCommand(), spjRunConfig, testCaseInputFileName, userOutputFileName, testCaseOutputFileName),
+ spjRunConfig.getEnvs(),
+ userOutputFilePath,
+ userOutputFileName,
+ testCaseInputFilePath,
+ testCaseInputFileName,
+ testCaseOutputFilePath,
+ testCaseOutputFileName,
+ spjExeSrc,
+ spjRunConfig.getExeName());
+
+ JSONObject result = new JSONObject();
+
+ JSONObject spjJudgeResult = (JSONObject) spjJudgeResultList.get(0);
+
+ // 获取跑题用户输出或错误输出
+ String spjErrOut = ((JSONObject) spjJudgeResult.get("files")).getStr("stderr");
+
+ if (!StringUtils.isEmpty(spjErrOut)) {
+ result.set("errMsg", spjErrOut);
+ }
+
+ // 退出状态码
+ int exitCode = spjJudgeResult.getInt("exitStatus");
+ // 如果测试跑题无异常
+ if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
+ if (exitCode == Constants.Judge.STATUS_ACCEPTED.getStatus()) {
+ result.set("code", SPJ_AC);
+ } else {
+ result.set("code", exitCode);
+ }
+ } else if (spjJudgeResult.getInt("status").intValue() == Constants.Judge.STATUS_RUNTIME_ERROR.getStatus()) {
+ if (exitCode == SPJ_WA || exitCode == SPJ_ERROR || exitCode == SPJ_AC || exitCode == SPJ_PE) {
+ result.set("code", exitCode);
+ } else {
+ if (!StringUtils.isEmpty(spjErrOut)) {
+ // 适配testlib.h 根据错误信息前缀判断
+ return parseTestLibErr(spjErrOut);
+ } else {
+ result.set("code", SPJ_ERROR);
+ }
+ }
+ } else {
+ result.set("code", SPJ_ERROR);
+ }
+
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/service/JudgeService.java b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/service/JudgeService.java
index c6e78e310..8c3f491de 100644
--- a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/service/JudgeService.java
+++ b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/service/JudgeService.java
@@ -6,6 +6,8 @@
import top.hcode.hoj.pojo.entity.judge.Judge;
import top.hcode.hoj.pojo.entity.problem.Problem;
+import java.util.HashMap;
+
/**
*
@@ -19,7 +21,9 @@ public interface JudgeService extends IService {
Judge Judge(Problem problem, Judge judge);
- Boolean compileSpj(String code, Long pid, String spjLanguage) throws CompileError, SystemError;
+ Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap extraFiles) throws SystemError;
+
+ Boolean compileInteractive(String code, Long pid, String interactiveLanguage, HashMap extraFiles) throws SystemError;
void updateOtherTable(Long submitId, Integer status, Long cid, String uid, Long pid, Integer score,Integer useTime);
}
diff --git a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/service/impl/JudgeServiceImpl.java b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/service/impl/JudgeServiceImpl.java
index 3824cb18f..183ede762 100644
--- a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/service/impl/JudgeServiceImpl.java
+++ b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/service/impl/JudgeServiceImpl.java
@@ -78,10 +78,17 @@ public Judge Judge(Problem problem, Judge judge) {
return judge;
}
- public Boolean compileSpj(String code, Long pid, String spjLanguage) throws CompileError, SystemError {
- return Compiler.compileSpj(code, pid, spjLanguage);
+ @Override
+ public Boolean compileSpj(String code, Long pid, String spjLanguage, HashMap extraFiles) throws SystemError {
+ return Compiler.compileSpj(code, pid, spjLanguage, extraFiles);
}
+ @Override
+ public Boolean compileInteractive(String code, Long pid, String interactiveLanguage, HashMap extraFiles) throws SystemError {
+ return Compiler.compileInteractive(code, pid, interactiveLanguage, extraFiles);
+ }
+
+
@Override
public void updateOtherTable(Long submitId, Integer status, Long cid, String uid, Long pid, Integer score, Integer useTime) {
diff --git a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/util/Constants.java b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/util/Constants.java
index 3dde5df18..629502482 100644
--- a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/util/Constants.java
+++ b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/util/Constants.java
@@ -75,7 +75,7 @@ public enum RemoteJudge {
CF_REMOTE_JUDGE_ACCOUNT("Codeforces Remote Judge Account");
- private String name;
+ private final String name;
RemoteJudge(String remoteJudgeName) {
this.name = remoteJudgeName;
@@ -108,6 +108,30 @@ public String getName() {
}
+ public enum JudgeMode {
+ DEFAULT("default"),
+ SPJ("spj"),
+ INTERACTIVE("interactive");
+
+ private final String mode;
+
+ JudgeMode(String mode) {
+ this.mode = mode;
+ }
+
+ public String getMode() {
+ return mode;
+ }
+
+ public static JudgeMode getJudgeMode(String mode){
+ for (JudgeMode judgeMode : JudgeMode.values()) {
+ if (judgeMode.getMode().equals(mode)) {
+ return judgeMode;
+ }
+ }
+ return null;
+ }
+ }
public enum JudgeDir {
@@ -117,10 +141,12 @@ public enum JudgeDir {
SPJ_WORKPLACE_DIR("/judge/spj"),
+ INTERACTIVE_WORKPLACE_DIR("/judge/interactive"),
+
TMPFS_DIR("/w");
- private String content;
+ private final String content;
JudgeDir(String content) {
this.content = content;
@@ -134,7 +160,7 @@ public String getContent() {
public static List defaultEnv = Arrays.asList(
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=en_US.UTF-8",
- "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8","HOME=/w");
+ "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8", "HOME=/w");
public static List python3Env = Arrays.asList("LANG=en_US.UTF-8",
"LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8", "PYTHONIOENCODING=utf-8");
@@ -143,7 +169,6 @@ public String getContent() {
"GOCACHE=off", "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"LANG=en_US.UTF-8", "LANGUAGE=en_US:en", "LC_ALL=en_US.UTF-8");
- public static List javaEnv = Arrays.asList("HOME=/w");
/*
{0} --> tmpfs_dir
{1} --> srcName
@@ -158,7 +183,7 @@ public enum CompileConfig {
CPPWithO2("C++ With O2", "main.cpp", "main", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv),
- JAVA("Java", "Main.java", "Main.jar", 10000L, 20000L, 512 * 1024 * 1024L, "/bin/bash -c \"javac -encoding utf8 {1} && jar -cvf {2} *.class\"", javaEnv),
+ JAVA("Java", "Main.java", "Main.jar", 10000L, 20000L, 512 * 1024 * 1024L, "/bin/bash -c \"javac -encoding utf8 {1} && jar -cvf {2} *.class\"", defaultEnv),
PYTHON2("Python2", "main.py", "main.pyc", 3000L, 10000L, 128 * 1024 * 1024L, "/usr/bin/python -m py_compile ./{1}", defaultEnv),
@@ -166,20 +191,24 @@ public enum CompileConfig {
GOLANG("Golang", "main.go", "main", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/go build -o {2} {1}", defaultEnv),
- CS("C#","Main.cs","main",5000L,10000L,512 * 1024 * 1024L,"/usr/bin/mcs -optimize+ -out:{0}/{2} {0}/{1}",defaultEnv),
+ CS("C#", "Main.cs", "main", 5000L, 10000L, 512 * 1024 * 1024L, "/usr/bin/mcs -optimize+ -out:{0}/{2} {0}/{1}", defaultEnv),
SPJ_C("SPJ-C", "spj.c", "spj", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}", defaultEnv),
- SPJ_CPP("SPJ-C++", "spj.cpp", "spj", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv);
+ SPJ_CPP("SPJ-C++", "spj.cpp", "spj", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv),
- private String language;
- private String srcName;
- private String exeName;
- private Long maxCpuTime;
- private Long maxRealTime;
- private Long maxMemory;
- private String command;
- private List envs;
+ INTERACTIVE_C("INTERACTIVE-C", "interactive.c", "interactive", 3000L, 5000L, 512 * 1024 * 1024L, "/usr/bin/gcc -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c99 {1} -lm -o {2}", defaultEnv),
+
+ INTERACTIVE_CPP("INTERACTIVE-C++", "interactive.cpp", "interactive", 10000L, 20000L, 512 * 1024 * 1024L, "/usr/bin/g++ -DONLINE_JUDGE -O2 -w -fmax-errors=3 -std=c++14 {1} -lm -o {2}", defaultEnv);
+
+ private final String language;
+ private final String srcName;
+ private final String exeName;
+ private final Long maxCpuTime;
+ private final Long maxRealTime;
+ private final Long maxMemory;
+ private final String command;
+ private final List envs;
CompileConfig(String language, String srcName, String exeName, Long maxCpuTime, Long maxRealTime, Long maxMemory,
String command, List envs) {
@@ -264,12 +293,16 @@ public enum RunConfig {
SPJ_C("SPJ-C", "{0}/{1} {2} {3} {4}", "spj", defaultEnv),
- SPJ_CPP("SPJ-C++", "{0}/{1} {2} {3} {4}", "spj", defaultEnv);
+ SPJ_CPP("SPJ-C++", "{0}/{1} {2} {3} {4}", "spj", defaultEnv),
+
+ INTERACTIVE_C("INTERACTIVE-C", "{0}/{1} {2} {3} {4}", "interactive", defaultEnv),
+
+ INTERACTIVE_CPP("INTERACTIVE-C++", "{0}/{1} {2} {3} {4}", "interactive", defaultEnv);
- private String language;
- private String command;
- private String exeName;
- private List envs;
+ private final String language;
+ private final String command;
+ private final String exeName;
+ private final List envs;
RunConfig(String language, String command, String exeName, List envs) {
this.language = language;
diff --git a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/util/JudgeUtils.java b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/util/JudgeUtils.java
index 9b112ba6d..a3a006600 100644
--- a/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/util/JudgeUtils.java
+++ b/hoj-springboot/JudgeServer/src/main/java/top/hcode/hoj/util/JudgeUtils.java
@@ -1,8 +1,11 @@
package top.hcode.hoj.util;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.StringTokenizer;
+import cn.hutool.json.JSONUtil;
+import org.springframework.util.StringUtils;
+import top.hcode.hoj.pojo.entity.problem.Problem;
+
+import java.io.File;
+import java.util.*;
/**
* @Author: Himit_ZH
@@ -11,7 +14,21 @@
*/
public class JudgeUtils {
- public static List translateCommandline(String toProcess){
+ @SuppressWarnings("All")
+ public static HashMap getProblemExtraFileMap(Problem problem, String type) {
+ if ("user".equals(type)) {
+ if (!StringUtils.isEmpty(problem.getUserExtraFile())) {
+ return (HashMap) JSONUtil.toBean(problem.getUserExtraFile(), Map.class);
+ }
+ } else if ("judge".equals(type)) {
+ if (!StringUtils.isEmpty(problem.getJudgeExtraFile())) {
+ return (HashMap) JSONUtil.toBean(problem.getJudgeExtraFile(), Map.class);
+ }
+ }
+ return null;
+ }
+
+ public static List translateCommandline(String toProcess) {
if (toProcess != null && !toProcess.isEmpty()) {
int state = 0;
StringTokenizer tok = new StringTokenizer(toProcess, "\"' ", true);
@@ -19,10 +36,10 @@ public static List translateCommandline(String toProcess){
StringBuilder current = new StringBuilder();
boolean lastTokenHasBeenQuoted = false;
- while(true) {
- while(tok.hasMoreTokens()) {
+ while (true) {
+ while (tok.hasMoreTokens()) {
String nextTok = tok.nextToken();
- switch(state) {
+ switch (state) {
case 1:
if ("'".equals(nextTok)) {
lastTokenHasBeenQuoted = true;
@@ -71,4 +88,5 @@ public static List translateCommandline(String toProcess){
return new ArrayList<>();
}
}
+
}
\ No newline at end of file
diff --git a/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/judge/CompileDTO.java b/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/judge/CompileDTO.java
new file mode 100644
index 000000000..897358b65
--- /dev/null
+++ b/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/judge/CompileDTO.java
@@ -0,0 +1,40 @@
+package top.hcode.hoj.pojo.entity.judge;
+
+import lombok.Data;
+
+import java.util.HashMap;
+
+
+/**
+ * @Author: Himit_ZH
+ * @Date: 2021/2/6 14:42
+ * @Description:
+ */
+@Data
+public class CompileDTO {
+
+ /**
+ * 编译的源代码
+ */
+ private String code;
+
+ /**
+ * 编译的源代码相关的题目id
+ */
+ private Long pid;
+
+ /**
+ * 编译的源代码所选语言
+ */
+ private String language;
+
+ /**
+ * 调用判题机的凭证
+ */
+ private String token;
+
+ /**
+ * 编译所需的额外文件,key:文件名,value:文件内容
+ */
+ private HashMap extraFiles;
+}
\ No newline at end of file
diff --git a/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/judge/CompileSpj.java b/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/judge/CompileSpj.java
deleted file mode 100644
index f74dc0bf8..000000000
--- a/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/judge/CompileSpj.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package top.hcode.hoj.pojo.entity.judge;
-
-import lombok.Data;
-
-
-/**
- * @Author: Himit_ZH
- * @Date: 2021/2/6 14:42
- * @Description:
- */
-@Data
-public class CompileSpj {
-
- private String spjSrc;
-
- private Long pid;
-
- private String spjLanguage;
-
- private String token;
-}
\ No newline at end of file
diff --git a/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/problem/Problem.java b/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/problem/Problem.java
index 9461ef64e..08653af13 100644
--- a/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/problem/Problem.java
+++ b/hoj-springboot/api/src/main/java/top/hcode/hoj/pojo/entity/problem/Problem.java
@@ -42,6 +42,9 @@ public class Problem implements Serializable {
@ApiModelProperty(value = "0为ACM,1为OI")
private Integer type;
+ @ApiModelProperty(value = "default,spj,interactive")
+ private String judgeMode;
+
@ApiModelProperty(value = "单位ms")
private Integer timeLimit;
@@ -78,20 +81,28 @@ public class Problem implements Serializable {
@ApiModelProperty(value = "默认为1公开,2为私有,3为比赛中")
private Integer auth;
- @ApiModelProperty(value = "当该题目为io题目时的分数")
+ @ApiModelProperty(value = "当该题目为oi题目时的分数")
private Integer ioScore;
@ApiModelProperty(value = "该题目对应的相关提交代码,用户是否可用分享")
private Boolean codeShare;
- @ApiModelProperty(value = "特判程序的代码 空代表无特判")
+ @ApiModelProperty(value = "特判程序或交互程序的代码")
@TableField(value="spj_code",updateStrategy = FieldStrategy.IGNORED)
private String spjCode;
- @ApiModelProperty(value = "特判程序的语言")
+ @ApiModelProperty(value = "特判程序或交互程序的语言")
@TableField(value="spj_language",updateStrategy = FieldStrategy.IGNORED)
private String spjLanguage;
+ @ApiModelProperty(value = "特判程序或交互程序的额外文件 json key:name value:content")
+ @TableField(value="user_extra_file",updateStrategy = FieldStrategy.IGNORED)
+ private String userExtraFile;
+
+ @ApiModelProperty(value = "特判程序或交互程序的额外文件 json key:name value:content")
+ @TableField(value="judge_extra_file",updateStrategy = FieldStrategy.IGNORED)
+ private String judgeExtraFile;
+
@ApiModelProperty(value = "是否默认去除用户代码的每行末尾空白符")
private Boolean isRemoveEndBlank;
diff --git a/hoj-vue/src/App.vue b/hoj-vue/src/App.vue
index 2ba928c8e..2b0bafd4e 100644
--- a/hoj-vue/src/App.vue
+++ b/hoj-vue/src/App.vue
@@ -689,7 +689,7 @@ footer h1 {
box-shadow: inset 0 0 12px rgb(219 219 219);
}
.markdown-body p {
- font-size: 14px;
+ font-size: 15px;
word-wrap: break-word;
word-break: break-word;
line-height: 1.8;
diff --git a/hoj-vue/src/common/api.js b/hoj-vue/src/common/api.js
index cfbedcd59..b32a1c6bc 100644
--- a/hoj-vue/src/common/api.js
+++ b/hoj-vue/src/common/api.js
@@ -1007,6 +1007,11 @@ const adminApi = {
data
})
},
+ compileInteractive(data){
+ return ajax('/api/admin/problem/compile-interactive', 'post', {
+ data
+ })
+ },
admin_addTag (data) {
return ajax('/api/admin/tag', 'post', {
diff --git a/hoj-vue/src/components/admin/Accordion.vue b/hoj-vue/src/components/admin/Accordion.vue
index d1d8e5427..d478fb016 100644
--- a/hoj-vue/src/components/admin/Accordion.vue
+++ b/hoj-vue/src/components/admin/Accordion.vue
@@ -47,7 +47,7 @@ export default {
diff --git a/hoj-vue/src/components/admin/CodeMirror.vue b/hoj-vue/src/components/admin/CodeMirror.vue
index 209b35e89..2eb7e8014 100644
--- a/hoj-vue/src/components/admin/CodeMirror.vue
+++ b/hoj-vue/src/components/admin/CodeMirror.vue
@@ -32,7 +32,7 @@ export default {
return {
currentValue: '',
options: {
- mode: 'text/x-csrc',
+ mode: 'text/x-c++src',
lineNumbers: true,
lineWrapping: false,
theme: 'solarized',
@@ -56,11 +56,11 @@ export default {
props: {
value: {
type: String,
- default: 'C',
+ default: '',
},
mode: {
type: String,
- default: 'text/x-csrc',
+ default: 'text/x-c++src',
},
},
mounted() {
@@ -102,6 +102,6 @@ export default {
.CodeMirror-scroll {
min-height: 300px;
- max-height: 1000px;
+ max-height: 600px;
}
diff --git a/hoj-vue/src/components/admin/Panel.vue b/hoj-vue/src/components/admin/Panel.vue
deleted file mode 100644
index 32142eebb..000000000
--- a/hoj-vue/src/components/admin/Panel.vue
+++ /dev/null
@@ -1,96 +0,0 @@
-
-
-
-
-
-
-
-
- {{title}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/hoj-vue/src/components/oj/common/CodeMirror.vue b/hoj-vue/src/components/oj/common/CodeMirror.vue
index 353a3272f..dfd4521bd 100644
--- a/hoj-vue/src/components/oj/common/CodeMirror.vue
+++ b/hoj-vue/src/components/oj/common/CodeMirror.vue
@@ -168,7 +168,6 @@ export default {
showCursorWhenSelecting: true,
highlightSelectionMatches: { showToken: /\w/, annotateScrollbar: true },
// extraKeys: { Ctrl: 'autocomplete' }, //自定义快捷键
- autoFocus: true,
matchBrackets: true, //括号匹配
indentUnit: 4, //一个块(编辑语言中的含义)应缩进多少个空格
styleActiveLine: true,
diff --git a/hoj-vue/src/i18n/admin/en-US.js b/hoj-vue/src/i18n/admin/en-US.js
index 87f57323a..ff0b9f44e 100644
--- a/hoj-vue/src/i18n/admin/en-US.js
+++ b/hoj-vue/src/i18n/admin/en-US.js
@@ -194,13 +194,18 @@ export const m = {
Example_Input:'Example Input',
Example_Output:'Example Output',
Add_Example: 'Add Example',
- Special_Judge: 'Special Judge',
- Special_Judge_Code: 'Special Judge Code',
- Special_Judge_Tips1:'Why use special judge?',
- Special_Judge_Tips2:'The output required by the problem may not be unique, and different results are allowed.',
- Special_Judge_Tips3:'The output within a certain precision range is acceptable.',
+ Judge_Mode:'Judge Mode',
+ General_Judge:'General Judge',
+ Special_Judge:'Special Judge',
+ Interactive_Judge:'Interactive Judge',
+ Special_Judge_Code: 'Special Judge Program Code',
+ Interactive_Judge_Code:'Interactive Judge Program Code',
+ General_Judge_Mode_Tips:'1. General Judge: the contestant program reads the problem standard input file, executes the code logic to obtain the contestant\'s output, and compares the contents of the problem standard output file to obtain the problem judgment result',
+ Special_Judge_Mode_Tips:'2. Special Judge: the output results required by the problem may not be unique, and different results are allowed. Therefore, a special program is needed to read standard output, player output and standard input, and compare them to obtain the final judgment result',
+ Interactive_Judge_Mode_Tips:'3. Interactive Judge: the standard output of the interactive program is written to the standard input of the player program through the interactive channel, and the standard output of the player program is written to the standard input of the interactive program through the interactive channel. Both need to flush the output buffer',
Use_Special_Judge: 'Use Special Judge',
- SPJ_language: 'SPJ language',
+ SPJ_Language: 'SPJ Program Language',
+ Interactive_Language:'Interactive Program Langugae',
Compile: 'Compile',
Code_Template: 'Code Template',
Type: 'Type',
@@ -228,8 +233,13 @@ export const m = {
is_required:'is required!',
Score_must_be_greater_than_or_equal_to_0:'Score must be greater than or equal to 0!',
Score_must_be_an_integer:'Score must be an integer!',
- Spj_Code:'Spj Code',
- Spj_Code_not_Compile_Success:'Spj Code was not compiled successfully, please compile again!',
+ Spj_Or_Interactive_Code:'Spj Or Interactive Code',
+ Spj_Or_Interactive_Code_not_Compile_Success:'Spj Or Interactive Code was not compiled successfully, please compile again!',
+ Judge_Extra_File:'Judge Extra File',
+ Judge_Extra_File_Tips1:'1. User Program: Provide additional library files for user program',
+ Judge_Extra_File_Tips2:'2. Special Or Interactive Program: Provide additional library files for special or interactive programs',
+ User_Program:'User Program',
+ SPJ_Or_Interactive_Program:'Special Or Interactive Program',
// /views/admin/problem/tag
@@ -342,4 +352,9 @@ export const m = {
Content:'Content',
Report_Content:'Report Content',
The_number_of_discussions_selected_cannot_be_empty:'The number of discussions selected cannot be empty',
+
+ // components/admin/AddExtraFile.vue
+ Delete_Extra_File_Tips:'Are you sure you want to delete this extra file?',
+ File_Name:'File Name',
+ File_Content:'File Content'
}
diff --git a/hoj-vue/src/i18n/admin/zh-CN.js b/hoj-vue/src/i18n/admin/zh-CN.js
index 9819597d1..4e0bc0b52 100644
--- a/hoj-vue/src/i18n/admin/zh-CN.js
+++ b/hoj-vue/src/i18n/admin/zh-CN.js
@@ -193,13 +193,17 @@ export const m = {
Example_Input:'样例输入',
Example_Output:'样例输出',
Add_Example: '添加样例',
- Special_Judge: '特殊判题',
- Special_Judge_Code:'特殊判题代码',
- Special_Judge_Tips1:'为什么要使用特殊判题?',
- Special_Judge_Tips2:'题目要求的输出结果可能不唯一,允许不同结果存在。',
- Special_Judge_Tips3:'题目最终要求输出一个浮点数,而且会告诉只要答案和标准答案相差不超过某个较小的数就可以。例如题目要求保留几位小数,输出结果后几位小数不相同也是正确的。',
- Use_Special_Judge: '使用特殊判题',
- SPJ_language: 'SPJ语言',
+ Judge_Mode:'判题模式',
+ General_Judge:'普通判题',
+ Special_Judge:'特殊判题',
+ Interactive_Judge:'交互判题',
+ Special_Judge_Code:'特殊判题程序代码',
+ Interactive_Judge_Code:'交互判题程序代码',
+ General_Judge_Mode_Tips:'1. 普通判题:选手程序读取题目标准输入文件,执行代码逻辑得到选手输出,对比题目标准输出文件内容得到判题结果',
+ Special_Judge_Mode_Tips:'2. 特殊判题:题目要求的输出结果可能不唯一,允许不同结果存在,所以需要一个特殊程序读取标准输出、选手输出和标准输入,进行对比得出最终判题结果',
+ Interactive_Judge_Mode_Tips:'3. 交互判题:交互程序的标准输出通过交互通道写到选手程序标准输入,选手程序的标准输出通过交互通道写到交互程序的标准输入,两者需要刷新输出缓冲区',
+ Interactive_Language:'交互判题程序语言',
+ SPJ_Language: '特殊判题程序语言',
Compile: '编译',
Code_Template: '代码模板',
Type: '类型',
@@ -227,9 +231,13 @@ export const m = {
is_required:'不能为空!',
Score_must_be_greater_than_or_equal_to_0:'分数必须大于0!',
Score_must_be_an_integer:'分数必须是整数!',
- Spj_Code:'Spj代码',
- Spj_Code_not_Compile_Success:'Spj代码没有编译成功,请重新编译!',
-
+ Spj_Or_Interactive_Code:'Spj或交互程序的代码',
+ Spj_Or_Interactive_Code_not_Compile_Success:'Spj或交互程序的代码没有编译成功,请重新编译!',
+ Judge_Extra_File:'评测额外文件',
+ Judge_Extra_File_Tips1:'1. 选手程序:给选手程序提供额外的库文件',
+ Judge_Extra_File_Tips2:'2. 特殊或交互程序:给特殊或交互程序提供额外的库文件',
+ User_Program:'选手程序',
+ SPJ_Or_Interactive_Program:'特殊或交互程序',
// /views/admin/problem/tag
Admin_Tag:'标签管理',
@@ -340,4 +348,9 @@ export const m = {
Content:'内容',
Report_Content:'举报内容',
The_number_of_discussions_selected_cannot_be_empty:'勾选的讨论不能为空',
+
+ // components/admin/AddExtraFile.vue
+ Delete_Extra_File_Tips:'你是否确定要删除该额外文件?',
+ File_Name:'文件名字',
+ File_Content:'文件内容'
}
diff --git a/hoj-vue/src/views/admin/problem/Problem.vue b/hoj-vue/src/views/admin/problem/Problem.vue
index 695b3ffb1..a7fc2efe9 100644
--- a/hoj-vue/src/views/admin/problem/Problem.vue
+++ b/hoj-vue/src/views/admin/problem/Problem.vue
@@ -138,6 +138,11 @@
+
+
+
+
+
@@ -159,6 +164,20 @@
+
+
+
+
+ ACM
+ OI
+
+
+
+
-
+
+
{{ $t('m.Add_Example') }}
+
+
+
+ {{ $t('m.Judge_Extra_File') }}
+
+ {{ $t('m.Judge_Extra_File_Tips1') }}
+ {{ $t('m.Judge_Extra_File_Tips2') }}
+
+
+
+
+
+
+
+ {{
+ $t('m.User_Program')
+ }}
+
+
+
+
+
+
+
+ {{
+ $t('m.SPJ_Or_Interactive_Program')
+ }}
+
+
+
+
+
+
+
+
- {{ $t('m.Special_Judge') }}
+ {{ $t('m.Judge_Mode') }}
- {{ $t('m.Special_Judge_Tips1') }}
- 1. {{ $t('m.Special_Judge_Tips2') }}
- 2. {{ $t('m.Special_Judge_Tips3') }}
+ {{ $t('m.General_Judge_Mode_Tips') }}
+ {{ $t('m.Special_Judge_Mode_Tips') }}
+ {{ $t('m.Interactive_Judge_Mode_Tips') }}
- {{ $t('m.Use_Special_Judge') }}
+
+ {{ $t('m.General_Judge') }}
+ {{ $t('m.Special_Judge') }}
+ {{
+ $t('m.Interactive_Judge')
+ }}
+
-
-
+
+
{{ $t('m.SPJ_language') }}:{{
+ problem.judgeMode == 'spj'
+ ? $t('m.SPJ_Language')
+ : $t('m.Interactive_Language')
+ }}:
-
-
-
-
-
+ {{ $t('m.Code_Template') }}
+
-
-
-
-
- ACM
- OI
-
-
-
-
-
{{ $t('m.Judge_Samples') }}
@@ -582,11 +641,13 @@ import myMessage from '@/common/message';
import { PROBLEM_LEVEL_RESERVE } from '@/common/constants';
const Editor = () => import('@/components/admin/Editor.vue');
const Accordion = () => import('@/components/admin/Accordion.vue');
+const AddExtraFile = () => import('@/components/admin/AddExtraFile.vue');
const CodeMirror = () => import('@/components/admin/CodeMirror.vue');
export default {
name: 'Problem',
components: {
Accordion,
+ AddExtraFile,
CodeMirror,
Editor,
},
@@ -635,7 +696,6 @@ export default {
auth: 1,
codeShare: true,
examples: [], // 题面上的样例输入输出
- spj: false,
spjLanguage: '',
spjCode: '',
spjCompileOk: false,
@@ -647,6 +707,9 @@ export default {
hint: '',
source: '',
cid: null,
+ judgeMode: 'default',
+ userExtraFile: '',
+ judgeExtraFile: '',
},
problemTags: [], //指定问题的标签列表
problemLanguages: [], //指定问题的编程语言列表
@@ -677,6 +740,10 @@ export default {
spjCode: '',
spjLanguage: '',
},
+ addUserExtraFile: false,
+ addJudgeExtraFile: false,
+ userExtraFile: null,
+ judgeExtraFile: null,
};
},
mounted() {
@@ -723,7 +790,6 @@ export default {
auth: 1,
codeShare: true,
examples: [],
- spj: false,
spjLanguage: '',
spjCode: '',
spjCompileOk: false,
@@ -735,6 +801,9 @@ export default {
hint: '',
source: '',
cid: null,
+ judgeMode: 'default',
+ userExtraFile: null,
+ judgeExtraFile: null,
};
this.contestID = contestID;
@@ -829,13 +898,11 @@ export default {
}[this.routeName];
api[funcName](this.pid).then((problemRes) => {
let data = problemRes.data.data;
- (data.spjCompileOk = false),
- (data.uploadTestcaseDir = ''),
- (data.testCaseScore = []);
- data.spj = true;
+ data.spjCompileOk = false;
+ data.uploadTestcaseDir = '';
+ data.testCaseScore = [];
if (!data.spjCode) {
data.spjCode = '';
- data.spj = false;
}
data.spjLanguage = data.spjLanguage || 'C';
this.spjRecord.spjLanguage = data.spjLanguage;
@@ -844,7 +911,14 @@ export default {
this.problem['examples'] = utils.stringToExamples(data.examples);
this.problem['examples'][0]['isOpen'] = true;
this.testCaseUploaded = true;
-
+ if (this.problem.userExtraFile) {
+ this.addUserExtraFile = true;
+ this.userExtraFile = JSON.parse(this.problem.userExtraFile);
+ }
+ if (this.problem.judgeExtraFile) {
+ this.addJudgeExtraFile = true;
+ this.judgeExtraFile = JSON.parse(this.problem.judgeExtraFile);
+ }
api
.admin_getProblemCases(this.pid, this.problem.isUploadCase)
.then((res) => {
@@ -891,7 +965,7 @@ export default {
});
},
- switchSpj() {
+ switchMode(mode) {
if (this.testCaseUploaded) {
this.$confirm(this.$i18n.t('m.Change_Judge_Method'), 'Tips', {
confirmButtonText: this.$i18n.t('m.OK'),
@@ -899,12 +973,12 @@ export default {
type: 'warning',
})
.then(() => {
- this.problem.spj = !this.problem.spj;
+ this.problem.judgeMode = mode;
this.resetTestCase();
})
.catch(() => {});
} else {
- this.problem.spj = !this.problem.spj;
+ this.problem.judgeMode = mode;
}
},
querySearch(queryString, cb) {
@@ -965,6 +1039,34 @@ export default {
);
},
+ deleteFile(type, name) {
+ if (type == 'user') {
+ this.$delete(this.userExtraFile, name);
+ } else {
+ this.$delete(this.judgeExtraFile, name);
+ }
+ },
+
+ upsertFile(type, name, oldname, content) {
+ if (type == 'user') {
+ if (oldname && oldname != name) {
+ this.$delete(this.userExtraFile, oldname);
+ }
+ if (!this.userExtraFile) {
+ this.userExtraFile = {};
+ }
+ this.userExtraFile[name] = content;
+ } else {
+ if (oldname && oldname != name) {
+ this.$delete(this.judgeExtraFile, name);
+ }
+ if (!this.judgeExtraFile) {
+ this.judgeExtraFile = {};
+ }
+ this.judgeExtraFile[name] = content;
+ }
+ },
+
problemTypeChange(type) {
if (type == 1) {
let length = this.problemSamples.length;
@@ -1036,7 +1138,7 @@ export default {
fileList[i].score = averSorce;
}
}
- if (!fileList[i].output && this.problem.spj) {
+ if (!fileList[i].output) {
fileList[i].output = '-';
}
fileList[i].pid = this.problem.id;
@@ -1052,11 +1154,16 @@ export default {
compileSPJ() {
let data = {
pid: this.problem.id,
- spjSrc: this.problem.spjCode,
- spjLanguage: this.problem.spjLanguage,
+ code: this.problem.spjCode,
+ language: this.problem.spjLanguage,
+ extraFiles: this.judgeExtraFile,
};
this.loadingCompile = true;
- api.compileSPJ(data).then(
+ let apiMethodName = 'compileSPJ';
+ if (this.problem.judgeMode == 'interactive') {
+ apiMethodName = 'compileInteractive';
+ }
+ api[apiMethodName](data).then(
(res) => {
this.loadingCompile = false;
this.problem.spjCompileOk = true;
@@ -1218,14 +1325,21 @@ export default {
myMessage.error(this.error.tags);
return;
}
+ let isChangeModeCode =
+ this.spjRecord.spjLanguage != this.problem.spjLanguage ||
+ this.spjRecord.spjCode != this.problem.spjCode;
- if (this.problem.spj) {
+ if (this.problem.judgeMode != 'default') {
if (!this.problem.spjCode) {
this.error.spj =
- this.$i18n.t('m.Spj_Code') + ' ' + this.$i18n.t('m.is_required');
+ this.$i18n.t('m.Spj_Or_Interactive_Code') +
+ ' ' +
+ this.$i18n.t('m.is_required');
myMessage.error(this.error.spj);
- } else if (!this.problem.spjCompileOk) {
- this.error.spj = this.$i18n.t('m.Spj_Code_not_Compile_Success');
+ } else if (!this.problem.spjCompileOk && isChangeModeCode) {
+ this.error.spj = this.$i18n.t(
+ 'm.Spj_Or_Interactive_Code_not_Compile_Success'
+ );
}
if (this.error.spj) {
myMessage.error(this.error.spj);
@@ -1294,21 +1408,31 @@ export default {
}
let problemDto = {}; // 上传给后台的数据
- if (this.problem.spj) {
- if (
- this.spjRecord.spjLanguage != this.problem.spjLanguage ||
- this.spjRecord.spjCode != this.problem.spjCode
- ) {
- problemDto['changeSpj'] = true;
+ if (this.problem.judgeMode != 'default') {
+ if (isChangeModeCode) {
+ problemDto['changeModeCode'] = true;
}
} else {
- // 原本是spj,但现在关闭了
+ // 原本是spj或交互,但现在关闭了
if (!this.spjRecord.spjCode) {
- problemDto['changeSpj'] = true;
+ problemDto['changeModeCode'] = true;
this.problem.spjCode = null;
this.problem.spjLanguage = null;
}
}
+
+ if (this.userExtraFile && Object.keys(this.userExtraFile).length != 0) {
+ this.problem.userExtraFile = JSON.stringify(this.userExtraFile);
+ } else {
+ this.problem.userExtraFile = null;
+ }
+
+ if (this.judgeExtraFile && Object.keys(this.judgeExtraFile).length != 0) {
+ this.problem.judgeExtraFile = JSON.stringify(this.judgeExtraFile);
+ } else {
+ this.problem.judgeExtraFile = null;
+ }
+
problemDto['problem'] = Object.assign({}, this.problem); // 深克隆
problemDto.problem.examples = utils.examplesToString(
this.problem.examples
@@ -1319,7 +1443,7 @@ export default {
problemDto['languages'] = problemLanguageList;
problemDto['isUploadTestCase'] = this.problem.isUploadCase;
problemDto['uploadTestcaseDir'] = this.problem.uploadTestcaseDir;
- problemDto['isSpj'] = this.problem.spj;
+ problemDto['judgeMode'] = this.problem.judgeMode;
// 如果选择上传文件,则使用上传后的结果
if (this.problem.isUploadCase) {
diff --git a/hoj-vue/vue.config.js b/hoj-vue/vue.config.js
index 749716571..1b29a1a29 100644
--- a/hoj-vue/vue.config.js
+++ b/hoj-vue/vue.config.js
@@ -57,7 +57,7 @@ module.exports={
port: 8088, // 开发服务器运行端口号
proxy: {
'/api': { // 以'/api'开头的请求会被代理进行转发
- target: 'http://localhost:6688', // 要发向的后台服务器地址 如果后台服务跑在后台开发人员的机器上,就写成 `http://ip:port` 如 `http:192.168.12.213:8081` ip为后台服务器的ip
+ target: 'https://hdoi.cn', // 要发向的后台服务器地址 如果后台服务跑在后台开发人员的机器上,就写成 `http://ip:port` 如 `http:192.168.12.213:8081` ip为后台服务器的ip
changeOrigin: true
}
},