mirror of
https://github.com/renbaoshuo/S2OJ.git
synced 2024-11-25 10:58:41 +00:00
feat: decimal score range (#36)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
commit
c2791a911e
@ -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;
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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<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")
|
||||
: 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<int> 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<PointInfo> 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<string, string> 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, "<tests>\n");
|
||||
fprintf(fres, "%s", details_out.str().c_str());
|
||||
fprintf(fres, "</tests>\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 << "<tests>\n";
|
||||
fres << details_out.str();
|
||||
fres << "</tests>\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, "<error>%s</error>\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 << "<error>" << htmlspecialchars(info) << "</error>\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, "<error>%s</error>\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 << "<error>" << htmlspecialchars(res.info) << "</error>\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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -453,7 +453,7 @@ if (UOJContest::cur()) {
|
||||
<?php $his_score = DB::selectSingle(["select max(score)", "from submissions", "where", ["problem_id" => UOJProblem::info('id'), "submitter" => Auth::id()]]) ?>
|
||||
|
||||
<a class="<?= is_null($his_score) ? '' : 'uoj-score' ?>" href="<?= HTML::url('/submissions', ['params' => ['problem_id' => UOJProblem::info('id'), 'submitter' => Auth::id()]]) ?>">
|
||||
<?= is_null($his_score) ? '无' : $his_score ?>
|
||||
<?= is_null($his_score) ? '无' : UOJSubmission::roundedScore($his_score) ?>
|
||||
</a>
|
||||
</li>
|
||||
<?php endif ?>
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
<?= UOJLocale::get('score range') ?>:
|
||||
</label>
|
||||
<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>
|
||||
<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 id="form-group-language" class="col-auto">
|
||||
|
@ -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([
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -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']);
|
||||
}
|
||||
|
@ -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).'\'';
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -1,238 +1,240 @@
|
||||
<?php
|
||||
|
||||
class UOJSubmissionHistory {
|
||||
use UOJDataTrait;
|
||||
use UOJDataTrait;
|
||||
|
||||
public $submission = null;
|
||||
public $n_versions;
|
||||
public $submission = null;
|
||||
public $n_versions;
|
||||
|
||||
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_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 '<div class="list-group timeline">';
|
||||
if ($this->submission->isLatest()) {
|
||||
echo '<p class="text-success"><i class="bi bi-check-circle"></i> 你现在查看的是最新测评结果</p>';
|
||||
} else if (UOJSubmission::info('judge_time') !== null) {
|
||||
if ($this->submission->isMajor()) {
|
||||
echo '<p class="text-danger"><i class="bi bi-exclamation-circle"></i> 你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'),' 的历史记录</p>';
|
||||
} else {
|
||||
echo '<p class="text-warning"><i class="bi bi-exclamation-circle"></i> 你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'),' 的隐藏记录</p>';
|
||||
}
|
||||
}
|
||||
$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 '<div class="list-group timeline">';
|
||||
if ($this->submission->isLatest()) {
|
||||
echo '<p class="text-success"><i class="bi bi-check-circle"></i> 你现在查看的是最新测评结果</p>';
|
||||
} else if (UOJSubmission::info('judge_time') !== null) {
|
||||
if ($this->submission->isMajor()) {
|
||||
echo '<p class="text-danger"><i class="bi bi-exclamation-circle"></i> 你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'), ' 的历史记录</p>';
|
||||
} else {
|
||||
echo '<p class="text-warning"><i class="bi bi-exclamation-circle"></i> 你现在查看的是测评时间为 ', UOJSubmission::info('judge_time'), ' 的隐藏记录</p>';
|
||||
}
|
||||
}
|
||||
$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 '<div class="', $cls, '">';
|
||||
$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'];
|
||||
} else {
|
||||
$show_result = false;
|
||||
echo '<div class="list-group-item rounded-0">';
|
||||
}
|
||||
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 '<div class="', $cls, '">';
|
||||
$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'];
|
||||
} else {
|
||||
$show_result = false;
|
||||
echo '<div class="list-group-item rounded-0">';
|
||||
}
|
||||
|
||||
if ($extra) {
|
||||
echo '<div class="row align-items-center">';
|
||||
echo '<div class="', $split_cls[0], '">';
|
||||
}
|
||||
if ($extra) {
|
||||
echo '<div class="row align-items-center">';
|
||||
echo '<div class="', $split_cls[0], '">';
|
||||
}
|
||||
|
||||
echo '<ul class="list-group-item-text list-inline text-primary mb-1">';
|
||||
echo '<li class="list-inline-item">';
|
||||
if ($his['time'] !== null) {
|
||||
echo '<strong>[', $his['time'], ']</strong>';
|
||||
} else {
|
||||
$show_result = false;
|
||||
if ($his['type'] == 'major' || $his['type'] == 'minor') {
|
||||
echo '<strong>[', $h->echoStatusBarTD('result', $cfg), ']</strong>';
|
||||
} else {
|
||||
echo '<strong>[error]</strong>';
|
||||
}
|
||||
}
|
||||
echo '</li>';
|
||||
echo '<ul class="list-group-item-text list-inline text-primary mb-1">';
|
||||
echo '<li class="list-inline-item">';
|
||||
if ($his['time'] !== null) {
|
||||
echo '<strong>[', $his['time'], ']</strong>';
|
||||
} else {
|
||||
$show_result = false;
|
||||
if ($his['type'] == 'major' || $his['type'] == 'minor') {
|
||||
echo '<strong>[', $h->echoStatusBarTD('result', $cfg), ']</strong>';
|
||||
} else {
|
||||
echo '<strong>[error]</strong>';
|
||||
}
|
||||
}
|
||||
echo '</li>';
|
||||
|
||||
echo '<li class="list-inline-item">';
|
||||
if (empty($message['text'])) {
|
||||
if ($his['type'] == 'submit') {
|
||||
echo '提交';
|
||||
} else {
|
||||
echo '评测';
|
||||
}
|
||||
} else {
|
||||
echo HTML::escape($message['text']);
|
||||
}
|
||||
echo '</li>';
|
||||
echo '<li class="list-inline-item">';
|
||||
if (empty($message['text'])) {
|
||||
if ($his['type'] == 'submit') {
|
||||
echo '提交';
|
||||
} else {
|
||||
echo '评测';
|
||||
}
|
||||
} else {
|
||||
echo HTML::escape($message['text']);
|
||||
}
|
||||
echo '</li>';
|
||||
|
||||
echo '<li class="list-inline-item">';
|
||||
if (!empty($message['url'])) {
|
||||
echo '(', HTML::autolink($message['url']), ')';
|
||||
}
|
||||
echo '</li>';
|
||||
echo '</ul>';
|
||||
echo '<li class="list-inline-item">';
|
||||
if (!empty($message['url'])) {
|
||||
echo '(', HTML::autolink($message['url']), ')';
|
||||
}
|
||||
echo '</li>';
|
||||
echo '</ul>';
|
||||
|
||||
if ($show_result) {
|
||||
echo '<ul class="-text list-inline mb-0">';
|
||||
echo '<li class="list-inline-item">';
|
||||
echo '<strong>测评结果:</strong>';
|
||||
echo $h->echoStatusBarTD('result', $cfg);
|
||||
echo '</li>';
|
||||
echo '<li class="list-inline-item">';
|
||||
echo '<strong>用时:</strong>';
|
||||
echo $h->echoStatusBarTD('used_time', $cfg);
|
||||
echo '</li>';
|
||||
echo '<li class="list-inline-item">';
|
||||
echo '<strong>内存:</strong>';
|
||||
echo $h->echoStatusBarTD('used_memory', $cfg);
|
||||
echo '</li>';
|
||||
echo '</ul>';
|
||||
}
|
||||
if ($show_result) {
|
||||
echo '<ul class="-text list-inline mb-0">';
|
||||
echo '<li class="list-inline-item">';
|
||||
echo '<strong>测评结果:</strong>';
|
||||
echo $h->echoStatusBarTD('result', $cfg);
|
||||
echo '</li>';
|
||||
echo '<li class="list-inline-item">';
|
||||
echo '<strong>用时:</strong>';
|
||||
echo $h->echoStatusBarTD('used_time', $cfg);
|
||||
echo '</li>';
|
||||
echo '<li class="list-inline-item">';
|
||||
echo '<strong>内存:</strong>';
|
||||
echo $h->echoStatusBarTD('used_memory', $cfg);
|
||||
echo '</li>';
|
||||
echo '</ul>';
|
||||
}
|
||||
|
||||
if ($extra) {
|
||||
echo '</div>'; // col-md-9
|
||||
echo '<div class="', $split_cls[1],' text-end">', $extra, '</div>';
|
||||
echo '</div>'; // row
|
||||
}
|
||||
if ($extra) {
|
||||
echo '</div>'; // col-md-9
|
||||
echo '<div class="', $split_cls[1], ' text-end">', $extra, '</div>';
|
||||
echo '</div>'; // row
|
||||
}
|
||||
|
||||
echo '</div>'; //
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
echo '</div>'; //
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
|
4
web/app/upgrade/36_decimal_score_range/up.sql
Normal file
4
web/app/upgrade/36_decimal_score_range/up.sql
Normal file
@ -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;
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user