Add contest tester/curator (DMOJ)
This commit is contained in:
parent
06318a97e5
commit
297b8a2a36
25 changed files with 611 additions and 230 deletions
|
@ -82,6 +82,14 @@ class BaseContestFormat(six.with_metaclass(ABCMeta)):
|
|||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@abstractmethod
|
||||
def get_contest_problem_label_script(self):
|
||||
"""
|
||||
Returns the default Lua script to generate contest problem labels.
|
||||
:return: A string, the Lua script.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
@classmethod
|
||||
def best_solution_state(cls, points, total):
|
||||
if not points:
|
||||
|
|
|
@ -68,3 +68,10 @@ class DefaultContestFormat(BaseContestFormat):
|
|||
|
||||
def get_problem_breakdown(self, participation, contest_problems):
|
||||
return [(participation.format_data or {}).get(str(contest_problem.id)) for contest_problem in contest_problems]
|
||||
|
||||
def get_contest_problem_label_script(self):
|
||||
return '''
|
||||
function(n)
|
||||
return tostring(math.floor(n + 1))
|
||||
end
|
||||
'''
|
129
judge/contest_format/icpc.py
Normal file
129
judge/contest_format/icpc.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
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
|
||||
from judge.timezone import from_database_time
|
||||
from judge.utils.timedelta import nice_repr
|
||||
|
||||
|
||||
@register_contest_format('icpc')
|
||||
class ICPCContestFormat(DefaultContestFormat):
|
||||
name = gettext_lazy('ICPC')
|
||||
config_defaults = {'penalty': 20}
|
||||
config_validators = {'penalty': lambda x: x >= 0}
|
||||
'''
|
||||
penalty: Number of penalty minutes each incorrect submission adds. Defaults to 20.
|
||||
'''
|
||||
|
||||
@classmethod
|
||||
def validate(cls, config):
|
||||
if config is None:
|
||||
return
|
||||
|
||||
if not isinstance(config, dict):
|
||||
raise ValidationError('ICPC-styled contest expects no config or dict as config')
|
||||
|
||||
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):
|
||||
raise ValidationError('invalid value "%s" for config key "%s"' % (value, key))
|
||||
|
||||
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 = {}
|
||||
|
||||
with connection.cursor() as cursor:
|
||||
cursor.execute('''
|
||||
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)
|
||||
GROUP BY cp.id
|
||||
''', (participation.id, participation.id))
|
||||
|
||||
for points, time, prob in cursor.fetchall():
|
||||
time = from_database_time(time)
|
||||
dt = (time - participation.start).total_seconds()
|
||||
|
||||
# Compute penalty
|
||||
if self.config['penalty']:
|
||||
# An IE can have a submission result of `None`
|
||||
subs = participation.submissions.exclude(submission__result__isnull=True) \
|
||||
.exclude(submission__result__in=['IE', 'CE']) \
|
||||
.filter(problem_id=prob)
|
||||
if points:
|
||||
prev = subs.filter(submission__date__lte=time).count() - 1
|
||||
penalty += prev * self.config['penalty'] * 60
|
||||
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)
|
||||
|
||||
format_data[str(prob)] = {'time': dt, 'points': points, 'penalty': prev}
|
||||
score += points
|
||||
|
||||
participation.cumtime = cumtime + penalty
|
||||
participation.score = score
|
||||
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):
|
||||
format_data = (participation.format_data or {}).get(str(contest_problem.id))
|
||||
if format_data:
|
||||
penalty = format_html('<small style="color:red"> ({penalty})</small>',
|
||||
penalty=floatformat(format_data['penalty'])) if format_data['penalty'] else ''
|
||||
return format_html(
|
||||
'<td class="{state}"><a href="{url}">{points}{penalty}<div class="solving-time">{time}</div></a></td>',
|
||||
state=(('pretest-' if self.contest.run_pretests_only and contest_problem.is_pretested else '') +
|
||||
self.best_solution_state(format_data['points'], contest_problem.points)),
|
||||
url=reverse('contest_user_submissions',
|
||||
args=[self.contest.key, participation.user.user.username, contest_problem.problem.code]),
|
||||
points=floatformat(format_data['points']),
|
||||
penalty=penalty,
|
||||
time=nice_repr(timedelta(seconds=format_data['time']), 'noday'),
|
||||
)
|
||||
else:
|
||||
return mark_safe('<td></td>')
|
||||
|
||||
def get_contest_problem_label_script(self):
|
||||
return '''
|
||||
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
|
||||
'''
|
Loading…
Add table
Add a link
Reference in a new issue