mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-24 20:18:45 +00:00
457 lines
14 KiB
PHP
457 lines
14 KiB
PHP
<?php
|
|
define("CONTEST_NOT_STARTED", 0);
|
|
define("CONTEST_IN_PROGRESS", 1);
|
|
define("CONTEST_PENDING_FINAL_TEST", 2);
|
|
define("CONTEST_TESTING", 10);
|
|
define("CONTEST_FINISHED", 20);
|
|
|
|
function updateContestPlayerNum($contest) {
|
|
DB::update([
|
|
"update contests",
|
|
"set", [
|
|
"player_num" => DB::rawbracket([
|
|
"select count(*) from contests_registrants",
|
|
"where", ["contest_id" => $contest['id']]
|
|
])
|
|
], "where", ["id" => $contest['id']]
|
|
]);
|
|
}
|
|
|
|
// return value: ['problems' => $problems, 'data' => $data, 'people' => $people]
|
|
// problems: pos => id
|
|
//
|
|
// for individual competition:
|
|
// people : username, realname, null, username_color
|
|
// for team competition:
|
|
// people : username, null, ['team_name' => team_name, 'members' => members], null
|
|
//
|
|
// for OI/IOI contest:
|
|
// data : id, submit_time, submitter, problem_pos, score
|
|
// for ACM contest:
|
|
// data : id, submit_time (plus penalty), submitter, problem_pos, score, cnt, n_failures
|
|
// if the contest is not finished, then cnt = null, n_failures = null;
|
|
// otherwise, cnt is the total number of submission of this subitter for this problem
|
|
// (by the time of getting 100, including the first submission with score 100)
|
|
// n_failures is the number of failure attempts of this submitter for this problem
|
|
function queryContestData($contest, $config = []) {
|
|
mergeConfig($config, [
|
|
'pre_final' => false,
|
|
'after_contest' => false,
|
|
]);
|
|
|
|
$problems = [];
|
|
$prob_pos = [];
|
|
$n_problems = 0;
|
|
$res = DB::selectAll([
|
|
"select problem_id from contests_problems",
|
|
"where", ["contest_id" => $contest['id']],
|
|
"order by level, problem_id"
|
|
], DB::NUM);
|
|
foreach ($res as $row) {
|
|
$prob_pos[$problems[] = (int)$row[0]] = $n_problems++;
|
|
}
|
|
|
|
if ($contest['extra_config']['basic_rule'] == 'OI' || $contest['extra_config']['basic_rule'] == 'IOI' || $config['after_contest']) {
|
|
$data = queryOIorIOIContestSubmissionData($contest, $problems, $prob_pos, $config);
|
|
} elseif ($contest['extra_config']['basic_rule'] == 'ACM') {
|
|
$data = queryACMContestSubmissionData($contest, $problems, $prob_pos, $config);
|
|
}
|
|
|
|
$people = [];
|
|
|
|
if ($contest['extra_config']['individual_or_team'] == 'individual') {
|
|
$res = DB::selectAll([
|
|
"select contests_registrants.username, user_info.realname, user_info.extra, user_info.usergroup from contests_registrants",
|
|
"inner join user_info on contests_registrants.username = user_info.username",
|
|
"where", [
|
|
"contest_id" => $contest['id'],
|
|
"has_participated" => 1
|
|
]
|
|
], DB::NUM);
|
|
foreach ($res as $row) {
|
|
$extra = json_decode($row[2], true);
|
|
$people[] = [
|
|
$row[0],
|
|
trim(HTML::escape($row[1])),
|
|
null,
|
|
UOJUser::getUserColor2($row[3], $extra['username_color']),
|
|
];
|
|
}
|
|
} elseif ($contest['extra_config']['individual_or_team'] == 'team') {
|
|
$res = DB::selectAll([
|
|
"select user_info.username, null, user_info.extra from contests_registrants, user_info",
|
|
"where", [
|
|
"contest_id" => $contest['id'],
|
|
"has_participated" => 1,
|
|
"contests_registrants.username = user_info.username"
|
|
]
|
|
], DB::NUM);
|
|
foreach ($res as $row) {
|
|
$extra = json_decode($row[2], true);
|
|
$people[] = [
|
|
$row[0],
|
|
null,
|
|
[
|
|
'team_name' => $extra['acm']['team_name'],
|
|
'members' => $extra['acm']['members'],
|
|
],
|
|
null,
|
|
];
|
|
}
|
|
}
|
|
|
|
return ['problems' => $problems, 'data' => $data, 'people' => $people];
|
|
}
|
|
function queryOIorIOIContestSubmissionData($contest, $problems, $prob_pos, $config = []) {
|
|
$data = [];
|
|
|
|
$use_final_res = $config['pre_final'] && $contest['extra_config']['basic_rule'] == 'OI';
|
|
|
|
if ($use_final_res) {
|
|
$res = DB::selectAll([
|
|
"select id, submit_time, submitter, problem_id, result from submissions",
|
|
"where", [
|
|
"contest_id" => $contest['id'],
|
|
["score", "is not", null]
|
|
], "order by id"
|
|
], DB::NUM);
|
|
foreach ($res as $row) {
|
|
$r = json_decode($row[4], true);
|
|
if (!isset($r['final_result'])) {
|
|
continue;
|
|
}
|
|
$row[0] = (int)$row[0];
|
|
$row[3] = $prob_pos[$row[3]];
|
|
$row[4] = $row[4] = UOJSubmission::roundedScore($r['final_result']['score']);
|
|
$data[] = $row;
|
|
}
|
|
} else {
|
|
if ($contest['cur_progress'] < CONTEST_FINISHED) {
|
|
$res = DB::selectAll([
|
|
"select id, submit_time, submitter, problem_id, score from submissions",
|
|
"where", [
|
|
"contest_id" => $contest['id'],
|
|
["score", "is not", null]
|
|
], "order by id"
|
|
], DB::NUM);
|
|
} elseif ($config['after_contest']) {
|
|
$res = DB::selectAll([
|
|
"select id, submit_time, submitter, problem_id, score from submissions",
|
|
"where", [
|
|
["problem_id", "in", DB::rawtuple($problems)],
|
|
["submitter", "in", DB::rawbracket([
|
|
"select username from contests_registrants",
|
|
"where", [
|
|
"contest_id" => $contest['id'],
|
|
"has_participated" => 1,
|
|
],
|
|
])],
|
|
], "order by score",
|
|
], DB::NUM);
|
|
} else {
|
|
$esc_start_time_str = DB::escape($contest['start_time_str']);
|
|
$res = DB::selectAll([
|
|
"select submission_id, date_add('{$esc_start_time_str}', interval penalty second), submitter, problem_id, score from contests_submissions",
|
|
"where", ["contest_id" => $contest['id']],
|
|
], DB::NUM);
|
|
}
|
|
foreach ($res as $row) {
|
|
$row[0] = (int)$row[0];
|
|
$row[3] = $prob_pos[$row[3]];
|
|
$row[4] = UOJSubmission::roundedScore($row[4]);
|
|
$data[] = $row;
|
|
}
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
function queryACMContestSubmissionData($contest, $problems, $prob_pos, $config = []) {
|
|
$data = [];
|
|
|
|
$username_or_empty = Auth::id();
|
|
if (!isset($username_or_empty)) {
|
|
$username_or_empty = '';
|
|
}
|
|
|
|
$actual_score = UOJSubmission::sqlForActualScore();
|
|
$visible_score = 'if(' . DB::land(['hide_score_to_others' => 1, 'submitter' => $username_or_empty]) . ', hidden_score, score)';
|
|
|
|
if ($config['pre_final']) {
|
|
$res = DB::selectAll([
|
|
"select id, submit_time, submitter, problem_id, $actual_score as actual_score, null, null from submissions",
|
|
"where", [
|
|
"contest_id" => $contest['id'],
|
|
[$actual_score, "is not", null]
|
|
], "order by id"
|
|
], DB::NUM);
|
|
} else {
|
|
if ($contest['cur_progress'] < CONTEST_FINISHED) {
|
|
$res = DB::selectAll([
|
|
"select id, submit_time, submitter, problem_id, $visible_score as visible_score, null, null from submissions",
|
|
"where", [
|
|
"contest_id" => $contest['id'],
|
|
DB::lor([
|
|
[$visible_score, "is not", null],
|
|
DB::land([
|
|
"hide_score_to_others" => 1,
|
|
["submitter", "!=", $username_or_empty]
|
|
])
|
|
])
|
|
], "order by id"
|
|
], DB::NUM);
|
|
} elseif ($config['after_contest']) {
|
|
$res = DB::selectAll([
|
|
"select id, submit_time, submitter, problem_id, score, null, null from submissions",
|
|
"where", [
|
|
["problem_id", "in", DB::rawtuple($problems)],
|
|
["submitter", "in", DB::rawbracket([
|
|
"select username from contests_registrants",
|
|
"where", [
|
|
"contest_id" => $contest['id'],
|
|
"has_participated" => 1,
|
|
],
|
|
])],
|
|
], "order by score",
|
|
], DB::NUM);
|
|
} else {
|
|
$esc_start_time_str = DB::escape($contest['start_time_str']);
|
|
$res = DB::selectAll([
|
|
"select submission_id, date_add('{$esc_start_time_str}', interval penalty second), submitter, problem_id, score, cnt, n_failures from contests_submissions",
|
|
"where", ["contest_id" => $contest['id']],
|
|
], DB::NUM);
|
|
}
|
|
}
|
|
foreach ($res as $row) {
|
|
$row[0] = (int)$row[0];
|
|
$row[3] = $prob_pos[$row[3]];
|
|
if (isset($row[4])) {
|
|
$row[4] = UOJSubmission::roundedScore($row[4]);
|
|
}
|
|
if (isset($row[5])) {
|
|
$row[5] = (int)$row[5];
|
|
}
|
|
if (isset($row[6])) {
|
|
$row[6] = (int)$row[6];
|
|
}
|
|
$data[] = $row;
|
|
}
|
|
return $data;
|
|
}
|
|
|
|
// standings: rank => score, penalty, [username, realname], virtual_rank
|
|
function calcStandings($contest, $contest_data, &$score, &$standings, $cfg = []) {
|
|
$cfg += [
|
|
'update_contests_submissions' => false,
|
|
];
|
|
|
|
// score for OI: username, problem_pos => score, penalty, id
|
|
// score for ACM: username, problem_pos => score, penalty, id, cnt, n_failures, n_frozen
|
|
$score = [];
|
|
$n_people = count($contest_data['people']);
|
|
$n_problems = count($contest_data['problems']);
|
|
foreach ($contest_data['people'] as $person) {
|
|
$score[$person[0]] = [];
|
|
}
|
|
|
|
if ($contest['extra_config']['basic_rule'] === 'OI') {
|
|
foreach ($contest_data['data'] as $sub) {
|
|
$penalty = (new DateTime($sub[1]))->getTimestamp() - $contest['start_time']->getTimestamp();
|
|
if ($contest['extra_config']['standings_version'] >= 2) {
|
|
if ($sub[4] == 0) {
|
|
$penalty = 0;
|
|
}
|
|
}
|
|
$score[$sub[2]][$sub[3]] = array($sub[4], $penalty, $sub[0]);
|
|
}
|
|
} else if ($contest['extra_config']['basic_rule'] === 'IOI' || $cfg['after_contest']) {
|
|
foreach ($contest_data['data'] as $sub) {
|
|
$penalty = (new DateTime($sub[1]))->getTimestamp() - $contest['start_time']->getTimestamp();
|
|
if ($sub[4] == 0) {
|
|
$penalty = 0;
|
|
}
|
|
if (!isset($score[$sub[2]][$sub[3]]) || $score[$sub[2]][$sub[3]][0] < $sub[4]) {
|
|
$score[$sub[2]][$sub[3]] = array($sub[4], $penalty, $sub[0]);
|
|
}
|
|
}
|
|
} else if ($contest['extra_config']['basic_rule'] === 'ACM') {
|
|
// sub: id, submit_time, submitter, problem_pos, score
|
|
// id, submit_time (plus penalty), submitter, problem_pos, score, cnt, n_failures
|
|
foreach ($contest_data['data'] as $sub) {
|
|
if (!isset($score[$sub[2]][$sub[3]])) {
|
|
$score[$sub[2]][$sub[3]] = [];
|
|
}
|
|
$score[$sub[2]][$sub[3]][] = $sub;
|
|
}
|
|
|
|
foreach ($contest_data['people'] as $person) {
|
|
$uname = $person[0];
|
|
for ($pr = 0; $pr < $n_problems; $pr++) {
|
|
if (isset($score[$uname][$pr])) {
|
|
// username, problem_pos => score, penalty, id, cnt, n_failures, n_frozen
|
|
$final_scr = null;
|
|
$penalty = 0;
|
|
$key_sub = null;
|
|
$cnt = 0;
|
|
$n_failures = 0;
|
|
$n_frozen = 0;
|
|
|
|
if (isset($score[$uname][$pr][0][5])) { // the stored contest data is used
|
|
$sub = $score[$uname][$pr][0];
|
|
$final_scr = $sub[4];
|
|
$penalty = (new DateTime($sub[1]))->getTimestamp() - $contest['start_time']->getTimestamp();
|
|
$key_sub = $sub;
|
|
$cnt = $sub[5];
|
|
$n_failures = $sub[6];
|
|
$n_frozen = 0;
|
|
} else {
|
|
for ($i = 0; $i < count($score[$uname][$pr]); $i++) {
|
|
$sub = $score[$uname][$pr][$i];
|
|
$cnt++;
|
|
if (!isset($sub[4])) {
|
|
$n_frozen++;
|
|
} elseif (!isset($final_scr) || $final_scr < $sub[4]) {
|
|
$final_scr = $sub[4];
|
|
if ($final_scr == 100) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isset($final_scr)) {
|
|
$key_sub = end($score[$uname][$pr]);
|
|
} else if ($final_scr == 0) {
|
|
for ($i = 0; $i < count($score[$uname][$pr]); $i++) {
|
|
$sub = $score[$uname][$pr][$i];
|
|
if (!isset($sub[4])) {
|
|
break;
|
|
} else {
|
|
$n_failures++;
|
|
$key_sub = $sub;
|
|
}
|
|
}
|
|
|
|
list($final_scr, $penalty) = calcACMScoreAndPenaltyForOneProblem(
|
|
$contest,
|
|
$contest_data['problems'][$pr],
|
|
$key_sub,
|
|
$n_failures
|
|
);
|
|
} else {
|
|
$scr_set = [];
|
|
for ($i = 0; $i < count($score[$uname][$pr]); $i++) {
|
|
$sub = $score[$uname][$pr][$i];
|
|
if ($sub[4] > 0 && $sub[4] != 97 && !isset($scr_set[$sub[4]])) {
|
|
$scr_set[$sub[4]] = true;
|
|
} else {
|
|
$n_failures++;
|
|
}
|
|
if ($sub[4] === $final_scr) {
|
|
$key_sub = $sub;
|
|
break;
|
|
}
|
|
}
|
|
|
|
list($final_scr, $penalty) = calcACMScoreAndPenaltyForOneProblem(
|
|
$contest,
|
|
$contest_data['problems'][$pr],
|
|
$key_sub,
|
|
$n_failures
|
|
);
|
|
}
|
|
}
|
|
|
|
if ($final_scr == 0) {
|
|
$penalty = 0;
|
|
}
|
|
|
|
$score[$uname][$pr] = [
|
|
$final_scr,
|
|
$penalty,
|
|
$key_sub[0],
|
|
$cnt,
|
|
$n_failures,
|
|
$n_frozen
|
|
];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// standings: rank => score, penalty, [username, realname, null|array, null|color], virtual_rank, ?review
|
|
$standings = [];
|
|
foreach ($contest_data['people'] as $person) {
|
|
$cur = array(0, 0, $person);
|
|
for ($i = 0; $i < $n_problems; $i++) {
|
|
if (isset($score[$person[0]][$i])) {
|
|
$cur_row = $score[$person[0]][$i];
|
|
$cur[0] = UOJSubmission::roundedScore($cur[0] + $cur_row[0]);
|
|
$cur[1] += $cur_row[1];
|
|
if ($cfg['update_contests_submissions']) {
|
|
DB::insert([
|
|
"replace into contests_submissions",
|
|
"(contest_id, submitter, problem_id, submission_id, score, penalty, cnt, n_failures)",
|
|
"values", DB::tuple([
|
|
$contest['id'], $person[0], $contest_data['problems'][$i], $cur_row[2],
|
|
$cur_row[0], $cur_row[1],
|
|
isset($cur_row[3]) ? $cur_row[3] : null,
|
|
isset($cur_row[4]) ? $cur_row[4] : null
|
|
])
|
|
]);
|
|
}
|
|
}
|
|
}
|
|
$standings[] = $cur;
|
|
}
|
|
|
|
usort($standings, function ($lhs, $rhs) {
|
|
if ($lhs[0] != $rhs[0]) {
|
|
return $rhs[0] - $lhs[0];
|
|
} else if ($lhs[1] != $rhs[1]) {
|
|
return $lhs[1] - $rhs[1];
|
|
} else {
|
|
return strcmp($lhs[2][0], $rhs[2][0]);
|
|
}
|
|
});
|
|
|
|
$is_same_rank = function ($lhs, $rhs) {
|
|
return $lhs[0] == $rhs[0] && $lhs[1] == $rhs[1];
|
|
};
|
|
|
|
for ($i = 0; $i < $n_people; $i++) {
|
|
if ($i == 0 || !$is_same_rank($standings[$i - 1], $standings[$i])) {
|
|
$standings[$i][] = $i + 1;
|
|
} else {
|
|
$standings[$i][] = $standings[$i - 1][3];
|
|
}
|
|
}
|
|
}
|
|
|
|
function calcACMScoreAndPenaltyForOneProblem($contest, $problem_id, $sub, $n_failures) {
|
|
if (isset($contest['extra_config']['bonus']["problem_{$problem_id}"])) {
|
|
if ($sub[4] == 100) {
|
|
return [0, -60 * 20];
|
|
} else {
|
|
return [0, 0];
|
|
}
|
|
} else {
|
|
$penalty = (new DateTime($sub[1]))->getTimestamp() - $contest['start_time']->getTimestamp();
|
|
$penalty += $n_failures * 60 * 20;
|
|
if ($sub[4] === 0) {
|
|
$penalty = 0;
|
|
}
|
|
return [$sub[4], $penalty];
|
|
}
|
|
}
|
|
|
|
function getContestBlogLink($contest, $title) {
|
|
if (!isset($contest['extra_config']['links'])) {
|
|
return null;
|
|
}
|
|
foreach ($contest['extra_config']['links'] as $link) {
|
|
if ($link[0] === $title) {
|
|
return '/blogs/' . $link[1];
|
|
}
|
|
}
|
|
return null;
|
|
}
|