From 24bacde02532cb876271d69a11861f1354bdabd8 Mon Sep 17 00:00:00 2001 From: Menci Date: Tue, 6 Jun 2017 21:51:27 +0800 Subject: [PATCH] Add support for ACM and IOI contest --- models/contest.js | 8 ++- models/contest_player.js | 93 ++++++++++++++++++++++++++--- models/contest_ranklist.js | 48 ++++++++++----- models/judge_state.js | 17 +++++- modules/contest.js | 36 +++++++++--- modules/problem.js | 6 +- modules/submission.js | 4 ++ views/contest.ejs | 27 ++++++--- views/contest_edit.ejs | 21 +++++++ views/contest_ranklist.ejs | 106 +++++++++++++++++++++++++--------- views/contest_submissions.ejs | 2 +- views/header.ejs | 3 + views/submission_content.ejs | 58 +++++++++---------- 13 files changed, 330 insertions(+), 99 deletions(-) diff --git a/models/contest.js b/models/contest.js index 309a7c7..44be281 100644 --- a/models/contest.js +++ b/models/contest.js @@ -40,6 +40,8 @@ let model = db.define('contest', { key: 'id' } }, + // type: noi, ioi, acm + type: { type: Sequelize.STRING(10) }, information: { type: Sequelize.TEXT }, problems: { type: Sequelize.TEXT }, @@ -71,6 +73,7 @@ class Contest extends Model { title: '', problems: '', information: '', + type: 'noi', start_time: 0, end_time: 0, holder: 0, @@ -88,6 +91,7 @@ class Contest extends Model { } async isAllowedSeeResultBy(user) { + if (this.type === 'acm' || this.type === 'ioi') return true; return (user && (user.is_admin || this.holder_id === user.id)) || !(await this.isRunning()); } @@ -111,6 +115,8 @@ class Contest extends Model { } async newSubmission(judge_state) { + if (judge_state.pending) return; + let problems = await this.getProblems(); if (!problems.includes(judge_state.problem_id)) throw new ErrorMessage('当前比赛中无此题目。'); @@ -130,7 +136,7 @@ class Contest extends Model { await player.save(); await this.loadRelationships(); - await this.ranklist.updatePlayer(player); + await this.ranklist.updatePlayer(this, player); await this.ranklist.save(); } diff --git a/models/contest_player.js b/models/contest_player.js index ea3ab79..5744329 100644 --- a/models/contest_player.js +++ b/models/contest_player.js @@ -24,7 +24,6 @@ let db = syzoj.db; let User = syzoj.model('user'); let Problem = syzoj.model('problem'); -let Contest = syzoj.model('contest'); let model = db.define('contest_player', { id: { type: Sequelize.INTEGER, primaryKey: true, autoIncrement: true }, @@ -64,18 +63,96 @@ class ContestPlayer extends Model { } async loadRelationships() { + let Contest = syzoj.model('contest'); this.user = await User.fromID(this.user_id); this.contest = await Contest.fromID(this.contest_id); } async updateScore(judge_state) { - this.score_details[judge_state.problem_id] = { - score: judge_state.score, - judge_id: judge_state.id - }; - this.score = 0; - for (let x in this.score_details) { - this.score += this.score_details[x].score; + await this.loadRelationships(); + if (this.contest.type === 'ioi') { + if (!this.score_details[judge_state.problem_id]) { + this.score_details[judge_state.problem_id] = { + score: judge_state.score, + judge_id: judge_state.id, + submissions: {} + }; + } + + this.score_details[judge_state.problem_id].submissions[judge_state.id] = { + judge_id: judge_state.id, + score: judge_state.score, + time: judge_state.submit_time + }; + + let arr = Object.values(this.score_details[judge_state.problem_id].submissions); + arr.sort((a, b) => a.time - b.time); + + let maxScoreSubmission = null; + for (let x of arr) { + if (!maxScoreSubmission || x.score >= maxScoreSubmission.score && maxScoreSubmission.score < 100) { + maxScoreSubmission = x; + } + } + + this.score_details[judge_state.problem_id].judge_id = maxScoreSubmission.judge_id; + this.score_details[judge_state.problem_id].score = maxScoreSubmission.score; + this.score_details[judge_state.problem_id].time = maxScoreSubmission.time; + + this.score = 0; + for (let x in this.score_details) { + this.score += this.score_details[x].score; + } + } else if (this.contest.type === 'noi') { + this.score_details[judge_state.problem_id] = { + score: judge_state.score, + judge_id: judge_state.id + }; + this.score = 0; + for (let x in this.score_details) { + this.score += this.score_details[x].score; + } + } else if (this.contest.type === 'acm') { + if (!this.score_details[judge_state.problem_id]) { + this.score_details[judge_state.problem_id] = { + accepted: false, + unacceptedCount: 0, + acceptedTime: 0, + judge_id: 0, + submissions: {} + }; + } + + this.score_details[judge_state.problem_id].submissions[judge_state.id] = { + judge_id: judge_state.id, + accepted: judge_state.status === 'Accepted', + time: judge_state.submit_time + }; + + let arr = Object.values(this.score_details[judge_state.problem_id].submissions); + arr.sort((a, b) => a.time - b.time); + + this.score_details[judge_state.problem_id].unacceptedCount = 0; + this.score_details[judge_state.problem_id].judge_id = 0; + for (let x of arr) { + if (x.accepted) { + this.score_details[judge_state.problem_id].accepted = true; + this.score_details[judge_state.problem_id].acceptedTime = x.time; + this.score_details[judge_state.problem_id].judge_id = x.judge_id; + break; + } else { + this.score_details[judge_state.problem_id].unacceptedCount++; + } + } + + if (!this.score_details[judge_state.problem_id].accepted) { + this.score_details[judge_state.problem_id].judge_id = arr[arr.length - 1].judge_id; + } + + this.score = 0; + for (let x in this.score_details) { + if (this.score_details[x].accepted) this.score++; + } } } diff --git a/models/contest_ranklist.js b/models/contest_ranklist.js index 1587516..7140066 100644 --- a/models/contest_ranklist.js +++ b/models/contest_ranklist.js @@ -50,7 +50,7 @@ class ContestRanklist extends Model { return a; } - async updatePlayer(player) { + async updatePlayer(contest, player) { let players = await this.getPlayers(), newPlayer = true; for (let x of players) { if (x.user_id === player.user_id) { @@ -65,21 +65,41 @@ class ContestRanklist extends Model { let JudgeState = syzoj.model('judge_state'); - for (let player of players) { - player.latest = 0; - for (let i in player.score_details) { - let judge_state = await JudgeState.fromID(player.score_details[i].judge_id); - player.latest = Math.max(player.latest, judge_state.submit_time); + if (contest.type === 'noi' || contest.type === 'ioi') { + for (let player of players) { + player.latest = 0; + for (let i in player.score_details) { + console.log(player.score_details); + let judge_state = await JudgeState.fromID(player.score_details[i].judge_id); + player.latest = Math.max(player.latest, judge_state.submit_time); + } + } + + players.sort((a, b) => { + if (a.score > b.score) return -1; + if (b.score > a.score) return 1; + if (a.latest < b.latest) return -1; + if (a.latest > b.latest) return 1; + return 0; + }); + } else { + for (let player of players) { + player.timeSum = 0; + for (let i in player.score_details) { + if (player.score_details[i].accepted) { + player.timeSum += (player.score_details[i].acceptedTime - contest.start_time) + (player.score_details[i].unacceptedCount * 20 * 60); + } + } } - } - players.sort((a, b) => { - if (a.score > b.score) return -1; - if (b.score > a.score) return 1; - if (a.latest < b.latest) return -1; - if (a.latest > b.latest) return 1; - return 0; - }); + players.sort((a, b) => { + if (a.score > b.score) return -1; + if (b.score > a.score) return 1; + if (a.timeSum < b.timeSum) return -1; + if (a.timeSum > b.timeSum) return 1; + return 0; + }); + } this.ranklist = { player_num: players.length }; for (let i = 0; i < players.length; i++) this.ranklist[i + 1] = players[i].id; diff --git a/models/judge_state.js b/models/judge_state.js index 3bf928e..69bd936 100644 --- a/models/judge_state.js +++ b/models/judge_state.js @@ -107,7 +107,7 @@ class JudgeState extends Model { else if (this.type === 1) { let contest = await Contest.fromID(this.type_info); if (await contest.isRunning()) { - return false; + return contest.type === 'acm' || contest.type === 'ioi'; } else { return true; } @@ -129,6 +129,21 @@ class JudgeState extends Model { } else if (this.type === 2) return false; } + async isAllowedSeeDataBy(user) { + await this.loadRelationships(); + + if (user && (await user.hasPrivilege('manage_problem') || user.id === this.problem.user_id)) return true; + else if (this.type === 0) return this.problem.is_public; + else if (this.type === 1) { + let contest = await Contest.fromID(this.type_info); + if (await contest.isRunning()) { + return false; + } else { + return true; + } + } else if (this.type === 2) return true; + } + async updateResult(result) { this.score = result.score; this.pending = result.pending; diff --git a/modules/contest.js b/modules/contest.js index 244ae2f..3baadf1 100644 --- a/modules/contest.js +++ b/modules/contest.js @@ -91,6 +91,8 @@ app.post('/contest/:id/edit', async (req, res) => { contest.title = req.body.title; if (!Array.isArray(req.body.problems)) req.body.problems = [req.body.problems]; contest.problems = req.body.problems.join('|'); + if (!['noi', 'ioi', 'acm'].includes(req.body.type)) throw new ErrorMessage('无效的赛制。'); + contest.type = req.body.type; contest.information = req.body.information; contest.start_time = syzoj.utils.parseDate(req.body.start_time); contest.end_time = syzoj.utils.parseDate(req.body.end_time); @@ -132,17 +134,35 @@ app.get('/contest/:id', async (req, res) => { problems = problems.map(x => ({ problem: x, status: null, judge_id: null })); if (player) { for (let problem of problems) { - if (player.score_details[problem.problem.id]) { - if (await contest.isRunning()) { - problem.status = true; + if (contest.type === 'noi') { + if (player.score_details[problem.problem.id]) { + if (await contest.isRunning()) { + problem.status = true; + } else { + let judge_state = await JudgeState.fromID(player.score_details[problem.problem.id].judge_id); + problem.status = judge_state.status; + } + problem.judge_id = player.score_details[problem.problem.id].judge_id; } else { + if (contest.isRunning()) { + problem.status = false; + } + } + } else if (contest.type === 'ioi') { + if (player.score_details[problem.problem.id]) { let judge_state = await JudgeState.fromID(player.score_details[problem.problem.id].judge_id); problem.status = judge_state.status; + problem.judge_id = player.score_details[problem.problem.id].judge_id; } - problem.judge_id = player.score_details[problem.problem.id].judge_id; - } else { - if (contest.isRunning()) { - problem.status = false; + } else if (contest.type === 'acm') { + if (player.score_details[problem.problem.id]) { + problem.status = { + accepted: player.score_details[problem.problem.id].accepted, + unacceptedCount: player.score_details[problem.problem.id].unacceptedCount + }; + problem.judge_id = player.score_details[problem.problem.id].judge_id; + } else { + problem.status = null; } } } @@ -221,7 +241,7 @@ app.get('/contest/:id/submissions', async (req, res) => { where.type = 1; where.type_info = contest_id; - if (contest.ended || (res.locals.user && res.locals.user.is_admin)) { + if (contest.ended || contest.type !== 'noi' || (res.locals.user && res.locals.user.is_admin)) { let minScore = parseInt(req.query.min_score); if (isNaN(minScore)) minScore = 0; let maxScore = parseInt(req.query.max_score); diff --git a/modules/problem.js b/modules/problem.js index 2be95d7..5221dda 100644 --- a/modules/problem.js +++ b/modules/problem.js @@ -489,7 +489,7 @@ app.post('/problem/:id/submit', async (req, res) => { problem_id: req.params.id }); - let contest_id = parseInt(req.query.contest_id); + let contest_id = parseInt(req.query.contest_id), redirectToContest = false; if (contest_id) { let contest = await Contest.fromID(contest_id); if (!contest) throw new ErrorMessage('无此比赛。'); @@ -500,6 +500,8 @@ app.post('/problem/:id/submit', async (req, res) => { judge_state.type_info = contest_id; await judge_state.save(); + + if (contest.type === 'noi') redirectToContest = true; } else { if (!await problem.isAllowedUseBy(res.locals.user)) throw new ErrorMessage('您没有权限进行此操作。'); judge_state.type = problem.is_public ? 0 : 2; @@ -513,7 +515,7 @@ app.post('/problem/:id/submit', async (req, res) => { await waiting_judge.save(); - if (contest_id) { + if (redirectToContest) { res.redirect(syzoj.utils.makeUrl(['contest', contest_id])); } else { res.redirect(syzoj.utils.makeUrl(['submission', judge_state.id])); diff --git a/modules/submission.js b/modules/submission.js index 405e56b..81799d7 100644 --- a/modules/submission.js +++ b/modules/submission.js @@ -54,6 +54,7 @@ app.get('/submissions', async (req, res) => { await judge_state.forEachAsync(async obj => obj.loadRelationships()); await judge_state.forEachAsync(async obj => obj.hidden = !(await obj.isAllowedSeeResultBy(res.locals.user))); await judge_state.forEachAsync(async obj => obj.allowedSeeCode = await obj.isAllowedSeeCodeBy(res.locals.user)); + await judge_state.forEachAsync(async obj => obj.allowedSeeData = await obj.isAllowedSeeDataBy(res.locals.user)); res.render('submissions', { judge_state: judge_state, @@ -77,6 +78,7 @@ app.get('/submissions/:id/ajax', async (req, res) => { judge_state.hidden = !(await judge_state.isAllowedSeeResultBy(res.locals.user)); judge_state.allowedSeeCode = await judge_state.isAllowedSeeCodeBy(res.locals.user); + judge_state.allowedSeeData = await judge_state.isAllowedSeeDataBy(res.locals.user); res.render('submissions_item', { judge: judge_state @@ -103,6 +105,7 @@ app.get('/submission/:id', async (req, res) => { judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); judge.allowedSeeResult = await judge.isAllowedSeeResultBy(res.locals.user); judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user); + judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user); judge.allowedRejudge = await judge.problem.isAllowedEditBy(res.locals.user); if (contest) { @@ -137,6 +140,7 @@ app.get('/submission/:id/ajax', async (req, res) => { judge.code = await syzoj.utils.highlight(judge.code, syzoj.config.languages[judge.language].highlight); judge.allowedSeeResult = await judge.isAllowedSeeResultBy(res.locals.user); judge.allowedSeeCode = await judge.isAllowedSeeCodeBy(res.locals.user); + judge.allowedSeeData = await judge.isAllowedSeeDataBy(res.locals.user); judge.allowedRejudge = await judge.problem.isAllowedEditBy(res.locals.user); if (contest) { diff --git a/views/contest.ejs b/views/contest.ejs index 3ac3efe..04efaa7 100644 --- a/views/contest.ejs +++ b/views/contest.ejs @@ -4,6 +4,7 @@ .ui.label.pointing.below.right::before { left: 88%; } .ui.label.pointing.below.left { margin-bottom: 0; } .ui.label.pointing.below.right { margin-bottom: 0; float: right; } +#back_to_contest { display: none; } <% include header %>
@@ -20,7 +21,7 @@
- <% if (contest.allowedEdit || (unveiled && !contest.running)) { %> + <% if (contest.allowedEdit || (unveiled && (!contest.running || contest.type !== 'noi'))) { %>
+
+ +
+
+ checked="checked"<% } %>> + +
+
+
+
+ checked="checked"<% } %>> + +
+
+
+
+ checked="checked"<% } %>> + +
+
+
diff --git a/views/contest_ranklist.ejs b/views/contest_ranklist.ejs index 6f9a772..4b9b6c6 100644 --- a/views/contest_ranklist.ejs +++ b/views/contest_ranklist.ejs @@ -4,6 +4,7 @@ .submit_time { font-size: 0.8em; margin-top: 5px; + color: #000; }
@@ -12,12 +13,20 @@ # 用户名 - <% for (let problem of problems) { %> - - <%= syzoj.utils.removeTitleTag(problem.title) %> - + <% if (contest.type === 'acm') { %> + 通过数量 + 罚时 + <% } %> + <% for (let i = 0; i < problems.length; i++) { %> + + + <%= syzoj.utils.removeTitleTag(problems[i].title) %> + + + <% } %> + <% if (contest.type === 'noi' || contest.type === 'ioi') { %> + 总分 <% } %> - 总分 @@ -25,7 +34,7 @@ let i = 0; for (let item of ranklist) { i++ - let latest = contest.start_time; + let latest = contest.start_time, timeSum = 0, unacceptedCount = 0; %> @@ -42,30 +51,73 @@
<%= item.user.username %><% if (item.user.nameplate) { %><%- item.user.nameplate %><% } %> + <% + if (contest.type === 'acm') { + for (let problem of problems) { + if (item.player.score_details[problem.id] && item.player.score_details[problem.id].accepted) { + timeSum += (item.player.score_details[problem.id].acceptedTime - contest.start_time) + (item.player.score_details[problem.id].unacceptedCount * 20 * 60); + unacceptedCount += item.player.score_details[problem.id].unacceptedCount; + } + } + %> + + + <%= item.player.score %> + + + + <%= syzoj.utils.formatTime(timeSum) %> + + <% } %> <% for (let problem of problems) { %> - <% if (item.player.score_details[problem.id]) { %> - - - <%= item.player.score_details[problem.id].score %> - - -
- <%= syzoj.utils.formatTime(item.player.score_details[problem.id].judge_state.submit_time - contest.start_time) %> - <% latest = Math.max(latest, item.player.score_details[problem.id].judge_state.submit_time) %> -
- - <% } else { %> - + <% if (!item.player.score_details[problem.id]) { %> + + <% } else if (contest.type === 'acm') { %> + + + <% if (item.player.score_details[problem.id].accepted) { %> + + <% if (item.player.score_details[problem.id].unacceptedCount) { %> + +<%= item.player.score_details[problem.id].unacceptedCount %> + <% } else { %> + + + <% } %> + + +
+ <%= syzoj.utils.formatTime(item.player.score_details[problem.id].acceptedTime - contest.start_time) %> +
+ <% } else { %> + + -<%= item.player.score_details[problem.id].unacceptedCount %> + + <% } %> +
+ + <% } else if (contest.type === 'noi' || contest.type === 'ioi') { %> + + + + <%= item.player.score_details[problem.id].score %> + + +
+ <%= syzoj.utils.formatTime(item.player.score_details[problem.id].judge_state.submit_time - contest.start_time) %> + <% latest = Math.max(latest, item.player.score_details[problem.id].judge_state.submit_time) %> +
+ + <% } %> <% } %> + <% if (contest.type === 'noi' || contest.type === 'ioi') { %> + + + <%= item.player.score %> + +
+ <%= syzoj.utils.formatTime(latest - contest.start_time) %> +
+ <% } %> - - - <%= item.player.score %> - -
- <%= syzoj.utils.formatTime(latest - contest.start_time) %> -
- <% } %> diff --git a/views/contest_submissions.ejs b/views/contest_submissions.ejs index 1046468..9d14797 100644 --- a/views/contest_submissions.ejs +++ b/views/contest_submissions.ejs @@ -7,7 +7,7 @@
- <% if (contest.ended || (user && user.is_admin)) { %> + <% if (contest.ended || contest.type !== 'noi' || (user && user.is_admin)) { %>
diff --git a/views/header.ejs b/views/header.ejs index 5f6c1f4..3fad227 100644 --- a/views/header.ejs +++ b/views/header.ejs @@ -24,6 +24,9 @@ 排名 讨论 帮助 + <% if (typeof contest !== 'undefined' && contest && contest.id) { %> + 返回比赛 + <% } %>