Browse Source

Add non-traditional problem support; add problem additional_file

pull/6/head
Menci 8 years ago
parent
commit
6bb873810e
  1. 115
      models/file.js
  2. 21
      models/judge_state.js
  3. 109
      models/problem.js
  4. 64
      models/testdata.js
  5. 52
      modules/api.js
  6. 73
      modules/problem.js
  7. 17
      modules/submission.js
  8. 0
      uploads/additional_file/.gitkeep
  9. 0
      uploads/answer/.gitkeep
  10. 0
      uploads/testdata/.gitkeep
  11. 2
      utility.js
  12. 124
      views/problem.ejs
  13. 153
      views/problem_data.ejs
  14. 34
      views/statistics.ejs
  15. 29
      views/submission_content.ejs
  16. 3
      views/submissions.ejs
  17. 5
      views/submissions_item.ejs

115
models/file.js

@ -0,0 +1,115 @@
/*
* This file is part of SYZOJ.
*
* Copyright (c) 2016 Menci <huanghaorui301@gmail.com>
*
* SYZOJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* SYZOJ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with SYZOJ. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
let Sequelize = require('sequelize');
let db = syzoj.db;
let model = db.define('file', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
type: { type: Sequelize.STRING(80) },
md5: { type: Sequelize.STRING(80), unique: true }
}, {
timestamps: false,
tableName: 'file',
indexes: [
{
fields: ['type'],
},
{
fields: ['md5'],
}
]
});
let Model = require('./common');
class File extends Model {
static create(val) {
return File.fromRecord(File.model.build(Object.assign({
type: '',
md5: ''
}, val)));
}
getPath() {
return File.resolvePath(this.type, this.md5);
}
static resolvePath(type, md5) {
return syzoj.utils.resolvePath(syzoj.config.upload_dir, type, md5);
}
static async upload(path, type) {
let fs = Promise.promisifyAll(require('fs-extra'));
let buf = await fs.readFileAsync(path);
if (buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('数据包太大。');
try {
let AdmZip = require('adm-zip');
let zip = new AdmZip(buf);
this.unzipSize = 0;
for (let x of zip.getEntries()) this.unzipSize += x.header.size;
} catch (e) {
this.unzipSize = null;
}
let key = syzoj.utils.md5(buf);
await fs.moveAsync(path, File.resolvePath(type, key), { overwrite: true });
let file = await File.findOne({ where: { md5: key } });
if (!file) {
file = await File.create({
type: type,
md5: key
});
await file.save();
}
return file;
}
async getUnzipSize() {
if (this.unzipSize === undefined) {
try {
let fs = Promise.promisifyAll(require('fs-extra'));
let buf = await fs.readFileAsync(this.getPath());
let AdmZip = require('adm-zip');
let zip = new AdmZip(buf);
this.unzipSize = 0;
for (let x of zip.getEntries()) this.unzipSize += x.header.size;
} catch (e) {
this.unzipSize = null;
}
}
if (this.unzipSize === null) throw new ErrorMessage('无效的 ZIP 文件。');
else return this.unzipSize;
}
getModel() { return model; }
}
File.model = model;
module.exports = File;

21
models/judge_state.js

@ -28,6 +28,8 @@ let Contest = syzoj.model('contest');
let model = db.define('judge_state', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
// The data zip's md5 if it's a submit-answer problem
code: { type: Sequelize.TEXT('medium') },
language: { type: Sequelize.STRING(20) },
@ -163,8 +165,11 @@ class JudgeState extends Model {
this.score = result.score;
this.pending = result.pending;
this.status = result.status;
this.total_time = result.total_time;
this.max_memory = result.max_memory;
if (this.language) {
// language is empty if it's a submit-answer problem
this.total_time = result.total_time;
this.max_memory = result.max_memory;
}
this.result = result;
}
@ -203,8 +208,11 @@ class JudgeState extends Model {
this.status = 'Waiting';
this.score = 0;
this.total_time = 0;
this.max_memory = 0;
if (this.language) {
// language is empty if it's a submit-answer problem
this.total_time = 0;
this.max_memory = 0;
}
this.pending = true;
this.result = { status: "Waiting", total_time: 0, max_memory: 0, score: 0, case_num: 0, compiler_output: "", pending: true };
await this.save();
@ -233,6 +241,11 @@ class JudgeState extends Model {
});
}
async getProblemType() {
await this.loadRelationships();
return this.problem.type;
}
getModel() { return model; }
}

109
models/problem.js

@ -144,6 +144,56 @@ FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `submit_time` ASC \
',
min:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`max_memory` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `max_memory` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
',
max:
' \
SELECT \
DISTINCT(`user_id`) AS `user_id`, \
( \
SELECT \
`id` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `id`, \
( \
SELECT \
`max_memory` \
FROM `judge_state` `inner_table` \
WHERE `problem_id` = `outer_table`.`problem_id` AND `user_id` = `outer_table`.`user_id` AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` ASC \
LIMIT 1 \
) AS `max_memory` \
FROM `judge_state` `outer_table` \
WHERE \
`problem_id` = __PROBLEM_ID__ AND `status` = "Accepted" AND `type` = 0 \
ORDER BY `max_memory` DESC \
'
};
@ -151,7 +201,7 @@ let Sequelize = require('sequelize');
let db = syzoj.db;
let User = syzoj.model('user');
let TestData = syzoj.model('testdata');
let File = syzoj.model('file');
let model = db.define('problem', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
@ -182,13 +232,8 @@ let model = db.define('problem', {
time_limit: { type: Sequelize.INTEGER },
memory_limit: { type: Sequelize.INTEGER },
testdata_id: {
type: Sequelize.INTEGER,
references: {
model: 'file',
key: 'id'
}
},
testdata_id: { type: Sequelize.INTEGER },
additional_file_id: { type: Sequelize.INTEGER },
ac_num: { type: Sequelize.INTEGER },
submit_num: { type: Sequelize.INTEGER },
@ -196,7 +241,12 @@ let model = db.define('problem', {
file_io: { type: Sequelize.BOOLEAN },
file_io_input_name: { type: Sequelize.TEXT },
file_io_output_name: { type: Sequelize.TEXT }
file_io_output_name: { type: Sequelize.TEXT },
type: {
type: Sequelize.ENUM,
values: ['traditional', 'submit-answer', 'interaction']
}
}, {
timestamps: false,
tableName: 'problem',
@ -234,14 +284,17 @@ class Problem extends Model {
file_io: false,
file_io_input_name: '',
file_io_output_name: ''
file_io_output_name: '',
type: ''
}, val)));
}
async loadRelationships() {
this.user = await User.fromID(this.user_id);
this.publicizer = await User.fromID(this.publicizer_id);
this.testdata = await TestData.fromID(this.testdata_id);
this.testdata = await File.fromID(this.testdata_id);
this.additional_file = await File.fromID(this.additional_file_id);
}
async isAllowedEditBy(user) {
@ -263,36 +316,15 @@ class Problem extends Model {
return user.is_admin;
}
async updateTestdata(path) {
let fs = Promise.promisifyAll(require('fs-extra'));
let buf = await fs.readFileAsync(path);
async updateFile(path, type) {
let file = await File.upload(path, type);
if (buf.length > syzoj.config.limit.data_size) throw new ErrorMessage('测试数据太大。');
let key = syzoj.utils.md5(buf);
await fs.moveAsync(path, TestData.resolvePath(key), { overwrite: true });
if (this.testdata_id) {
let tmp = this.testdata_id;
this.testdata_id = null;
await this.save();
let file = await TestData.fromID(tmp);
if (file) await file.destroy();
if (type === 'testdata') {
this.testdata_id = file.id;
} else if (type === 'additional_file') {
this.additional_file_id = file.id;
}
let filename = `test_data_${this.id}.zip`;
let file = await TestData.findOne({ where: { filename: filename } });
if (file) await file.destroy();
file = await TestData.create({
filename: filename,
md5: key
});
await file.save();
this.testdata_id = file.id;
await this.save();
}
@ -453,7 +485,6 @@ class Problem extends Model {
await db.query('UPDATE `problem` SET `id` = ' + id + ' WHERE `id` = ' + this.id);
await db.query('UPDATE `judge_state` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await db.query('UPDATE `problem_tag_map` SET `problem_id` = ' + id + ' WHERE `problem_id` = ' + this.id);
await db.query('UPDATE `file` SET `filename` = ' + `"test_data_${id}.zip"` + ' WHERE `filename` = ' + `"test_data_${this.id}.zip"`);
let Contest = syzoj.model('contest');
let contests = await Contest.all();

64
models/testdata.js

@ -1,64 +0,0 @@
/*
* This file is part of SYZOJ.
*
* Copyright (c) 2016 Menci <huanghaorui301@gmail.com>
*
* SYZOJ is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* SYZOJ is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public
* License along with SYZOJ. If not, see <http://www.gnu.org/licenses/>.
*/
'use strict';
let Sequelize = require('sequelize');
let db = syzoj.db;
let model = db.define('file', {
id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true },
filename: { type: Sequelize.STRING(80), unique: true },
md5: { type: Sequelize.STRING(80), unique: true }
}, {
timestamps: false,
tableName: 'file',
indexes: [
{
fields: ['filename'],
},
{
fields: ['md5'],
}
]
});
let Model = require('./common');
class TestData extends Model {
static create(val) {
return TestData.fromRecord(TestData.model.build(Object.assign({
filename: '',
md5: ''
}, val)));
}
getPath() {
return TestData.resolvePath(this.md5);
}
static resolvePath(md5) {
return syzoj.utils.resolvePath(syzoj.config.upload_dir, 'testdata', md5);
}
getModel() { return model; }
}
TestData.model = model;
module.exports = TestData;

52
modules/api.js

@ -23,7 +23,7 @@ let User = syzoj.model('user');
let Problem = syzoj.model('problem');
let WaitingJudge = syzoj.model('waiting_judge');
let JudgeState = syzoj.model('judge_state');
let TestData = syzoj.model('testdata');
let File = syzoj.model('file');
function setLoginCookie(username, password, res) {
res.cookie('login', JSON.stringify([username, password]));
@ -109,18 +109,32 @@ app.get('/api/waiting_judge', async (req, res) => {
});
if (judge_state) {
res.send({
have_task: 1,
judge_id: judge_state.id,
code: judge_state.code,
language: judge_state.language,
testdata: judge_state.problem.testdata ? judge_state.problem.testdata.md5 : '',
time_limit: judge_state.problem.time_limit,
memory_limit: judge_state.problem.memory_limit,
file_io: judge_state.problem.file_io,
file_io_input_name: judge_state.problem.file_io_input_name,
file_io_output_name: judge_state.problem.file_io_output_name
});
await judge_state.loadRelationships();
await judge_state.problem.loadRelationships();
if (judge_state.problem.type === 'submit-answer') {
res.send({
have_task: 1,
judge_id: judge_state.id,
answer_file: judge_state.code,
testdata: judge_state.problem.testdata ? judge_state.problem.testdata.md5 : '',
problem_type: judge_state.problem.type
});
} else {
res.send({
have_task: 1,
judge_id: judge_state.id,
code: judge_state.code,
language: judge_state.language,
testdata: judge_state.problem.testdata ? judge_state.problem.testdata.md5 : '',
time_limit: judge_state.problem.time_limit,
memory_limit: judge_state.problem.memory_limit,
file_io: judge_state.problem.file_io,
file_io_input_name: judge_state.problem.file_io_input_name,
file_io_output_name: judge_state.problem.file_io_output_name,
problem_type: judge_state.problem.type
});
}
} else {
res.send({ have_task: 0 });
}
@ -145,9 +159,17 @@ app.post('/api/update_judge/:id', async (req, res) => {
}
});
app.get('/static/uploads/:md5', async (req, res) => {
app.get('/static/uploads/testdata/:md5', async (req, res) => {
try {
res.sendFile(File.resolvePath('testdata', req.params.md5));
} catch (e) {
res.status(500).send(e);
}
});
app.get('/static/uploads/answer/:md5', async (req, res) => {
try {
res.sendFile(TestData.resolvePath(req.params.md5));
res.sendFile(File.resolvePath('answer', req.params.md5));
} catch (e) {
res.status(500).send(e);
}

73
modules/problem.js

@ -464,7 +464,7 @@ app.get('/problem/:id/data', async (req, res) => {
}
});
app.post('/problem/:id/data', app.multer.single('testdata'), async (req, res) => {
app.post('/problem/:id/data', app.multer.fields([{ name: 'testdata', maxCount: 1 }, { name: 'additional_file', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
@ -480,11 +480,22 @@ app.post('/problem/:id/data', app.multer.single('testdata'), async (req, res) =>
problem.file_io_input_name = req.body.file_io_input_name;
problem.file_io_output_name = req.body.file_io_output_name;
if (problem.type === 'submit-answer' && req.body.type !== 'submit-answer' || problem.type !== 'submit-answer' && req.body.type === 'submit-answer') {
if (await JudgeState.count({ problem_id: id }) !== 0) {
throw new ErrorMessage('已有提交的题目不允许在提交答案和非提交答案之间更改。');
}
}
problem.type = req.body.type;
let validateMsg = await problem.validate();
if (validateMsg) throw new ErrorMessage('无效的题目数据配置。', null, validateMsg);
if (req.file) {
await problem.updateTestdata(req.file.path);
if (req.files['testdata']) {
await problem.updateFile(req.files['testdata'][0].path, 'testdata');
}
if (req.files['additional_file']) {
await problem.updateFile(req.files['additional_file'][0].path, 'additional_file');
}
await problem.save();
@ -529,21 +540,37 @@ app.get('/problem/:id/dis_public', async (req, res) => {
await setPublic(req, res, false);
});
app.post('/problem/:id/submit', async (req, res) => {
app.post('/problem/:id/submit', app.multer.fields([{ name: 'answer', maxCount: 1 }]), async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!syzoj.config.languages[req.body.language]) throw new ErrorMessage('不支持该语言。');
if (problem.type !== 'submit-answer' && !syzoj.config.languages[req.body.language]) throw new ErrorMessage('不支持该语言。');
if (!res.locals.user) throw new ErrorMessage('请登录后继续。', { '登录': syzoj.utils.makeUrl(['login'], { 'url': syzoj.utils.makeUrl(['problem', id]) }) });
let judge_state = await JudgeState.create({
code: req.body.code,
language: req.body.language,
user_id: res.locals.user.id,
problem_id: req.params.id
});
let judge_state;
if (problem.type === 'submit-answer') {
let File = syzoj.model('file');
let file = await File.upload(req.files['answer'][0].path, 'answer');
let size = await file.getUnzipSize();
if (!file.md5) throw new ErrorMessage('上传答案文件失败。');
judge_state = await JudgeState.create({
code: file.md5,
max_memory: size,
language: '',
user_id: res.locals.user.id,
problem_id: req.params.id
});
} else {
judge_state = await JudgeState.create({
code: req.body.code,
language: req.body.language,
user_id: res.locals.user.id,
problem_id: req.params.id
});
}
let contest_id = parseInt(req.query.contest_id), redirectToContest = false;
if (contest_id) {
@ -579,7 +606,7 @@ app.post('/problem/:id/submit', async (req, res) => {
}
});
app.get('/problem/:id/download', async (req, res) => {
app.get('/problem/:id/download/testdata', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
@ -601,6 +628,28 @@ app.get('/problem/:id/download', async (req, res) => {
}
});
app.get('/problem/:id/download/additional_file', async (req, res) => {
try {
let id = parseInt(req.params.id);
let problem = await Problem.fromID(id);
if (!problem) throw new ErrorMessage('无此题目。');
if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。');
await problem.loadRelationships();
if (!problem.additional_file) throw new ErrorMessage('无附加文件。');
res.download(problem.additional_file.getPath(), `additional_file_${id}.zip`);
} catch (e) {
syzoj.log(e);
res.status(404);
res.render('error', {
err: e
});
}
});
app.get('/problem/:id/statistics/:type', async (req, res) => {
try {
let id = parseInt(req.params.id);

17
modules/submission.js

@ -42,7 +42,10 @@ app.get('/submissions', async (req, res) => {
}
};
if (req.query.language) where.language = req.query.language;
if (req.query.language) {
if (req.query.language === 'submit-answer') where.language = '';
else where.language = req.query.language;
}
if (req.query.status) where.status = { $like: req.query.status + '%' };
where.type = { $ne: 1 };
@ -136,8 +139,10 @@ app.get('/submission/:id', async (req, res) => {
await judge.loadRelationships();
judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
if (judge.problem.type !== 'submit-answer') {
judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
}
judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user);
judge.allowedSeeCase = await judge.isAllowedSeeCaseBy(res.locals.user);
judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user);
@ -181,8 +186,10 @@ app.get('/submission/:id/ajax', async (req, res) => {
await judge.loadRelationships();
judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
if (judge.problem.type !== 'submit-answer') {
judge.codeLength = judge.code.length;
judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight);
}
judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user);
judge.allowedSeeCase = await judge.isAllowedSeeCaseBy(res.locals.user);
judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user);

0
uploads/testdata/.placeholder → uploads/additional_file/.gitkeep

0
uploads/answer/.gitkeep

0
uploads/testdata/.gitkeep vendored

2
utility.js

@ -195,7 +195,7 @@ module.exports = {
gravatar(email, size) {
return gravatar.url(email, { s: size, d: 'mm' }).replace('www', 'cn');
},
parseTestData(filename) {
parseTestdata(filename) {
let zip = new AdmZip(filename);
let list = zip.getEntries().filter(e => !e.isDirectory).map(e => e.entryName);
let res = [];

124
views/problem.ejs

@ -88,8 +88,9 @@ if (contest) {
<a class="small ui primary button" href="#submit_code">提交</a>
<a class="small ui positive button" href="<%= syzoj.utils.makeUrl(['submissions'], { problem_id: problem.id }) %>">提交记录</a>
<a class="small ui orange button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'statistics', 'fastest']) %>">统计</a>
<a class="small ui yellow button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download']) %>">下载测试数据</a>
<% if (problem.testdata) { %><a class="small ui yellow button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'testdata']) %>">下载测试数据</a><% } %>
<% } %>
<% if (problem.additional_file) { %><a class="small ui teal button" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'download', 'additional_file']) %>">下载附加文件</a><% } %>
</div>
<% if (!contest) { %>
<div class="ui buttons right floated">
@ -187,71 +188,88 @@ if (contest) {
if (contest) formUrl = syzoj.utils.makeUrl(['problem', problem.id, 'submit'], { contest_id: contest.id });
else formUrl = syzoj.utils.makeUrl(['problem', problem.id, 'submit']);
%>
<form class="ui form" action="<%= formUrl %>" method="post" onsubmit="return submit_code()" id="submit_code">
<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; ">
<%
let language = Object.getOwnPropertyNames(syzoj.config.languages).shift();
if (state) {
language = state.language;
} else if (lastLanguage) language = lastLanguage;
%>
<% for (lang in syzoj.config.languages) { %>
<a class="item<%= lang === language ? ' active' : '' %>" data-value="<%= lang %>" data-mode="<%= syzoj.config.languages[lang].editor %>">
<%= syzoj.config.languages[lang].show %>
<div class="ui right floated" style="opacity: 0.4; margin-top: 8px; font-size: 0.7em; "><%= syzoj.config.languages[lang].version %></div>
</a>
<% } %>
<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">
<div class="field" style="margin: 0 auto; ">
<label for="answer">上传答案(请使用 ZIP 格式压缩)</label>
<input type="file" id="answer" name="answer">
</div>
</div>
<div class="twelve wide stretched column" style="padding-left: 0; margin-left: calc(-1rem - 1px); width: calc(75% + 1rem + 1px + 25px) !important; ">
<div id="editor" style="border: 1px solid #D4D4D5; "><% if (state) { %><%= state.code %><% } %></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; ">
<%
let language = Object.getOwnPropertyNames(syzoj.config.languages).shift();
if (state) {
language = state.language;
} else if (lastLanguage) language = lastLanguage;
%>
<% for (lang in syzoj.config.languages) { %>
<a class="item<%= lang === language ? ' active' : '' %>" data-value="<%= lang %>" data-mode="<%= syzoj.config.languages[lang].editor %>">
<%= syzoj.config.languages[lang].show %>
<div class="ui right floated" style="opacity: 0.4; margin-top: 8px; font-size: 0.7em; "><%= syzoj.config.languages[lang].version %></div>
</a>
<% } %>
</div>
</div>
<div class="twelve wide stretched column" style="padding-left: 0; margin-left: calc(-1rem - 1px); width: calc(75% + 1rem + 1px + 25px) !important; ">
<div id="editor" style="border: 1px solid #D4D4D5; "><% if (state) { %><%= state.code %><% } %></div>
</div>
</div>
</div>
<% } %>
<div class="ui center aligned vertical segment" style="padding-bottom: 0; "><button type="submit" class="ui button">提交</button></div>
</form>
</form>
</div>
</div>
<% } %>
</div>
<script src="/libs/ace/ace.js"></script>
<script type="text/javascript">
var editor = ace.edit("editor");
<% if (problem.type !== 'submit-answer') { %>
<script src="/libs/ace/ace.js"></script>
<script type="text/javascript">
var editor = ace.edit("editor");
editor.setTheme("ace/theme/tomorrow");
editor.getSession().setMode("ace/mode/" + $('#languages-menu .item.active').data('mode'));
editor.getSession().setUseSoftTabs(false);
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();
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();
function submit_code() {
if (!editor.getValue().trim()) return false;
$('#submit_code input[name=language]').val($('#languages-menu .item.active').data('value'));
$('#submit_code input[name=code]').val(editor.getValue());
return true;
}
function submit_code() {
if (!editor.getValue().trim()) return false;
$('#submit_code input[name=language]').val($('#languages-menu .item.active').data('value'));
$('#submit_code input[name=code]').val(editor.getValue());
return true;
}
$('#languages-menu')[0].scrollTop = $('#languages-menu .active')[0].offsetTop - $('#languages-menu')[0].firstElementChild.offsetTop;
$('#languages-menu')[0].scrollTop = $('#languages-menu .active')[0].offsetTop - $('#languages-menu')[0].firstElementChild.offsetTop;
$(function () {
$('#languages-menu .item').click(function() {
$(this)
.addClass('active')
.closest('.ui.menu')
.find('.item')
.not($(this))
.removeClass('active')
;
editor.getSession().setMode("ace/mode/" + $(this).data('mode'));
$(function () {
$('#languages-menu .item').click(function() {
$(this)
.addClass('active')
.closest('.ui.menu')
.find('.item')
.not($(this))
.removeClass('active')
;
editor.getSession().setMode("ace/mode/" + $(this).data('mode'));
});
});
});
</script>
</script>
<% } else { %>
<script>
function submit_code() {
if ($('#answer')[0].files.length === 0) return false;
}
</script>
<% } %>
<% include footer %>

153
views/problem_data.ejs

@ -14,7 +14,7 @@ let subtaskType = {
<% if (problem.testdata) { %>
<%
try {
let list = syzoj.utils.parseTestData(problem.testdata.getPath());
let list = syzoj.utils.parseTestdata(problem.testdata.getPath());
%>
<% if (list.spj) { %>
<p>评测方式:Special Judge</p>
@ -51,76 +51,93 @@ let subtaskType = {
<% } %>
</div>
<div class="nine wide column">
<form class="ui form" method="post" enctype="multipart/form-data">
<div class="two fields">
<div class="field">
<label for="doc-ds-ipt-1">时间限制(单位: ms)</label>
<input type="number" name="time_limit" value="<%= problem.time_limit %>">
</div>
<div class="field">
<label for="doc-ds-ipt-1">内存限制(单位: MiB)</label>
<input type="number" name="memory_limit" value="<%= problem.memory_limit %>">
</div>
<form class="ui form" method="post" enctype="multipart/form-data" onsubmit="return checkSubmit()">
<input type="hidden" name="type" value="<%= problem.type %>">
<div class="ui pointing secondary menu" id="problem-type-tab" style="margin-top: -10px; ">
<a class="<%= problem.type === 'traditional' ? 'active ' : '' %>item" data-tab="traditional">传统</a>
<a class="<%= problem.type === 'interaction' ? 'active ' : '' %>item" data-tab="interaction">交互</a>
<a class="<%= problem.type === 'submit-answer' ? 'active ' : '' %>item" data-tab="submit-answer">提交答案</a>
</div>
<div class="ui <%= problem.type !== 'submit-answer' ? 'active ' : '' %>tab" data-tab="traditional" data-tab="interaction">
<div class="two fields">
<div class="field">
<label for="doc-ds-ipt-1">时间限制(单位: ms)</label>
<input type="number" name="time_limit" value="<%= problem.time_limit %>">
</div>
<% if (!problem.file_io) { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()" checked>
<label for="std-io">标准 IO</label>
<div class="field">
<label for="doc-ds-ipt-1">内存限制(单位: MiB)</label>
<input type="number" name="memory_limit" value="<%= problem.memory_limit %>">
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()">
<label for="file-io">文件 IO</label>
<% if (!problem.file_io) { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()" checked>
<label for="std-io">标准 IO</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()">
<label for="file-io">文件 IO</label>
</div>
</div>
</div>
</div>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>" disabled>
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>" disabled>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>" disabled>
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>" disabled>
</div>
</div>
</div>
<% } else { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()">
<label for="std-io">标准 IO</label>
<% } else { %>
<div class="inline fields">
<label>IO 方式</label>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="std-io" id="std-io" type="radio" onclick="goDisable()">
<label for="std-io">标准 IO</label>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()" checked>
<label for="file-io">文件 IO</label>
</div>
</div>
</div>
<div class="field">
<div class="ui radio checkbox">
<input name="io_method" value="file-io" id="file-io" type="radio" onclick="goEnable()" checked>
<label for="file-io">文件 IO</label>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>">
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>">
</div>
</div>
<% } %>
</div>
<div class="two fields">
<div class="field">
<label for="file_io_input_name">输入文件名</label>
<input type="text" id="file-io-input-name" name="file_io_input_name" value="<%= problem.file_io_input_name %>">
</div>
<div class="field">
<label for="file_io_output_name">输出文件名</label>
<input type="text" id="file-io-output-name" name="file_io_output_name" value="<%= problem.file_io_output_name %>">
</div>
<div class="ui <%= problem.type === 'submit-answer' ? 'active ' : '' %>tab" data-tab="submit-answer" style="margin-bottom: 10px; ">
<b>为了避免系统出错,已有提交的题目不允许在提交答案和非提交答案之间更改。</b><br>
提交答案题目不需要设置时间限制、空间限制以及 IO 方式。<br>
提交时使用的答案文件名需要与测试数据中每个测试点输出文件相同,且后缀名为 <code>.out</code>。
</div>
<% } %>
<div class="field">
<label for="testdata"><% if (!problem.testdata_id) { %>上传测试数据<% } else { %>更新测试数据<% } %></label>
<input type="file" id="testdata" name="testdata">
<label for="testdata">上传测试数据(请使用 ZIP 格式)</label>
<input type="file" id="testdata" name="testdata">
</div>
<button type="submit" class="ui button">提交</button>
<a href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>" class="ui blue button">返回题目</a>
<div class="field">
<label for="additional_file">上传附加文件(请使用 ZIP 格式)</label>
<input type="file" id="additional_file" name="additional_file">
</div>
<button type="submit" class="ui button">提交</button>
<a href="<%= syzoj.utils.makeUrl(['problem', problem.id]) %>" class="ui blue button">返回题目</a>
</form>
</div>
</div>
@ -135,7 +152,7 @@ function goDisable() {
document.getElementById('file-io-output-name').disabled = true;
}
$(document).ready(function () {
$(function () {
$('#file-io-input-name').on('input keyup change', function (e) {
var prob = $('#file-io-input-name').val();
if (prob.lastIndexOf('.') !== -1) prob = prob.substring(0, prob.lastIndexOf('.'));
@ -146,6 +163,26 @@ $(document).ready(function () {
$('#file-io-output-name').val($('#file-io-output-name').attr('placeholder'));
}
});
$('#problem-type-tab .item').tab();
$('a[data-tab="traditional"]').click(function () {
$('input[name=type]').val('traditional');
if ($('div[data-tab="interaction"]').attr('data-tab', 'traditional').length) $('a[data-tab="traditional"]').click();
});
$('a[data-tab="interaction"]').click(function () {
$('input[name=type]').val('interaction');
if ($('div[data-tab="traditional"]').attr('data-tab', 'interaction').length) $('a[data-tab="interaction"]').click();
});
$('a[data-tab="submit-answer"]').click(function () {
$('input[name=type]').val('submit-answer');
});
});
function checkSubmit() {
;
}
</script>
<% include footer %>

34
views/statistics.ejs

@ -1,11 +1,13 @@
<%
this.title = '统计';
let types = {
fastest: '最快',
slowest: '最慢',
shortest: '最短',
longest: '最长',
earliest: '最早'
fastest: problem.type === 'submit-answer' ? null : '最快',
slowest: problem.type === 'submit-answer' ? null : '最慢',
shortest: problem.type === 'submit-answer' ? null : '最短',
longest: problem.type === 'submit-answer' ? null : '最长',
earliest: '最早',
min: problem.type === 'submit-answer' ? '最小' : '最小内存',
max: problem.type === 'submit-answer' ? '最大' : '最大内存'
};
%>
<% include header %>
@ -38,7 +40,7 @@ function getColorOfScore(score) {
<i class="dropdown icon"></i>
<div class="menu">
<% for (let type in types) { %>
<% if (type !== statistics.type) { %>
<% if (type !== statistics.type && types[type] !== null) { %>
<a class="item" href="<%= syzoj.utils.makeUrl(['problem', problem.id, 'statistics', type]) %>"><%= types[type] %></a>
<% } %>
<% } %>
@ -54,9 +56,13 @@ function getColorOfScore(score) {
<th>题目</th>
<th>状态</th>
<th>分数</th>
<th>总时间</th>
<th>内存</th>
<th>代码</th>
<% if (problem.type !== 'submit-answer') { %>
<th>总时间</th>
<th>内存</th>
<th>代码</th>
<% } else { %>
<th>答案文件</th>
<% } %>
<th>提交者</th>
<th>提交时间</th>
</tr>
@ -74,9 +80,13 @@ function getColorOfScore(score) {
</span>
</a></td>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><span class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></span></a></td>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td>
<% if (problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / <%= syzoj.utils.formatSize(judge.code.length) %></td>
<% } else { %>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %></td>
</tr>

29
views/submission_content.ejs

@ -1,3 +1,4 @@
<% include util %>
<%
// Sanitize judge results for backward compatibility and clarity
if (!judge.result.subtasks) {
@ -38,9 +39,13 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<th>分数</th>
<% } %>
<th>总时间</th>
<th>内存</th>
<th>代码</th>
<% if (judge.problem.type !== 'submit-answer') { %>
<th>总时间</th>
<th>内存</th>
<th>代码</th>
<% } else { %>
<th>文件大小</th>
<% } %>
<th>提交者</th>
<th>提交时间</th>
</tr>
@ -56,12 +61,16 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<td class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></td>
<% } %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% if (judge.allowedSeeCode) { %>
<td><%= syzoj.config.languages[judge.language].show %> / <%= syzoj.utils.formatSize(judge.codeLength) %></td>
<% if (judge.problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% if (judge.allowedSeeCode) { %>
<td><%= syzoj.config.languages[judge.language].show %> / <%= syzoj.utils.formatSize(judge.codeLength) %></td>
<% } else { %>
<td><%= syzoj.config.languages[judge.language].show %> / 隐藏 %></td>
<% } %>
<% } else { %>
<td><%= syzoj.config.languages[judge.language].show %> / 隐藏 %></td>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %></td>
@ -76,7 +85,7 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
}
window.applyTextFit();
</script>
<% if (judge.allowedSeeCode) { %>
<% if (judge.problem.type !== 'submit-answer' && judge.allowedSeeCode) { %>
<div class="ui existing segment" style="position: relative; ">
<% if (judge.allowedRejudge) { %>
<a id="rejudge-button" href="<%= syzoj.utils.makeUrl(['submission', judge.id, 'rejudge']) %>" class="ui button" style="position: absolute; top: 0px; right: -4px; border-top-left-radius: 0; border-bottom-right-radius: 0; <% if (judge.pending) { %>display: none; <% } %>">重新评测</a>
@ -96,7 +105,7 @@ else problemUrl = syzoj.utils.makeUrl(['problem', judge.problem_id]);
});
</script>
<% } %>
<% if (judge.result.compiler_output && judge.status === 'Compile Error' && judge.allowedSeeCode) { %>
<% if (judge.problem.type !== 'submit-answer' && judge.result.compiler_output && judge.status === 'Compile Error' && judge.allowedSeeCode) { %>
<h3 class="ui header">编译信息</h3>
<div class="ui existing segment"><pre style="margin-top: 0; margin-bottom: 0; "><code><%- syzoj.utils.ansiToHTML(judge.result.compiler_output) %></code></pre></div>
<% } else if (judge.result.spj_compiler_output) { %>

3
views/submissions.ejs

@ -21,6 +21,7 @@
<div class="default text"></div>
<div class="menu">
<div class="item" data-value="">不限</div>
<div class="item" data-value="submit-answer">提交答案</div>
<% for (let lang in syzoj.config.languages) { %>
<div class="item" data-value="<%= lang %>"><%= syzoj.config.languages[lang].show %></div>
<% } %>
@ -70,7 +71,7 @@
<th>分数</th>
<th>总时间</th>
<th>内存</th>
<th>代码</th>
<th>代码 / 答案文件</th>
<th>提交者</th>
<th>提交时间</th>
</tr>

5
views/submissions_item.ejs

@ -19,6 +19,7 @@ textFit(e, { maxFontSize: 14 });
<% if ((typeof contest === 'undefined' || !contest) || !((!user || !user.is_admin) && !contest.ended && (contest.type === 'acm' || contest.type === 'noi'))) { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><span class="score score_<%= parseInt(judge.result.score / 10) || 0 %>"><%= judge.result.score %></span></a></td>
<% } %>
<% if (judge.problem.type !== 'submit-answer') { %>
<td><%= judge.result.total_time %> ms</td>
<td><%= parseInt(judge.result.max_memory) || 0 %> K</td>
<% if (judge.allowedSeeCode) { %>
@ -26,6 +27,10 @@ textFit(e, { maxFontSize: 14 });
<% } else { %>
<td><a href="<%= syzoj.utils.makeUrl(['submission', judge.id]) %>"><%= syzoj.config.languages[judge.language].show %></a> / 隐藏</td>
<% } %>
<% } else { %>
<td>-</td><td>-</td>
<td><%= syzoj.utils.formatSize(judge.max_memory) %></td>
<% } %>
<td><a href="<%= syzoj.utils.makeUrl(['user', judge.user_id]) %>"><%= judge.user.username %></a><% if (judge.user.nameplate) { %><%- judge.user.nameplate %><% } %></td>
<td><%= syzoj.utils.formatDate(judge.submit_time) %>
<% if (isPending(judge.status)) { %>

Loading…
Cancel
Save