NDOJ/judge/contest_format/icpc.py

169 lines
6.5 KiB
Python
Raw Permalink Normal View History

2021-05-24 20:00:36 +00:00
from datetime import timedelta
from django.core.exceptions import ValidationError
from django.db import connection
from django.template.defaultfilters import floatformat
from django.urls import reverse
from django.utils.html import format_html
from django.utils.safestring import mark_safe
from django.utils.translation import gettext_lazy
from judge.contest_format.default import DefaultContestFormat
from judge.contest_format.registry import register_contest_format
2022-11-18 22:59:58 +00:00
from judge.timezone import from_database_time, to_database_time
2021-05-24 20:00:36 +00:00
from judge.utils.timedelta import nice_repr
2022-05-14 17:57:27 +00:00
@register_contest_format("icpc")
2021-05-24 20:00:36 +00:00
class ICPCContestFormat(DefaultContestFormat):
2022-05-14 17:57:27 +00:00
name = gettext_lazy("ICPC")
config_defaults = {"penalty": 20}
config_validators = {"penalty": lambda x: x >= 0}
"""
2021-05-24 20:00:36 +00:00
penalty: Number of penalty minutes each incorrect submission adds. Defaults to 20.
2022-05-14 17:57:27 +00:00
"""
2021-05-24 20:00:36 +00:00
@classmethod
def validate(cls, config):
if config is None:
return
if not isinstance(config, dict):
2022-05-14 17:57:27 +00:00
raise ValidationError(
"ICPC-styled contest expects no config or dict as config"
)
2021-05-24 20:00:36 +00:00
for key, value in config.items():
if key not in cls.config_defaults:
raise ValidationError('unknown config key "%s"' % key)
if not isinstance(value, type(cls.config_defaults[key])):
raise ValidationError('invalid type for config key "%s"' % key)
if not cls.config_validators[key](value):
2022-05-14 17:57:27 +00:00
raise ValidationError(
'invalid value "%s" for config key "%s"' % (value, key)
)
2021-05-24 20:00:36 +00:00
def __init__(self, contest, config):
self.config = self.config_defaults.copy()
self.config.update(config or {})
self.contest = contest
def update_participation(self, participation):
cumtime = 0
last = 0
penalty = 0
score = 0
format_data = {}
2022-11-18 22:59:58 +00:00
frozen_time = self.contest.end_time
if self.contest.freeze_after:
frozen_time = participation.start + self.contest.freeze_after
2021-05-24 20:00:36 +00:00
with connection.cursor() as cursor:
2022-05-14 17:57:27 +00:00
cursor.execute(
"""
2021-05-24 20:00:36 +00:00
SELECT MAX(cs.points) as `points`, (
SELECT MIN(csub.date)
FROM judge_contestsubmission ccs LEFT OUTER JOIN
judge_submission csub ON (csub.id = ccs.submission_id)
WHERE ccs.problem_id = cp.id AND ccs.participation_id = %s AND ccs.points = MAX(cs.points)
) AS `time`, cp.id AS `prob`
FROM judge_contestproblem cp INNER JOIN
judge_contestsubmission cs ON (cs.problem_id = cp.id AND cs.participation_id = %s) LEFT OUTER JOIN
judge_submission sub ON (sub.id = cs.submission_id)
2022-11-18 22:59:58 +00:00
WHERE sub.date < %s
2021-05-24 20:00:36 +00:00
GROUP BY cp.id
2022-05-14 17:57:27 +00:00
""",
2022-11-18 22:59:58 +00:00
(participation.id, participation.id, to_database_time(frozen_time)),
2022-05-14 17:57:27 +00:00
)
2021-05-24 20:00:36 +00:00
for points, time, prob in cursor.fetchall():
time = from_database_time(time)
dt = (time - participation.start).total_seconds()
# Compute penalty
2022-05-14 17:57:27 +00:00
if self.config["penalty"]:
2021-05-24 20:00:36 +00:00
# An IE can have a submission result of `None`
2022-05-14 17:57:27 +00:00
subs = (
participation.submissions.exclude(
submission__result__isnull=True
)
.exclude(submission__result__in=["IE", "CE"])
.filter(problem_id=prob)
)
2021-05-24 20:00:36 +00:00
if points:
prev = subs.filter(submission__date__lte=time).count() - 1
2022-05-14 17:57:27 +00:00
penalty += prev * self.config["penalty"] * 60
2021-05-24 20:00:36 +00:00
else:
# We should always display the penalty, even if the user has a score of 0
prev = subs.count()
else:
prev = 0
if points:
cumtime += dt
last = max(last, dt)
2022-05-14 17:57:27 +00:00
format_data[str(prob)] = {"time": dt, "points": points, "penalty": prev}
2021-05-24 20:00:36 +00:00
score += points
2022-11-18 22:59:58 +00:00
self.handle_frozen_state(participation, format_data)
2021-08-11 20:38:03 +00:00
participation.cumtime = max(0, cumtime + penalty)
2022-11-22 04:05:35 +00:00
participation.score = round(score, self.contest.points_precision)
2021-05-24 20:00:36 +00:00
participation.tiebreaker = last # field is sorted from least to greatest
participation.format_data = format_data
participation.save()
def display_user_problem(self, participation, contest_problem, show_final=False):
2021-05-24 20:00:36 +00:00
format_data = (participation.format_data or {}).get(str(contest_problem.id))
if format_data:
2022-05-14 17:57:27 +00:00
penalty = (
format_html(
'<small style="color:red"> +{penalty}</small>',
2022-05-14 17:57:27 +00:00
penalty=floatformat(format_data["penalty"]),
)
2022-11-18 22:59:58 +00:00
if format_data.get("penalty")
2022-05-14 17:57:27 +00:00
else ""
)
2021-05-24 20:00:36 +00:00
return format_html(
'<td class="{state}"><a data-featherlight="{url}" href="#">{points}{penalty}<div class="solving-time">{time}</div></a></td>',
2022-05-14 17:57:27 +00:00
state=(
(
"pretest-"
if self.contest.run_pretests_only
and contest_problem.is_pretested
else ""
)
+ self.best_solution_state(
format_data["points"], contest_problem.points
)
2022-11-18 22:59:58 +00:00
+ (" frozen" if format_data.get("frozen") else "")
2022-05-14 17:57:27 +00:00
),
url=reverse(
"contest_user_submissions_ajax",
2022-05-14 17:57:27 +00:00
args=[
self.contest.key,
2022-11-22 04:05:35 +00:00
participation.id,
2022-05-14 17:57:27 +00:00
contest_problem.problem.code,
],
),
points=floatformat(format_data["points"]),
2021-05-24 20:00:36 +00:00
penalty=penalty,
2022-05-14 17:57:27 +00:00
time=nice_repr(timedelta(seconds=format_data["time"]), "noday"),
2021-05-24 20:00:36 +00:00
)
else:
2022-05-14 17:57:27 +00:00
return mark_safe("<td></td>")
2021-05-24 20:00:36 +00:00
def get_contest_problem_label_script(self):
2022-05-14 17:57:27 +00:00
return """
2021-05-24 20:00:36 +00:00
function(n)
n = n + 1
ret = ""
while n > 0 do
ret = string.char((n - 1) % 26 + 65) .. ret
n = math.floor((n - 1) / 26)
end
return ret
end
2022-05-14 17:57:27 +00:00
"""