Breakout¶
Breakout (Arkanoid) is a game of reflexes: a paddle at the bottom of a walled field keeps a ball in play while it breaks the bricks above. This page shows how a player that cannot see the future still never drops a ball it could have reached, why aiming the ball well needs a guarantee about the next catch too, and what changes when the world puts spin on the ball against you. It assumes you have read the games overview; every number below comes from the commands shown next to it.
The game¶
The field is 13 x 18 cells. The game runs in ticks, 20 a second; on
every tick the player chooses left, right, stay, launch (a ball
resting on the paddle) or fire (with the laser). Where the ball meets
the paddle sets its new angle, as in Arkanoid: the paddle is cut into
eight zones, the middle ones send the ball steeply up and the ends
flattest. Bricks break in one hit, silver ones need two, gold ones never
break; some bricks drop capsules (expand, catch, laser, slow,
disruption, break, extra life). The ball speeds up every ten brick hits.
Lose every ball and the game is lost; clear the level (or --levels N)
and it is won.
The physics is exact. Positions are integers, 64 units to a cell, and a ball's crossing of a cell boundary happens at a rational fraction of the tick, compared exactly, never sampled; a reflection keeps every position an integer. So the same seed plays the same game to the unit on every machine and in the browser.
Options: --size WxH, --level N (eight built-in levels after
Arkanoid's rounds), --board random (a symmetric layout drawn from the
seed), --layout FILE (your own), --lives, --ball-speed,
--speedup, --paddle-speed and --epsilon E: the spin the paddle
puts on the ball, shifting each meeting point by up to E units before
its zone is decided (0 by default). A layout file has one line per row
of bricks, top first: . empty, w o c g r b p y a brick of that
colour, the capital letter for the same brick carrying a capsule, =
silver and # gold; lines starting with # are comments.
Worlds¶
| World | Capsules | Spin |
|---|---|---|
random |
drawn at random (Arkanoid's mix) | uniform in [-E, E] |
kind |
the one worth most to the player now | the one that eases the next catch most |
adversarial |
the one worth least now | the one that makes the next catch hardest |
The adversary looks at the actual position and answers it one bounce at
a time. Capsules are never harmful, so the capsule adversary can only
make them useless; the spin is the real threat. Only random is fixed
by the seed, so a clairvoyant benchmark is allowed there only.
Agents¶
| Agent | Regime | What it does |
|---|---|---|
random |
worst-case | a random action each tick |
follow |
worst-case | moves under the lowest ball as it is now |
intercept |
worst-case | the exact landing point of the first ball down, and the shortest way there |
aim |
probabilistic | meets the ball where the next flight breaks the most bricks, accepting a 25% chance (over uniform spin) that the next flight cannot be caught |
worst_case |
worst-case | meets the ball where every allowed spin leaves the next flight catchable |
gated |
worst-case | aim, behind the gate below |
clairvoyant |
clairvoyant (benchmark) | aim with the coming spins known |
What the gate proves¶
Every tick the gate plays the proposed action one tick ahead, for every spin a bounce in that tick may get, and checks:
catches_next: the paddle can still get under the first ball to come down before it lands. The landing comes from a forecast: the game's own physics played forward, brick bounces included, which is exact because until that ball reaches the paddle nothing the paddle does can move a ball. The reach is a closed form, checked against a breadth-first search over every paddle position in the tests.bounce_safe: the paddle can still meet that ball at a point where every spin leaves the next flight catchable.
When a check fails, the fallback moves towards the best spin-proof meeting point, or under the ball.
Theorem (worst case: in every world, for every spin up to E; one ball
in play). Once the first ball down is catchable, the gated agent
catches it; if a spin-proof meeting point exists during a descent, the
next ball is catchable whatever the spin; so a ball is lost only after
a recorded DEGRADE announcing that no spin-proof bounce existed, a whole
flight earlier. The proof is in the module's docstring
(games/breakout.py), and tests/test_games_breakout.py checks the
forecast, the reach and the announcement.
What it does not cover: with several balls (disruption) only the first one down is guaranteed; the zones of a planned bounce assume the paddle keeps its width until the bounce.
Measured¶
grounded-decision examples games_breakout plays level 1 for 800 ticks
in the default setting and in a hard one (spin up to 32 units, a slow
paddle, a fast ball, nine lives):
world agent kept all balls lost bricks score overrides
random, default follow 3/3 0 16.0 1530.0 0
random, default gated 3/3 0 25.7 3453.3 0
random, hard follow 0/3 14 31.0 3356.7 0
random, hard intercept 1/3 3 32.3 4800.0 0
random, hard aim 3/3 0 36.3 4230.0 0
random, hard worst_case 3/3 0 25.0 2403.3 0
random, hard gated 3/3 0 36.0 4193.3 18
adversarial, hard follow 0/3 24 27.0 2670.0 0
adversarial, hard intercept 0/3 15 26.0 2490.0 0
adversarial, hard aim 3/3 0 36.0 6520.0 0
adversarial, hard worst_case 3/3 0 26.0 2480.0 0
adversarial, hard gated 3/3 0 46.0 7550.0 129
Over longer games the probabilistic aim starts losing balls to the
adversarial spin while worst_case and gated keep them all:
$ grounded-decision compare breakout --agents intercept,aim,worst_case,gated --world adversarial --epsilon 32 --paddle-speed 12 --ball-speed 4 --lives 9 --seeds 4 --max-steps 1500
agent games won drawn lost cut points moves score overrides win rate (95% interval)
aim 4 0 4 0 4 4.0 1500.0 9140.0 0 0.00 [0.00, 0.60]
gated 4 0 4 0 4 4.0 1500.0 9750.0 172 0.00 [0.00, 0.60]
worst_case 4 0 4 0 4 4.0 1500.0 5340.0 0 0.00 [0.00, 0.60]
intercept 4 0 0 4 0 0.0 1263.0 4010.0 0 0.00 [0.00, 0.60]
intercept lost all nine lives in every game; the others were cut off
at 1,500 ticks still playing. The gated agent breaks the most (it aims)
and overrides aim whenever its meeting point would leave the next
catch to luck. grounded-decision audit breakout adds the regime table
(keeping every ball for 1,500 ticks, random and adversarial worlds).
Try it¶
play breakout --agents you puts you on the clock: the arrow keys or a
and d move the paddle (it keeps moving until another key), down or s
stops it, space, up or w launches, f fires, p pauses and q
quits. With --guarded the gate checks every tick. Without a terminal
(piped input) each tick reads one line.