Write an online controller for four traffic lights. Cars travel straight through a 20 × 20 map at one cell per second, stopping only for signals and other cars. Your objective is to minimize the root mean square of their delays.
Coordinates run from 0 to 19, left to right and top to bottom. There are two horizontal roads and two vertical roads. Each has two lanes: one per direction. Cars are 1 × 1, cannot turn or change lane, require no gap, and accelerate or stop instantly.
| Lane ID | Direction | Fixed coordinate | Intersections in travel order |
|---|---|---|---|
| 0 | East → | y = 7 | 0, 1 |
| 1 | West ← | y = 6 | 1, 0 |
| 2 | East → | y = 13 | 2, 3 |
| 3 | West ← | y = 12 | 3, 2 |
| 4 | South ↓ | x = 6 | 0, 2 |
| 5 | North ↑ | x = 7 | 2, 0 |
| 6 | South ↓ | x = 12 | 1, 3 |
| 7 | North ↑ | x = 13 | 3, 1 |
Intersection IDs are [0: top-left, 1: top-right, 2: bottom-left, 3: bottom-right]. Each intersection is a 2 × 2 box. A car’s position p is its distance along its own lane: it starts at 0 and exits at 20. The intersection cells are positions 6–7 and 12–13 in every lane. Stop lines are positions 5 and 11. A car entering at time 0 and never waiting exits at time 20.
Submit a Python module defining choose(state). It is called once per simulated second. Return a list or tuple of exactly four integers in intersection order. 1 grants green to both horizontal lanes and red to both vertical lanes; 0 does the reverse. Booleans, floats, missing values, and other values are invalid. You cannot control individual lanes.
def choose(state):
# Simple valid baseline: switch orientation every 15 seconds.
horizontal = 1 if (state["t"] // 15) % 2 == 0 else 0
return [horizontal, horizontal, horizontal, horizontal]
Module-level variables persist within a task. Each task starts in a fresh CPython process. Future arrivals, official seeds, and task IDs are not passed to your controller. The only input is this observation:
{
"t": 0, # current integer second
"signals": [1, 1, 1, 1], # 1 = H, 0 = V, -1 = closed while clearing
"pending": [None, None, None, None],
"clearance": [0, 0, 0, 0], # outgoing-green drain ticks remaining
"lanes": [ # exactly 8 entries, in lane-ID order
{"cars": [[0, 0, 0]], # [car_id, position, delay_so_far]
"queue": 0, # cars waiting outside this entrance
"oldest_wait": 0}, # age of oldest outside car, or 0
# ... seven more lane objects ...
],
"arrived": 1, # arrivals revealed so far, including queues
"exited": 0
}
oldest_wait is the oldest outside car’s time since scheduled arrival; it remains demand information and is not scored. cars is sorted by increasing position. delay_so_far = t − actual_map_entry − position. Car IDs are zero-based, assigned in sorted arrival order. Equal-time arrivals are ordered by lane ID; ties within the same lane preserve generator order. Standard-library imports are available; third-party packages are not loaded. Python hashing is fixed and the standard random module starts with seed 0 in each task. Avoid clocks, OS entropy, and nondeterministic external state if you want reproducible results. Printing is discarded. Do not read stdin or write to protocol descriptors; return your action from the function.
t, append arrivals scheduled for t to FIFO queues outside their entrances. At most one car per lane enters position 0, and only if that position is empty. The travel-time clock starts at this actual map-entry time. Waiting outside is excluded.choose(state).t + 1. Decrease positive clearance counters by one, then advance the clock to t + 1.All signals initially grant horizontal green. Queues and roads start empty. Arrivals occur at integer times 0 ≤ arrival < T − 60. No new cars are scheduled during [T − 60, T). Cars already queued outside may still enter and finish; only time after actual map entry counts toward their score. Multiple cars may arrive at one entrance simultaneously.
A change starts with two outgoing-green draining ticks. Requesting the opposite orientation locks that request in pending and sets clearance to 2. The outgoing orientation remains green during those ticks. Cars cannot enter their first junction if either junction on their route has a pending change. Cars already committed to the route may still enter their second junction under its outgoing green. This frees downstream space so occupied junctions can clear without gridlock.
After the two draining ticks, the changing junction becomes all red (signals = −1). At the beginning of a later tick, the pending green activates as soon as its box is empty. If it is already empty immediately after draining, the next tick starts with the new green. Otherwise the all-red wait continues until the remaining cars leave. Requests while a change is pending, including its completion tick, are ignored.
Example: a change requested at t = 10 keeps the outgoing green for ticks 10 and 11, subject to the first-junction entry restriction. The new green starts at tick 12 if the box is empty; otherwise it starts later. Existing cars inside a box may always move forward into an available cell.
A fully green corridor supports one car per second per lane. When both junctions on a lane are green for its direction and neither is changing, first-junction entry needs only an available next cell. There is no artificial gap or subtraction for cars already moving inside the junction.
If the second junction is red, first-junction admission reserves room in positions 8–11: the number of empty cells there must exceed the number of cars in positions 6–7. This restriction applies only while the downstream light is red. No reservation is needed at the second junction because its road continues directly to the exit. All occupancy checks use the lane after cars farther ahead have moved this tick. The published simulator is the executable reference.
travel_time[i] = exit_time[i] - actual_map_entry_time[i]
delay[i] = travel_time[i] - 20
RMSE = sqrt(sum(delay[i] ** 2 for i in range(N)) / N)
Lower is better; zero is perfect. New arrivals stop at T − 60, leaving the final 60 seconds of the task horizon free of new arrivals. Simulation still continues until every car exits; 60 seconds does not guarantee all queues have cleared. A task must finish before T + 4N + 200 ticks; otherwise it receives DNF, not a partial score. Long waits are penalized more heavily than many short waits.
Each leaderboard pools squared delays across its tasks: sqrt(sum(task_squared_delay) / sum(task_N)). This is not the arithmetic mean of task RMSEs. Every task in a split must be OK to rank in that split. Exact scores determine order; three decimal places are shown. Equal scores are ordered by submission creation time.
The server first compiles source to CPython bytecode once without executing it. Compilation and interpreter bootstrap are excluded. Each task then has 3.000 seconds of wall time for the complete simulation, including module initialization, controller calls, JSON communication, and the trusted simulator. A watchdog kills a stalled worker. Tasks execute sequentially so official runs do not compete with one another for CPU. Runtime varies with hardware and OS load; identical inputs and deterministic code produce identical simulation scores, but timing near the limit is machine-dependent.
| Verdict | Meaning |
|---|---|
| OK | All cars exited within both limits. |
| CE | Source could not be compiled; no tasks run. |
| RE | Exception, invalid action, protocol error, or worker failure. |
| TLE | Complete task simulation exceeded three seconds. |
| DNF | Tick cap reached with cars remaining. |
| CANCELLED | The run was cancelled; completed task results remain saved. |
Submit runs all 30 fixed official tasks: 15 public and 15 private. Use a label such as Selene / pressure-v2 to identify your code. Public and private leaderboards are separate. Private scores stay hidden in the interface until Show private is enabled; this preference is saved.
Test runs one selected generator recipe with your own seed and records a full replay. The seed field accepts 1–128 characters. Its UTF-8 SHA-256 hex digest is passed to the published generator. For example, seed text 42 corresponds to hashlib.sha256(b"42").hexdigest(). Tests use the same three-second limit, including recording, and do not enter leaderboards. Replays include every simulated tick up to completion or failure.
The RMSE-over-time chart shows cumulative exited-car RMSE, with no value before the first exit. Its marker follows playback; click the chart to seek. The visualizer supports play/pause, stepping, speed changes, seeking, car inspection, queue counts, and light states. During playback, displayed RMSE covers exited cars only; it is the official score only once all cars exit. A frame at time t > 0 shows the state after the preceding movement tick and signal states at the end of that tick, before new arrivals at t are admitted. The initial frame includes time-zero arrivals.
The generator is public. Thirty independent 256-bit seeds are created with the OS random source on first server launch and stored in .judge/seeds-v2-30.json. They are reused on every submission and restart. Seeds are never sent through the web interface or controller observations. Changing seeds, judge source, or CPython version changes the suite fingerprint; older submissions remain in history but not the current leaderboard.
History, labels, source snapshots, results, and your draft live in this browser’s localStorage. Export history before clearing browser data. Up to eight recent test replays live in server memory; download one to keep it, or rerun the same test after restarting. The private toggle hides scores visually; it is not an access-control boundary. This is a trusted local practice judge, not a hardened multi-user contest sandbox. Submitted Python has local OS permissions and a malicious submission could read local seed files or forge local results.
Samples specify arrivals and a simple policy so the entire result is reproducible. They are not part of either leaderboard. All use T = 61. Download samples.json for machine-readable cases.
| Sample | Arrivals [time, lane] | Policy | Exit times | RMSE |
|---|---|---|---|---|
| 1. Free road | [0, 0] | Always [1, 1, 1, 1] | 20 | 0.000000 |
| 2. Entrance queue | [0, 0], [0, 0] | Always [1, 1, 1, 1] | 20, 21 | 0.000000 |
| 3. Crossing traffic | [0, 0], [0, 4] | All horizontal for t < 14, then all vertical | 20, 31 | 7.778175 |
Sample 2 enters the map at times 0 and 1 and exits at 20 and 21. Both travel times are 20 seconds, so both delays and the RMSE are zero. In sample 3, the eastbound car passes both intersections before switching. The southbound car waits at its first light; after the two clearing ticks, it crosses its first stop line at tick 16 and exits at 31. Its delay is 11, giving √(121/2).
# Sample 3 policy
def choose(state):
return [1, 1, 1, 1] if state["t"] < 14 else [0, 0, 0, 0]
generator.py publishes all 30 task recipes (15 traffic profiles in each split, using independent seeds) and its SHA-256 counter PRNG. simulator.py defines movement and scoring. starter.py provides a queue-aware controller. The downloaded server bundle includes the statement, runner, tests, samples, and a command-line test helper.