Add two Breakout examples and the tutorials that build them
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m10s
akbasic CI Build / sanitizers (push) Failing after 4m5s
akbasic CI Build / coverage (push) Failing after 3m29s
akbasic CI Build / akgl_build (push) Failing after 21s
akbasic CI Build / mutation_test (push) Failing after 3m19s

Two complete games in `examples/breakout/`, both 100% BASIC: `characters/`
draws its wall in the text grid with two `DATA` sprites for the ball and
paddle, and `sprites/` loads CC0 artwork and captures its whole screen with
`SSHAPE`/`SPRSAV`. They take opposite shapes for reasons that are entirely
this interpreter's, which is what the chapters are for.

`docs/17-tutorial-breakout.md` and `docs/18-tutorial-breakout-artwork.md`
build each one a step at a time, and end in a checklist of the rules a real
program runs into: create every name before the loop starts, write a text row
whole, loop with `GOTO` rather than `DO`, put the float on the left. Every
trap is a runnable block with its own output rather than a claim -- the value
pool dying at four thousand names, the skipped `BEGIN` block that breaks its
caller's `RETURN`, `SSHAPE` ignoring a subscript, `READ`'s single cursor.

Five figures, generated from the listings beside them by `docs_screenshots`,
and a `breakout_art` setup so the ones that load artwork load the example's
own. The character game's wall cannot be photographed -- the screenshot host
omits the text layer on purpose -- so it is shown as compared output instead.

`docs/07-sound.md` never said `SOUND`'s frequency is a SID register value
rather than hertz, which both games depend on. It says so now, with the
conversion from `src/audio_tables.c:84`.

`TODO.md` gains the thirteen defects the two games turned up -- §6 items 30
to 33 and all of §9 -- each with a reduction that fits on a screen, the file
and line of the cause, and what a fix would touch.

Verified: `docs_examples` passes in both build configurations,
`docs_screenshots --check` re-renders all thirteen figures and byte-compares
them, the full 109-test suite passes in both builds, every quoted fragment
was checked to appear verbatim in the listing it came from, and every
relative link and anchor resolves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-01 22:54:42 -04:00
parent 8a50d07eef
commit cb0e2d0800
28 changed files with 4424 additions and 2 deletions

View File

@@ -0,0 +1,843 @@
REM ####################################################################
REM BREAKOUT
REM
REM One hundred percent BASIC, for the AKGL BASIC interpreter.
REM ../akbasic/build-akgl/basic breakout.bas
REM
REM The ball and the paddle are sprites, defined from DATA in this listing.
REM The bricks, the HUD and the messages are characters in the text grid.
REM Nothing here loads an asset and nothing here uses DRAW, BOX or CIRCLE:
REM the drawing verbs do not survive a frame in this interpreter, while the
REM text grid and the sprites are both redrawn from persistent state every
REM frame. See README.md for the whole list of things that shaped this.
REM
REM Three rules this listing never breaks, each one earned the hard way:
REM * Every name is created once, in the pool block below -- variables,
REM loop counters, all of it. A name first created inside a GOSUB or a
REM FOR costs a value slot that is never handed back, and there are 4096
REM of them for the whole run. A game that creates one name per tick is
REM dead in half a minute. This one creates none after startup.
REM * A row of text is written whole, from column 0. CHAR terminates the
REM row where it stops, so writing at column N erases N+1 onwards -- and
REM writing PAST the terminator of a short row draws nothing at all.
REM * Mixed + and - are parenthesised. This parser reads a - b + c as
REM a - (b + c), and matches only ONE unparenthesised AND or OR.
REM
REM Written by Claude Opus (1M)
REM ####################################################################
LABEL SETUP
SCNCLR
REM --- geometry -------------------------------------------------------
REM The cell is 16x16 and the grid is 50x37, measured with the bundled
REM C64_Pro_Mono at 16 points in the 800x600 window. BASIC cannot ask for
REM the cell size -- WINDOW would tell us, but the standalone frontend's
REM tee sink never offers it -- so these two are constants. Change them
REM together with the font or the window and everything else follows.
CW# = 16
CH# = 16
SCW# = RGR(1)
SCH# = RGR(2)
COLS# = SCW# / CW#
ROWS# = SCH# / CH#
REM --- the brick wall, in cells ---------------------------------------
BRW# = 4
BCOLS# = 10
BROWS# = 6
BTOP# = 4
BLEFT# = (COLS# - (BRW# * BCOLS#)) / 2
REM --- the play area, in pixels ---------------------------------------
TOPY# = 2 * CH#
MAXX# = SCW# - 8
PW# = 144
PY# = 528
LOSEY# = PY# + 32
MSGROW# = 35
TITROW# = 20
PSPD# = 10
REM --- pools ----------------------------------------------------------
REM Every name in the program is created HERE, and this is load-bearing
REM twice over.
REM
REM A GOSUB gets its own scope. Assignment walks up the chain and finds an
REM outer variable, but a name first seen inside a subroutine is created in
REM the subroutine and dies at RETURN -- so HIT# and BI# would mean nothing
REM to the caller if they were not already out here.
REM
REM And a name that is created costs a value slot for good. The pool is a
REM bump allocator with no free (../akbasic/src/value.c:142) and recycling a
REM variable takes fresh slots, so 4096 creations ends the run whenever they
REM happen. Declared once, the whole game then runs on nothing but stores.
DIM BR#(60)
DIM SB#(63)
DIM SP#(63)
DIM LAY$(6)
DIM BSG$(6)
SCORE# = 0
HIGH# = 0
LIVES# = 3
LEVEL# = 1
LEFTN# = 0
BSPD# = 4
STATE# = 0
DEMO# = 0
PAUSED# = 0
HELD# = 0
PDIR# = 0
PDEC# = 0
PX# = 0
BX# = 0
BY# = 0
BVX# = 0
BVY# = 0
OX# = 0
OY# = 0
K# = 0
HIT# = 0
PVX# = 0
DOFF# = 0
STALL# = 0
NUDGE# = 0
BI# = 0
BR2# = 0
RB# = 0
CB# = 0
RC# = 0
CC2# = 0
TX# = 0
TY# = 0
PTS# = 0
V# = 0
L# = 0
C2# = 0
D# = 0
M# = 0
X2# = 0
X3# = 0
Z# = 0
BB# = 0
RX# = 0
N# = 0
MROW# = 0
RMAX# = 2
RND# = 0
SND# = 0
P$ = ""
H$ = ""
S$ = ""
T$ = ""
MSG$ = ""
BSEG$ = ""
FRAME$ = ""
I# = 0
R# = 0
R2# = 0
C# = 0
CC# = 0
KI# = 0
T# = 0
REM Brick faces, one per wall row. CHAR ignores its colour argument --
REM the sink draws in one colour -- so the rows are told apart by shape.
BSG$(0) = "[##]"
BSG$(1) = "[##]"
BSG$(2) = "[==]"
BSG$(3) = "[==]"
BSG$(4) = "[--]"
BSG$(5) = "[--]"
REM --- the seed -------------------------------------------------------
REM There is no RND in this dialect. TI# is jiffies off the host's clock
REM and is host uptime rather than zero-based, which makes it a fine seed.
SEED# = TI#
REM The ceiling, drawn once. TOPY# is the bottom edge of this row, so the
REM ball turns against something the player can see rather than against an
REM invisible line two rows down from the top of the window.
FRAME$ = "=" * COLS#
CHAR 1, 0, 1, FRAME$
GOSUB MKSPR
GOSUB SNDPROBE
GOTO TITLE
REM ####################################################################
REM SPRITES
REM ####################################################################
LABEL MKSPR
FOR I# = 0 TO 62
READ SB#(I#)
NEXT I#
FOR I# = 0 TO 62
READ SP#(I#)
NEXT I#
SPRSAV SB#, 1
SPRITE 1, 1, 2
SPRSAV SP#, 2
SPRSAV SP#, 3
SPRSAV SP#, 4
SPRITE 2, 1, 15, 0, 1, 0
SPRITE 3, 1, 15, 0, 1, 0
SPRITE 4, 1, 15, 0, 1, 0
RETURN
REM ####################################################################
REM SOUND
REM
REM SOUND refuses on a machine with no audio device, and an untrapped
REM refusal would end the game. Ask once, remember the answer, and never
REM ask again -- the game is silent where there is nothing to play on.
REM ####################################################################
LABEL SNDPROBE
SND# = 1
TRAP NOAUDIO
SOUND 1, 2000, 1
TRAP
RETURN
LABEL NOAUDIO
SND# = 0
RESUME NEXT
LABEL BEEPB
IF SND# = 1 THEN SOUND 1, 12000, 2
RETURN
LABEL BEEPP
IF SND# = 1 THEN SOUND 2, 6000, 3
RETURN
LABEL BEEPW
IF SND# = 1 THEN SOUND 3, 9000, 1
RETURN
LABEL BEEPL
IF SND# = 1 THEN SOUND 1, 1500, 20
RETURN
REM ####################################################################
REM TITLE AND ATTRACT MODE
REM
REM The title screen plays itself. It is also how this game gets tested
REM without a human at the keyboard.
REM ####################################################################
LABEL TITLE
DEMO# = 1
SCORE# = 0
LIVES# = 3
LEVEL# = 1
BSPD# = 4
PAUSED# = 0
GOSUB LOADLAY
GOSUB BUILDW
GOSUB DRAWW
GOSUB DRAWHUD
GOSUB TITTEXT
GOSUB SERVE
STATE# = 0
GOTO TICK
LABEL TITTEXT
MROW# = TITROW#
MSG$ = "B R E A K O U T"
GOSUB SHOWAT
MROW# = TITROW# + 2
MSG$ = "PRESS SPACE TO PLAY"
GOSUB SHOWAT
MROW# = TITROW# + 4
MSG$ = "LEFT AND RIGHT MOVE P PAUSES Q QUITS"
GOSUB SHOWAT
MROW# = TITROW# + 6
MSG$ = "ATTRACT MODE"
GOSUB SHOWAT
RETURN
LABEL CLRTIT
MROW# = TITROW#
GOSUB CLRAT
MROW# = TITROW# + 2
GOSUB CLRAT
MROW# = TITROW# + 4
GOSUB CLRAT
MROW# = TITROW# + 6
GOSUB CLRAT
RETURN
REM ####################################################################
REM GAME AND LEVEL SETUP
REM ####################################################################
LABEL NEWGAME
DEMO# = 0
SCORE# = 0
LIVES# = 3
LEVEL# = 1
BSPD# = 4
PAUSED# = 0
GOSUB CLRTIT
GOSUB NEWLEV
STATE# = 0
GOTO TICK
LABEL NEWLEV
GOSUB LOADLAY
GOSUB BUILDW
GOSUB DRAWW
GOSUB DRAWHUD
GOSUB SERVE
MSG$ = "PRESS SPACE TO LAUNCH"
GOSUB SHOWMSG
RETURN
REM Three layouts, then round again with a faster ball. RESTORE takes a
REM label -- it wants a line and a label evaluates to one -- so a layout is
REM simply a named place in the DATA stream.
LABEL LOADLAY
N# = MOD((LEVEL# - 1), 3)
IF N# = 0 THEN RESTORE LAY1
IF N# = 1 THEN RESTORE LAY2
IF N# = 2 THEN RESTORE LAY3
FOR R# = 0 TO 5
READ LAY$(R#)
NEXT R#
RETURN
LABEL BUILDW
LEFTN# = 0
FOR R# = 0 TO 5
S$ = LAY$(R#)
GOSUB BUILDR
NEXT R#
RETURN
LABEL BUILDR
FOR C# = 0 TO 9
T$ = MID(S$, C#, 1)
I# = (R# * BCOLS#) + C#
BR#(I#) = 0
IF T$ = "1" THEN BR#(I#) = 1
IF T$ = "1" THEN LEFTN# = LEFTN# + 1
NEXT C#
RETURN
LABEL DRAWW
FOR R# = 0 TO 5
BR2# = R#
GOSUB DRAWBR
NEXT R#
RETURN
REM One brick row, rebuilt whole and written in a single CHAR. Anything
REM else would truncate the row at the first character it wrote.
LABEL DRAWBR
S$ = ""
IF BLEFT# > 0 THEN S$ = " " * BLEFT#
BSEG$ = BSG$(BR2#)
FOR CC# = 0 TO 9
I# = (BR2# * BCOLS#) + CC#
IF BR#(I#) = 1 THEN S$ = S$ + BSEG$
IF BR#(I#) = 0 THEN S$ = S$ + " "
NEXT CC#
R2# = BTOP# + BR2#
CHAR 1, 0, R2#, S$
RETURN
REM Put the ball back on the paddle, pointing up, left or right at random.
LABEL SERVE
PX# = (SCW# - PW#) / 2
HELD# = 1
IF DEMO# = 1 THEN HELD# = 0
BX# = PX# + ((PW# / 2) - 4)
BY# = PY# - 10
RMAX# = 2
GOSUB RANDOM
BVX# = BSPD#
IF RND# = 0 THEN BVX# = 0 - BSPD#
BVY# = 0 - BSPD#
PDEC# = 0
GOSUB SHOWSPR
RETURN
REM A linear congruential generator, because this dialect has no RND.
REM The multiply stays inside int64 for any seed under 2^31.
LABEL RANDOM
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
RND# = MOD((SEED# / 65536), RMAX#)
RETURN
REM ####################################################################
REM THE MAIN LOOP
REM
REM A GOTO loop rather than DO/LOOP on purpose: every loop and every
REM GOSUB pushes one of 32 scopes, and a state change that jumped out of
REM a DO would leak one every time. This loop pushes nothing.
REM ####################################################################
LABEL TICK
GOSUB READKEY
IF STATE# <> 0 THEN GOTO DISPATCH
IF PAUSED# = 1 THEN GOTO TICKEND
GOSUB MOVEPAD
IF HELD# = 1 THEN GOSUB HOLDBAL
IF HELD# = 0 THEN GOSUB MOVEBAL
GOSUB SHOWSPR
LABEL TICKEND
SLEEP 0.02
GOTO TICK
LABEL DISPATCH
IF STATE# = 1 THEN GOTO LOSTLIF
IF STATE# = 2 THEN GOTO LEVELUP
IF STATE# = 3 THEN GOTO BYE
IF STATE# = 5 THEN GOTO NEWGAME
STATE# = 0
GOTO TICK
REM ####################################################################
REM INPUT
REM
REM GET is the only non-blocking read there is, and there is no key-up
REM event and no held-key state. What there IS is the host's own key
REM repeat, which arrives as ordinary key-down events about once per tick.
REM So a keystroke refills a countdown and the paddle coasts to a stop
REM when the repeats stop: held reads as held, tapped reads as a nudge.
REM ####################################################################
LABEL READKEY
FOR KI# = 1 TO 8
GET K#
IF K# <> 0 THEN GOSUB HANDKEY
NEXT KI#
RETURN
LABEL HANDKEY
IF K# = 1073741904 THEN PDIR# = 0 - 1
IF K# = 1073741904 THEN PDEC# = 12
IF K# = 1073741903 THEN PDIR# = 1
IF K# = 1073741903 THEN PDEC# = 12
IF K# = 32 THEN GOSUB KEYSPC
IF K# = 112 THEN GOSUB KEYPAU
IF K# = 113 THEN STATE# = 3
IF K# = 27 THEN STATE# = 3
RETURN
LABEL KEYSPC
IF DEMO# = 1 THEN STATE# = 5
IF DEMO# = 1 THEN RETURN
IF HELD# = 0 THEN RETURN
HELD# = 0
GOSUB CLRMSG
RETURN
LABEL KEYPAU
IF DEMO# = 1 THEN RETURN
PAUSED# = 1 - PAUSED#
IF PAUSED# = 1 THEN MSG$ = "PAUSED"
IF PAUSED# = 1 THEN GOSUB SHOWMSG
IF PAUSED# = 0 THEN GOSUB CLRMSG
RETURN
REM ####################################################################
REM THE PADDLE
REM ####################################################################
LABEL MOVEPAD
IF DEMO# = 1 THEN GOSUB DEMOPAD
IF DEMO# = 1 THEN GOTO PADCLMP
IF PDEC# < 1 THEN GOTO PADCLMP
PDEC# = PDEC# - 1
PX# = PX# + (PDIR# * PSPD#)
LABEL PADCLMP
IF PX# < 0 THEN PX# = 0
M# = SCW# - PW#
IF PX# > M# THEN PX# = M#
RETURN
LABEL DEMOPAD
TX# = (BX# + 4) - (PW# / 2)
TX# = TX# + DOFF#
D# = TX# - PX#
IF D# > PSPD# THEN D# = PSPD#
M# = 0 - PSPD#
IF D# < M# THEN D# = M#
PX# = PX# + D#
RETURN
LABEL HOLDBAL
BX# = PX# + ((PW# / 2) - 4)
BY# = PY# - 10
RETURN
REM ####################################################################
REM THE BALL
REM
REM X first and then Y, each move tested on its own, so a ball arriving
REM at the corner of a brick reverses one axis rather than both.
REM ####################################################################
LABEL MOVEBAL
OX# = BX#
BX# = BX# + BVX#
IF BX# < 0 THEN GOSUB WALLL
IF BX# > MAXX# THEN GOSUB WALLR
GOSUB XBRICK
OY# = BY#
BY# = BY# + BVY#
IF BY# < TOPY# THEN GOSUB WALLT
GOSUB YBRICK
GOSUB PADHIT
IF BY# > LOSEY# THEN STATE# = 1
STALL# = STALL# + 1
IF STALL# > 500 THEN NUDGE# = 1
RETURN
LABEL WALLL
BX# = 0
BVX# = 0 - BVX#
GOSUB BEEPW
RETURN
LABEL WALLR
BX# = MAXX#
BVX# = 0 - BVX#
GOSUB BEEPW
RETURN
LABEL WALLT
BY# = TOPY#
BVY# = 0 - BVY#
GOSUB BEEPW
RETURN
REM The leading edge, not the centre: a ball tested by its centre sinks
REM four pixels into a brick before it turns, which looks like the brick
REM was hit late and the one beside it was not hit at all.
LABEL XBRICK
TX# = BX#
IF BVX# > 0 THEN TX# = BX# + 7
TY# = BY# + 4
GOSUB HITTEST
IF HIT# = 0 THEN RETURN
BX# = OX#
BVX# = 0 - BVX#
GOSUB KILLBR
RETURN
LABEL YBRICK
TX# = BX# + 4
TY# = BY#
IF BVY# > 0 THEN TY# = BY# + 7
GOSUB HITTEST
IF HIT# = 0 THEN RETURN
BY# = OY#
BVY# = 0 - BVY#
GOSUB KILLBR
RETURN
REM Which brick, if any, holds the pixel (TX#, TY#). This is the only
REM place in the game that converts pixels into cells.
LABEL HITTEST
HIT# = 0
RC# = TY# / CH#
IF RC# < BTOP# THEN RETURN
RB# = RC# - BTOP#
IF RB# > (BROWS# - 1) THEN RETURN
CC2# = TX# / CW#
IF CC2# < BLEFT# THEN RETURN
CB# = (CC2# - BLEFT#) / BRW#
IF CB# > (BCOLS# - 1) THEN RETURN
BI# = (RB# * BCOLS#) + CB#
BR2# = RB#
IF BR#(BI#) = 1 THEN HIT# = 1
RETURN
LABEL KILLBR
BR#(BI#) = 0
LEFTN# = LEFTN# - 1
STALL# = 0
PTS# = (BROWS# - BR2#) * 10
SCORE# = SCORE# + PTS#
IF DEMO# = 0 THEN GOSUB HISCORE
GOSUB DRAWBR
GOSUB DRAWHUD
GOSUB BEEPB
IF LEFTN# < 1 THEN STATE# = 2
RETURN
REM Paddle bounce. Five zones across the face, so where you catch it
REM decides where it goes -- the whole game, really.
LABEL PADHIT
IF BVY# < 1 THEN RETURN
BB# = BY# + 8
IF BB# < PY# THEN RETURN
IF BY# > (PY# + 10) THEN RETURN
RX# = PX# + PW#
IF (BX# + 8) < PX# THEN RETURN
IF BX# > RX# THEN RETURN
BY# = PY# - 8
BVY# = 0 - BSPD#
Z# = ((BX# + 4) - PX#) / (PW# / 5)
IF Z# < 0 THEN Z# = 0
IF Z# > 4 THEN Z# = 4
PVX# = BVX#
BVX# = (Z# - 2) * 3
IF BVX# <> 0 THEN GOTO PADAIM
REM Dead centre. Keep the direction it came in on and give it a shallow
REM angle: a ball with no sideways speed at all rises and falls down one
REM column for ever, which is how attract mode used to hang itself.
BVX# = 2
IF PVX# < 0 THEN BVX# = 0 - 2
LABEL PADAIM
IF NUDGE# = 1 THEN GOSUB UNSTICK
IF DEMO# = 1 THEN GOSUB DEMOAIM
GOSUB BEEPP
RETURN
REM Ten seconds without touching a brick means the ball has found an orbit
REM that misses the wall -- straight up and straight back down the same
REM empty column, usually. Give it a different angle, but do it HERE, on a
REM bounce the player is already watching, rather than in mid-flight where
REM it would look like the ball hit something that was not there.
LABEL UNSTICK
NUDGE# = 0
STALL# = 0
RMAX# = 4
GOSUB RANDOM
BVX# = (RND# * 3) - 6
IF BVX# = 0 THEN BVX# = 3
RETURN
REM Attract mode aims off-centre by a random amount, so the machine plays
REM like something with a hand on the paddle rather than a mirror.
LABEL DEMOAIM
RMAX# = 81
GOSUB RANDOM
DOFF# = RND# - 40
RETURN
REM ####################################################################
REM DRAWING
REM ####################################################################
REM The sprite position is its top-left corner in device pixels, and the
REM paddle is three 48-wide segments laid end to end. Each coordinate is
REM worked out into a variable first: MOVSPR reads a leading sign as
REM "move by this much" rather than "move to here".
LABEL SHOWSPR
MOVSPR 1, BX#, BY#
MOVSPR 2, PX#, PY#
X2# = PX# + 48
MOVSPR 3, X2#, PY#
X3# = PX# + 96
MOVSPR 4, X3#, PY#
RETURN
LABEL DRAWHUD
V# = SCORE#
GOSUB PAD6
H$ = " SCORE " + P$
H$ = (H$ + " LIVES ") + LIVES#
H$ = (H$ + " LEVEL ") + LEVEL#
V# = HIGH#
GOSUB PAD6
H$ = (H$ + " HIGH ") + P$
CHAR 1, 0, 0, H$
RETURN
LABEL PAD6
P$ = "" + V#
DO WHILE LEN(P$) < 6
P$ = "0" + P$
LOOP
RETURN
LABEL SHOWMSG
MROW# = MSGROW#
GOSUB SHOWAT
RETURN
LABEL CLRMSG
MROW# = MSGROW#
GOSUB CLRAT
RETURN
REM Centre MSG$ on row MROW#. No padding on the right is needed: CHAR
REM terminates the row where the message ends, which erases the rest.
LABEL SHOWAT
L# = LEN(MSG$)
C2# = (COLS# - L#) / 2
S$ = MSG$
IF C2# > 0 THEN S$ = (" " * C2#) + MSG$
CHAR 1, 0, MROW#, S$
RETURN
LABEL CLRAT
CHAR 1, 0, MROW#, " "
RETURN
REM ####################################################################
REM WHAT HAPPENS WHEN THE BALL GETS PAST YOU
REM ####################################################################
LABEL LOSTLIF
GOSUB BEEPL
IF DEMO# = 1 THEN GOTO DEMOSRV
LIVES# = LIVES# - 1
GOSUB DRAWHUD
IF LIVES# < 1 THEN GOTO GAMEOVR
GOSUB SERVE
MSG$ = "BALL LOST -- PRESS SPACE"
GOSUB SHOWMSG
STATE# = 0
GOTO TICK
LABEL DEMOSRV
GOSUB SERVE
HELD# = 0
STATE# = 0
GOTO TICK
LABEL LEVELUP
MSG$ = "LEVEL CLEARED"
GOSUB SHOWMSG
SLEEP 1.5
LEVEL# = LEVEL# + 1
IF BSPD# < 7 THEN BSPD# = BSPD# + 1
GOSUB NEWLEV
IF DEMO# = 1 THEN GOSUB CLRMSG
STATE# = 0
GOTO TICK
LABEL GAMEOVR
MSG$ = "GAME OVER -- SPACE PLAYS AGAIN, Q QUITS"
GOSUB SHOWMSG
LABEL GOWAIT
GET K#
IF K# = 32 THEN GOTO NEWGAME
IF K# = 113 THEN GOTO BYE
IF K# = 27 THEN GOTO BYE
SLEEP 0.05
GOTO GOWAIT
LABEL BYE
SPRITE 1, 0
SPRITE 2, 0
SPRITE 3, 0
SPRITE 4, 0
SCNCLR
PRINT "BREAKOUT"
PRINT "FINAL SCORE " + SCORE#
PRINT "HIGH SCORE " + HIGH#
SLEEP 2
QUIT
REM The machine does not get on the scoreboard: attract mode keeps score
REM so the wall behaves, but only a player's score is ever the high one.
LABEL HISCORE
IF SCORE# > HIGH# THEN HIGH# = SCORE#
RETURN
REM ####################################################################
REM SPRITE PATTERNS
REM
REM 63 bytes each: three per row, twenty-one rows, most significant bit
REM leftmost. A clear bit is transparent, which is what lets an 8x8 ball
REM live in the corner of a 24x21 pattern with nothing drawn around it.
REM
REM byte 0 byte 1 byte 2
REM 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
REM ^ column 0 ^ column 23
REM ####################################################################
REM Ball: an 8x8 disc in the top-left corner, so its pixel rectangle is
REM the sprite position and eight pixels each way. No offset arithmetic.
REM . . # # # # . . 60
REM . # # # # # # . 126
REM # # # # # # # # 255
DATA 60, 0, 0
DATA 126, 0, 0
DATA 255, 0, 0
DATA 255, 0, 0
DATA 255, 0, 0
DATA 255, 0, 0
DATA 126, 0, 0
DATA 60, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
REM Paddle segment: the full 24 wide and 6 deep, expanded to 48 wide by
REM SPRITE's xexpand bit. Three of them make one 144 pixel paddle.
DATA 255, 255, 255
DATA 255, 255, 255
DATA 255, 255, 255
DATA 255, 255, 255
DATA 255, 255, 255
DATA 255, 255, 255
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
DATA 0, 0, 0
REM ####################################################################
REM LEVEL LAYOUTS
REM
REM Six rows of ten bricks. 1 is a brick, 0 is a hole. RESTORE picks the
REM layout by line number, so adding a fourth is a block and one IF.
REM ####################################################################
LABEL LAY1
REM Level 1: the whole wall.
DATA "1111111111"
DATA "1111111111"
DATA "1111111111"
DATA "1111111111"
DATA "1111111111"
DATA "1111111111"
LABEL LAY2
REM Level 2: a barrel, open at the corners.
DATA "0011111100"
DATA "0111111110"
DATA "1111111111"
DATA "1111111111"
DATA "0111111110"
DATA "0011111100"
LABEL LAY3
REM Level 3: gaps to shoot through.
DATA "1010101010"
DATA "0101010101"
DATA "1111001111"
DATA "1100110011"
DATA "1011111101"
DATA "0110110110"