diff --git a/db/app_uoj233.sql b/db/app_uoj233.sql index f7c8b6c..870225a 100644 --- a/db/app_uoj233.sql +++ b/db/app_uoj233.sql @@ -332,7 +332,7 @@ CREATE TABLE `contests_submissions` ( `submitter` varchar(20) NOT NULL, `problem_id` int NOT NULL, `submission_id` int NOT NULL, - `score` int NOT NULL, + `score` DECIMAL(15, 10) NOT NULL, `penalty` int NOT NULL, `cnt` int DEFAULT NULL, `n_failures` int DEFAULT NULL, @@ -875,9 +875,9 @@ CREATE TABLE `submissions` ( `result` mediumblob NOT NULL, `status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, `result_error` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `score` int DEFAULT NULL, + `score` DECIMAL(15, 10) DEFAULT NULL, `hide_score_to_others` tinyint(1) NOT NULL DEFAULT '0', - `hidden_score` int DEFAULT NULL, + `hidden_score` DECIMAL(15, 10) DEFAULT NULL, `used_time` int NOT NULL DEFAULT '0', `used_memory` int NOT NULL DEFAULT '0', `is_hidden` tinyint(1) NOT NULL, @@ -922,7 +922,7 @@ CREATE TABLE `submissions_history` ( `status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, `status_details` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `result_error` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL, - `score` int DEFAULT NULL, + `score` DECIMAL(15, 10) DEFAULT NULL, `used_time` int NOT NULL DEFAULT '0', `used_memory` int NOT NULL DEFAULT '0', `major` tinyint(1) NOT NULL, @@ -994,7 +994,8 @@ INSERT INTO `upgrades` (`name`, `status`, `updated_at`) VALUES ('20_problem_difficulty', 'up', now()), ('21_problem_difficulty', 'up', now()), ('28_remote_judge', 'up', now()), - ('31_problem_resources', 'up', now()); + ('31_problem_resources', 'up', now()), + ('36_decimal_score_range', 'up', now()); /*!40000 ALTER TABLE `upgrades` ENABLE KEYS */; UNLOCK TABLES; diff --git a/judger/judge_client b/judger/judge_client index 2412934..6e7ce6f 100755 --- a/judger/judge_client +++ b/judger/judge_client @@ -231,7 +231,13 @@ class Judger: else: assert len(sp) == 2 res[sp[0]] = sp[1] - res['score'] = int(res['score']) + try: + res['score'] = int(res['score']) + except ValueError: + res['score'] = float(res['score']) + if not (0 <= res['score'] <= 100): + res['score'] = 0 + res['error'] = 'Score Error' res['time'] = int(res['time']) res['memory'] = int(res['memory']) res['status'] = 'Judged' diff --git a/judger/uoj_judger/include/uoj_judger.h b/judger/uoj_judger/include/uoj_judger.h index daf825e..b260727 100644 --- a/judger/uoj_judger/include/uoj_judger.h +++ b/judger/uoj_judger/include/uoj_judger.h @@ -273,8 +273,92 @@ struct InfoBlock { } }; -int scale_score(int scr100, int full) { - return scr100 * full / 100; +enum SCORE_MODE { + SM_INT, + SM_REAL +}; + +/** + * @brief the value type for storing the score of a point/subtask/submission + */ +class score_t { +private: + double __scr; +public: + static SCORE_MODE mode; + static int P; + static int D; + + score_t() = default; + score_t(const double &_scr) : __scr(_scr) { + } + +#define SCORE_OP(x) friend inline score_t operator x(const score_t &lhs, const score_t &rhs) { return lhs.__scr x rhs.__scr; } + SCORE_OP(+) + SCORE_OP(-) + SCORE_OP(*) + SCORE_OP(/) +#undef SCORE_OP + +#define SCORE_OP(x) friend inline bool operator x(const score_t &lhs, const score_t &rhs) { return lhs.__scr x rhs.__scr; } + SCORE_OP(==) + SCORE_OP(!=) + SCORE_OP(<) + SCORE_OP(<=) + SCORE_OP(>) + SCORE_OP(>=) +#undef SCORE_OP + +#define SCORE_OP(x) inline score_t &operator x(const score_t &rhs) { __scr x rhs.__scr; return *this; } + SCORE_OP(+=) + SCORE_OP(-=) +#undef SCORE_OP + +#define SCORE_OP(x) inline score_t operator x() { return x __scr; } + SCORE_OP(-) + SCORE_OP(+) +#undef SCORE_OP + + inline score_t rounded_score() const { + if (mode == SM_REAL) { + return round(__scr * P) / P; + } else { + // score_type = int. round to integer + return round(__scr); + } + } + + explicit inline operator int() const { + return (int)round(__scr); + } + + explicit inline operator double() const { + return __scr; + } + + friend inline ostream& operator<<(ostream &out, const score_t &scr) { + auto default_prec = out.precision(); + out.precision(13); + out << scr.__scr; + out.precision(default_prec); + return out; + } +}; + +SCORE_MODE score_t::mode; +int score_t::P; +int score_t::D; + +/** + * @brief given a score in a system where the full mark is 100, scale it so that the full mark equals "full" + */ +score_t scale_score(score_t scr100, score_t full) { + if (score_t::mode == SM_REAL) { + return (scr100 / 100 * full).rounded_score(); + } else { + // score_type = int. round scr100 and full to integers. do integer division + return int(scr100) * int(full) / 100; + } } struct PointInfo { @@ -283,14 +367,14 @@ struct PointInfo { static bool show_res; int num; - int scr; + score_t scr; int ust, usm; string info, in, out, res; bool use_li; vector li; - PointInfo(const int &_num, const int &_scr, + PointInfo(const int &_num, const score_t &_scr, const int &_ust, const int &_usm, const string &_info = "default") : num(_num), scr(_scr), ust(_ust), usm(_usm), info(_info) { @@ -306,7 +390,7 @@ struct PointInfo { } } - PointInfo(const int &_num, const int &_scr, + PointInfo(const int &_num, const score_t &_scr, const int &_ust, const int &_usm, const string &_info, const string &_in, const string &_out, const string &_res) : num(_num), scr(_scr), @@ -380,7 +464,7 @@ struct SubtaskMetaInfo { string subtask_type; string subtask_used_time_type; vector subtask_dependencies; - int full_score; + score_t full_score; inline bool is_ordinary() { return subtask_type == "packed" && subtask_used_time_type == "sum"; @@ -392,9 +476,10 @@ struct SubtaskInfo { bool passed = true; bool early_stop = false; - int scr, ust = 0, usm = 0; + score_t scr; + int ust = 0, usm = 0; string info = "Accepted"; - int unrescaled_min_score = 100; + score_t unrescaled_min_score = 100; vector points; SubtaskInfo() = default; @@ -489,7 +574,7 @@ struct SubtaskInfo { struct RunCheckerResult { int type; int ust, usm; - int scr; + score_t scr; string info; static RunCheckerResult from_file(const string &file_name, const RunResult &rres) { @@ -513,7 +598,7 @@ struct RunCheckerResult { if (fscanf(fres, "%lf", &d) != 1) { return RunCheckerResult::failed_result(); } else { - res.scr = (int)floor(100 * d + 0.5); + res.scr = 100 * d; } } else { res.scr = 0; @@ -577,9 +662,9 @@ string work_path; string data_path; string result_path; -int tot_time = 0; +int tot_time = 0; int max_memory = 0; -int tot_score = 0; +score_t tot_score = 0; ostringstream details_out; map config; @@ -654,6 +739,12 @@ double conf_double(const string &key, int num, const double &val) { double conf_double(const string &key) { return conf_double(key, 0); } +score_t conf_score(const string &key, const score_t &val) { + return score_t(conf_double(key, double(val))).rounded_score(); +} +score_t conf_score(const string &key, int num, const score_t &val) { + return score_t(conf_double(key, num, double(val))).rounded_score(); +} string conf_file_name_with_num(string s, int num) { ostringstream name; if (num < 0) { @@ -713,7 +804,7 @@ SubtaskMetaInfo conf_subtask_meta_info(string pre, const int &num) { meta.subtask_type = conf_str(pre + "subtask_type", num, "packed"); meta.subtask_used_time_type = conf_str(pre + "subtask_used_time_type", num, "sum"); - meta.full_score = conf_int(pre + "subtask_score", num, 100 / nT); + meta.full_score = conf_score(pre + "subtask_score", num, 100 / nT); if (conf_str("subtask_dependence", num, "none") == "many") { string cur = "subtask_dependence_" + vtos(num); int p = 1; @@ -762,7 +853,7 @@ void add_point_info(const PointInfo &info, bool update_tot_score = true) { } } if (update_tot_score) { - tot_score += info.scr; + tot_score = (tot_score + info.scr).rounded_score(); } details_out << info; @@ -791,35 +882,44 @@ void add_subtask_info(const SubtaskInfo &st_info) { if (st_info.usm >= 0) { max_memory = max(max_memory, st_info.usm); } - tot_score += st_info.scr; + tot_score = (tot_score + st_info.scr).rounded_score(); details_out << st_info; } void end_judge_ok() { - FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); - fprintf(fres, "score %d\n", tot_score); - fprintf(fres, "time %d\n", tot_time); - fprintf(fres, "memory %d\n", max_memory); - fprintf(fres, "details\n"); - fprintf(fres, "\n"); - fprintf(fres, "%s", details_out.str().c_str()); - fprintf(fres, "\n"); - fclose(fres); + ofstream fres(result_path + "/result.txt"); + if (!fres) { + exit(1); + } + fres << "score " << tot_score << "\n"; + fres << "time " << tot_time << "\n"; + fres << "memory " << max_memory << "\n"; + fres << "details\n"; + fres << "\n"; + fres << details_out.str(); + fres << "\n"; + fres.close(); exit(0); } void end_judge_judgement_failed(const string &info) { - FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); - fprintf(fres, "error Judgment Failed\n"); - fprintf(fres, "details\n"); - fprintf(fres, "%s\n", htmlspecialchars(info).c_str()); - fclose(fres); + ofstream fres(result_path + "/result.txt"); + if (!fres) { + exit(1); + } + fres << "error Judgment Failed\n"; + fres << "details\n"; + fres << "" << htmlspecialchars(info) << "\n"; + fres.close(); exit(0); } void end_judge_compile_error(const RunCompilerResult &res) { - FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); - fprintf(fres, "error Compile Error\n"); - fprintf(fres, "details\n"); - fprintf(fres, "%s\n", htmlspecialchars(res.info).c_str()); - fclose(fres); + ofstream fres(result_path + "/result.txt"); + if (!fres) { + exit(1); + } + fres << "error Compile Error\n"; + fres << "details\n"; + fres << "" << htmlspecialchars(res.info) << "\n"; + fres.close(); exit(0); } @@ -1543,7 +1643,7 @@ bool main_data_test(TP test_point_func) { if (po.scr != 100) { passed = false; } - po.scr = scale_score(po.scr, conf_int("point_score", i, 100 / n)); + po.scr = scale_score(po.scr, conf_score("point_score", i, 100 / n)); add_point_info(po); } } else if (nT == 1 && conf_subtask_meta_info(1).is_ordinary()) { // ACM @@ -1663,6 +1763,18 @@ void judger_init(int argc, char **argv) { PointInfo::show_out = conf_str("show_out", "on") == "on"; PointInfo::show_res = conf_str("show_res", "on") == "on"; + string score_type = conf_str("score_type", "int"); + if (score_type == "int") { + score_t::mode = SM_INT; + } else { + score_t::mode = SM_REAL; + sscanf(score_type.c_str(), "real-%d", &score_t::D); + score_t::P = 1; + for (int i = 0; i < score_t::D; i++) { + score_t::P *= 10; + } + } + if (chdir(work_path.c_str()) != 0) { cerr << "invalid work path" << endl; exit(1); diff --git a/web/app/controllers/group_assignment.php b/web/app/controllers/group_assignment.php index ded4ebb..a74bf10 100644 --- a/web/app/controllers/group_assignment.php +++ b/web/app/controllers/group_assignment.php @@ -63,9 +63,9 @@ UOJGroupAssignment::cur()->userCanView(['ensure' => true]); if ($submission) { $row[2][] = [ (int)$submission['id'], - (int)$submission['score'], + UOJSubmission::roundedScore($submission['score']), ]; - $row[0] += $submission['score']; + $row[0] = UOJSubmission::roundedScore($row[0] + $submission['score']); } else { $row[2][] = null; } diff --git a/web/app/controllers/problem.php b/web/app/controllers/problem.php index 85c0ad2..bcb6387 100644 --- a/web/app/controllers/problem.php +++ b/web/app/controllers/problem.php @@ -453,7 +453,7 @@ if (UOJContest::cur()) { UOJProblem::info('id'), "submitter" => Auth::id()]]) ?> - + diff --git a/web/app/controllers/problem_statistics.php b/web/app/controllers/problem_statistics.php index 26669ef..7d71ccc 100644 --- a/web/app/controllers/problem_statistics.php +++ b/web/app/controllers/problem_statistics.php @@ -29,7 +29,7 @@ function scoreDistributionData() { } else if ($row[0] == 100) { $has_score_100 = true; } - $score = $row[0] * 100; + $score = UOJSubmission::roundedScore($row[0]) * 100; $data[] = ['score' => $score, 'count' => $row[1]]; } if (!$has_score_0) { diff --git a/web/app/controllers/submissions_list.php b/web/app/controllers/submissions_list.php index a915a31..791e421 100644 --- a/web/app/controllers/submissions_list.php +++ b/web/app/controllers/submissions_list.php @@ -15,8 +15,8 @@ $config = [ $q_problem_id = UOJRequest::get('problem_id', 'validateUInt', null); $q_submitter = UOJRequest::get('submitter', 'validateUsername', null); -$q_min_score = UOJRequest::get('min_score', 'validateUInt', null); -$q_max_score = UOJRequest::get('max_score', 'validateUInt', null); +$q_min_score = UOJRequest::get('min_score', 'validateUFloat', null); +$q_max_score = UOJRequest::get('max_score', 'validateUFloat', null); $q_lang = UOJRequest::get('language', 'is_short_string', null); if ($q_problem_id !== null) { @@ -83,9 +83,9 @@ if (!$conds) { :
- + ~ - +
diff --git a/web/app/libs/uoj-contest-lib.php b/web/app/libs/uoj-contest-lib.php index f360bf0..a88f28e 100644 --- a/web/app/libs/uoj-contest-lib.php +++ b/web/app/libs/uoj-contest-lib.php @@ -149,7 +149,7 @@ function queryOIorIOIContestSubmissionData($contest, $problems, $prob_pos, $conf } $row[0] = (int)$row[0]; $row[3] = $prob_pos[$row[3]]; - $row[4] = (int)($r['final_result']['score']); + $row[4] = $row[4] = UOJSubmission::roundedScore($r['final_result']['score']); $data[] = $row; } } else { @@ -185,7 +185,7 @@ function queryOIorIOIContestSubmissionData($contest, $problems, $prob_pos, $conf foreach ($res as $row) { $row[0] = (int)$row[0]; $row[3] = $prob_pos[$row[3]]; - $row[4] = (int)$row[4]; + $row[4] = UOJSubmission::roundedScore($row[4]); $data[] = $row; } } @@ -238,7 +238,7 @@ function queryACMContestSubmissionData($contest, $problems, $prob_pos, $config = $row[0] = (int)$row[0]; $row[3] = $prob_pos[$row[3]]; if (isset($row[4])) { - $row[4] = (int)$row[4]; + $row[4] = UOJSubmission::roundedScore($row[4]); } if (isset($row[5])) { $row[5] = (int)$row[5]; @@ -393,7 +393,7 @@ function calcStandings($contest, $contest_data, &$score, &$standings, $cfg = []) for ($i = 0; $i < $n_problems; $i++) { if (isset($score[$person[0]][$i])) { $cur_row = $score[$person[0]][$i]; - $cur[0] += $cur_row[0]; + $cur[0] = UOJSubmission::roundedScore($cur[0] + $cur_row[0]); $cur[1] += $cur_row[1]; if ($cfg['update_contests_submissions']) { DB::insert([ diff --git a/web/app/libs/uoj-data-lib.php b/web/app/libs/uoj-data-lib.php index 932b97d..8fe456b 100644 --- a/web/app/libs/uoj-data-lib.php +++ b/web/app/libs/uoj-data-lib.php @@ -310,7 +310,7 @@ class SyncProblemDataHandler { if ($this->problem_conf === -1) { throw new UOJFileNotFoundException("problem.conf"); } elseif ($this->problem_conf === -2) { - throw new UOJProblemConfException("syntax error"); + throw new UOJProblemConfException("syntax error: duplicate keys"); } $this->allow_files = array_flip(FS::scandir($this->upload_dir)); diff --git a/web/app/libs/uoj-html-lib.php b/web/app/libs/uoj-html-lib.php index 48182db..52a7360 100644 --- a/web/app/libs/uoj-html-lib.php +++ b/web/app/libs/uoj-html-lib.php @@ -677,12 +677,10 @@ class JudgmentDetailsPrinter { public function __construct($details, $styler, $name) { $this->name = $name; $this->styler = $styler; - $this->details = $details; $this->dom = new DOMDocument(); - if (!$this->dom->loadXML($this->details)) { + if (!$this->dom->loadXML($details)) { throw new Exception("XML syntax error"); } - $this->details = ''; } public function printHTML() { $this->subtask_num = null; diff --git a/web/app/libs/uoj-validate-lib.php b/web/app/libs/uoj-validate-lib.php index 44a22c3..0d17760 100644 --- a/web/app/libs/uoj-validate-lib.php +++ b/web/app/libs/uoj-validate-lib.php @@ -27,7 +27,7 @@ function validateUInt($x, $len = 8) { // [0, 1000000000) if ($x === '0') { return true; } - return preg_match('/^[1-9][0-9]{0,'.$len.'}$/', $x); + return preg_match('/^[1-9][0-9]{0,' . $len . '}$/', $x); } function validateInt($x) { @@ -40,6 +40,29 @@ function validateInt($x) { return validateUInt($x); } +function validateUFloat($x) { + if (!is_string($x)) { + return false; + } + if (!preg_match('/^([1-9][0-9]*|0)(\.[0-9]+)?$/', $x)) { + return false; + } + return filter_var($x, FILTER_VALIDATE_FLOAT) !== false; +} + +function validateFloat($x) { + if (!is_string($x)) { + return false; + } + if (strlen($x) == 0) { + return false; + } + if ($x[0] == '-') { + $x = substr($x, 1); + } + return validateUFloat($x); +} + function validateUploadedFile($name) { return isset($_FILES[$name]) && is_uploaded_file($_FILES[$name]['tmp_name']); } diff --git a/web/app/models/DB.php b/web/app/models/DB.php index 57fea56..3edc229 100644 --- a/web/app/models/DB.php +++ b/web/app/models/DB.php @@ -110,7 +110,7 @@ class DB { return 'true'; } elseif ($str === false) { return 'false'; - } elseif (is_int($str)) { + } elseif (is_int($str)|| is_float($str)) { return $str; } elseif (is_string($str)) { return '\''.DB::escape($str).'\''; diff --git a/web/app/models/UOJSubmission.php b/web/app/models/UOJSubmission.php index 6ae9327..d29306f 100644 --- a/web/app/models/UOJSubmission.php +++ b/web/app/models/UOJSubmission.php @@ -83,6 +83,15 @@ class UOJSubmission { return $this->info['hide_score_to_others'] ? $this->info['hidden_score'] : $this->info['score']; } + public static function roundedScore($score): float { + return round($score, 10); + } + public static function roundScoreInArray(&$info, $key) { + if (isset($info[$key])) { + $info[$key] = static::roundedScore($info[$key]); + } + } + public static function onUpload($zip_file_name, $content, $tot_size, $is_contest_submission) { $judge_reason = ''; @@ -367,6 +376,8 @@ class UOJSubmission { public function __construct($info) { $this->info = $info; + static::roundScoreInArray($this->info, 'score'); + static::roundScoreInArray($this->info, 'hidden_score'); } public function hasFullyJudged() { @@ -638,6 +649,7 @@ class UOJSubmission { if (!$his) { return false; } + static::roundScoreInArray($his, 'actual_score'); return $this->loadHistory($his); } @@ -659,6 +671,7 @@ class UOJSubmission { if (!$his) { return false; } + static::roundScoreInArray($his, 'actual_score'); return $this->loadHistory($his); } } diff --git a/web/app/models/UOJSubmissionHistory.php b/web/app/models/UOJSubmissionHistory.php index 0e0d902..8e26eab 100644 --- a/web/app/models/UOJSubmissionHistory.php +++ b/web/app/models/UOJSubmissionHistory.php @@ -1,238 +1,240 @@ 'id', 'message' => 'judge_reason', 'time' => 'judge_time', - 'judger', 'status', 'result_error', - 'actual_score' => 'score', 'used_time', 'used_memory', - 'type' => 'if(major, "major", "minor")' - ]; + public static $fields_without_result = [ + 'tid' => 'id', 'message' => 'judge_reason', 'time' => 'judge_time', + 'judger', 'status', 'result_error', + 'actual_score' => 'score', 'used_time', 'used_memory', + 'type' => 'if(major, "major", "minor")' + ]; - public static $fields = [ - 'tid' => 'id', 'message' => 'judge_reason', 'time' => 'judge_time', - 'judger', 'result', 'status', 'result_error', - 'actual_score' => 'score', 'used_time', 'used_memory', - 'type' => 'if(major, "major", "minor")' - ]; + public static $fields = [ + 'tid' => 'id', 'message' => 'judge_reason', 'time' => 'judge_time', + 'judger', 'result', 'status', 'result_error', + 'actual_score' => 'score', 'used_time', 'used_memory', + 'type' => 'if(major, "major", "minor")' + ]; - public static function query(UOJSubmission $submission, $cfg = []) { - $cfg += [ - 'minor' => false, - 'system' => true - ]; + public static function query(UOJSubmission $submission, $cfg = []) { + $cfg += [ + 'minor' => false, + 'system' => true + ]; - $q = [ - "select", DB::fields([ - 'tid' => DB::value(0), - 'message' => 'judge_reason', - 'time' => DB::if_func(['judge_time' => null], UOJTime::MAX_TIME, DB::raw('judge_time')), - 'judger', 'status', 'result_error', - 'actual_score' => UOJSubmission::sqlForActualScore(), 'used_time', 'used_memory', - 'type' => DB::value('major'), - 'priority' => 1000, - ]), "from submissions", - "where", ["id" => $submission->info['id']], - "union all", - "select", DB::fields( - UOJSubmissionHistory::$fields_without_result + [ - 'priority' => 100 - ]), - "from submissions_history", - "where", [ - 'submission_id' => $submission->info['id'], - ['judge_time', 'is not', null] - ] + ($cfg['minor'] ? [] : ['major' => true]), - "union all", - "select", DB::fields([ - 'tid' => DB::value(-1), - 'message', - 'time', - 'judger' => DB::value(null), 'status' => DB::value(null), 'result_error' => DB::value(null), - 'actual_score' => DB::value(null), 'used_time' => DB::value(null), 'used_memory' => DB::value(null), - 'type', - 'priority' => 99 - ]), "from system_updates", - "where", [ - 'type' => 'problem', - 'target_id' => $submission->info['problem_id'] - ], - "union all", - "select", DB::fields([ - 'tid' => DB::value(-1), - 'message' => DB::value(''), - 'time' => 'submit_time', - 'judger' => DB::value(null), 'status' => DB::value(null), 'result_error' => DB::value(null), - 'actual_score' => DB::value(null), 'used_time' => DB::value(null), 'used_memory' => DB::value(null), - 'type' => DB::value('submit'), - 'priority' => 10 - ]), "from submissions", - "where", ["id" => $submission->info['id']], - ]; - if ($cfg['system']) { - $q = array_merge($q, [ - "union all", - "select", DB::fields([ - 'tid' => DB::value(-1), - 'message', - 'time', - 'judger' => DB::value(null), 'status' => DB::value(null), 'result_error' => DB::value(null), - 'actual_score' => DB::value(null), 'used_time' => DB::value(null), 'used_memory' => DB::value(null), - 'type', - 'priority' => 10 - ]), "from system_updates", - "where", [ - ['type', 'in', DB::rawtuple(['judge', 'submissions_history'])] - ] - ]); - } - $q = array_merge($q, [ - "order by time, priority asc" - ]); + $q = [ + "select", DB::fields([ + 'tid' => DB::value(0), + 'message' => 'judge_reason', + 'time' => DB::if_func(['judge_time' => null], UOJTime::MAX_TIME, DB::raw('judge_time')), + 'judger', 'status', 'result_error', + 'actual_score' => UOJSubmission::sqlForActualScore(), 'used_time', 'used_memory', + 'type' => DB::value('major'), + 'priority' => 1000, + ]), "from submissions", + "where", ["id" => $submission->info['id']], + "union all", + "select", DB::fields( + UOJSubmissionHistory::$fields_without_result + [ + 'priority' => 100 + ] + ), + "from submissions_history", + "where", [ + 'submission_id' => $submission->info['id'], + ['judge_time', 'is not', null] + ] + ($cfg['minor'] ? [] : ['major' => true]), + "union all", + "select", DB::fields([ + 'tid' => DB::value(-1), + 'message', + 'time', + 'judger' => DB::value(null), 'status' => DB::value(null), 'result_error' => DB::value(null), + 'actual_score' => DB::value(null), 'used_time' => DB::value(null), 'used_memory' => DB::value(null), + 'type', + 'priority' => 99 + ]), "from system_updates", + "where", [ + 'type' => 'problem', + 'target_id' => $submission->info['problem_id'] + ], + "union all", + "select", DB::fields([ + 'tid' => DB::value(-1), + 'message' => DB::value(''), + 'time' => 'submit_time', + 'judger' => DB::value(null), 'status' => DB::value(null), 'result_error' => DB::value(null), + 'actual_score' => DB::value(null), 'used_time' => DB::value(null), 'used_memory' => DB::value(null), + 'type' => DB::value('submit'), + 'priority' => 10 + ]), "from submissions", + "where", ["id" => $submission->info['id']], + ]; + if ($cfg['system']) { + $q = array_merge($q, [ + "union all", + "select", DB::fields([ + 'tid' => DB::value(-1), + 'message', + 'time', + 'judger' => DB::value(null), 'status' => DB::value(null), 'result_error' => DB::value(null), + 'actual_score' => DB::value(null), 'used_time' => DB::value(null), 'used_memory' => DB::value(null), + 'type', + 'priority' => 10 + ]), "from system_updates", + "where", [ + ['type', 'in', DB::rawtuple(['judge', 'submissions_history'])] + ] + ]); + } + $q = array_merge($q, [ + "order by time, priority asc" + ]); - $ret = DB::selectAll($q); - if ($ret === false) { - return null; - } + $ret = DB::selectAll($q); + if ($ret === false) { + return null; + } - $st = null; - $ed = null; - foreach ($ret as $idx => &$his) { - if ($his['type'] == 'major' || $his['type'] == 'minor' || $his['type'] == 'submit') { - $ed = $idx; - if ($his['type'] == 'submit') { - $st = $idx; - } - } - if ($his['time'] == UOJTime::MAX_TIME) { - $his['time'] = null; - } - } + $st = null; + $ed = null; + foreach ($ret as $idx => &$his) { + if ($his['type'] == 'major' || $his['type'] == 'minor' || $his['type'] == 'submit') { + $ed = $idx; + if ($his['type'] == 'submit') { + $st = $idx; + } + } + if ($his['time'] == UOJTime::MAX_TIME) { + $his['time'] = null; + } + UOJSubmission::roundScoreInArray($his, 'actual_score'); + } - $res = []; - foreach ($ret as $idx => &$his) { - if ($st !== null && $idx >= $st) { - $res[] = $his; - } - } - $res = array_reverse($res); - return new UOJSubmissionHistory($res, $submission); - } + $res = []; + foreach ($ret as $idx => &$his) { + if ($st !== null && $idx >= $st) { + $res[] = $his; + } + } + $res = array_reverse($res); + return new UOJSubmissionHistory($res, $submission); + } - public function __construct($info, UOJSubmission $submission) { - $this->info = $info; - $this->submission = $submission; + public function __construct($info, UOJSubmission $submission) { + $this->info = $info; + $this->submission = $submission; - $this->n_versions = 0; - foreach ($this->info as $his) { - if ($his['type'] == 'major' || $his['type'] == 'minor') { - $this->n_versions++; - } - } - } + $this->n_versions = 0; + foreach ($this->info as $his) { + if ($his['type'] == 'major' || $his['type'] == 'minor') { + $this->n_versions++; + } + } + } - public function echoTimeline() { - echo '
'; - if ($this->submission->isLatest()) { - echo '

你现在查看的是最新测评结果

'; - } else if (UOJSubmission::info('judge_time') !== null) { - if ($this->submission->isMajor()) { - echo '

你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'),' 的历史记录

'; - } else { - echo '

你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'),' 的隐藏记录

'; - } - } - $h = clone $this->submission; - $cfg = [ - 'show_actual_score' => true - ]; - foreach ($this->info as $his) { - $message = json_decode($his['message'], true); - $show_result = true; + public function echoTimeline() { + echo '
'; + if ($this->submission->isLatest()) { + echo '

你现在查看的是最新测评结果

'; + } else if (UOJSubmission::info('judge_time') !== null) { + if ($this->submission->isMajor()) { + echo '

你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'), ' 的历史记录

'; + } else { + echo '

你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'), ' 的隐藏记录

'; + } + } + $h = clone $this->submission; + $cfg = [ + 'show_actual_score' => true + ]; + foreach ($this->info as $his) { + $message = json_decode($his['message'], true); + $show_result = true; - $extra = null; + $extra = null; - if ($his['type'] == 'major' || $his['type'] == 'minor') { - $h->loadHistory($his); - $cls = 'list-group-item rounded-0'; - if ($h->info['tid'] == $this->submission->getTID()) { - $cls .= ' list-group-item-warning'; - } - echo '
'; - $extra = ' 查看'; - $split_cls = ['col-sm-10', 'col-sm-2']; - } else { - $show_result = false; - echo '
'; - } + if ($his['type'] == 'major' || $his['type'] == 'minor') { + $h->loadHistory($his); + $cls = 'list-group-item rounded-0'; + if ($h->info['tid'] == $this->submission->getTID()) { + $cls .= ' list-group-item-warning'; + } + echo '
'; + $extra = ' 查看'; + $split_cls = ['col-sm-10', 'col-sm-2']; + } else { + $show_result = false; + echo '
'; + } - if ($extra) { - echo '
'; - echo '
'; - } + if ($extra) { + echo '
'; + echo '
'; + } - echo '
    '; - echo '
  • '; - if ($his['time'] !== null) { - echo '[', $his['time'], ']'; - } else { - $show_result = false; - if ($his['type'] == 'major' || $his['type'] == 'minor') { - echo '[', $h->echoStatusBarTD('result', $cfg), ']'; - } else { - echo '[error]'; - } - } - echo '
  • '; + echo '
      '; + echo '
    • '; + if ($his['time'] !== null) { + echo '[', $his['time'], ']'; + } else { + $show_result = false; + if ($his['type'] == 'major' || $his['type'] == 'minor') { + echo '[', $h->echoStatusBarTD('result', $cfg), ']'; + } else { + echo '[error]'; + } + } + echo '
    • '; - echo '
    • '; - if (empty($message['text'])) { - if ($his['type'] == 'submit') { - echo '提交'; - } else { - echo '评测'; - } - } else { - echo HTML::escape($message['text']); - } - echo '
    • '; + echo '
    • '; + if (empty($message['text'])) { + if ($his['type'] == 'submit') { + echo '提交'; + } else { + echo '评测'; + } + } else { + echo HTML::escape($message['text']); + } + echo '
    • '; - echo '
    • '; - if (!empty($message['url'])) { - echo '(', HTML::autolink($message['url']), ')'; - } - echo '
    • '; - echo '
    '; + echo '
  • '; + if (!empty($message['url'])) { + echo '(', HTML::autolink($message['url']), ')'; + } + echo '
  • '; + echo '
'; - if ($show_result) { - echo '
    '; - echo '
  • '; - echo '测评结果:'; - echo $h->echoStatusBarTD('result', $cfg); - echo '
  • '; - echo '
  • '; - echo '用时:'; - echo $h->echoStatusBarTD('used_time', $cfg); - echo '
  • '; - echo '
  • '; - echo '内存:'; - echo $h->echoStatusBarTD('used_memory', $cfg); - echo '
  • '; - echo '
'; - } + if ($show_result) { + echo '
    '; + echo '
  • '; + echo '测评结果:'; + echo $h->echoStatusBarTD('result', $cfg); + echo '
  • '; + echo '
  • '; + echo '用时:'; + echo $h->echoStatusBarTD('used_time', $cfg); + echo '
  • '; + echo '
  • '; + echo '内存:'; + echo $h->echoStatusBarTD('used_memory', $cfg); + echo '
  • '; + echo '
'; + } - if ($extra) { - echo '
'; // col-md-9 - echo '
', $extra, '
'; - echo '
'; // row - } + if ($extra) { + echo '
'; // col-md-9 + echo '
', $extra, '
'; + echo '
'; // row + } - echo '
'; // - } - echo '
'; - } + echo '
'; // + } + echo '
'; + } } diff --git a/web/app/upgrade/36_decimal_score_range/up.sql b/web/app/upgrade/36_decimal_score_range/up.sql new file mode 100644 index 0000000..ec0b8f6 --- /dev/null +++ b/web/app/upgrade/36_decimal_score_range/up.sql @@ -0,0 +1,4 @@ +ALTER TABLE `contests_submissions` MODIFY `score` DECIMAL(15, 10) NOT NULL; +ALTER TABLE `submissions` MODIFY `score` DECIMAL(15, 10) DEFAULT NULL; +ALTER TABLE `submissions` MODIFY `hidden_score` DECIMAL(15, 10) DEFAULT NULL; +ALTER TABLE `submissions_history` MODIFY `score` DECIMAL(15, 10) DEFAULT NULL; diff --git a/web/js/uoj.js b/web/js/uoj.js index 5f11ce1..2d74f7a 100644 --- a/web/js/uoj.js +++ b/web/js/uoj.js @@ -419,8 +419,8 @@ $.fn.uoj_highlight = function() { $(this).find("span.uoj-username, span[data-uoj-username]").each(replaceWithHighlightUsername); $(this).find(".uoj-honor").uoj_honor(); $(this).find(".uoj-score").each(function() { - var score = parseInt($(this).text()); - var maxscore = parseInt($(this).data('max')); + var score = parseFloat($(this).text()); + var maxscore = parseFloat($(this).data('max')); if (isNaN(score)) { return; }