feat: decimal score range

Co-authored-by: Baoshuo Ren <i@baoshuo.ren>
Co-authored-by: Kaifeng Lyu <vfleaking@163.com>
Co-authored-by: Haoxiang Yu <yhx12243@gmail.com>
This commit is contained in:
Baoshuo Ren 2023-02-04 18:17:31 +08:00
parent 9e595099b8
commit 33ba0c9d4a
Signed by: baoshuo
GPG Key ID: 00CB9680AB29F51A
13 changed files with 425 additions and 271 deletions

View File

@ -332,7 +332,7 @@ CREATE TABLE `contests_submissions` (
`submitter` varchar(20) NOT NULL, `submitter` varchar(20) NOT NULL,
`problem_id` int NOT NULL, `problem_id` int NOT NULL,
`submission_id` int NOT NULL, `submission_id` int NOT NULL,
`score` int NOT NULL, `score` DECIMAL(15, 10) NOT NULL,
`penalty` int NOT NULL, `penalty` int NOT NULL,
`cnt` int DEFAULT NULL, `cnt` int DEFAULT NULL,
`n_failures` int DEFAULT NULL, `n_failures` int DEFAULT NULL,
@ -875,9 +875,9 @@ CREATE TABLE `submissions` (
`result` mediumblob NOT NULL, `result` mediumblob NOT NULL,
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, `status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`result_error` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT 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', `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_time` int NOT NULL DEFAULT '0',
`used_memory` int NOT NULL DEFAULT '0', `used_memory` int NOT NULL DEFAULT '0',
`is_hidden` tinyint(1) NOT NULL, `is_hidden` tinyint(1) NOT NULL,
@ -922,7 +922,7 @@ CREATE TABLE `submissions_history` (
`status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL, `status` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
`status_details` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '', `status_details` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '',
`result_error` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL, `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_time` int NOT NULL DEFAULT '0',
`used_memory` int NOT NULL DEFAULT '0', `used_memory` int NOT NULL DEFAULT '0',
`major` tinyint(1) NOT NULL, `major` tinyint(1) NOT NULL,

View File

@ -231,7 +231,13 @@ class Judger:
else: else:
assert len(sp) == 2 assert len(sp) == 2
res[sp[0]] = sp[1] res[sp[0]] = sp[1]
try:
res['score'] = int(res['score']) 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['time'] = int(res['time'])
res['memory'] = int(res['memory']) res['memory'] = int(res['memory'])
res['status'] = 'Judged' res['status'] = 'Judged'

View File

@ -273,8 +273,92 @@ struct InfoBlock {
} }
}; };
int scale_score(int scr100, int full) { enum SCORE_MODE {
return scr100 * full / 100; 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 { struct PointInfo {
@ -283,14 +367,14 @@ struct PointInfo {
static bool show_res; static bool show_res;
int num; int num;
int scr; score_t scr;
int ust, usm; int ust, usm;
string info, in, out, res; string info, in, out, res;
bool use_li; bool use_li;
vector<InfoBlock> li; vector<InfoBlock> 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") const int &_ust, const int &_usm, const string &_info = "default")
: num(_num), scr(_scr), : num(_num), scr(_scr),
ust(_ust), usm(_usm), info(_info) { 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 int &_ust, const int &_usm, const string &_info,
const string &_in, const string &_out, const string &_res) const string &_in, const string &_out, const string &_res)
: num(_num), scr(_scr), : num(_num), scr(_scr),
@ -380,7 +464,7 @@ struct SubtaskMetaInfo {
string subtask_type; string subtask_type;
string subtask_used_time_type; string subtask_used_time_type;
vector<int> subtask_dependencies; vector<int> subtask_dependencies;
int full_score; score_t full_score;
inline bool is_ordinary() { inline bool is_ordinary() {
return subtask_type == "packed" && subtask_used_time_type == "sum"; return subtask_type == "packed" && subtask_used_time_type == "sum";
@ -392,9 +476,10 @@ struct SubtaskInfo {
bool passed = true; bool passed = true;
bool early_stop = false; bool early_stop = false;
int scr, ust = 0, usm = 0; score_t scr;
int ust = 0, usm = 0;
string info = "Accepted"; string info = "Accepted";
int unrescaled_min_score = 100; score_t unrescaled_min_score = 100;
vector<PointInfo> points; vector<PointInfo> points;
SubtaskInfo() = default; SubtaskInfo() = default;
@ -489,7 +574,7 @@ struct SubtaskInfo {
struct RunCheckerResult { struct RunCheckerResult {
int type; int type;
int ust, usm; int ust, usm;
int scr; score_t scr;
string info; string info;
static RunCheckerResult from_file(const string &file_name, const RunResult &rres) { static RunCheckerResult from_file(const string &file_name, const RunResult &rres) {
@ -513,7 +598,7 @@ struct RunCheckerResult {
if (fscanf(fres, "%lf", &d) != 1) { if (fscanf(fres, "%lf", &d) != 1) {
return RunCheckerResult::failed_result(); return RunCheckerResult::failed_result();
} else { } else {
res.scr = (int)floor(100 * d + 0.5); res.scr = 100 * d;
} }
} else { } else {
res.scr = 0; res.scr = 0;
@ -579,7 +664,7 @@ string result_path;
int tot_time = 0; int tot_time = 0;
int max_memory = 0; int max_memory = 0;
int tot_score = 0; score_t tot_score = 0;
ostringstream details_out; ostringstream details_out;
map<string, string> config; map<string, string> config;
@ -654,6 +739,12 @@ double conf_double(const string &key, int num, const double &val) {
double conf_double(const string &key) { double conf_double(const string &key) {
return conf_double(key, 0); 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) { string conf_file_name_with_num(string s, int num) {
ostringstream name; ostringstream name;
if (num < 0) { 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_type = conf_str(pre + "subtask_type", num, "packed");
meta.subtask_used_time_type = conf_str(pre + "subtask_used_time_type", num, "sum"); 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") { if (conf_str("subtask_dependence", num, "none") == "many") {
string cur = "subtask_dependence_" + vtos(num); string cur = "subtask_dependence_" + vtos(num);
int p = 1; int p = 1;
@ -762,7 +853,7 @@ void add_point_info(const PointInfo &info, bool update_tot_score = true) {
} }
} }
if (update_tot_score) { if (update_tot_score) {
tot_score += info.scr; tot_score = (tot_score + info.scr).rounded_score();
} }
details_out << info; details_out << info;
@ -791,35 +882,44 @@ void add_subtask_info(const SubtaskInfo &st_info) {
if (st_info.usm >= 0) { if (st_info.usm >= 0) {
max_memory = max(max_memory, st_info.usm); 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; details_out << st_info;
} }
void end_judge_ok() { void end_judge_ok() {
FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); ofstream fres(result_path + "/result.txt");
fprintf(fres, "score %d\n", tot_score); if (!fres) {
fprintf(fres, "time %d\n", tot_time); exit(1);
fprintf(fres, "memory %d\n", max_memory); }
fprintf(fres, "details\n"); fres << "score " << tot_score << "\n";
fprintf(fres, "<tests>\n"); fres << "time " << tot_time << "\n";
fprintf(fres, "%s", details_out.str().c_str()); fres << "memory " << max_memory << "\n";
fprintf(fres, "</tests>\n"); fres << "details\n";
fclose(fres); fres << "<tests>\n";
fres << details_out.str();
fres << "</tests>\n";
fres.close();
exit(0); exit(0);
} }
void end_judge_judgement_failed(const string &info) { void end_judge_judgement_failed(const string &info) {
FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); ofstream fres(result_path + "/result.txt");
fprintf(fres, "error Judgment Failed\n"); if (!fres) {
fprintf(fres, "details\n"); exit(1);
fprintf(fres, "<error>%s</error>\n", htmlspecialchars(info).c_str()); }
fclose(fres); fres << "error Judgment Failed\n";
fres << "details\n";
fres << "<error>" << htmlspecialchars(info) << "</error>\n";
fres.close();
exit(0); exit(0);
} }
void end_judge_compile_error(const RunCompilerResult &res) { void end_judge_compile_error(const RunCompilerResult &res) {
FILE *fres = fopen((result_path + "/result.txt").c_str(), "w"); ofstream fres(result_path + "/result.txt");
fprintf(fres, "error Compile Error\n"); if (!fres) {
fprintf(fres, "details\n"); exit(1);
fprintf(fres, "<error>%s</error>\n", htmlspecialchars(res.info).c_str()); }
fclose(fres); fres << "error Compile Error\n";
fres << "details\n";
fres << "<error>" << htmlspecialchars(res.info) << "</error>\n";
fres.close();
exit(0); exit(0);
} }
@ -1543,7 +1643,7 @@ bool main_data_test(TP test_point_func) {
if (po.scr != 100) { if (po.scr != 100) {
passed = false; 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); add_point_info(po);
} }
} else if (nT == 1 && conf_subtask_meta_info(1).is_ordinary()) { // ACM } 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_out = conf_str("show_out", "on") == "on";
PointInfo::show_res = conf_str("show_res", "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) { if (chdir(work_path.c_str()) != 0) {
cerr << "invalid work path" << endl; cerr << "invalid work path" << endl;
exit(1); exit(1);

View File

@ -29,7 +29,7 @@ function scoreDistributionData() {
} else if ($row[0] == 100) { } else if ($row[0] == 100) {
$has_score_100 = true; $has_score_100 = true;
} }
$score = $row[0] * 100; $score = UOJSubmission::roundedScore($row[0]) * 100;
$data[] = ['score' => $score, 'count' => $row[1]]; $data[] = ['score' => $score, 'count' => $row[1]];
} }
if (!$has_score_0) { if (!$has_score_0) {

View File

@ -15,8 +15,8 @@ $config = [
$q_problem_id = UOJRequest::get('problem_id', 'validateUInt', null); $q_problem_id = UOJRequest::get('problem_id', 'validateUInt', null);
$q_submitter = UOJRequest::get('submitter', 'validateUsername', null); $q_submitter = UOJRequest::get('submitter', 'validateUsername', null);
$q_min_score = UOJRequest::get('min_score', 'validateUInt', null); $q_min_score = UOJRequest::get('min_score', 'validateUFloat', null);
$q_max_score = UOJRequest::get('max_score', 'validateUInt', null); $q_max_score = UOJRequest::get('max_score', 'validateUFloat', null);
$q_lang = UOJRequest::get('language', 'is_short_string', null); $q_lang = UOJRequest::get('language', 'is_short_string', null);
if ($q_problem_id !== null) { if ($q_problem_id !== null) {
@ -83,9 +83,9 @@ if (!$conds) {
<?= UOJLocale::get('score range') ?>: <?= UOJLocale::get('score range') ?>:
</label> </label>
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<input type="text" class="form-control" name="min_score" id="input-min_score" value="<?= $q_min_score ?>" maxlength="3" style="width:4em" placeholder="0" /> <input type="text" class="form-control" name="min_score" id="input-min_score" value="<?= $q_min_score ?>" maxlength="15" style="width:4em" placeholder="0" />
<span class="input-group-text" id="basic-addon3">~</span> <span class="input-group-text" id="basic-addon3">~</span>
<input type="text" class="form-control" name="max_score" id="input-max_score" value="<?= $q_max_score ?>" maxlength="3" style="width:4em" placeholder="100" /> <input type="text" class="form-control" name="max_score" id="input-max_score" value="<?= $q_max_score ?>" maxlength="15" style="width:4em" placeholder="100" />
</div> </div>
</div> </div>
<div id="form-group-language" class="col-auto"> <div id="form-group-language" class="col-auto">

View File

@ -149,7 +149,7 @@ function queryOIorIOIContestSubmissionData($contest, $problems, $prob_pos, $conf
} }
$row[0] = (int)$row[0]; $row[0] = (int)$row[0];
$row[3] = $prob_pos[$row[3]]; $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; $data[] = $row;
} }
} else { } else {
@ -185,7 +185,7 @@ function queryOIorIOIContestSubmissionData($contest, $problems, $prob_pos, $conf
foreach ($res as $row) { foreach ($res as $row) {
$row[0] = (int)$row[0]; $row[0] = (int)$row[0];
$row[3] = $prob_pos[$row[3]]; $row[3] = $prob_pos[$row[3]];
$row[4] = (int)$row[4]; $row[4] = UOJSubmission::roundedScore($row[4]);
$data[] = $row; $data[] = $row;
} }
} }
@ -238,7 +238,7 @@ function queryACMContestSubmissionData($contest, $problems, $prob_pos, $config =
$row[0] = (int)$row[0]; $row[0] = (int)$row[0];
$row[3] = $prob_pos[$row[3]]; $row[3] = $prob_pos[$row[3]];
if (isset($row[4])) { if (isset($row[4])) {
$row[4] = (int)$row[4]; $row[4] = UOJSubmission::roundedScore($row[4]);
} }
if (isset($row[5])) { if (isset($row[5])) {
$row[5] = (int)$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++) { for ($i = 0; $i < $n_problems; $i++) {
if (isset($score[$person[0]][$i])) { if (isset($score[$person[0]][$i])) {
$cur_row = $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]; $cur[1] += $cur_row[1];
if ($cfg['update_contests_submissions']) { if ($cfg['update_contests_submissions']) {
DB::insert([ DB::insert([

View File

@ -310,7 +310,7 @@ class SyncProblemDataHandler {
if ($this->problem_conf === -1) { if ($this->problem_conf === -1) {
throw new UOJFileNotFoundException("problem.conf"); throw new UOJFileNotFoundException("problem.conf");
} elseif ($this->problem_conf === -2) { } 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)); $this->allow_files = array_flip(FS::scandir($this->upload_dir));

View File

@ -718,12 +718,10 @@ class JudgmentDetailsPrinter {
public function __construct($details, $styler, $name) { public function __construct($details, $styler, $name) {
$this->name = $name; $this->name = $name;
$this->styler = $styler; $this->styler = $styler;
$this->details = $details;
$this->dom = new DOMDocument(); $this->dom = new DOMDocument();
if (!$this->dom->loadXML($this->details)) { if (!$this->dom->loadXML($details)) {
throw new Exception("XML syntax error"); throw new Exception("XML syntax error");
} }
$this->details = '';
} }
public function printHTML() { public function printHTML() {
$this->subtask_num = null; $this->subtask_num = null;

View File

@ -27,7 +27,7 @@ function validateUInt($x, $len = 8) { // [0, 1000000000)
if ($x === '0') { if ($x === '0') {
return true; 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) { function validateInt($x) {
@ -40,6 +40,29 @@ function validateInt($x) {
return validateUInt($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) { function validateUploadedFile($name) {
return isset($_FILES[$name]) && is_uploaded_file($_FILES[$name]['tmp_name']); return isset($_FILES[$name]) && is_uploaded_file($_FILES[$name]['tmp_name']);
} }

View File

@ -110,7 +110,7 @@ class DB {
return 'true'; return 'true';
} elseif ($str === false) { } elseif ($str === false) {
return 'false'; return 'false';
} elseif (is_int($str)) { } elseif (is_int($str)|| is_float($str)) {
return $str; return $str;
} elseif (is_string($str)) { } elseif (is_string($str)) {
return '\''.DB::escape($str).'\''; return '\''.DB::escape($str).'\'';

View File

@ -83,6 +83,15 @@ class UOJSubmission {
return $this->info['hide_score_to_others'] ? $this->info['hidden_score'] : $this->info['score']; 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) { public static function onUpload($zip_file_name, $content, $tot_size, $is_contest_submission) {
$judge_reason = ''; $judge_reason = '';
@ -367,6 +376,8 @@ class UOJSubmission {
public function __construct($info) { public function __construct($info) {
$this->info = $info; $this->info = $info;
static::roundScoreInArray($this->info, 'score');
static::roundScoreInArray($this->info, 'hidden_score');
} }
public function hasFullyJudged() { public function hasFullyJudged() {
@ -638,6 +649,7 @@ class UOJSubmission {
if (!$his) { if (!$his) {
return false; return false;
} }
static::roundScoreInArray($his, 'actual_score');
return $this->loadHistory($his); return $this->loadHistory($his);
} }
@ -659,6 +671,7 @@ class UOJSubmission {
if (!$his) { if (!$his) {
return false; return false;
} }
static::roundScoreInArray($his, 'actual_score');
return $this->loadHistory($his); return $this->loadHistory($his);
} }
} }

View File

@ -41,7 +41,8 @@ class UOJSubmissionHistory {
"select", DB::fields( "select", DB::fields(
UOJSubmissionHistory::$fields_without_result + [ UOJSubmissionHistory::$fields_without_result + [
'priority' => 100 'priority' => 100
]), ]
),
"from submissions_history", "from submissions_history",
"where", [ "where", [
'submission_id' => $submission->info['id'], 'submission_id' => $submission->info['id'],
@ -111,6 +112,7 @@ class UOJSubmissionHistory {
if ($his['time'] == UOJTime::MAX_TIME) { if ($his['time'] == UOJTime::MAX_TIME) {
$his['time'] = null; $his['time'] = null;
} }
UOJSubmission::roundScoreInArray($his, 'actual_score');
} }
$res = []; $res = [];
@ -141,9 +143,9 @@ class UOJSubmissionHistory {
echo '<p class="text-success"><i class="bi bi-check-circle"></i> 你现在查看的是最新测评结果</p>'; echo '<p class="text-success"><i class="bi bi-check-circle"></i> 你现在查看的是最新测评结果</p>';
} else if (UOJSubmission::info('judge_time') !== null) { } else if (UOJSubmission::info('judge_time') !== null) {
if ($this->submission->isMajor()) { if ($this->submission->isMajor()) {
echo '<p class="text-danger"><i class="bi bi-exclamation-circle"></i> 你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'),' 的历史记录</p>'; echo '<p class="text-danger"><i class="bi bi-exclamation-circle"></i> 你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'), ' 的历史记录</p>';
} else { } else {
echo '<p class="text-warning"><i class="bi bi-exclamation-circle"></i> 你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'),' 的隐藏记录</p>'; echo '<p class="text-warning"><i class="bi bi-exclamation-circle"></i> 你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'), ' 的隐藏记录</p>';
} }
} }
$h = clone $this->submission; $h = clone $this->submission;
@ -163,7 +165,7 @@ class UOJSubmissionHistory {
$cls .= ' list-group-item-warning'; $cls .= ' list-group-item-warning';
} }
echo '<div class="', $cls, '">'; echo '<div class="', $cls, '">';
$extra = '<a class="text-decoration-none" href="'.$h->getUri().'"><i class="bi bi-info-circle"></i> 查看</a>'; $extra = '<a class="text-decoration-none" href="' . $h->getUri() . '"><i class="bi bi-info-circle"></i> 查看</a>';
$split_cls = ['col-sm-10', 'col-sm-2']; $split_cls = ['col-sm-10', 'col-sm-2'];
} else { } else {
$show_result = false; $show_result = false;
@ -227,7 +229,7 @@ class UOJSubmissionHistory {
if ($extra) { if ($extra) {
echo '</div>'; // col-md-9 echo '</div>'; // col-md-9
echo '<div class="', $split_cls[1],' text-end">', $extra, '</div>'; echo '<div class="', $split_cls[1], ' text-end">', $extra, '</div>';
echo '</div>'; // row echo '</div>'; // row
} }

View File

@ -419,8 +419,8 @@ $.fn.uoj_highlight = function() {
$(this).find("span.uoj-username, span[data-uoj-username]").each(replaceWithHighlightUsername); $(this).find("span.uoj-username, span[data-uoj-username]").each(replaceWithHighlightUsername);
$(this).find(".uoj-honor").uoj_honor(); $(this).find(".uoj-honor").uoj_honor();
$(this).find(".uoj-score").each(function() { $(this).find(".uoj-score").each(function() {
var score = parseInt($(this).text()); var score = parseFloat($(this).text());
var maxscore = parseInt($(this).data('max')); var maxscore = parseFloat($(this).data('max'));
if (isNaN(score)) { if (isNaN(score)) {
return; return;
} }