Contest rule: ACM/ICPC

This commit is contained in:
masnn committed 2020-04-15 18:48:17 +08:00
1 parent d1ec9064fc
commit e06283c0cc
43 files changed
+158 -891

No files matched your search

+23 -271
View File
@@ -83,9 +83,12 @@ async function updateStatus(tid, uid, rid, pid, accept, score) {
}, { upsert: true });
if (!tsdoc.value.attend) throw new ContestNotAttendedError(tid, uid);
}
function getStatus(tid, uid) {
return collStatus.findOne({ tid, uid });
}
async function getListStatus(uid, tids) {
const r = {};
for (const tid of tids) r[tid] = await this.getStatus(tid, uid); // eslint-disable-line no-await-in-loop
for (const tid of tids) r[tid] = await getStatus(tid, uid); // eslint-disable-line no-await-in-loop
return r;
}
async function attend(tid, uid) {
@@ -99,24 +102,24 @@ async function attend(tid, uid) {
function getMultiStatus(query) {
return collStatus.find(query);
}
function is_new(tdoc, days = 1) {
function isNew(tdoc, days = 1) {
const now = new Date().getTime();
const readyAt = tdoc.beginAt.getTime();
return (now < readyAt - days * 24 * 3600 * 1000);
}
function is_upcoming(tdoc, days = 1) {
function isUpcoming(tdoc, days = 1) {
const now = new Date().getTime();
const readyAt = tdoc.beginAt.getTime();
return (now > readyAt - days * 24 * 3600 * 1000 && now < tdoc.beginAt);
}
function is_not_started(tdoc) {
function isNotStarted(tdoc) {
return (new Date()) < tdoc.beginAt;
}
function is_ongoing(tdoc) {
function isOngoing(tdoc) {
const now = new Date();
return (tdoc.beginAt <= now && now < tdoc.endAt);
}
function is_done(tdoc) {
function isDone(tdoc) {
return tdoc.endAt <= new Date();
}
@@ -169,168 +172,38 @@ module.exports = {
edit,
get,
updateStatus,
getStatus,
count: (query) => coll.find(query).count(),
getMulti: (query) => coll.find(query),
getStatus: (tid, uid) => coll_status.findOne({ tid, uid }),
setStatus: (tid, uid, $set) => coll_status.findOneAndUpdate({ tid, uid }, { $set }),
is_new,
is_upcoming,
is_not_started,
is_ongoing,
is_done,
status_text: (tdoc) => (
is_new(tdoc)
setStatus: (tid, uid, $set) => collStatus.findOneAndUpdate({ tid, uid }, { $set }),
isNew,
isUpcoming,
isNotStarted,
isOngoing,
isDone,
statusText: (tdoc) => (
isNew(tdoc)
? 'New'
: is_upcoming(tdoc)
: isUpcoming(tdoc)
? 'Ready (☆▽☆)'
: is_ongoing(tdoc)
: isOngoing(tdoc)
? 'Live...'
: 'Done'),
get_status: (tdoc) => (
is_not_started(tdoc)
getStatusText: (tdoc) => (
isNotStarted(tdoc)
? 'not_started'
: is_ongoing(tdoc)
: isOngoing(tdoc)
? 'ongoing'
: 'finished'),
};
/*
journal_key_func = lambda j: j['rid']
Rule = collections.namedtuple('Rule', ['show_record_func',
'show_scoreboard_func',
'stat_func',
'status_sort',
'rank_func',
'scoreboard_func'])
def _oi_equ_func(a, b):
return a.get('score', 0) == b.get('score', 0)
RULES = {
constant.contest.RULE_OI: Rule(lambda tdoc, now: now > tdoc['end_at'],
lambda tdoc, now: now > tdoc['end_at'],
_oi_stat,
[('score', -1)],
functools.partial(rank.ranked, equ_func=_oi_equ_func),
_oi_scoreboard),
constant.contest.RULE_ACM: Rule(lambda tdoc, now: now >= tdoc['begin_at'],
lambda tdoc, now: now >= tdoc['begin_at'],
_acm_stat,
[('accept', -1), ('time', 1)],
functools.partial(enumerate, start=1),
_acm_scoreboard),
constant.contest.RULE_ASSIGNMENT: Rule(lambda tdoc, now: now >= tdoc['begin_at'],
lambda tdoc, now: False, # TODO: show scoreboard according to assignment preference
_assignment_stat,
[('penalty_score', -1), ('time', 1)],
functools.partial(enumerate, start=1),
_assignment_scoreboard),
}
def get_multi(domainId: str, doc_type: int, fields=None, **kwargs):
# TODO(twd2): projection.
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
return document.get_multi(domainId=domainId,
doc_type=doc_type,
fields=fields,
**kwargs) \
.sort([('doc_id', -1)])
@argmethod.wrap
async def attend(domainId: str, doc_type: int, cid: objeccid.Objeccid, uid: int):
# TODO(iceboy): check time.
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
try:
await document.capped_inc_status(domainId, doc_type, cid,
uid, 'attend', 1, 0, 1)
except errors.DuplicateKeyError:
if doc_type == document.TYPE_CONTEST:
raise error.ContestAlreadyAttendedError(domainId, cid, uid) from None
elif doc_type == document.TYPE_HOMEWORK:
raise error.HomeworkAlreadyAttendedError(domainId, cid, uid) from None
return await document.inc(domainId, doc_type, cid, 'attend', 1)
@argmethod.wrap
async def get_status(domainId: str, doc_type: int, cid: objeccid.Objeccid, uid: int, fields=None):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
return await document.get_status(domainId, doc_type, doc_id=cid,
uid=uid, fields=fields)
def get_multi_status(doc_type: int, *, fields=None, **kwargs):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
return document.get_multi_status(doc_type=doc_type,
fields=fields, **kwargs)
async def get_dict_status(domainId, uid, doc_type, cids, *, fields=None):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
result = dict()
async for tsdoc in get_multi_status(domainId=domainId,
uid=uid,
doc_type=doc_type,
doc_id={'$in': list(set(cids))},
fields=fields):
result[tsdoc['doc_id']] = tsdoc
return result
@argmethod.wrap
async def get_and_list_status(domainId: str, doc_type: int, cid: objeccid.Objeccid, fields=None):
# TODO(iceboy): projection, pagination.
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
tdoc = await get(domainId, doc_type, cid)
tsdocs = await document.get_multi_status(domainId=domainId,
doc_type=doc_type,
doc_id=tdoc['doc_id'],
fields=fields) \
.sort(RULES[tdoc['rule']].status_sort) \
.to_list()
return tdoc, tsdocs
def _get_status_journal(tsdoc):
# Sort and uniquify journal of the contest status document, by rid.
return [list(g)[-1] for _, g in itertools.groupby(sorted(tsdoc['journal'], key=journal_key_func),
key=journal_key_func)]
@argmethod.wrap
async def update_status(domainId: str, doc_type: int, cid: objeccid.Objeccid, uid: int,
rid: objeccid.Objeccid, pid: document.convert_doc_id,
accept: bool, score: int):
"""This method returns None when the modification has been superseded by a parallel operation."""
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
tdoc = await document.get(domainId, doc_type, cid)
tsdoc = await document.rev_push_status(
domainId, tdoc['doc_type'], tdoc['doc_id'], uid,
'journal', {'rid': rid, 'pid': pid, 'accept': accept, 'score': score})
if 'attend' not in tsdoc or not tsdoc['attend']:
if tdoc['doc_type'] == document.TYPE_CONTEST:
raise error.ContestNotAttendedError(domainId, cid, uid)
elif tdoc['doc_type'] == document.TYPE_HOMEWORK:
raise error.HomeworkNotAttendedError(domainId, cid, uid)
else:
raise error.InvalidArgumentError('doc_type')
journal = _get_status_journal(tsdoc)
stats = RULES[tdoc['rule']].stat_func(tdoc, journal)
tsdoc = await document.rev_set_status(domainId, tdoc['doc_type'], cid, uid, tsdoc['rev'],
journal=journal, **stats)
return tsdoc
@argmethod.wrap
async def recalc_status(domainId: str, doc_type: int, cid: objeccid.Objeccid):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
@@ -346,125 +219,4 @@ async def recalc_status(domainId: str, doc_type: int, cid: objeccid.Objeccid):
stats = RULES[tdoc['rule']].stat_func(tdoc, journal)
await document.rev_set_status(domainId, doc_type, cid, tsdoc['uid'], tsdoc['rev'],
return_doc=False, journal=journal, **stats)
def _parse_pids(pids_str):
pids = misc.dedupe(map(document.convert_doc_id, pids_str.split(',')))
return pids
def _format_pids(pids_list):
return ','.join([str(pid) for pid in pids_list])
class ContestStatusMixin(object):
@property
@functools.lru_cache()
def now(self):
# TODO(iceboy): This does not work on multi-machine environment.
return datetime.datetime.utcnow()
def is_new(self, tdoc):
ready_at = tdoc['begin_at'] - datetime.timedelta(days=1)
return self.now < ready_at
def is_upcoming(self, tdoc):
ready_at = tdoc['begin_at'] - datetime.timedelta(days=1)
return ready_at <= self.now < tdoc['begin_at']
def is_not_started(self, tdoc):
return self.now < tdoc['begin_at']
def is_ongoing(self, tdoc):
return tdoc['begin_at'] <= self.now < tdoc['end_at']
def is_done(self, tdoc):
return tdoc['end_at'] <= self.now
def is_homework_extended(self, tdoc):
return tdoc['penalty_since'] <= self.now < tdoc['end_at']
def status_text(self, tdoc):
if self.is_new(tdoc):
return 'New'
elif self.is_upcoming(tdoc):
return 'Ready (☆▽☆)'
elif self.is_ongoing(tdoc):
return 'Live...'
else:
return 'Done'
def get_status(self, tdoc):
if self.is_not_started(tdoc):
return 'not_started'
elif self.is_ongoing(tdoc):
return 'ongoing'
else:
return 'finished'
class ContestVisibilityMixin(object):
def can_view_hidden_scoreboard(self, tdoc):
if self.domainId != tdoc['domainId']:
return False
if tdoc['doc_type'] == document.TYPE_CONTEST:
return self.hasPerm(builtin.PERM_VIEW_CONTEST_HIDDEN_SCOREBOARD)
elif tdoc['doc_type'] == document.TYPE_HOMEWORK:
return self.hasPerm(builtin.PERM_VIEW_HOMEWORK_HIDDEN_SCOREBOARD)
else:
return False
def can_show_record(self, tdoc, allow_perm_override=True):
if RULES[tdoc['rule']].show_record_func(tdoc, datetime.datetime.utcnow()):
return True
if allow_perm_override and self.can_view_hidden_scoreboard(tdoc):
return True
return False
def can_show_scoreboard(self, tdoc, allow_perm_override=True):
if RULES[tdoc['rule']].show_scoreboard_func(tdoc, datetime.datetime.utcnow()):
return True
if allow_perm_override and self.can_view_hidden_scoreboard(tdoc):
return True
return False
class ContestCommonOperationMixin(object):
async def get_scoreboard(self, doc_type: int, cid: objeccid.Objeccid, is_export: bool=False):
if doc_type not in [document.TYPE_CONTEST, document.TYPE_HOMEWORK]:
raise error.InvalidArgumentError('doc_type')
tdoc, tsdocs = await get_and_list_status(self.domainId, doc_type, cid)
if not self.can_show_scoreboard(tdoc):
if doc_type == document.TYPE_CONTEST:
raise error.ContestScoreboardHiddenError(self.domainId, cid)
elif doc_type == document.TYPE_HOMEWORK:
raise error.HomeworkScoreboardHiddenError(self.domainId, cid)
udict, dudict, pdict = await asyncio.gather(
user.get_dict([tsdoc['uid'] for tsdoc in tsdocs]),
domain.get_dict_user_by_uid(self.domainId, [tsdoc['uid'] for tsdoc in tsdocs]),
problem.get_dict(self.domainId, tdoc['pids']))
ranked_tsdocs = RULES[tdoc['rule']].rank_func(tsdocs)
rows = RULES[tdoc['rule']].scoreboard_func(is_export, self.translate, tdoc,
ranked_tsdocs, udict, dudict, pdict)
return tdoc, rows, udict
async def verify_problems(self, pids):
pdocs = await problem.get_multi(domainId=self.domainId, doc_id={'$in': pids},
fields={'doc_id': 1}) \
.sort('doc_id', 1) \
.to_list()
exist_pids = [pdoc['_id'] for pdoc in pdocs]
if len(pids) != len(exist_pids):
for pid in pids:
if pid not in exist_pids:
raise error.ProblemNotFoundError(self.domainId, pid)
return pids
async def hide_problems(self, pids):
await asyncio.gather(*[problem.set_hidden(self.domainId, pid, True) for pid in pids])
class ContestMixin(ContestStatusMixin, ContestVisibilityMixin, ContestCommonOperationMixin):
pass
*/