Skip to content

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

make watch-breakout
make play-breakout
make audit-breakout

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.