The problem in brief
You walk through a hidden labyrinth, one room at a time. In each room you only learn which of the four doors are open, what the room is (start, boss, treasure, one of five lever rooms, or empty) and, once you have entered a lever room, where the treasure room is. Your program must reach the boss in as few moves as possible. It is an interactive task: the judge answers every move. The organisers give you 10 000 layouts produced by the same generator as the hidden test layouts, plus a local simulator, and say outright that the point is to learn from those examples how labyrinths are built. Source: Swedish AI Olympiad qualifier 2025 (problem by Joshua Andersson), judged on Kattis.
Since the Kattis tests are hidden, we score our program by replaying 2000 of the 10 000 example layouts that we never trained on, with our own copy of the judge (checked move for move against the organisers' testing_tool.py).
How you are scored
For one layout solved in moves:
So 10 moves or fewer earn 100, every extra move costs 7 points, and beyond 24 moves you get a single consolation point. The score is the average over test layouts. The curve is merciless: an explorer who is "pretty good" but averages 26 moves scores almost nothing.
What to aim for. There is no threshold. The Kattis statistics page shows no full solve and a best score of 21.7.
First look at the data
Measuring the 10 000 layouts reveals a very regular generator:
- Every layout is connected, with 23 to 32 rooms, and is a tree (35 %) or a tree plus one loop (65 %).
- There are always exactly 7 dead ends, and the boss, the treasure and the five levers are those 7 dead ends. Every special room sits at the end of a corridor.
- The boss is always 10, 11 or 12 moves from the start (46 %, 32 % and 22 %), even though it is usually only 4 squares away as the crow flies. The path to it winds.
- The start is always near the centre (21 possible cells).
- The treasure says little about the boss: their distance is spread from 2 to 12.
If you knew the map you would always need 10 to 12 moves, an average of 94.7 points. All the difficulty is in the 7 dead ends: you cannot tell which one hides the boss, and every wrong corridor costs you its length twice, there and back.
A first baseline
Two classical explorers:
| policy | mean moves | score |
|---|---|---|
| always go to the nearest unexplored door (like a depth-first search) | 27.1 | 13.0 |
| "deep first": prefer unexplored doors far from the start, since the boss is far | 26.6 | 13.9 to 16.0 |
Both waste most of their moves in lever corridors and land on the far side of the 24-move cliff, where every layout is worth one point. This is about the level of the best Kattis submissions.
The key idea
At any moment the only real decision is which unexplored door to open next. Call these doors the frontier. The examples let us learn, for every frontier door, the probability that the boss lies behind it:
This is plain supervised learning once you have the right labels, and the local simulator gives labels for free: replay a training layout with some policy, and at every decision write down, for each frontier door, a feature vector and whether the boss really lies behind it (a flood fill on the true map).
The features describe the door in the language of the generator: its absolute cell and its offset from the start, its corridor depth from the start (the boss is at depth 10 to 12), the walking distance from where we stand, how many rooms, dead ends and levers we have already found, the treasure's position relative to the door once a lever revealed it, how many unseen cells surround it, and the direction of the door. A gradient-boosted tree model learns .
The policy then trades probability against walking cost:
Walk the shortest known path to , look at the new room, update, repeat.
One trap: the data depends on the policy. If you collect training states with a bad explorer, the model only learns about the situations that bad explorer gets into. The learned policy then visits other states that it has never seen. The fix is DAgger (dataset aggregation): collect again with the learned policy itself, add those states to the data, retrain, and repeat. Here the first model, trained on states from the "deep first" explorer, scored 54.4. One round of DAgger lifted it to 63.4, and later rounds settled at 64.5 to 64.7.
The full solution
- Our own judge replays a layout and enforces the rules (legal moves only, a lever reveals the treasure).
- DAgger, 4 rounds over the 8000 training layouts (2500, 2500, 3000, 4000 layouts): 733 000 labelled frontier doors in total. Each round refits
HistGradientBoostingClassifier(300 trees, learning rate 0.05). - We choose by watching the score on 1000 of the held-out layouts (values from 0.1 to 0.5 all land within 1 point). The final number is reported on all 2000 held-out layouts.
- Shipping it to Kattis. The judge runs plain Python with no scikit-learn, so we export the trees ourselves. Every feature is an integer, so a threshold can be stored as in one signed byte, and leaf values as 16-bit floats. Each tree is flattened in preorder (the left child follows its parent; only the right child's index is stored). After zlib and base85, the whole program takes 56 KiB, under the 128 KiB source limit. The policy code (
COREinsolution.py) is literally the same text in the local evaluation and in the submission. Running the generatedsubmission.pythrough the organisers' testing tool gives exactly the same move counts as our simulator, in a few hundredths of a second per layout.
The tree walk at the heart of the shipped predictor:
for t in trees: # t = preorder list of nodes
i = 0
while True:
node = t[i]
if node[0] < 0: z += node[1]; break # leaf: add its value to the logit
i = i + 1 if x[node[0]] <= node[1] else node[2]

The histogram shows what changed. The learned policy reaches the boss by the shortest route in about a quarter of the layouts (the spike at 10 to 12 moves). Most of the others take 13 to 22 moves, still worth points, and only a thin tail falls past 24. The example on the right is a typical "partial" win: the policy explored one lever corridor and the treasure branch, then found the boss in 23 moves instead of 11.
Results
On 2000 held-out example layouts (the Kattis test layouts are hidden; the organisers say they come from the same generator).
| approach | mean moves | score |
|---|---|---|
| nearest unexplored door | 27.1 | 13.0 |
| deep-first heuristic | 26.6 | 16.0 |
| learned , trained on deep-first states only | 16.3 | 57.8 |
| same, smaller model (150 trees of 15 leaves) after DAgger | not logged | 61.0 |
| extra "known walls" features, small model | 15.7 | 60.8 |
| learned + DAgger, 300 trees (final) | 15.3 | 63.7 |
| oracle that knows the map | 10.8 | 94.7 |
For comparison, the best Kattis score is 21.7.
What to remember
- When an interactive task gives you many example worlds, turn exploration into supervised learning: label each decision with what was really behind it, using a simulator.
- Look for the generator's invariants first (7 dead ends that are exactly the special rooms, a boss always at depth 10 to 12). They tell you which features matter.
- A learned policy changes the states it visits: re-collect with the learned policy (DAgger) or you train on the wrong distribution.
- Combine a probability with a cost in one score () rather than chasing the most probable door at any price.
- Read the scoring curve: here moving most layouts from 26 moves to 16 is what counts.