Browse Source

Submit answer

pull/6/head
Menci 8 years ago
parent
commit
62e8153faa
  1. 10
      models/file.js
  2. 2
      models/problem.js
  3. 30
      modules/problem.js
  4. 34
      utility.js
  5. 11
      views/help.ejs
  6. 86
      views/problem.ejs
  7. 12
      views/problem_testcases.ejs

10
models/file.js

@ -56,10 +56,10 @@ class File extends Model {
return syzoj.utils.resolvePath(syzoj.config.upload_dir, type, md5);
}
static async upload(path, type, noLimit) {
static async upload(pathOrData, type, noLimit) {
let fs = Promise.promisifyAll(require('fs-extra'));
let buf = await fs.readFileAsync(path);
let buf = Buffer.isBuffer(pathOrData) ? pathOrData : await fs.readFileAsync(pathOrData);
if (!noLimit && buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('数据包太大。');
@ -73,7 +73,11 @@ class File extends Model {
}
let key = syzoj.utils.md5(buf);
await fs.moveAsync(path, File.resolvePath(type, key), { overwrite: true });
if (Buffer.isBuffer(pathOrData)) {
await fs.writeFileAsync(File.resolvePath(type, key), pathOrData);
} else {
await fs.moveAsync(pathOrData, File.resolvePath(type, key), { overwrite: true });
}
let file = await File.findOne({ where: { md5: key } });
if (!file) {

2
models/problem.js

@ -335,7 +335,7 @@ class Problem extends Model {
await fs.moveAsync(path, dir + '.zip', { overwrite: true });
}
async uploadTestdataSingleFile(filename, filepath, size) {
async uploadTestdataSingleFile(filename, filepath, size, noLimit) {
let dir = this.getTestdataPath();
let fs = Promise.promisifyAll(require('fs-extra')), path = require('path');
await fs.ensureDirAsync(dir);

30
modules/problem.js

@ -190,7 +190,6 @@ app.get('/problem/:id', async (req, res) => {
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user);
problem.allowedManage = await problem.isAllowedManageBy(res.locals.user);
problem.specialJudge = await problem.hasSpecialJudge();
if (problem.is_public || problem.allowedEdit) {
await syzoj.utils.markdown(problem, [ 'description', 'input_format', 'output_format', 'example', 'limit_and_hint' ]);
@ -203,10 +202,13 @@ app.get('/problem/:id', async (req, res) => {
problem.tags = await problem.getTags();
await problem.loadRelationships();
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer');
res.render('problem', {
problem: problem,
state: state,
lastLanguage: res.locals.user ? await res.locals.user.getLastSubmitLanguage() : null
lastLanguage: res.locals.user ? await res.locals.user.getLastSubmitLanguage() : null,
testcases: testcases
});
} catch (e) {
syzoj.log(e);
@ -461,7 +463,7 @@ app.get('/problem/:id/manage', async (req, res) => {
await problem.loadRelationships();
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath());
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer');
res.render('problem_manage', {
problem: problem,
@ -566,11 +568,27 @@ app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1
let judge_state;
if (problem.type === 'submit-answer') {
if (!req.files['answer']) throw new ErrorMessage('请上传答案文件。');
let pathOrData;
if (!req.files['answer']) {
// Submited by editor
try {
let files = JSON.parse(req.body.answer_by_editor);
let AdmZip = require('adm-zip');
let zip = new AdmZip();
for (let file of files) {
zip.addFile(file.filename, file.data);
}
pathOrData = zip.toBuffer();
} catch (e) {
throw new ErrorMessage('无法解析提交数据。');
}
} else {
if (req.files['answer'][0].size > syzoj.config.limit.submit_answer) throw new ErrorMessage('答案文件太大。');
pathOrData = req.files['answer'][0].path;
}
let File = syzoj.model('file');
let file = await File.upload(req.files['answer'][0].path, 'answer');
let file = await File.upload(pathOrData, 'answer');
let size = await file.getUnzipSize();
if (size > syzoj.config.limit.submit_answer) throw new ErrorMessage('答案文件太大。');
@ -645,7 +663,7 @@ app.get('/problem/:id/testdata', async (req, res) => {
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
let testdata = await problem.listTestdata();
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath());
let testcases = await syzoj.utils.parseTestdata(problem.getTestdataPath(), problem.type === 'submit-answer');
problem.allowedEdit = await problem.isAllowedEditBy(res.locals.user)

34
utility.js

@ -196,7 +196,7 @@ module.exports = {
gravatar(email, size) {
return gravatar.url(email, { s: size, d: 'mm' }).replace('www', 'cn');
},
async parseTestdata(dir) {
async parseTestdata(dir, submitAnswer) {
if (!await syzoj.utils.isDir(dir)) return null;
try {
@ -211,17 +211,21 @@ module.exports = {
let parsedName = path.parse(file);
if (parsedName.ext === '.in') {
if (list.includes(`${parsedName.name}.out`)) {
res[0].cases.push({
let o = {
input: file,
output: `${parsedName.name}.out`
});
};
if (submitAnswer) o.answer = `${parsedName.name}.out`;
res[0].cases.push(o);
}
if (list.includes(`${parsedName.name}.ans`)) {
res[0].cases.push({
let o = {
input: file,
output: `${parsedName.name}.ans`
});
};
if (submitAnswer) o.answer = `${parsedName.name}.out`;
res[0].cases.push(o);
}
}
}
@ -241,12 +245,19 @@ module.exports = {
} else {
let lines = (await fs.readFileAsync(path.join(dir, 'data_rule.txt'))).toString().split('\r').join('').split('\n').filter(x => x.length !== 0);
let input, output, answer;
if (submitAnswer) {
if (lines.length < 4) throw '无效的数据配置文件(data_rule.txt)。';
input = lines[lines.length - 3];
output = lines[lines.length - 2];
answer = lines[lines.length - 1];
} else {
if (lines.length < 3) throw '无效的数据配置文件(data_rule.txt)。';
input = lines[lines.length - 2];
output = lines[lines.length - 1];
}
let input = lines[lines.length - 2];
let output = lines[lines.length - 1];
for (let s = 0; s < lines.length - 2; ++s) {
for (let s = 0; s < lines.length - (submitAnswer ? 3 : 2); ++s) {
res[s] = {};
res[s].cases = [];
let numbers = lines[s].split(' ').filter(x => x);
@ -265,6 +276,8 @@ module.exports = {
output: output.replace('#', i)
};
if (submitAnswer) testcase.answer = answer.replace('#', i);
if (!list.includes(testcase.input)) throw `找不到文件 ${testcase.input}`;
if (!list.includes(testcase.output)) throw `找不到文件 ${testcase.output}`;
res[s].cases.push(testcase);
@ -274,9 +287,10 @@ module.exports = {
res = res.filter(x => x.cases && x.cases.length !== 0);
}
res.spj = list.includes('spj.js') || list.some(s => s.startsWith('spj_'));
res.spj = list.some(s => s.startsWith('spj_'));
return res;
} catch (e) {
console.log(e);
return { error: e };
}
},

11
views/help.ejs

@ -60,9 +60,18 @@ output#.out
<p>注意:<span><code>zip</code> 包内<strong>没有</strong>一层文件夹,直接是上述文件。</span>
</p>
<p>如果没有 <code>data_rule.txt</code>,则评测系统会自动将 <code>.in</code> 文件与同名的 <code>.out</code> 或 <code>.ans</code> 匹配。</p>
<p>如果需要配置提交答案题目的数据,请添加额外的一行,表示用户提交的文件名:</p>
<div class="ui existing segment" style="border-radius: 0.285714rem; font-size: 14px;"><pre style="margin-top: 0px; margin-bottom: 0px;"><code>sum:30 1 2 3
min:20 4 5 6
mul:50 7 8 9 10
input#.in
output#.out
submit#.out
</code></pre></div>
<h3 class="ui header">Special Judge</h3>
<p>如果需要使用 Special Judge 评分,请在数据包中添加 <code>spj_LANG.xxx</code>,其中 <code>xxx</code> 为任意后缀名,<code>LANG</code> 为所用语言的简称,可为 <code>c</code>、<code>cpp</code>、<code>cpp11</code>、<code>csharp</code>、<code>haskell</code>、<code>java</code>、<code>lua</code>、<code>nodejs</code>、<code>pascal</code>、<code>python2</code>、<code>python3</code>、<code>ruby</code>、<code>vala</code>、<code>vbnet</code>。</p>
<p>Special Judge 程序运行时,其目录下会有四个文件 <code>input</code>、<code>user_out</code>、<code>answer</code>、<code>code</code>,分别对应该测试点的输入文件、用户输出、该测试点的输出文件、用户的代码。</p>
<p>Special Judge 程序运行时,其目录下会有四个文件 <code>input</code>、<code>user_out</code>、<code>answer</code>、<code>code</code>,分别对应该测试点的输入文件、用户输出、该测试点的输出文件、用户的代码(对于非提交答案题目)。</p>
<p>Special Judge 程序运行完成后,应将该测试点的得分输出到<strong>标准输出</strong>(<code>stdout</code>)中(范围为 0 到 100,将自动折合为测试点分数),并将提供给用户的额外信息输出到<strong>标准错误输出</strong>(<code>stderr</code>)中。</p>
</div>
</div>

86
views/problem.ejs

@ -18,6 +18,7 @@ if (contest) {
background: transparent;
}
</style>
<script src="/libs/ace/ace.js"></script>
<div class="ui center aligned grid">
<div class="row">
<h1 class="ui header">
@ -53,7 +54,7 @@ if (contest) {
<% } %>
</div>
<div class="row" style="margin-top: -23px">
<span class="ui label">评测方式:<%= problem.specialJudge ? 'Special Judge' : '文本比较' %></span>
<span class="ui label">评测方式:<%= testcases.spj ? 'Special Judge' : '文本比较' %></span>
<span class="ui label">题目类型:<%= { 'submit-answer': '答案提交', 'interaction': '交互', 'traditional': '传统' }[problem.type] %></span>
</div>
<div class="row" style="margin-top: -23px">
@ -194,18 +195,84 @@ if (contest) {
%>
<form class="ui form" action="<%= formUrl %>" method="post" onsubmit="return submit_code()" id="submit_code" enctype="multipart/form-data">
<% if (problem.type === 'submit-answer') { %>
<div class="inline fields">
<%
let cases = [];
for (let subtasks of testcases) {
for (let testcase of subtasks.cases) {
cases.push(testcase.answer);
}
}
%>
<script>
var cases = <%- JSON.stringify(cases) %>, currCase = 0;
</script>
<div class="ui grid">
<div class="four wide column" style="margin-right: -25px; ">
<div class="ui attached vertical fluid pointing menu" id="testcase-menu" style="height: 370px; overflow-y: scroll; overflow-x: hidden; ">
<% for (let i = 0; i < cases.length; i++) { %>
<a class="item<%= i === 0 ? ' active' : '' %>" data-value="<%= i %>">
<%= cases[i] %>
</a>
<% } %>
</div>
</div>
<div class="twelve wide stretched column" style="position: relative; padding-left: 0; margin-left: calc(-1rem - 1px); width: calc(75% + 1rem + 12px) !important; ">
<% for (let i = 0; i < cases.length; i++) { %>
<div id="editor-<%= i %>" style="position: absolute; width: 100%; height: calc(100% - 28px); border: 1px solid #D4D4D5; <%= i === 0 ? '' : 'visibility: hidden; ' %>"></div>
<% } %>
</div>
<input id="answer_by_editor" name="answer_by_editor" type="hidden">
<script>
var editors = [];
for (var i = 0; i < cases.length; i++) {
var editor = ace.edit("editor-" + i);
var lastSubmitted = '';
editor.setTheme("ace/theme/tomorrow");
// editor.getSession().setMode("ace/mode/" + $('#languages-menu .item.active').data('mode'));
editor.getSession().setUseSoftTabs(false);
editor.container.style.lineHeight = 1.6;
editor.container.style.fontSize = '14px';
editor.container.style.fontFamily = "'Roboto Mono', 'Bitstream Vera Sans Mono', 'Menlo', 'Consolas', 'Lucida Console', monospace";
editor.setShowPrintMargin(false);
editor.renderer.updateFontSize();
editors[i] = editor;
}
$(function () {
$('#testcase-menu .item').click(function() {
$(this)
.addClass('active')
.closest('.ui.menu')
.find('.item')
.not($(this))
.removeClass('active')
;
var x = $(this).attr('data-value');
if (currCase != x) {
$('#editor-' + currCase).css('visibility', 'hidden');
$('#editor-' + x).css('visibility', 'visible');
currCase = x;
}
});
});
</script>
<div class="inline fields" style="width: 100%; ">
<div class="field" style="margin: 0 auto; ">
<label for="answer">上传答案(请使用 ZIP 格式压缩)</label>
<label for="answer">或者,上传答案(请使用 ZIP 格式压缩)</label>
<input type="file" id="answer" name="answer">
</div>
</div>
</div>
<% } else { %>
<input name="language" type="hidden" id="form">
<input name="code" type="hidden">
<div class="ui grid">
<div class="four wide column" style="margin-right: -25px; ">
<div class="ui attached vertical fluid pointing menu" id="languages-menu" style="max-height: 370px; overflow-y: scroll; overflow-x: hidden; ">
<div class="ui attached vertical fluid pointing menu" id="languages-menu" style="height: 370px; overflow-y: scroll; overflow-x: hidden; ">
<%
let language = Object.getOwnPropertyNames(syzoj.config.languages).shift();
if (state) {
@ -239,7 +306,6 @@ if (contest) {
</div>
<% if (problem.type !== 'submit-answer') { %>
<script src="/libs/ace/ace.js"></script>
<script type="text/javascript">
var editor = ace.edit("editor");
var lastSubmitted = '';
@ -280,7 +346,15 @@ if (contest) {
<% } else { %>
<script>
function submit_code() {
if ($('#answer')[0].files.length === 0) return false;
var a = [];
for (var i = 0; i < cases.length; i++) {
a.push({
filename: cases[i],
data: editors[i].getValue()
});
}
console.log(a);
$('#answer_by_editor').val(JSON.stringify(a));
}
</script>
<% } %>

12
views/problem_testcases.ejs

@ -21,20 +21,28 @@ let subtaskType = {
<% for (let subtask of testcases) { %>
<% if (testcases.length !== 1) { %>
<tr>
<td style="background-color: #F9FAFB" colspan="2"><h4 style="margin-bottom: 3px; ">子任务 <%= ++i %></h4><span style="font-weight: normal; "><%= subtaskType[subtask.type] %>,总分值 <%= subtask.score %></span></th>
<td style="background-color: #F9FAFB" colspan="<%= problem.type === 'submit-answer' ? 3 : 2 %>"><h4 style="margin-bottom: 3px; ">子任务 <%= ++i %></h4><span style="font-weight: normal; "><%= subtaskType[subtask.type] %>,总分值 <%= subtask.score %></span></th>
</tr>
<% } else { %>
<tr>
<td style="background-color: #F9FAFB" colspan="2"><h4 style="margin-bottom: 3px; ">单个子任务</h4><span style="font-weight: normal; "><%= subtaskType[subtask.type] %></span></th>
<td style="background-color: #F9FAFB" colspan="<%= problem.type === 'submit-answer' ? 3 : 2 %>"><h4 style="margin-bottom: 3px; ">单个子任务</h4><span style="font-weight: normal; "><%= subtaskType[subtask.type] %></span></th>
</tr>
<% } %>
<% for (let testcase of subtask.cases) { %>
<% if (problem.type === 'submit-answer') { %>
<tr class="center aligned">
<td style="width: 33%; "><%= testcase.input || '-' %></td>
<td style="width: 33%; "><%= testcase.output || '-' %></td>
<td style="width: 33%; "><%= testcase.answer %></td>
</tr>
<% } else { %>
<tr class="center aligned">
<td style="width: 50%; "><%= testcase.input %></td>
<td style="width: 50%; "><%= testcase.output %></td>
</tr>
<% } %>
<% } %>
<% } %>
</tbody>
</table>
<% } else { %>

Loading…
Cancel
Save