Files
akbasic/docs/18-tutorial-breakout-artwork.md
Andrew Kesterson 1fb808f480
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m21s
akbasic CI Build / sanitizers (push) Failing after 4m32s
akbasic CI Build / coverage (push) Failing after 3m37s
akbasic CI Build / akgl_build (push) Failing after 22s
akbasic CI Build / mutation_test (push) Failing after 3m30s
Rewrite the two game tutorials as instructions rather than commentary
Chapters 17 and 18 read as a code review of a finished listing: they explained
why each decision had been made, walked through the project's own history, and
led with what had once been broken. A reader who wanted to build the game got
the reasoning and had to reconstruct the program.

Both are now step-by-step. Each opens with a picture of the finished game and a
bullet list of the steps, each bullet is a section, and each section states its
goal, shows the code, and says how to check it. Chapter 17 is sixteen steps and
Chapter 18 is thirteen, and the last of each is the assembly: the order of the
file, the full declaration block, and the routines the earlier steps referred to.
Project history is gone -- it belongs in Chapter 14 and in git -- and where a
listing has to do something awkward, the tutorial shows how first and names the
`TODO.md` item that will make it unnecessary second.

**Three defects had no entry anywhere**, which the rewrite found by trying to
state each rule as a rule. §6 item 35: `a - b + c` computes `a - (b + c)`,
because `subtraction()` sits above `addition()` as its own precedence level and
the inner loop eats the `+`. Item 36: only one unparenthesised `AND` or `OR` is
matched, which is item 12's `if`-where-`while` on the one operator pair item 12
did not reach. Item 37: a `GOTO` out of a `FOR` or a `DO` leaks the loop's scope,
so a main loop written that way stops on the thirty-second lost life -- which is
why both games are built out of `LABEL` and `GOTO`, and it is a workaround rather
than a preference. Chapter 3 gains the identifier rule the third trial ran into:
there is no underscore in a name, and the error says `UNKNOWN TOKEN _`.

**`tools/screenshot.c` learned to draw the text layer**, behind a new `text=1`
fence attribute, because Chapter 17's game is characters in the grid and a figure
without that layer is two sprites on black. It opens the bundled font at the size
the standalone frontend uses, so a figure's cell size is the reader's cell size,
and it uses the akgl sink alone rather than a tee so the program's output lands in
the picture instead of on the stdout the caller reads to decide a figure failed.
Both new figures -- `breakout-game.png` and `breakout-game-artwork.png` -- are
generated from listings in the chapters like every other one.

Verified by handing each chapter, alone, to an agent on a much smaller model and
telling it to build the game from the tutorial text with the `examples/` tree off
limits. The first pass scored 3.5 and 3 out of 10 and named what was missing:
routines referred to but never shown, the third level layout, the sprite `DATA`,
the font table, edits to earlier routines that were never marked as edits. Those
are now in. The second pass built a 658-line Chapter 17 game that plays itself
for ninety seconds with the score at 1890 and no error line, and the third built
a 1053-line Chapter 18 game with 61 labels, no invented routines, no gaps found,
and forty seconds clean. 9/10 and 8/10.

One real bug in the new prose, caught in review: Chapter 18's `HITBAR` did not
set the `HIT#` that `BALLPADDLE` reads to decide whether the paddle already
caught the ball. Both suites green in both configurations, `docs_examples` and
`docs_screenshots --check` pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
2026-08-02 08:08:23 -04:00

2250 lines
60 KiB
Markdown

# 18. Tutorial: Breakout with artwork
This chapter builds Breakout a second time, out of loaded PNG artwork, with a drawn
brick field, five powerups, three voices of sound and a HUD in colour. It is a bigger
program than [Chapter 17](17-tutorial-breakout.md)'s and it uses a completely different set
of verbs: everything here is drawn or loaded, and nothing is written into the text grid.
This is what you are building:
![The finished game: a coloured HUD across the top, five rows of coloured bricks, a gem falling, the ball above the paddle](images/breakout-game-artwork.png)
The finished listing is
[`examples/breakout/sprites/breakout.bas`](../examples/breakout/sprites/breakout.bas).
Read Chapter 17 first if you have not. The rules it teaches — declare every name up front,
loop with `GOTO`, parenthesise mixed `+` and `-`, build a random-number generator — all
apply here too, and are not repeated.
One of them is worth repeating, because this chapter leans on it harder than Chapter 17
does. **`#` is an integer, `%` is a float and `$` is a string.** That is the opposite way
round from Commodore BASIC, and it is why ball positions and velocities here are `BLX%` and
`BLVX%` while counters and flags are `BLN#` and `BLON#`. Get one wrong and the ball moves in
whole pixels, or stops.
[Chapter 3](03-the-language.md) is the language reference for the rest — `MOD`, `INSTR`,
`MID`, `LEN` — and [Chapter 4](04-control-flow.md) is `BEGIN`/`BEND`, `DO ... LOOP UNTIL`
and `GOSUB`.
```sh norun
$ ./build-akgl/basic examples/breakout/sprites/breakout.bas
```
| Key | Does |
|---|---|
| left / right | move the paddle |
| space | start a game, launch the ball, release a stuck ball |
| P | pause |
| S | sound on and off |
| Q or escape | quit |
A broken brick drops a gem about one time in seven. Catch it with the paddle; the colour
tells you which it is.
| Gem | Name | Does | For |
|---|---|---|---|
| red | EXPAND | doubles the paddle's width | 20 seconds |
| yellow | MULTI | throws two more balls off the one in play | until they are lost |
| green | SLOW | drops the ball's speed to about two thirds | 16 seconds |
| blue | STICKY | the ball sticks where it lands; space fires it | 18 seconds |
| purple | CATCH | a second bar appears higher up the field | 24 seconds |
## What you will do
- **[Step 1](#step-1-put-artwork-on-the-screen)** — load PNG artwork into sprites
- **[Step 2](#step-2-budget-the-eight-sprite-slots)** — decide what each of the eight
sprites is, before writing anything else
- **[Step 3](#step-3-turn-a-drawing-into-a-sprite)** — draw something and turn it into a
sprite so it stays on the screen
- **[Step 4](#step-4-make-the-brick-stamps)** — make one brick per colour and stamp the
field out of them
- **[Step 5](#step-5-find-the-frame-boundary)** — find the host's frame boundary, so a
drawing survives being captured
- **[Step 6](#step-6-draw-at-most-one-thing-per-frame)** — draw at most one thing per
frame, chosen by dirty flags
- **[Step 7](#step-7-keep-a-flattened-list-of-live-bricks)** — keep a flattened list of the
bricks still standing, so the draw routine decides nothing
- **[Step 8](#step-8-draw-lettering-with-a-stroke-font)** — draw lettering with a stroke
font, because text has no colour
- **[Step 9](#step-9-move-and-bounce-the-ball)** — move the ball and bounce it off bars and
bricks without a square root
- **[Step 10](#step-10-gems-and-powerups)** — drop gems and apply what catching one does
- **[Step 11](#step-11-three-voices-and-a-mute)** — three voices of sound and a mute that
costs nothing
- **[Step 12](#step-12-the-states)** — the frame loop's states: title, serve, play, lost,
cleared
- **[Step 13](#step-13-put-the-program-together)** — put the pieces in one file, in the
right order
---
## Step 1: Put artwork on the screen
**Goal: a picture from a file, on the screen, at its own size.**
`SPRSAV` takes an image file path as well as an array of pattern bytes, and a sprite
loaded from a file **keeps the image's own size** rather than being squeezed into 24 by
21. That is the whole of it:
```basic requires=akgl setup=breakout_art screenshot=breakout-artwork
I# = 0
SPRSAV "art/paddleBlu.png", 3
SPRSAV "art/paddleRed.png", 4
SPRSAV "art/ballBlue.png", 5
SPRSAV "art/element_red_polygon_glossy.png", 6
SPRSAV "art/element_green_polygon_glossy.png", 7
SPRSAV "art/element_purple_polygon_glossy.png", 8
FOR I# = 3 TO 8
SPRITE I#, 1, 2
NEXT I#
MOVSPR 3, 20, 20
MOVSPR 4, 20, 60
MOVSPR 5, 160, 30
MOVSPR 6, 30, 120
MOVSPR 7, 130, 120
MOVSPR 8, 230, 120
```
![Two paddles, a ball and three coloured gems](images/breakout-artwork.png)
That is the whole game's cast: two bars, a ball and five gems.
**Write down the sizes now**, because every collision test later in this chapter is built
out of them:
| Artwork | Is | Shows up later as |
|---|---|---|
| `paddleBlu.png`, `paddleRed.png` | 104 by 24 | `PDW# = 104`, and the bar's height in `HITBAR` |
| `ballBlue.png`, `ballGrey.png` | 22 by 22 | `+ 21` on every edge of the ball's box |
| the five gems | 48 by 46 | the `+ 48` and `+ 46` in the catch test |
Three more things to note.
**The path is tried against the working directory first, then against the directory the
program was loaded from.** A `.bas` stored beside its own `art/` folder therefore runs from
anywhere.
**`SPRITE n, 1, 2` turns sprite `n` on in colour 2.** A sprite's colour *multiplies* the
artwork rather than replacing it, so colour 2 — white — is the one that leaves loaded
artwork looking like itself. Any other colour tints it.
**Put the art where the licence lets you.** The artwork here is Kenney's
[Puzzle Pack 1](https://kenney.nl/assets/puzzle-pack-1), released under
[CC0](http://creativecommons.org/publicdomain/zero/1.0/), and
`examples/breakout/sprites/art/PROVENANCE.md` records which file is used for what. CC0
does not require crediting Kenney. Do it anyway.
## Step 2: Budget the eight sprite slots
**Goal: know what all eight sprites are before you write the program.**
There are eight sprite slots and no more. That is not a limit you will design your way
around later, so spend them on paper first. This game spends them like this:
| Slot | Is |
|---|---|
| 1 | the HUD strip — a captured drawing |
| 2 | the playing field — a captured drawing |
| 3 | the paddle |
| 4 | the catcher bar (the purple gem) |
| 5, 6, 7 | up to three balls |
| 8 | the falling gem |
Two of the eight are **the screen itself**, and Step 3 explains why. That leaves six for
everything that moves, and two consequences fall straight out of it:
- **The bricks are drawn rather than made of artwork.** Sixty bricks will not fit in six
slots, and a sprite is the only way to get an image file onto the screen — `GSHAPE`
cannot stamp a sprite and `SPRSAV` cannot read one back out.
- **Only one gem falls at a time.** There is one slot for it, so a brick broken while a gem
is already falling drops nothing.
Deciding this first is what stops a feature costing an afternoon before it is abandoned.
## Step 3: Turn a drawing into a sprite
**Goal: something you drew, still on the screen on the next frame.**
Two layers are redrawn for you every frame from state the interpreter keeps: the text grid
and the sprites. **The drawing verbs are not one of them.** `DRAW`, `BOX`, `CIRCLE` and
`PAINT` write straight into the renderer's buffer, and by default the text layer owns
every row of the window and repaints over them before the frame is shown.
So a drawing has to be turned into something that persists. The sequence is: draw it,
capture it with `SSHAPE`, install the capture into a sprite with `SPRSAV`.
```basic norun
SSHAPE Z$, 0, 60, 800, 600
SPRSAV Z$, 2
SPRITE 2, 1, 2
MOVSPR 2, 0, 60
```
Four lines, and they are the last four lines of every draw routine in this game. `SSHAPE`
captures the rectangle from (0, 60) to (800, 600) into `Z$`; `SPRSAV` installs it into
slot 2; `SPRITE` turns the slot on; `MOVSPR` puts it back where it was drawn.
`Z$` holds a **handle**, not pixels — see
[Chapter 6](06-graphics.md#saving-and-stamping-regions). You can pass it to `GSHAPE` and
`SPRSAV`. You cannot print it, save it or take its `LEN`.
Once it is a sprite it stays on the screen for nothing, and that is the point: a sprite is
drawn from state the interpreter keeps, so it costs the program no work per frame at all.
`WINDOW 0, 0, 49, 1` would shrink the text area and hand the rest of the window to the
drawing verbs, which is the other way to make a drawing visible. This program does not use
it, because a drawing kept that way still has to be re-issued every frame *and* fit inside
one batch (Step 5), and a sprite has neither constraint. Making the drawing verbs visible
without a `WINDOW` call is [`TODO.md` §9 item 3](../TODO.md).
The figures in this chapter are rendered by a tool that draws no text layer, which is why
they can show a bare drawing at all.
## Step 4: Make the brick stamps
**Goal: six coloured bricks, and a field stamped out of them.**
Draw one brick per row colour, capture the six of them, and then stamp them wherever a
brick belongs with `GSHAPE`. Drawing six things and stamping sixty is far cheaper than
drawing sixty.
```basic requires=akgl screenshot=breakout-stamps size=100x130
DIM BRC#(6)
I# = 0
R# = 0
K# = 0
T1# = 0
T2# = 0
FOR I# = 0 TO 5
READ BRC#(I#)
NEXT I#
GRAPHIC 1, 1
WIDTH 1
FOR R# = 0 TO 5
COLOR 1, BRC#(R#)
T1# = R# * 20
T2# = T1# + 8
FOR K# = 0 TO 7
DRAW 1, 0, T1# + K# TO 67, T1# + K# : DRAW 1, 0, T2# + K# TO 67, T2# + K#
NEXT K#
NEXT R#
DATA 3, 9, 8, 6, 4, 5
```
![Six bricks, one per row colour, stacked vertically](images/breakout-stamps.png)
Reading that from the top: `GRAPHIC 1, 1` selects the graphics mode and clears it.
`COLOR 1, BRC#(R#)` sets **colour source 1** to the palette entry that row wants — a
drawing verb names a source, not a colour, and there are seven sources. `DRAW 1, x1, y1 TO
x2, y2` draws a line using source 1.
Each brick is 68 by 16 and is filled with horizontal lines. The inner loop draws **two
scan lines per pass**, `T1# + K#` and `T2# + K#`, so eight passes fill sixteen rows. That
costs about a hundred and thirty lines for the whole set instead of the two hundred and
seventy a line-at-a-time loop would take, and Step 5 explains why that number matters.
Capturing the six is six `SSHAPE`s:
```basic norun
SSHAPE Z$, 0, 0, 68, 16 : S0$ = Z$
SSHAPE Z$, 0, 20, 68, 36 : S1$ = Z$
SSHAPE Z$, 0, 40, 68, 56 : S2$ = Z$
SSHAPE Z$, 0, 60, 68, 76 : S3$ = Z$
SSHAPE Z$, 0, 80, 68, 96 : S4$ = Z$
SSHAPE Z$, 0, 100, 68, 116 : S5$ = Z$
```
**`SSHAPE` has sixteen slots and nothing gives one back.** `GRAPHIC 5` gives back all of
them at once, so count what you have spent and rebuild the stamps when the pool runs dry:
```basic norun
LABEL DRAWPROTOS
GRAPHIC 5
GRAPHIC 1, 1
WIDTH 1
SHN# = 99
```
with the six `DRAW` loops and six `SSHAPE`s after it, and `SHN# = 6` at the end. Every
routine that captures adds one to `SHN#`, and the frame loop rebuilds when it reaches 14.
**`PAINT` would be one statement instead of sixteen, and is not usable here.** It costs
nearly four milliseconds a call; sixty of those is seven frames' worth of time.
Now the whole screen — walls, the brick field stamped out of the six, and the artwork on
top:
```basic requires=akgl setup=breakout_art screenshot=breakout-screen size=800x600
DIM BRC#(6)
I# = 0
R# = 0
C# = 0
K# = 0
T1# = 0
T2# = 0
Z$ = ""
FOR I# = 0 TO 5
READ BRC#(I#)
NEXT I#
GRAPHIC 1, 1
WIDTH 1
FOR R# = 0 TO 5
COLOR 1, BRC#(R#)
T1# = R# * 20
T2# = T1# + 8
FOR K# = 0 TO 7
DRAW 1, 0, T1# + K# TO 67, T1# + K# : DRAW 1, 0, T2# + K# TO 67, T2# + K#
NEXT K#
NEXT R#
SSHAPE Z$, 0, 0, 68, 16 : S0$ = Z$
SSHAPE Z$, 0, 20, 68, 36 : S1$ = Z$
SSHAPE Z$, 0, 40, 68, 56 : S2$ = Z$
SSHAPE Z$, 0, 60, 68, 76 : S3$ = Z$
SSHAPE Z$, 0, 80, 68, 96 : S4$ = Z$
SSHAPE Z$, 0, 100, 68, 116 : S5$ = Z$
GRAPHIC 1, 1
WIDTH 2
COLOR 5, 16 : COLOR 1, 4
BOX 5, 2, 62, 797, 597
BOX 1, 6, 66, 793, 593
FOR C# = 0 TO 9
Z$ = S0$ : GSHAPE Z$, 42 + C# * 72, 108
Z$ = S1$ : GSHAPE Z$, 42 + C# * 72, 132
Z$ = S2$ : GSHAPE Z$, 42 + C# * 72, 156
Z$ = S3$ : GSHAPE Z$, 42 + C# * 72, 180
Z$ = S4$ : GSHAPE Z$, 42 + C# * 72, 204
Z$ = S5$ : GSHAPE Z$, 42 + C# * 72, 228
NEXT C#
SSHAPE Z$, 0, 60, 800, 600
SPRSAV Z$, 2
SPRITE 2, 1, 2
MOVSPR 2, 0, 60
SPRSAV "art/paddleBlu.png", 3
SPRSAV "art/ballBlue.png", 5
SPRITE 3, 1, 2
SPRITE 5, 1, 2
MOVSPR 3, 348, 540
MOVSPR 5, 389, 517
DATA 3, 9, 8, 6, 4, 5
```
![The full field: a double border, six rows of coloured bricks, the paddle and the ball](images/breakout-screen.png)
Everything above the `SSHAPE Z$, 0, 60, 800, 600` is a drawing nobody would ever see.
Those four lines are what make it the screen.
A brick is 68 by 16 on a 72 by 24 pitch, ten columns by six rows, with the field's top-left
corner at (42, 108). Those numbers are worth writing down once; they come back in Steps 7
and 9.
The finished game keeps its six stamps in six separate scalars — `S0$` to `S5$` — rather
than in an array. An array works too.
## Step 5: Find the frame boundary
**Goal: a drawing that is not cut in half by the host presenting the frame.**
The host runs **256 source lines and then presents the frame**, and presenting throws the
drawing buffer away. So all the drawing between a `GRAPHIC 1, 1` and its `SSHAPE` has to
happen inside one of those batches. Draw more than fits and the capture comes back holding
only what was issued since the present, over whatever the frame before it left behind —
which looks exactly like a ghost.
`TI#` is the host's clock in sixtieths of a second, and it is refreshed **once per batch**.
So the step on which `TI#` changes is the first step of a batch, and spinning until it
changes is how a program finds a frame boundary:
```basic norun
LABEL PACE
LASTT# = TI#
LABEL PACEEDGE
IF TI# - LASTT# < 2 THEN GOTO PACEEDGE
RETURN
```
Two jiffies is thirty frames a second.
**`LASTT#` is sampled on entry rather than carried over from the last frame, and that is
the whole correctness of the routine.** Carried over, a frame whose work ran long would
find the time already spent, return immediately from somewhere in the middle of a batch,
and ruin the capture that followed. Sampling here means the loop always sees `TI#` change
under it, and a change is only ever seen on the first step of a batch.
Measured on one machine: after a jiffy edge, 220 lines of drawing survive the capture
intact and 250 do not. Write every draw routine to stay near 200 and you have margin.
**Arithmetic is free.** A routine that computes for two thousand steps costs frame rate and
nothing else — only *drawing* has a deadline. That single fact is what Steps 7 and 8 are
built on: move everything that is not a drawing verb out of the routine that draws.
This is the host's budget rather than the interpreter's, so the pacing routine is the
answer rather than a workaround for something being fixed;
[`TODO.md` §9 item 5](../TODO.md) records the measurements and why no fix belongs in the
library.
## Step 6: Draw at most one thing per frame
**Goal: a frame loop that paces, draws one thing, and then plays the game.**
```basic norun
LABEL FRAME
GOSUB PACE
GOSUB DRAWJOB
GOSUB READKEYS
IF STATE# = 0 THEN GOSUB TITLETICK
IF STATE# = 1 THEN GOSUB SERVETICK
IF STATE# = 2 THEN GOSUB PLAYTICK
IF STATE# = 3 THEN GOSUB LOSTTICK
IF STATE# = 4 THEN GOSUB CLEARTICK
IF RUNNING# = 0 THEN GOTO SHUTDOWN
GOTO FRAME
```
Pace first, because that is what puts the drawing at the top of a batch. Then at most one
capture, because a capture is the only thing with a deadline. Then the game, which may
take as long as it likes.
`DRAWJOB` is a queue of one, chosen by dirty flags — stamps first, because everything else
draws with them, then the field, then the HUD:
```basic norun
LABEL DRAWJOB
IF SHN# < 14 THEN GOTO DRAWJOB2
GOSUB DRAWPROTOS
RETURN
LABEL DRAWJOB2
IF DPLAY# = 0 THEN GOTO DRAWJOB3
GOSUB DRAWPLAY
DPLAY# = 0
RETURN
LABEL DRAWJOB3
IF DHUD# = 0 THEN RETURN
GOSUB DRAWHUD
DHUD# = 0
RETURN
```
The rest of the game never draws. It sets `DPLAY# = 1` or `DHUD# = 1` when something has
changed and gets on with its frame. One consequence is visible and deliberate: **the score
lags the bricks by one frame**, because the field is drawn first. At thirty frames a second
nobody can see it.
Those are `LABEL`s and `GOTO`s rather than `BEGIN` blocks. Either works; the labels keep
each arm to one `RETURN` and read the same.
## Step 7: Keep a flattened list of live bricks
**Goal: a draw routine that decides nothing.**
The draw routine has a deadline and the rest of the program does not, so any decision that
can be made earlier should be. When a brick breaks, walk the grid once and write out a
**list of the bricks still standing**, row by row — so a row becomes a contiguous run of
that list, and drawing it is a stamp and an advance:
```basic norun
LABEL BUILDLIVE
LN# = 0
FOR R# = 0 TO 5
RS#(R#) = LN#
RC#(R#) = 0
GOSUB BUILDROW
NEXT R#
RETURN
LABEL BUILDROW
FOR C# = 0 TO 9
IF BRK#(R# * 10 + C#) > 0 THEN BEGIN
LX#(LN#) = BRKX# + C# * 72
LY#(LN#) = BRKY# + R# * 24
LN# = LN# + 1
RC#(R#) = RC#(R#) + 1
BEND
NEXT C#
RETURN
```
`LX#()` and `LY#()` are the pixel positions of the bricks still alive. `RS#(R#)` is where
row `R#`'s run starts in that list and `RC#(R#)` is how long it is.
Drawing a row is then one loop with no tests in it:
```basic norun
LABEL STAMPROW
IF T2# < 1 THEN RETURN
IF T2# = 1 THEN GSHAPE Z$, LX#(T1#), LY#(T1#)
IF T2# < 2 THEN RETURN
FOR I# = T1# TO T1# + T2# - 1
GSHAPE Z$, LX#(I#), LY#(I#)
NEXT I#
RETURN
```
and `DRAWPLAY` calls it six times, once per stamp:
```basic norun
Z$ = S0$ : T1# = RS#(0) : T2# = RC#(0) : GOSUB STAMPROW
Z$ = S1$ : T1# = RS#(1) : T2# = RC#(1) : GOSUB STAMPROW
Z$ = S2$ : T1# = RS#(2) : T2# = RC#(2) : GOSUB STAMPROW
Z$ = S3$ : T1# = RS#(3) : T2# = RC#(3) : GOSUB STAMPROW
Z$ = S4$ : T1# = RS#(4) : T2# = RC#(4) : GOSUB STAMPROW
Z$ = S5$ : T1# = RS#(5) : T2# = RC#(5) : GOSUB STAMPROW
```
Building the list costs about four lines a brick and has all the time in the world.
Drawing costs two and has a deadline. **That trade is the spine of this program**, and it
comes back in Step 8 for the lettering.
### Write the one-item case out beside every loop
Notice the two lines before the `FOR` in `STAMPROW`. **A `FOR` whose bounds are equal does
not run its body**, and the last brick left in a row is exactly that case:
```basic
FOR I# = 0 TO 0
PRINT "THE BODY RAN"
NEXT I#
PRINT "AFTER THE LOOP"
```
```output
AFTER THE LOOP
```
So a loop over a list of unknown length needs its count of one written out beside it, as
`STAMPROW` does. The alternative is a `DO ... LOOP UNTIL`, which tests at the bottom and
therefore always runs once:
```basic
I# = 0
DO
PRINT "PASS " + I#
I# = I# + 1
LOOP UNTIL I# >= 1
```
```output
PASS 0
```
Use whichever reads better. This is [`TODO.md` §6 item 19](../TODO.md), which is waiting on
a decision about a checked-in acceptance file rather than on the work; when it lands,
`FOR I# = 0 TO 0` will run once and both shapes above will still be correct.
## Step 8: Draw lettering with a stroke font
**Goal: a HUD in more than one colour.**
The text grid draws in one colour and has no verb that changes it — `CHAR` accepts a
colour argument and ignores it. A coloured HUD therefore has to be *drawn*, which means
carrying a font.
The font here is four units wide and seven tall, one glyph per `DATA` line: **how many
strokes, then that many pairs of points**. A point is coded `X * 10 + Y`, so 0 is the
top-left corner, 30 the top right, 6 the bottom left and 36 the bottom right.
```basic requires=akgl screenshot=breakout-lettering size=260x110
DIM FNC#(5)
DIM FNI#(5)
DIM FNS#(60)
I# = 0
K# = 0
N# = 0
D# = 0
GP# = 0
GX# = 0
GX2# = 0
P1# = 0
P2# = 0
X1# = 0
Y1# = 0
X2# = 0
Y2# = 0
FOR I# = 0 TO 4
READ N#
FNC#(I#) = N#
FNI#(I#) = GX#
GOSUB READGLYPH
NEXT I#
GRAPHIC 1, 1
WIDTH 2
COLOR 1, 8
SZ# = 9
FOR I# = 0 TO 4
GOSUB DRAWGLYPH
NEXT I#
END
LABEL READGLYPH
FOR K# = 1 TO N# * 2
READ D#
FNS#(GX#) = D#
GX# = GX# + 1
NEXT K#
RETURN
LABEL DRAWGLYPH
GP# = FNI#(I#)
GX2# = 20 + I# * SZ# * 5
FOR K# = 1 TO FNC#(I#)
P1# = FNS#(GP#)
P2# = FNS#(GP# + 1)
GP# = GP# + 2
X1# = GX2# + (P1# / 10) * SZ#
Y1# = 20 + MOD(P1#, 10) * SZ#
X2# = GX2# + (P2# / 10) * SZ#
Y2# = 20 + MOD(P2#, 10) * SZ#
DRAW 1, X1#, Y1# TO X2#, Y2#
NEXT K#
RETURN
REM S
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
REM C
DATA 3, 0,30, 0,6, 6,36
REM O
DATA 4, 0,30, 6,36, 0,6, 30,36
REM R
DATA 5, 0,6, 0,30, 3,33, 30,33, 13,36
REM E
DATA 4, 0,6, 0,30, 3,33, 6,36
```
![The word SCORE drawn as line strokes](images/breakout-lettering.png)
`SZ#` is the scale, so the same table draws a 12-unit `BREAKOUT` on the title screen and a
4-unit `SCORE` in the HUD.
Turning a character into a glyph number is one call: `INSTR(ALPHA$, MID(TX$, TXI#, 1))`
over an alphabet string. That is why the order of the `DATA` lines matters — they have to
match the order of the characters in `ALPHA$`. The finished game uses a 41-character
alphabet covering `A` to `Z`, `0` to `9`, space, colon, dash, full stop and exclamation
mark.
**Building the strokes and drawing them are separate jobs, on different frames.** Turning a
string into strokes costs about sixteen lines a character and happens when a number
changes; the draw routine merely replays the list at two lines a stroke, and it is the one
with the deadline:
```basic norun
LABEL DRAWHUD
GRAPHIC 1, 1
WIDTH 1
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
BOX 5, 0, 56, 799, 57
IF HN# = 1 THEN DRAW HC#(0), HX1#(0), HY1#(0) TO HX2#(0), HY2#(0)
IF HN# < 2 THEN GOTO HUDONE
FOR I# = 0 TO HN# - 1
DRAW HC#(I#), HX1#(I#), HY1#(I#) TO HX2#(I#), HY2#(I#)
NEXT I#
LABEL HUDONE
SSHAPE Z$, 0, 0, 800, 60
SPRSAV Z$, 1
SPRITE 1, 1, 2
MOVSPR 1, 0, 0
SHN# = SHN# + 1
RETURN
```
`HX1#()` to `HY2#()` are the stroke endpoints and `HC#()` is the colour source each stroke
wants, so one pass over the list draws in as many colours as it likes: labels cyan, the
score yellow, the lives green, the level red. **Seven colour sources is the ceiling**,
which is why the purple gem's banner is the closest purple the palette has rather than the
gem's own.
Note the one-stroke case written out beside the loop, for the reason in Step 7. And note
`SHN# = SHN# + 1`: every routine that captures counts the slot it spent, which is what
Step 4's rebuild-when-dry test reads.
## Step 9: Move and bounce the ball
**Goal: bounces at sensible angles, computed without a square root.**
There is no `SQR` in this dialect, so never compute a magnitude. Instead, tabulate eight
landing zones across the bar, each holding **very nearly a unit vector**, and make every
velocity one of them times the current speed:
```basic norun
DATA -0.85, -0.62, -0.40, -0.18, 0.18, 0.40, 0.62, 0.85
DATA -0.53, -0.78, -0.92, -0.98, -0.98, -0.92, -0.78, -0.53
```
Read those into `ZVX%()` and `ZVY%()`. A bar bounce is then: work out which zone the ball
landed in, and assign.
```basic norun
LABEL HITBAR
IF BLY%(B#) + 21 < T2# THEN RETURN
IF BLY%(B#) > T2# + 23 THEN RETURN
IF BLX%(B#) + 21 < T1# THEN RETURN
IF BLX%(B#) > T1# + T3# THEN RETURN
T4# = (BLX%(B#) + 11 - T1#) * 8 / T3#
IF T4# < 0 THEN T4# = 0
IF T4# > 7 THEN T4# = 7
BLVX%(B#) = ZVX%(T4#) * SPD%
BLVY%(B#) = ZVY%(T4#) * SPD%
BLY%(B#) = T2# - 23
SOUND 1, 4298, 5
HIT# = 1
RETURN
```
`T1#` is the bar's left edge, `T2#` its top and `T3#` its width, so the same routine serves
the paddle and the catcher bar — `BALLPADDLE` in Step 13 calls it twice with different
values and reads `HIT#` to find out whether the first call already caught the ball. The
four tests at the top are the overlap; each can bail out, and reaching the fifth line means
the ball is on the bar.
Step 10 adds two more lines before that `RETURN`, for the sticky gem.
### Which type a variable is decides the arithmetic
Two rules matter here and they matter a lot, because breaking either produces **no error at
all** — the program computes something else and carries on.
**A ball's velocity must be a float.** `BLVX%` has a `%` suffix; a `#` variable holds
`-0.85 * 6.0` as `-5`, and a ball whose velocity is quantised to whole pixels bleeds speed
away at every bounce.
**The left operand of an expression decides whether it is done in integers or floats.**
Put the float on the left:
```basic
LEVEL# = 4
SPD% = 5.6 + LEVEL# * 0.45
PRINT "INTEGER FIRST " + SPD%
SPD% = 5.6 + 0.45 * LEVEL#
PRINT "FLOAT FIRST " + SPD%
V% = 6.4
PRINT "0 - V% " + (0 - V%)
PRINT "0.0 - V% " + (0.0 - V%)
```
```output
INTEGER FIRST 5.600000
FLOAT FIRST 7.400000
0 - V% -6
0.0 - V% -6.400000
```
`LEVEL# * 0.45` is zero because `LEVEL#` is an integer, so reversing a ball is
`0.0 - BLVX%(B#)` and never `0 - BLVX%(B#)`. This is a decision of the dialect rather than
a defect — [Chapter 3](03-the-language.md#the-left-operand-decides-whether-the-arithmetic-is-integer-or-float)
has the two rules that keep you out of it, and
[Chapter 13](13-differences.md) names it as the difference from BASIC 7.0 most likely to
turn a working listing into a quietly wrong one.
### Bricks
A bounce off a wall or a brick only ever flips a sign, so a ball is always travelling at
exactly the `SPD%` that was in force when it last left a bar. That makes changing the speed
a **ratio of two speeds** rather than a change of magnitude, which is what the SLOW gem
needs:
```basic norun
LABEL RESCALE
IF BSPD% < 0.1 THEN BSPD% = SPD%
RAT% = SPD% / BSPD%
BSPD% = SPD%
FOR B# = 0 TO 2
IF BLON#(B#) = 1 THEN BEGIN
BLVX%(B#) = BLVX%(B#) * RAT%
BLVY%(B#) = BLVY%(B#) * RAT%
BEND
NEXT B#
RETURN
```
`RAT%` is a float on purpose: an integer one holds the 0.75 of a slowdown as 0 and stops
the ball dead.
Scaling by the *vertical component* instead — setting `BLVY%` to the new speed and taking
the horizontal along with it — is not a slowdown at all. A shallow ball's small vertical
gets stretched up and drags the large horizontal with it, so SLOW makes the ball faster. The
table above is why: the outermost zones keep only 0.53 of their speed in the vertical, so
scaling that up to the new speed multiplies everything by 1/0.53, and the ball ends up
nearly twice as fast as intended. Every zone inflates; only the middle two are close enough
to vertical for it not to show.
A ball can only be over four cells at once, so work out **which** cells from its bounding
box rather than walking all sixty bricks:
```basic norun
LABEL BALLBRICKS
IF BRN# = 0 THEN RETURN
LFT# = BLX%(B#)
TOP# = BLY%(B#)
RGT# = LFT# + 21
BOT# = TOP# + 21
IF BOT# < BRKY# THEN RETURN
IF TOP# > BRKY# + 6 * 24 THEN RETURN
CC1# = (LFT# - BRKX#) / 72
CC2# = (RGT# - BRKX#) / 72
RR1# = (TOP# - BRKY#) / 24
RR2# = (BOT# - BRKY#) / 24
IF CC1# < 0 THEN CC1# = 0
IF CC2# > 9 THEN CC2# = 9
IF RR1# < 0 THEN RR1# = 0
IF RR2# > 5 THEN RR2# = 5
IF CC2# < CC1# THEN RETURN
IF RR2# < RR1# THEN RETURN
HIT# = 0
R# = RR1#
DO
C# = CC1#
DO
IF HIT# = 0 THEN GOSUB TESTCELL
C# = C# + 1
LOOP UNTIL C# > CC2# OR HIT# = 1
R# = R# + 1
LOOP UNTIL R# > RR2# OR HIT# = 1
RETURN
```
`LFT#`, `TOP#`, `RGT#` and `BOT#` are the ball's own box — the artwork is 22 pixels across,
so the ball's right edge is its left plus 21. `CC1#` to `RR2#` are the range of cells that
box can touch, clamped to the field. Both loops are `DO ... LOOP UNTIL` rather than `FOR`,
because a ball entirely inside one column gives `CC1#` and `CC2#` the same value and a
`FOR` would not run at all.
One brick at a time, then. A collision reflects off whichever face the ball has less of
itself past. `T1#` to `T4#` are the **overlapping** rectangle, and its width against its
height is the whole test:
```basic norun
LABEL TESTCELL
N# = R# * 10 + C#
IF BRK#(N#) = 0 THEN RETURN
BX1# = BRKX# + C# * 72
BY1# = BRKY# + R# * 24
IF RGT# < BX1# THEN RETURN
IF LFT# > BX1# + 67 THEN RETURN
IF BOT# < BY1# THEN RETURN
IF TOP# > BY1# + 15 THEN RETURN
T1# = RGT# : IF BX1# + 67 < T1# THEN T1# = BX1# + 67
T2# = LFT# : IF BX1# > T2# THEN T2# = BX1#
T3# = BOT# : IF BY1# + 15 < T3# THEN T3# = BY1# + 15
T4# = TOP# : IF BY1# > T4# THEN T4# = BY1#
IF T1# - T2# < T3# - T4# THEN BLVX%(B#) = 0.0 - BLVX%(B#)
IF T1# - T2# >= T3# - T4# THEN BLVY%(B#) = 0.0 - BLVY%(B#)
BRK#(N#) = 0
BRN# = BRN# - 1
SCORE# = SCORE# + BRV#(R#)
SOUND 1, 17175 - R# * 2100, 4
HIT# = 1
GOSUB BUILDLIVE
GOSUB HUDTEXT
DPLAY# = 1
IF GMON# = 1 THEN RETURN
RNMAX# = 7
GOSUB NEXTRAND
IF RNVAL# = 0 THEN GOSUB SPAWNGEM
RETURN
```
That is the standard box resolution, and it is the reason a ball clipping the end of a row
goes sideways instead of straight back down.
Everything after `BRK#(N#) = 0` is the consequences of the hit, in one place: score it,
sound it, rebuild the live list from Step 7, rebuild the HUD text, mark the field dirty so
Step 6's queue redraws it, and roll one chance in seven for a gem — but only if there is not
one already falling, because Step 2 left exactly one slot for it. `BX1#` and `BY1#` are the
broken brick's own corner, which is where the gem will appear.
## Step 10: Gems and powerups
**Goal: one falling gem that can be any of five things.**
A gem is one sprite, one type number and two timers. The type picks the artwork, the
banner colour and what catching it does:
```basic norun
LABEL SPAWNGEM
RNMAX# = 5
GOSUB NEXTRAND
GMTYP# = RNVAL# + 1
GMX% = BX1# + 10
GMY% = BY1#
IF GMTYP# = 1 THEN SPRSAV "art/element_red_polygon_glossy.png", 8
IF GMTYP# = 2 THEN SPRSAV "art/element_yellow_polygon_glossy.png", 8
IF GMTYP# = 3 THEN SPRSAV "art/element_green_polygon_glossy.png", 8
IF GMTYP# = 4 THEN SPRSAV "art/element_blue_polygon_glossy.png", 8
IF GMTYP# = 5 THEN SPRSAV "art/element_purple_polygon_glossy.png", 8
GMON# = 1
SPRITE 8, 1, 2
MOVSPR 8, GMX%, GMY%
RETURN
```
**Reloading slot 8 is how one sprite becomes five gems.** `SPRSAV` over a live slot replaces
its artwork, so there is no need for a slot per gem — which is just as well, because Step 2
did not leave one.
Catching one is one routine with five arms. Each sets a timer, sets the banner text, and
does whatever that gem does:
```basic norun
LABEL TAKEGEM
SOUND 2, 8579, 14, 1, 17175, 400
SCORE# = SCORE# + 50
GOSUB HUDTEXT
IF GMTYP# = 1 THEN BEGIN
PDW# = 208
PDEXP# = 600
SPRITE 3, 1, 2, 0, 1, 0
BAN$ = "EXPAND"
BEND
IF GMTYP# = 2 THEN BEGIN
BAN$ = "MULTI"
GOSUB SPLITBALLS
BEND
IF GMTYP# = 3 THEN BEGIN
SLOWT# = 480
SPD% = 4.2
GOSUB RESCALE
BAN$ = "SLOW"
BEND
IF GMTYP# = 4 THEN BEGIN
STKON# = 1
STKT# = 540
BAN$ = "STICKY"
BEND
IF GMTYP# = 5 THEN BEGIN
CTON# = 1
CTTM# = 720
SPRITE 4, 1, 2
BAN$ = "CATCH"
BEND
BANT# = 90
GOSUB SETBANNER
RETURN
```
EXPAND is `SPRITE 3, 1, 2, 0, 1, 0` — the x-expand flag, which is the only scaling a sprite
has, and doubling the artwork is exactly what it wants. `PDW#` is the width the collision
test uses, so it has to be doubled with it.
MULTI throws two more balls off the one in play, at slightly different angles so they do
not travel as a stack:
```basic norun
LABEL SPLITBALLS
IF BLN# > 2 THEN RETURN
IF BLON#(0) = 0 THEN RETURN
FOR B# = 1 TO 2
IF BLON#(B#) = 0 THEN BEGIN
BLON#(B#) = 1
BLST#(B#) = 0
BLX%(B#) = BLX%(0)
BLY%(B#) = BLY%(0)
BLVX%(B#) = ZVX%(B# * 5 - 4) * SPD%
BLVY%(B#) = ZVY%(B# * 5 - 4) * SPD%
BLN# = BLN# + 1
SPRITE 5 + B#, 1, 2
BEND
NEXT B#
RETURN
```
STICKY is three lines at the end of `HITBAR` in Step 9, replacing its final `RETURN` — the
ball stops where it landed and remembers its offset along the bar:
```basic norun
IF STKON# = 0 THEN RETURN
BLST#(B#) = 1
BLOFF# = BLX%(B#) - PDX%
RETURN
```
and space releases it, straight up the middle:
```basic norun
LABEL UNSTICK
FOR B# = 0 TO 2
IF BLST#(B#) = 1 THEN BEGIN
BLST#(B#) = 0
BLVX%(B#) = ZVX%(4) * SPD%
BLVY%(B#) = ZVY%(4) * SPD%
BEND
NEXT B#
RETURN
```
The CATCH bar **mirrors** the paddle rather than following it — the line is in `MOVEPADDLE`
in Step 13:
```basic norun
CTX% = 0.0 - PDX% + WALLL# + WALLR# - 104
```
A second bar directly above the first is worth nothing; a mirrored one turns a dive across
the field into a save at both ends. Note the leading `0.0` rather than `0`, for the reason
in Step 9.
### Every timer in one place
A timer is a frame count decremented in one routine, so an expiry is exactly where the
effect is undone and there is only one place to look:
```basic norun
LABEL TIMERS
IF BANT# > 0 THEN BEGIN
BANT# = BANT# - 1
IF BANT# = 0 THEN BEGIN
BAN$ = ""
GOSUB SETBANNER
BEND
BEND
IF PDEXP# > 0 THEN BEGIN
PDEXP# = PDEXP# - 1
IF PDEXP# = 0 THEN BEGIN
PDW# = 104
SPRITE 3, 1, 2, 0, 0, 0
BEND
BEND
IF SLOWT# > 0 THEN BEGIN
SLOWT# = SLOWT# - 1
IF SLOWT# = 0 THEN BEGIN
GOSUB LEVELSPEED
GOSUB RESCALE
BEND
BEND
IF STKT# > 0 THEN BEGIN
STKT# = STKT# - 1
IF STKT# = 0 THEN BEGIN
STKON# = 0
GOSUB UNSTICK
BEND
BEND
IF CTTM# > 0 THEN BEGIN
CTTM# = CTTM# - 1
IF CTTM# = 0 THEN BEGIN
CTON# = 0
SPRITE 4, 0
BEND
BEND
RETURN
```
Each arm is the same shape: count down, and on the frame it reaches zero, put back what the
gem changed. SLOW expiring calls `LEVELSPEED` rather than assigning a number, so the speed
it returns to is the speed the current level should be running at.
## Step 11: Three voices and a mute
**Goal: sound that does not cut itself off, and a mute that costs one line.**
Give each kind of sound its own voice. Voice 1 is the ball hitting things, voice 2 is the
gem and the ball being lost, and voice 3 is reserved for `PLAY` — so a brick going cannot
cut a tune off mid-note.
**`SOUND`'s frequency argument is a SID register value, not hertz.** The pitch is
`register * 1022730 / 16777216` — see [Chapter 7](07-sound.md#sound) — so work the notes
out once and write them down: 17175 is C6, 8579 C5, 4298 C4, 3609 A3.
Pitch the brick tone by the row it came from and the wall plays itself down a scale as it
comes apart:
```basic norun
SOUND 1, 17175 - R# * 2100, 4
```
Mute with `VOL 0` rather than a flag tested at every call site:
```basic norun
LABEL PRESSMUTE
SNDON# = 1 - SNDON#
IF SNDON# = 1 THEN VOL 8
IF SNDON# = 0 THEN VOL 0
RETURN
```
A silenced voice costs nothing to issue, so one line here beats eleven scattered through
the game.
Set up the envelope and tempo once at startup:
```basic norun
VOL 8
ENVELOPE 0, 0, 6, 0, 4
TEMPO 12
```
`PLAY` takes a string of notes, which is what the fanfares are:
```basic norun
PLAY "V3 T2 U8 O4 QC QE QG O5 HC"
```
## Step 12: The states
**Goal: the five states the frame loop in Step 6 dispatches to.**
Each is a subroutine called once a frame while that state is current. This is a different
shape from Chapter 17's branch targets, and it works here because nothing jumps out of a
loop to reach it.
```basic norun
LABEL TITLETICK
STIMER# = STIMER# + 1
RETURN
LABEL SERVETICK
GOSUB MOVEPADDLE
BLX%(0) = PDX% + BLOFF#
BLY%(0) = PDY# - 23
MOVSPR 5, BLX%(0), BLY%(0)
RETURN
LABEL PLAYTICK
GOSUB MOVEPADDLE
GOSUB MOVEBALLS
GOSUB MOVEGEM
GOSUB TIMERS
IF BRN# = 0 THEN GOSUB LEVELDONE
IF BLN# = 0 AND STATE# = 2 THEN GOSUB BALLGONE
RETURN
LABEL LOSTTICK
STIMER# = STIMER# - 1
IF STIMER# > 0 THEN RETURN
IF LIVES# > 0 THEN GOSUB SERVEAGAIN
IF LIVES# < 1 THEN GOSUB GAMEISOVER
RETURN
LABEL CLEARTICK
STIMER# = STIMER# - 1
IF STIMER# > 0 THEN RETURN
LEVEL# = LEVEL# + 1
GOSUB SETUPLEVEL
RETURN
```
`STIMER#` is a frame countdown, which is how "hold this message up for a second and a half"
is written without stopping the game.
Laying a level out is one routine. Every third level is a row shorter, so the field changes
shape as well as pace:
```basic norun
LABEL SETUPLEVEL
GOSUB CLEARPOWERS
GOSUB LEVELSPEED
T3# = 6
IF MOD(LEVEL#, 3) = 2 THEN T3# = 5
IF MOD(LEVEL#, 3) = 0 THEN T3# = 4
BRN# = 0
FOR I# = 0 TO 59
BRK#(I#) = 0
NEXT I#
FOR R# = 0 TO 5
IF R# < T3# THEN GOSUB FILLROW
NEXT R#
GOSUB BUILDLIVE
GOSUB RESETBALL
GOSUB HUDTEXT
STATE# = 1
DPLAY# = 1
RETURN
LABEL LEVELSPEED
SPD% = 5.6 + 0.45 * LEVEL#
IF SPD% > 11.0 THEN SPD% = 11.0
RETURN
```
`CLEARPOWERS` turns off every powerup, every timer and the two sprites they use, and is
called from level setup, from a re-serve and from the title screen — so no state can leak
from one game into the next.
## Step 13: Put the program together
**Goal: one file, in an order that runs.**
The file runs from the top, so three things about the order matter and nothing else does:
setup first, `DATA` in the order it will be read, and every `LABEL` present somewhere.
```text
the declaration block, below
GOSUB LOADTABLES the row colours, values and bounce zones
GOSUB LOADFONT the stroke font
SEED# = MOD(1 + TI# * 7919, 2147483648)
the artwork from Step 1
VOL 8 / ENVELOPE / TEMPO from Step 11
GOSUB TITLESCREEN
LABEL FRAME the frame loop from Step 6
the pacing, the draw jobs, the input, the states, the ball,
the gems, the text builders, the generator
--- in any order ---
the tables as DATA read by LOADTABLES
the font as DATA read by LOADFONT
```
**`LOADTABLES` must run before `LOADFONT`, because the tables' `DATA` is written first.**
One `READ` cursor walks every `DATA` item in the file in the order they appear, no matter
which routine is reading, so whichever loader runs first gets whichever `DATA` comes first.
Get that backwards and the font table fills with brick colours, which does not fail — it
just draws nonsense.
### The declaration block
```basic norun
STATE# = 0
RUNNING# = 1
SCORE# = 0
LIVES# = 3
LEVEL# = 1
HISCORE# = 0
STIMER# = 0
LASTT# = 0
KEYV# = 0
SNDON# = 1
WALLL# = 12
WALLR# = 788
BRKX# = 42
BRKY# = 108
PDX% = 348
PDY# = 540
PDW# = 104
PDDIR# = 0
PDCO# = 0
PDEXP# = 0
CTON# = 0
CTX% = 348
CTTM# = 0
DIM BLON#(3)
DIM BLX%(3)
DIM BLY%(3)
DIM BLVX%(3)
DIM BLVY%(3)
DIM BLST#(3)
BLN# = 0
BLOFF# = 41
SPD% = 6.0
BSPD% = 6.0
RAT% = 1.0
SLOWT# = 0
STKON# = 0
STKT# = 0
GMON# = 0
GMTYP# = 0
GMX% = 0
GMY% = 0
DIM BRK#(60)
DIM LX#(60)
DIM LY#(60)
DIM RS#(6)
DIM RC#(6)
DIM BRC#(6)
DIM BRV#(6)
LN# = 0
BRN# = 0
S0$ = "" : S1$ = "" : S2$ = ""
S3$ = "" : S4$ = "" : S5$ = ""
Z$ = ""
SHN# = 99
DHUD# = 1
DPLAY# = 1
BAN$ = ""
BANT# = 0
ALPHA$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :-.!"
DIM FNS#(280)
DIM FNI#(41)
DIM FNC#(41)
DIM HX1#(120)
DIM HY1#(120)
DIM HX2#(120)
DIM HY2#(120)
DIM HC#(120)
HN# = 0
DIM PX1#(110)
DIM PY1#(110)
DIM PX2#(110)
DIM PY2#(110)
DIM PC#(110)
PN# = 0
BUILDH# = 0
TX$ = "" : TXX# = 0 : TXY# = 0
TXS# = 3 : TXC# = 1 : TLEN# = 0 : TXI# = 0
GC# = 0 : GN# = 0 : GP# = 0 : GX# = 0 : GK# = 0
P1# = 0 : P2# = 0
NUM$ = ""
DIM ZVX%(8)
DIM ZVY%(8)
I# = 0 : K# = 0 : N# = 0 : R# = 0 : C# = 0 : D# = 0 : B# = 0
T1# = 0 : T2# = 0 : T3# = 0 : T4# = 0
BX1# = 0 : BY1# = 0
LFT# = 0 : TOP# = 0 : RGT# = 0 : BOT# = 0
CC1# = 0 : CC2# = 0 : RR1# = 0 : RR2# = 0
HIT# = 0
SEED# = 1 : RNMAX# = 2 : RNVAL# = 0
```
`SHN# = 99` means "the brick stamps do not exist", so the frame loop's first job is to
build them. `DPLAY#` and `DHUD#` start at 1 so the first frame draws both.
That block is 121 of the interpreter's 128 variables, which is why several constants in the
geometry are spelled out where they are used rather than given names: a name costs a slot
whether it holds a constant or not.
### The tables and the generator
```basic norun
LABEL LOADTABLES
FOR I# = 0 TO 5
READ BRC#(I#)
NEXT I#
FOR I# = 0 TO 5
READ BRV#(I#)
NEXT I#
FOR I# = 0 TO 7
READ ZVX%(I#)
NEXT I#
FOR I# = 0 TO 7
READ ZVY%(I#)
NEXT I#
RETURN
LABEL NEXTRAND
SEED# = MOD(SEED# * 1103515245 + 12345, 2147483648)
RNVAL# = MOD(SHR(SEED#, 16), RNMAX#)
RETURN
```
```basic norun
DATA 3, 9, 8, 6, 4, 5
DATA 60, 50, 40, 30, 20, 10
DATA -0.85, -0.62, -0.40, -0.18, 0.18, 0.40, 0.62, 0.85
DATA -0.53, -0.78, -0.92, -0.98, -0.98, -0.92, -0.78, -0.53
```
Row colours, what a brick in each row is worth, and the eight bounce zones from Step 9.
`NEXTRAND` is a `GOSUB` over globals rather than a `DEF` function. Set `RNMAX#` to the
number of answers you want and read `RNVAL#`.
The font loader is the same shape, and it needs a subroutine of its own for the inner read
rather than a `BEGIN` block, because a `FOR` nested inside a block that is itself inside a
`FOR` does not find its own `NEXT`:
```basic norun
LABEL LOADFONT
GX# = 0
FOR I# = 0 TO 40
READ N#
FNC#(I#) = N#
FNI#(I#) = GX#
IF N# > 0 THEN GOSUB READGLYPH
NEXT I#
RETURN
LABEL READGLYPH
FOR K# = 1 TO N# * 2
READ D#
FNS#(GX#) = D#
GX# = GX# + 1
NEXT K#
RETURN
```
`FNC#(g)` is glyph `g`'s stroke count and `FNI#(g)` is where its points start in `FNS#()`.
The 41 `DATA` lines are one per character of `ALPHA$`, **in that order** — the `INSTR` in
`BUILDTEXT` gives a position in `ALPHA$` and that position is used directly as the glyph
number, so getting the two out of step silently draws the wrong letters. Here is the whole
font, in the format Step 8 gives:
```basic norun
REM A
DATA 4, 0,30, 0,6, 30,36, 3,33
REM B
DATA 6, 0,6, 0,30, 3,33, 6,36, 30,33, 33,36
REM C
DATA 3, 0,30, 0,6, 6,36
REM D
DATA 6, 0,6, 0,20, 20,31, 31,35, 35,26, 26,6
REM E
DATA 4, 0,6, 0,30, 3,33, 6,36
REM F
DATA 3, 0,6, 0,30, 3,33
REM G
DATA 5, 0,30, 0,6, 6,36, 33,36, 13,33
REM H
DATA 3, 0,6, 30,36, 3,33
REM I
DATA 1, 10,16
REM J
DATA 3, 30,36, 6,36, 3,6
REM K
DATA 3, 0,6, 30,3, 3,36
REM L
DATA 2, 0,6, 6,36
REM M
DATA 4, 0,6, 30,36, 0,13, 13,30
REM N
DATA 3, 0,6, 30,36, 0,36
REM O
DATA 4, 0,30, 6,36, 0,6, 30,36
REM P
DATA 4, 0,6, 0,30, 3,33, 30,33
REM Q
DATA 5, 0,30, 6,36, 0,6, 30,36, 24,36
REM R
DATA 5, 0,6, 0,30, 3,33, 30,33, 13,36
REM S
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
REM T
DATA 2, 0,30, 10,16
REM U
DATA 3, 0,6, 30,36, 6,36
REM V
DATA 2, 0,16, 16,30
REM W
DATA 4, 0,6, 30,36, 6,13, 13,36
REM X
DATA 2, 0,36, 30,6
REM Y
DATA 3, 0,13, 30,13, 13,16
REM Z
DATA 3, 0,30, 30,6, 6,36
REM 0
DATA 5, 0,30, 6,36, 0,6, 30,36, 30,6
REM 1
DATA 2, 10,16, 1,10
REM 2
DATA 5, 0,30, 30,33, 3,33, 3,6, 6,36
REM 3
DATA 4, 0,30, 30,36, 3,33, 6,36
REM 4
DATA 3, 0,3, 3,33, 30,36
REM 5
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
REM 6
DATA 5, 0,30, 0,6, 3,33, 33,36, 6,36
REM 7
DATA 2, 0,30, 30,36
REM 8
DATA 5, 0,30, 6,36, 0,6, 30,36, 3,33
REM 9
DATA 5, 0,30, 0,3, 3,33, 30,36, 6,36
REM space
DATA 0
REM colon
DATA 2, 12,13, 14,15
REM dash
DATA 1, 3,33
REM full stop
DATA 1, 15,16
REM exclamation mark
DATA 2, 10,14, 15,16
```
Space is `DATA 0` — no strokes — which is what the `IF N# > 0` in `LOADFONT` and the
`IF GN# = 0 THEN RETURN` in `BUILDGLYPH` are for.
### Input
```basic norun
LABEL READKEYS
GET KEYV#
IF KEYV# = 0 THEN RETURN
IF KEYV# = 1073741904 THEN PDDIR# = 0 - 1 : PDCO# = 7
IF KEYV# = 1073741903 THEN PDDIR# = 1 : PDCO# = 7
IF KEYV# = 32 THEN GOSUB PRESSFIRE
IF KEYV# = 112 THEN GOSUB PRESSPAUSE
IF KEYV# = 115 THEN GOSUB PRESSMUTE
IF KEYV# = 113 THEN RUNNING# = 0
IF KEYV# = 27 THEN RUNNING# = 0
GOTO READKEYS
LABEL PRESSFIRE
IF STATE# = 0 THEN GOSUB STARTGAME : RETURN
IF STATE# = 5 THEN GOSUB TITLESCREEN : RETURN
IF STATE# = 1 THEN GOSUB LAUNCH : RETURN
IF STATE# = 2 THEN GOSUB UNSTICK
RETURN
LABEL PRESSPAUSE
IF STATE# = 2 THEN STATE# = 6 : GMTYP# = 0 : BAN$ = "PAUSED" : GOSUB SETBANNER : RETURN
IF STATE# = 6 THEN STATE# = 2 : BAN$ = "" : GOSUB SETBANNER
RETURN
```
`READKEYS` empties the queue by tail-calling itself with `GOTO`, which costs no scope.
A press buys seven frames of paddle travel in `PDCO#` and a held key keeps renewing it —
the same countdown idea Chapter 17 uses, sized for this game's frame rate. Seven frames
matters because the first key repeat is a quarter of a second behind the press, and a
paddle that stalled for a quarter of a second would be unusable.
### The paddle and the balls
```basic norun
LABEL MOVEPADDLE
IF PDCO# > 0 THEN BEGIN
PDCO# = PDCO# - 1
PDX% = PDX% + PDDIR# * 9
BEND
IF PDCO# < 1 THEN PDDIR# = 0
IF PDX% < WALLL# THEN PDX% = WALLL#
IF PDX% > WALLR# - PDW# THEN PDX% = WALLR# - PDW#
MOVSPR 3, PDX%, PDY#
IF CTON# = 0 THEN RETURN
CTX% = 0.0 - PDX% + WALLL# + WALLR# - 104
MOVSPR 4, CTX%, 470
RETURN
LABEL MOVEBALLS
FOR B# = 0 TO 2
IF BLON#(B#) = 1 THEN GOSUB MOVEONE
NEXT B#
RETURN
LABEL MOVEONE
IF BLST#(B#) = 1 THEN BEGIN
BLX%(B#) = PDX% + BLOFF#
BLY%(B#) = PDY# - 23
BEND
IF BLST#(B#) = 0 THEN BEGIN
BLX%(B#) = BLX%(B#) + BLVX%(B#)
BLY%(B#) = BLY%(B#) + BLVY%(B#)
GOSUB BALLWALLS
BEND
IF BLON#(B#) = 0 THEN RETURN
IF BLST#(B#) = 0 THEN BEGIN
GOSUB BALLBRICKS
GOSUB BALLPADDLE
BEND
MOVSPR 5 + B#, BLX%(B#), BLY%(B#)
RETURN
LABEL BALLWALLS
IF BLX%(B#) < WALLL# THEN BEGIN
BLX%(B#) = WALLL#
BLVX%(B#) = 0.0 - BLVX%(B#)
SOUND 1, 3609, 2
BEND
IF BLX%(B#) > WALLR# - 22 THEN BEGIN
BLX%(B#) = WALLR# - 22
BLVX%(B#) = 0.0 - BLVX%(B#)
SOUND 1, 3609, 2
BEND
IF BLY%(B#) < 72 THEN BEGIN
BLY%(B#) = 72
BLVY%(B#) = 0.0 - BLVY%(B#)
SOUND 1, 3609, 2
BEND
IF BLY%(B#) < 596 THEN RETURN
BLON#(B#) = 0
BLN# = BLN# - 1
SOUND 2, 2411, 6
SPRITE 5 + B#, 0
RETURN
LABEL BALLPADDLE
IF BLVY%(B#) < 0 THEN RETURN
HIT# = 0
T1# = PDX% : T2# = PDY# : T3# = PDW#
GOSUB HITBAR
IF CTON# = 0 THEN RETURN
IF HIT# = 1 THEN RETURN
T1# = CTX% : T2# = 470 : T3# = 104
GOSUB HITBAR
RETURN
```
Balls 0, 1 and 2 live in sprites 5, 6 and 7, which is why `MOVSPR 5 + B#` works.
`BLST#(B#)` is 1 while a ball is stuck to the paddle. Note the `0.0 -` on every sign flip,
for the reason in Step 9.
`BALLBRICKS` and `TESTCELL` are in Step 9.
### The gem's fall
```basic norun
LABEL MOVEGEM
IF GMON# = 0 THEN RETURN
GMY% = GMY% + 3.2
MOVSPR 8, GMX%, GMY%
HIT# = 0
IF GMY% > 596 THEN HIT# = 1
IF GMY% + 46 > PDY# AND GMY% < PDY# + 24 THEN GOSUB CATCHGEM
IF HIT# = 0 THEN RETURN
GMON# = 0
SPRITE 8, 0
RETURN
LABEL CATCHGEM
IF GMX% + 48 < PDX% THEN RETURN
IF GMX% > PDX% + PDW# THEN RETURN
GOSUB TAKEGEM
HIT# = 1
RETURN
```
`TAKEGEM` is in Step 10. `HIT#` is doing double duty here as "this gem is finished with" —
it is set both by the gem going off the bottom and by it being caught.
### Turning text into strokes
Step 8 drew a glyph directly. The game builds a list instead, so the drawing can happen on
a later frame:
```basic norun
LABEL BUILDTEXT
TLEN# = LEN(TX$)
IF TLEN# = 0 THEN RETURN
TXI# = 0
DO
GC# = INSTR(ALPHA$, MID(TX$, TXI#, 1))
IF GC# > 0 - 1 THEN GOSUB BUILDGLYPH
TXI# = TXI# + 1
LOOP UNTIL TXI# >= TLEN#
RETURN
LABEL BUILDGLYPH
GN# = FNC#(GC#)
IF GN# = 0 THEN RETURN
GP# = FNI#(GC#)
GX# = TXX# + TXI# * TXS# * 5
GK# = 0
DO
P1# = FNS#(GP#) : P2# = FNS#(GP# + 1) : GP# = GP# + 2
IF BUILDH# = 1 AND HN# < 120 THEN BEGIN
HX1#(HN#) = GX# + (P1# / 10) * TXS#
HY1#(HN#) = TXY# + MOD(P1#, 10) * TXS#
HX2#(HN#) = GX# + (P2# / 10) * TXS#
HY2#(HN#) = TXY# + MOD(P2#, 10) * TXS#
HC#(HN#) = TXC#
HN# = HN# + 1
BEND
IF BUILDH# = 0 AND PN# < 110 THEN BEGIN
PX1#(PN#) = GX# + (P1# / 10) * TXS#
PY1#(PN#) = TXY# + MOD(P1#, 10) * TXS#
PX2#(PN#) = GX# + (P2# / 10) * TXS#
PY2#(PN#) = TXY# + MOD(P2#, 10) * TXS#
PC#(PN#) = TXC#
PN# = PN# + 1
BEND
GK# = GK# + 1
LOOP UNTIL GK# >= GN#
RETURN
```
Set `TX$`, `TXX#`, `TXY#`, `TXS#` (the scale) and `TXC#` (the colour source), then set
`BUILDH#` to 1 for the HUD list or 0 for the field list, and `GOSUB BUILDTEXT`. Both `DO`
loops test at the bottom, so a one-character string and a one-stroke glyph both work.
The bounds tests — `HN# < 120` and `PN# < 110` — are what stops a long string running off
the end of the arrays.
```basic norun
LABEL HUDTEXT
HN# = 0
BUILDH# = 1
TXS# = 4
TXY# = 14
TXC# = 1 : TXX# = 24
TX$ = "SCORE"
GOSUB BUILDTEXT
TXC# = 2 : TXX# = 144
T4# = SCORE#
GOSUB FMTNUM
TX$ = NUM$
GOSUB BUILDTEXT
TXC# = 1 : TXX# = 316
TX$ = "LIVES"
GOSUB BUILDTEXT
TXC# = 6 : TXX# = 436
TX$ = "" + LIVES#
GOSUB BUILDTEXT
TXC# = 1 : TXX# = 500
TX$ = "LEVEL"
GOSUB BUILDTEXT
TXC# = 4 : TXX# = 620
TX$ = "" + LEVEL#
GOSUB BUILDTEXT
DHUD# = 1
RETURN
LABEL FMTNUM
NUM$ = ""
T2# = T4#
FOR I# = 1 TO 6
D# = MOD(T2#, 10)
NUM$ = MID("0123456789", D#, 1) + NUM$
T2# = T2# / 10
NEXT I#
RETURN
```
`HUDTEXT` rebuilds the whole list from `HN# = 0` and sets `DHUD#`; the frame loop draws it
whenever it next gets a turn.
`SETBANNER` is the same idea for the field list — the message that flashes over the play
area, in the colour of the gem that put it there:
```basic norun
LABEL SETBANNER
PN# = 0
DPLAY# = 1
IF BAN$ = "" THEN RETURN
BUILDH# = 0
TXS# = 8
TXY# = 430
TXX# = 400 - LEN(BAN$) * 20
TXC# = 5
IF GMTYP# = 1 THEN TXC# = 4
IF GMTYP# = 2 THEN TXC# = 2
IF GMTYP# = 3 THEN TXC# = 6
IF GMTYP# = 4 THEN TXC# = 1
IF GMTYP# = 5 THEN TXC# = 3
TX$ = BAN$
GOSUB BUILDTEXT
RETURN
```
Setting `BAN$` to `""` and calling it is how a banner is cleared: `PN# = 0` empties the
field's stroke list and `DPLAY# = 1` asks for a redraw without it. Sources 1 to 6 are cyan,
yellow, purple, light red, light grey and green, and the five gems are red, yellow, green,
blue and purple — so the banner is as close to the gem as seven colour sources allow.
### The state changes
Six routines, each the same shape: set `STATE#`, set `STIMER#` if the state has a duration,
set the banner, and mark whatever is now out of date.
```basic norun
LABEL BALLGONE
SOUND 2, 7218, 30, 2, 1804, 220
LIVES# = LIVES# - 1
STATE# = 3
STIMER# = 45
GMTYP# = 0
BAN$ = "MISS"
GOSUB SETBANNER
GOSUB HUDTEXT
RETURN
LABEL SERVEAGAIN
BAN$ = ""
GOSUB SETBANNER
GOSUB CLEARPOWERS
GOSUB RESETBALL
STATE# = 1
RETURN
LABEL LEVELDONE
PLAY "V3 T2 U8 O4 QC QE QG O5 HC"
STATE# = 4
STIMER# = 60
GMTYP# = 0
BAN$ = "CLEAR"
GOSUB SETBANNER
RETURN
LABEL STARTGAME
PLAY "V3 T2 U8 O4 IC IE IG O5 IC"
SCORE# = 0
LIVES# = 3
LEVEL# = 1
GOSUB SETUPLEVEL
RETURN
```
`GMTYP# = 0` before a banner that is not a gem's is what makes `SETBANNER` pick its default
colour rather than the last gem's.
`TITLESCREEN` and `GAMEISOVER` are longer only because both draw a screenful of lettering.
Both clear the brick array and call `BUILDLIVE` so the field draws empty, turn the paddle
and ball sprites off, reset `PN#`, and then build their text at a large `TXS#`:
```basic norun
LABEL TITLESCREEN
STATE# = 0
STIMER# = 0
GOSUB CLEARPOWERS
BLN# = 0
BRN# = 0
FOR I# = 0 TO 59
BRK#(I#) = 0
NEXT I#
GOSUB BUILDLIVE
SPRITE 3, 0
SPRITE 5, 0
SPRITE 6, 0
SPRITE 7, 0
PN# = 0
TXS# = 12 : TXC# = 2 : TXX# = 120 : TXY# = 170
TX$ = "BREAKOUT"
BUILDH# = 0
GOSUB BUILDTEXT
TXS# = 4 : TXC# = 1 : TXX# = 220 : TXY# = 370
TX$ = "SPACE TO START"
BUILDH# = 0
GOSUB BUILDTEXT
HN# = 0
TXS# = 3 : TXC# = 6 : TXX# = 40 : TXY# = 20
TX$ = "ARROWS MOVE P PAUSE Q QUIT"
BUILDH# = 1
GOSUB BUILDTEXT
DPLAY# = 1
DHUD# = 1
RETURN
```
`GAMEISOVER` is the same routine with `"GAME OVER"`, the final score and the best score,
and `STATE# = 5` — the state whose only exit is space, back to the title.
### Serving, and clearing up
```basic norun
LABEL RESETBALL
FOR B# = 0 TO 2
BLON#(B#) = 0
BLST#(B#) = 0
NEXT B#
SPRITE 6, 0
SPRITE 7, 0
BLON#(0) = 1
BLN# = 1
BSPD% = SPD%
PDX% = 348
PDW# = 104
BLOFF# = 41
SPRITE 3, 1, 2, 0, 0, 0
SPRITE 5, 1, 2
MOVSPR 3, PDX%, PDY#
BLX%(0) = PDX% + BLOFF#
BLY%(0) = PDY# - 23
MOVSPR 5, BLX%(0), BLY%(0)
RETURN
LABEL LAUNCH
SOUND 1, 6431, 3
STATE# = 2
BLST#(0) = 0
BLVX%(0) = ZVX%(5) * SPD%
BLVY%(0) = ZVY%(5) * SPD%
RETURN
LABEL CLEARPOWERS
GOSUB LEVELSPEED
PDW# = 104
PDEXP# = 0
SLOWT# = 0
STKON# = 0
STKT# = 0
CTON# = 0
CTTM# = 0
GMON# = 0
BANT# = 0
BLOFF# = 41
SPRITE 4, 0
SPRITE 8, 0
SPRITE 3, 1, 2, 0, 0, 0
RETURN
LABEL FILLROW
FOR C# = 0 TO 9
BRK#(R# * 10 + C#) = 1
BRN# = BRN# + 1
NEXT C#
RETURN
LABEL SHUTDOWN
FOR I# = 1 TO 8
SPRITE I#, 0
NEXT I#
IF SCORE# > HISCORE# THEN HISCORE# = SCORE#
PRINT "FINAL SCORE " + SCORE# + " BEST " + HISCORE#
END
```
The six state changes — `BALLGONE`, `SERVEAGAIN`, `LEVELDONE`, `GAMEISOVER`,
`TITLESCREEN` and `STARTGAME` — are above, with `SETBANNER`.
### The two draw routines, whole
Step 4 built the stamps and Step 7 stamped a row; these are the two routines Step 6's queue
actually calls, with those pieces in place.
```basic norun
LABEL DRAWPROTOS
GRAPHIC 5
GRAPHIC 1, 1
WIDTH 1
SHN# = 99
FOR R# = 0 TO 5
COLOR 1, BRC#(R#)
T1# = R# * 20
T2# = T1# + 8
FOR K# = 0 TO 7
DRAW 1, 0, T1# + K# TO 67, T1# + K# : DRAW 1, 0, T2# + K# TO 67, T2# + K#
NEXT K#
NEXT R#
SSHAPE Z$, 0, 0, 68, 16 : S0$ = Z$
SSHAPE Z$, 0, 20, 68, 36 : S1$ = Z$
SSHAPE Z$, 0, 40, 68, 56 : S2$ = Z$
SSHAPE Z$, 0, 60, 68, 76 : S3$ = Z$
SSHAPE Z$, 0, 80, 68, 96 : S4$ = Z$
SSHAPE Z$, 0, 100, 68, 116 : S5$ = Z$
SHN# = 6
DPLAY# = 1
DHUD# = 1
RETURN
```
`GRAPHIC 5` hands all sixteen `SSHAPE` slots back, which is the only way to get any of them
back — so `SHN#` goes to 99 first and to 6 at the end, and both flags are set because the
field and the HUD were drawn with stamps that no longer exist.
```basic norun
LABEL DRAWPLAY
GRAPHIC 1, 1
WIDTH 2
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
BOX 5, 2, 62, 797, 597 : BOX 1, 6, 66, 793, 593
Z$ = S0$ : T1# = RS#(0) : T2# = RC#(0) : GOSUB STAMPROW
Z$ = S1$ : T1# = RS#(1) : T2# = RC#(1) : GOSUB STAMPROW
Z$ = S2$ : T1# = RS#(2) : T2# = RC#(2) : GOSUB STAMPROW
Z$ = S3$ : T1# = RS#(3) : T2# = RC#(3) : GOSUB STAMPROW
Z$ = S4$ : T1# = RS#(4) : T2# = RC#(4) : GOSUB STAMPROW
Z$ = S5$ : T1# = RS#(5) : T2# = RC#(5) : GOSUB STAMPROW
IF PN# = 1 THEN DRAW PC#(0), PX1#(0), PY1#(0) TO PX2#(0), PY2#(0)
IF PN# < 2 THEN GOTO PLDONE
FOR I# = 0 TO PN# - 1
DRAW PC#(I#), PX1#(I#), PY1#(I#) TO PX2#(I#), PY2#(I#)
NEXT I#
LABEL PLDONE
SSHAPE Z$, 0, 60, 800, 600
SPRSAV Z$, 2
SPRITE 2, 1, 2
MOVSPR 2, 0, 60
SHN# = SHN# + 1
RETURN
```
Same shape as `DRAWHUD` in Step 8: the walls, then the bricks, then the field's own stroke
list with its one-item case beside the loop, then Step 3's four lines that turn all of it
into sprite 2.
**A label is one bare word.** `PLDONE` and `HUDONE` have no underscore in them, and cannot:
underscores are not part of an identifier here.
### Game over
`TITLESCREEN` is above. `GAMEISOVER` is the same routine with different text, `STATE# = 5`,
and the high score recorded on the way in:
```basic norun
LABEL GAMEISOVER
PLAY "V3 T1 U8 O4 QG QE QC O3 HG"
IF SCORE# > HISCORE# THEN HISCORE# = SCORE#
STATE# = 5
GOSUB CLEARPOWERS
BRN# = 0
FOR I# = 0 TO 59
BRK#(I#) = 0
NEXT I#
GOSUB BUILDLIVE
SPRITE 3, 0
SPRITE 5, 0
PN# = 0
TXS# = 9 : TXC# = 4 : TXX# = 148 : TXY# = 230
TX$ = "GAME OVER"
BUILDH# = 0
GOSUB BUILDTEXT
TXS# = 5 : TXC# = 2 : TXX# = 200 : TXY# = 380
T4# = SCORE#
GOSUB FMTNUM
TX$ = "SCORE " + NUM$
BUILDH# = 0
GOSUB BUILDTEXT
HN# = 0
TXS# = 3 : TXC# = 5 : TXY# = 20 : TXX# = 40
T4# = HISCORE#
GOSUB FMTNUM
TX$ = "BEST " + NUM$
BUILDH# = 1
GOSUB BUILDTEXT
TXC# = 6 : TXX# = 400
TX$ = "SPACE FOR TITLE"
BUILDH# = 1
GOSUB BUILDTEXT
DPLAY# = 1
DHUD# = 1
RETURN
```
Three sizes of lettering on one screen, from one font table: `TXS# = 9` for the message,
5 for the score and 3 for the HUD strip. Setting `HN# = 0` partway through is what switches
from building the field's list to rebuilding the HUD's.
### Check it before you have a window
Run the assembled file through the plain build first:
```sh norun
$ ./build/basic mybreakout.bas
```
It will stop at the first `SPRSAV` with a message about a missing sprite device, which
means everything above it parsed. A parse error, a bad `DATA` read or an `OUT OF DATA` at
this stage is a real problem, and it is much easier to find without a window in the way.
Then run it properly:
```sh norun
$ ./build-akgl/basic mybreakout.bas
```
## The budgets
This program sits close to five ceilings at once, and knowing where they are is what stops
a feature costing an afternoon before it is abandoned.
| Resource | There are | This game uses |
|---|---|---|
| Sprites | 8 | 8 |
| Variables | 128 | 121, plus 4 the interpreter makes |
| Labels | 64 | 61 |
| `SSHAPE` slots | 16, none reclaimed except by `GRAPHIC 5` | 6 stamps plus 1 capture a frame |
| Colour sources | 7 | 7 |
| Scopes | 32 | 6 deep at most |
| Tokens on a line | 32, and the 33rd stops the interpreter rather than raising | short lines, temporaries instead of long conditions |
| Value-pool slots | 4096 for arrays; a plain number costs none | fifteen arrays, all declared once |
That is also why several things are **not** in the game, and they are worth naming rather
than leaving to be discovered: no music under the play, only event sounds and two four-note
stings; one gem at a time; sticky and multiball share one offset, so two balls stuck to the
paddle sit on top of each other; and no high score on disk, because there is no disk.
## The picture at the top of this chapter
Every figure in this guide is generated by running the listing beside it, and the one at
the top of this chapter is no exception. It is Step 4's field, Step 8's stroke font and
Step 1's artwork, with the numbers filled in by hand and no frame loop, so it draws one
frame and stops.
The font here carries only the fourteen glyphs the HUD needs, in the order they appear in
`ALPHA$` — which is the ordering rule from Step 8, shown small enough to check by eye:
```basic requires=akgl setup=breakout_art screenshot=breakout-game-artwork size=800x600
DIM BRC#(6)
DIM FNC#(14)
DIM FNI#(14)
DIM FNS#(100)
ALPHA$ = "SCOERLIV 01235"
I# = 0
R# = 0
C# = 0
K# = 0
N# = 0
D# = 0
T1# = 0
T2# = 0
GP# = 0
GX# = 0
GX2# = 0
GK# = 0
TLEN# = 0
P1# = 0
P2# = 0
X1# = 0
Y1# = 0
X2# = 0
Y2# = 0
SZ# = 0
TX$ = ""
TXX# = 0
TXY# = 0
TXC# = 1
TXI# = 0
GC# = 0
Z$ = ""
S0$ = ""
S1$ = ""
S2$ = ""
S3$ = ""
S4$ = ""
FOR I# = 0 TO 5
READ BRC#(I#)
NEXT I#
GX# = 0
FOR I# = 0 TO 13
READ N#
FNC#(I#) = N#
FNI#(I#) = GX#
IF N# > 0 THEN GOSUB READGLYPH
NEXT I#
GRAPHIC 1, 1
WIDTH 1
FOR R# = 0 TO 5
COLOR 1, BRC#(R#)
T1# = R# * 20
T2# = T1# + 8
FOR K# = 0 TO 7
DRAW 1, 0, T1# + K# TO 67, T1# + K# : DRAW 1, 0, T2# + K# TO 67, T2# + K#
NEXT K#
NEXT R#
SSHAPE Z$, 0, 0, 68, 16 : S0$ = Z$
SSHAPE Z$, 0, 20, 68, 36 : S1$ = Z$
SSHAPE Z$, 0, 40, 68, 56 : S2$ = Z$
SSHAPE Z$, 0, 60, 68, 76 : S3$ = Z$
SSHAPE Z$, 0, 80, 68, 96 : S4$ = Z$
GRAPHIC 1, 1
WIDTH 2
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
BOX 5, 2, 62, 797, 597
BOX 1, 6, 66, 793, 593
FOR C# = 0 TO 9
Z$ = S0$ : GSHAPE Z$, 42 + C# * 72, 108
Z$ = S1$ : GSHAPE Z$, 42 + C# * 72, 132
Z$ = S2$ : GSHAPE Z$, 42 + C# * 72, 156
NEXT C#
FOR C# = 1 TO 8
Z$ = S3$ : GSHAPE Z$, 42 + C# * 72, 180
NEXT C#
FOR C# = 2 TO 6
Z$ = S4$ : GSHAPE Z$, 42 + C# * 72, 204
NEXT C#
SSHAPE Z$, 0, 60, 800, 600
SPRSAV Z$, 2
SPRITE 2, 1, 2
MOVSPR 2, 0, 60
GRAPHIC 1, 1
WIDTH 1
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
BOX 5, 0, 56, 799, 57
SZ# = 4
TXY# = 14
TXC# = 1 : TXX# = 24 : TX$ = "SCORE" : GOSUB DRAWTEXT
TXC# = 2 : TXX# = 144 : TX$ = "001250" : GOSUB DRAWTEXT
TXC# = 1 : TXX# = 316 : TX$ = "LIVES" : GOSUB DRAWTEXT
TXC# = 6 : TXX# = 436 : TX$ = "3" : GOSUB DRAWTEXT
TXC# = 1 : TXX# = 500 : TX$ = "LEVEL" : GOSUB DRAWTEXT
TXC# = 4 : TXX# = 620 : TX$ = "2" : GOSUB DRAWTEXT
SSHAPE Z$, 0, 0, 800, 60
SPRSAV Z$, 1
SPRITE 1, 1, 2
MOVSPR 1, 0, 0
SPRSAV "art/paddleBlu.png", 3
SPRSAV "art/ballBlue.png", 5
SPRSAV "art/element_green_polygon_glossy.png", 8
SPRITE 3, 1, 2
SPRITE 5, 1, 2
SPRITE 8, 1, 2
MOVSPR 3, 300, 540
MOVSPR 5, 420, 400
MOVSPR 8, 200, 300
END
LABEL READGLYPH
FOR K# = 1 TO N# * 2
READ D#
FNS#(GX#) = D#
GX# = GX# + 1
NEXT K#
RETURN
LABEL DRAWTEXT
TLEN# = LEN(TX$)
IF TLEN# = 0 THEN RETURN
TXI# = 0
DO
GC# = INSTR(ALPHA$, MID(TX$, TXI#, 1))
IF GC# > 0 - 1 THEN GOSUB DRAWGLYPH
TXI# = TXI# + 1
LOOP UNTIL TXI# >= TLEN#
RETURN
LABEL DRAWGLYPH
N# = FNC#(GC#)
IF N# = 0 THEN RETURN
GP# = FNI#(GC#)
GX2# = TXX# + TXI# * SZ# * 5
GK# = 0
DO
P1# = FNS#(GP#)
P2# = FNS#(GP# + 1)
GP# = GP# + 2
X1# = GX2# + (P1# / 10) * SZ#
Y1# = TXY# + MOD(P1#, 10) * SZ#
X2# = GX2# + (P2# / 10) * SZ#
Y2# = TXY# + MOD(P2#, 10) * SZ#
DRAW TXC#, X1#, Y1# TO X2#, Y2#
GK# = GK# + 1
LOOP UNTIL GK# >= N#
RETURN
DATA 3, 9, 8, 6, 4, 5
REM S
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
REM C
DATA 3, 0,30, 0,6, 6,36
REM O
DATA 4, 0,30, 6,36, 0,6, 30,36
REM E
DATA 4, 0,6, 0,30, 3,33, 6,36
REM R
DATA 5, 0,6, 0,30, 3,33, 30,33, 13,36
REM L
DATA 2, 0,6, 6,36
REM I
DATA 1, 10,16
REM V
DATA 2, 0,16, 16,30
REM space
DATA 0
REM 0
DATA 5, 0,30, 6,36, 0,6, 30,36, 30,6
REM 1
DATA 2, 10,16, 1,10
REM 2
DATA 5, 0,30, 30,33, 3,33, 3,6, 6,36
REM 3
DATA 4, 0,30, 30,36, 3,33, 6,36
REM 5
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
```
![](images/breakout-game-artwork.png)
`DRAWTEXT` and `DRAWGLYPH` are `DO ... LOOP UNTIL` rather than `FOR`, which is Step 7's
rule earning its keep twice on one screen: `"3"` is one character long and the `I` glyph is
one stroke, and a `FOR` would have drawn neither.
## Where to go next
- **[Chapter 8](08-sprites.md)** is the sprite reference: every form of `SPRSAV`, the
`MOVSPR` forms, collision and what `RSPPOS` reads back.
- **[Chapter 6](06-graphics.md)** is the drawing reference, including `SSHAPE`, `GSHAPE`
and what a shape handle is.
- **[Chapter 7](07-sound.md)** is `SOUND`, `PLAY`, `ENVELOPE` and `VOL`.
- **[Chapter 3](03-the-language.md)** has the arithmetic rules from Step 9 in full.
- **[Chapter 14](14-architecture.md)** explains the step loop and the pools this chapter
keeps running into, from the interpreter's side.