Skip to content

Commit 4b1cb4c

Browse files
committed
Rewrite the phase computation function
Simplify it and support delay_time (reenabling its tests).
1 parent 78b245c commit 4b1cb4c

2 files changed

Lines changed: 99 additions & 78 deletions

File tree

cms/server/__init__.py

Lines changed: 76 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import logging
3333
import tarfile
3434
import zipfile
35+
from datetime import datetime, timedelta
3536
from urllib import quote
3637

3738
from functools import wraps
@@ -93,80 +94,89 @@ def compute_actual_phase(timestamp, contest_start, contest_stop, per_user_time,
9394
datetimes (or None) defining two intervals.
9495
9596
"""
96-
if contest_start is not None and contest_start > timestamp:
97-
phase = -1
98-
elif contest_stop is None or contest_stop > timestamp:
99-
phase = 0
97+
# Validate arguments.
98+
assert (isinstance(timestamp, datetime) and
99+
isinstance(contest_start, datetime) and
100+
isinstance(contest_stop, datetime) and
101+
(per_user_time is None or isinstance(per_user_time, timedelta)) and
102+
(starting_time is None or isinstance(starting_time, datetime)) and
103+
isinstance(delay_time, timedelta) and
104+
isinstance(extra_time, timedelta))
105+
106+
assert contest_start <= contest_stop
107+
assert per_user_time is None or per_user_time >= timedelta()
108+
assert delay_time >= timedelta()
109+
assert extra_time >= timedelta()
110+
111+
if per_user_time is not None and starting_time is None:
112+
# "USACO-like" contest, but we still don't know when the user
113+
# started/will start.
114+
actual_start = None
115+
actual_stop = None
116+
117+
if contest_start <= timestamp <= contest_stop:
118+
actual_phase = -1
119+
current_phase_begin = contest_start
120+
current_phase_end = contest_stop
121+
elif timestamp < contest_start:
122+
actual_phase = -2
123+
current_phase_begin = None
124+
current_phase_end = contest_start
125+
elif contest_stop < timestamp:
126+
actual_phase = +2
127+
current_phase_begin = contest_stop
128+
current_phase_end = None
129+
else:
130+
raise RuntimeError("Logic doesn't seem to be working...")
100131
else:
101-
phase = 1
102-
103-
# "adjust" the phase, considering the per_user_time
104-
actual_phase = 2 * phase
105-
106-
if phase == -1:
107-
# pre-contest phase
108-
current_phase_begin = None
109-
current_phase_end = contest_start
110-
elif phase == 0:
111-
# contest phase
112132
if per_user_time is None:
113-
# "traditional" contest: every user can compete for
114-
# the whole contest time
133+
# "Traditional" contest.
134+
intended_start = contest_start
135+
intended_stop = contest_stop
136+
else:
137+
# "USACO-like" contest, and we already know when the user
138+
# started/will start.
139+
# Both values are lower- and upper-bounded to prevent the
140+
# ridiculous situations of starting_time being set by the
141+
# admin way before contest_start or after contest_stop.
142+
intended_start = min(max(starting_time,
143+
contest_start), contest_stop)
144+
intended_stop = min(max(starting_time + per_user_time,
145+
contest_start), contest_stop)
146+
actual_start = intended_start + delay_time
147+
actual_stop = intended_stop + delay_time + extra_time
148+
149+
assert contest_start <= actual_start <= actual_stop
150+
151+
if actual_start <= timestamp <= actual_stop:
152+
actual_phase = 0
153+
current_phase_begin = actual_start
154+
current_phase_end = actual_stop
155+
elif contest_start <= timestamp < actual_start:
156+
# This also includes a funny corner case: the user's start
157+
# is known but is in the future (the admin either set it
158+
# that way or added some delay after the user started).
159+
actual_phase = -1
115160
current_phase_begin = contest_start
161+
current_phase_end = actual_start
162+
elif timestamp < contest_start:
163+
actual_phase = -2
164+
current_phase_begin = None
165+
current_phase_end = contest_start
166+
elif actual_stop < timestamp <= contest_stop:
167+
actual_phase = +1
168+
current_phase_begin = actual_stop
116169
current_phase_end = contest_stop
170+
elif contest_stop < timestamp:
171+
actual_phase = +2
172+
current_phase_begin = max(contest_stop, actual_stop)
173+
current_phase_end = None
117174
else:
118-
# "USACO-like" contest: every user can compete only
119-
# for a limited time frame during the contest time
120-
if starting_time is None:
121-
actual_phase = -1
122-
current_phase_begin = contest_start
123-
current_phase_end = contest_stop
124-
else:
125-
user_begin_time = max(starting_time, contest_start)
126-
user_end_time = min(starting_time + per_user_time, contest_stop)
127-
if timestamp < user_begin_time:
128-
actual_phase = -1
129-
current_phase_begin = contest_start
130-
current_phase_end = user_begin_time
131-
elif timestamp <= user_end_time:
132-
current_phase_begin = user_begin_time
133-
current_phase_end = user_end_time
134-
else:
135-
actual_phase = +1
136-
current_phase_begin = user_end_time
137-
current_phase_end = contest_stop
138-
else: # phase == 1
139-
# post-contest phase
140-
current_phase_begin = contest_stop
141-
current_phase_end = None
142-
143-
# compute valid_phase_begin and valid_phase_end (that is,
144-
# the time at which actual_phase started/will start and
145-
# stopped/will stop being zero, or None if unknown).
146-
valid_phase_begin = None
147-
valid_phase_end = None
148-
if per_user_time is None:
149-
valid_phase_begin = contest_start
150-
valid_phase_end = contest_stop
151-
elif starting_time is not None:
152-
valid_phase_begin = max(starting_time, contest_start)
153-
valid_phase_end = min(starting_time + per_user_time, contest_stop)
154-
155-
# consider the extra time
156-
if valid_phase_end is not None:
157-
valid_phase_end += extra_time
158-
if valid_phase_begin <= timestamp <= valid_phase_end:
159-
# phase = 0
160-
actual_phase = 0
161-
current_phase_begin = valid_phase_begin
162-
current_phase_end = valid_phase_end
163-
164-
if actual_phase > 0:
165-
current_phase_begin = max(current_phase_begin, valid_phase_end)
175+
raise RuntimeError("Logic doesn't seem to be working...")
166176

167177
return (actual_phase,
168178
current_phase_begin, current_phase_end,
169-
valid_phase_begin, valid_phase_end)
179+
actual_start, actual_stop)
170180

171181

172182
def actual_phase_required(*actual_phases):

cmstestsuite/unit_tests/server/__init__.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,15 @@ def test_traditional():
167167
# we just test different delay_time/extra_time combinations.
168168
test("6", "18", None, None, "0", "0", ("6", 0, "18"))
169169
test("6", "18", None, None, "0", "2", ("6", 0, "20"))
170-
#test("6", "18", None, None, "2", "0", ("6", -1, "8", 0, "20"))
171-
#test("6", "18", None, None, "2", "2", ("6", -1, "8", 0, "22"))
170+
test("6", "18", None, None, "2", "0", ("6", -1, "8", 0, "20"))
171+
test("6", "18", None, None, "2", "2", ("6", -1, "8", 0, "22"))
172172

173173
# Almost identical, with starting_time set to make sure it
174174
# doesn't affect anything.
175175
test("6", "18", None, "9", "0", "0", ("6", 0, "18"))
176176
test("6", "18", None, "9", "0", "2", ("6", 0, "20"))
177-
#test("6", "18", None, "9", "2", "0", ("6", -1, "8", 0, "20"))
178-
#test("6", "18", None, "9", "2", "2", ("6", -1, "8", 0, "22"))
177+
test("6", "18", None, "9", "2", "0", ("6", -1, "8", 0, "20"))
178+
test("6", "18", None, "9", "2", "2", ("6", -1, "8", 0, "22"))
179179

180180
@staticmethod
181181
def test_usaco_like():
@@ -190,19 +190,30 @@ def test_usaco_like():
190190
test("6", "18", "6", "9", "0", "1", ("6", -1, "9", 0, "16", +1, "18"))
191191
test("6", "18", "6", "3", "0", "1", ("6", 0, "10", +1, "18"))
192192
test("6", "18", "6", "15", "0", "1", ("6", -1, "15", 0, "19"))
193-
#test("6", "18", "6", "9", "1", "0", ("6", -1, "10", 0, "16", +1, "18"))
194-
#test("6", "18", "6", "3", "1", "0", ("6", -1, "7", 0, "10", +1, "18"))
195-
#test("6", "18", "6", "15", "1", "0", ("6", -1, "16", 0, "19"))
196-
#test("6", "18", "6", "9", "1", "1", ("6", -1, "10", 0, "17", +1, "18"))
197-
#test("6", "18", "6", "3", "1", "1", ("6", -1, "7", 0, "11", +1, "18"))
198-
#test("6", "18", "6", "15", "1", "1", ("6", -1, "16", 0, "20"))
193+
test("6", "18", "6", "9", "1", "0", ("6", -1, "10", 0, "16", +1, "18"))
194+
test("6", "18", "6", "3", "1", "0", ("6", -1, "7", 0, "10", +1, "18"))
195+
test("6", "18", "6", "15", "1", "0", ("6", -1, "16", 0, "19"))
196+
test("6", "18", "6", "9", "1", "1", ("6", -1, "10", 0, "17", +1, "18"))
197+
test("6", "18", "6", "3", "1", "1", ("6", -1, "7", 0, "11", +1, "18"))
198+
test("6", "18", "6", "15", "1", "1", ("6", -1, "16", 0, "20"))
199199

200200
# Test "USACO-like" contests, with unknown starting_time. Just
201201
# make sure delay_time/extra_time don't affect anything.
202202
test("6", "18", "6", None, "0", "0", ("6", -1, "18"))
203203
test("6", "18", "6", None, "0", "1", ("6", -1, "18"))
204-
#test("6", "18", "6", None, "1", "0", ("6", -1, "18"))
205-
#test("6", "18", "6", None, "1", "1", ("6", -1, "18"))
204+
test("6", "18", "6", None, "1", "0", ("6", -1, "18"))
205+
test("6", "18", "6", None, "1", "1", ("6", -1, "18"))
206+
207+
# Test ridiculous corner cases.
208+
test("6", "18", "3", "2", "0", "0", ("6", 0, "6", +1, "18"))
209+
test("6", "18", "3", "2", "0", "1", ("6", 0, "7", +1, "18"))
210+
test("6", "18", "3", "2", "1", "0", ("6", -1, "7", 0, "7", +1, "18"))
211+
test("6", "18", "3", "2", "1", "1", ("6", -1, "7", 0, "8", +1, "18"))
212+
test("6", "18", "3", "19", "0", "0", ("6", -1, "18", 0, "18"))
213+
test("6", "18", "3", "19", "0", "1", ("6", -1, "18", 0, "19"))
214+
# These are plainly absurd.
215+
test("6", "18", "3", "19", "1", "0", ("6", -1, "19", 0, "19"))
216+
test("6", "18", "3", "19", "1", "1", ("6", -1, "19", 0, "20"))
206217

207218

208219
if __name__ == "__main__":

0 commit comments

Comments
 (0)