Files
akbasic/examples/breakout/sprites/breakout.bas
Andrew Kesterson a01554d304 Teach the artwork tutorial the converted listing
Chapter 18 still taught the machinery the listing lost when it moved onto
SOLID and the persistent drawing layer: the frame-boundary PACE routine, the
flattened live list, the SSHAPE pool accounting, and the sixty lines of
BALLBRICKS/TESTCELL that computed a minimum translation axis by hand.

Step 8 now registers each brick with SOLID, arms COLLISION 2 and reads the
contact back with RCOLLISION, including the point that costs an evening if it
is missed: the BUMP mask is by sprite, and the balls are sprites 5, 6 and 7,
so their bits are 16, 32 and 64. The FOR-with-equal-bounds rule that every
one-item case in the chapter depends on is now stated where it is first used
rather than referred to from a step that no longer exists.

The listing loses six declarations orphaned by the conversion and the comment
blocks that still described the captures.

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

1344 lines
37 KiB
QBasic

REM =====================================================================
REM BREAKOUT, for the AKGL BASIC interpreter, using sprite artwork.
REM
REM Run it with the SDL build: build-akgl/basic breakout-sprites.bas
REM
REM Four things about this interpreter shape everything below, and they are
REM worth knowing before the first line stops making sense.
REM
REM 1. The text layer repaints every row it owns, opaque, after the
REM program's steps have run -- and by default it owns the whole window,
REM so anything DRAW or BOX put on the screen would be wiped before it
REM was ever seen. WINDOW moves it to two rows at the bottom, which is
REM the first executable line of this program, and everything below
REM then draws into a layer the frame composites and *keeps*.
REM
REM Nothing here is captured into a sprite. It used to be: sprites 1
REM and 2 held an SSHAPE of the HUD strip and of the whole play field,
REM because a sprite was the one thing drawn from state the interpreter
REM kept and a drawing lasted exactly one frame. Both slots are free
REM now, and the field is drawn once per level rather than rebuilt.
REM
REM 2. Nothing has a drawing deadline. The host runs 256 source lines and
REM then presents, and a present used to throw the drawing buffer away
REM -- so a capture spanning that boundary came back half empty, and a
REM PACE routine spun on TI# to start each frame's drawing at the top
REM of a batch. With nothing captured, a drawing that spans two batches
REM simply arrives over two frames and the layer keeps both halves.
REM
REM What survives from that era is worth keeping anyway: the lettering
REM is turned into a list of strokes when a number changes and merely
REM replayed when it is drawn. Arithmetic away from the frame is free.
REM
REM 3. There are eight sprites and no more. Five are the artwork, one is
REM the gem, and two are spare. The bricks are still drawn rather than
REM made of art -- sixty of them would not fit in eight slots however
REM many are free -- but they are *collidable* without being sprites,
REM because SOLID registers a rectangle and costs no slot.
REM
REM 4. There are 128 variables and 64 labels, and this program is close
REM enough to both that a constant is spelled out rather than named
REM and several routines that would read better as subroutines are
REM BEGIN blocks instead.
REM
REM And five things in this dialect did not do what they looked like they
REM did, and each one cost an evening. THREE OF THEM HAVE SINCE BEEN
REM FIXED, and are marked below; the shape of this listing is still
REM theirs, which is why they are still written down:
REM
REM * **The left operand decides whether an expression is done in
REM integers or in floats.** 0 - V% throws V%'s fraction away and
REM 0.0 - V% does not; LEVEL# * 0.45 is zero and 0.45 * LEVEL# is
REM not. This is the dangerous one, because nothing fails -- the
REM program simply computes something else. It silently quantised
REM every bounce in this game to whole pixels and silently cancelled
REM the per-level speed increase. Put the float first, and put the
REM result somewhere with a % on it.
REM * A FOR whose bounds are equal does not run its body at all, so
REM every loop over a list has the one-item case written out beside
REM it.
REM * FIXED. A FOR inside a BEGIN block that was SKIPPED did not
REM survive the RETURN of the routine holding it; the interpreter
REM lost the GOSUB and reported "RETURN outside the context of
REM GOSUB". Loops in this program are guarded by GOTO rather than
REM wrapped in a block, and are left that way because it costs
REM nothing. TODO.md section 9 item 2.
REM * FIXED. SSHAPE and GSHAPE ignored the subscript on a string array,
REM so every element resolved to element zero. The six brick stamps
REM are six separate scalars for that reason; an array works now.
REM TODO.md section 9 item 6.
REM * READ walks one cursor through every DATA item in the file in the
REM order they were written, no matter which routine is reading.
REM
REM Artwork: Kenney's Puzzle Pack 1, CC0. See art/PROVENANCE.md.
REM Written by Claude Opus (1M)
REM =====================================================================
REM --------------------------------------------------------------- state
REM 0 title, 1 waiting to serve, 2 playing, 3 ball lost, 4 level cleared,
REM 5 game over, 6 paused.
STATE# = 0
RUNNING# = 1
SCORE# = 0
LIVES# = 3
LEVEL# = 1
HISCORE# = 0
STIMER# = 0
LASTT# = 0
KEYV# = 0
SNDON# = 1
REM ------------------------------------------------------------- geometry
REM WALLL and WALLR are the inside faces of the side walls; BRKX and BRKY
REM are the top left corner of the brick field.
WALLL# = 12
WALLR# = 788
BRKX# = 42
BRKY# = 108
REM The rest of the layout is written out where it is used rather than
REM named. A brick is 68 by 16 on a 72 by 24 pitch, ten columns by six
REM rows; the ceiling is at 72 and a ball below 596 is gone. This program
REM sits a handful of variables under the interpreter's ceiling of 128 and
REM a name costs one whether it holds a constant or not.
REM ---------------------------------------------------------------- paddle
PDX% = 348
PDY# = 540
PDW# = 104
PDDIR# = 0
PDCO# = 0
PDEXP# = 0
CTON# = 0
CTX% = 348
CTTM# = 0
REM ----------------------------------------------------------------- balls
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
REM What the balls on screen are actually travelling at, which is only the
REM same as SPD% between a change and the RESCALE that answers it. RAT% is
REM the ratio between the two, and it is a float because an integer one
REM would hold the 0.75 of a slowdown as 0 and stop the ball dead.
BSPD% = 6.0
RAT% = 1.0
SLOWT# = 0
STKON# = 0
STKT# = 0
REM ------------------------------------------------------------------ gem
GMON# = 0
GMTYP# = 0
GMX% = 0
GMY% = 0
REM --------------------------------------------------------------- bricks
DIM BRK#(60)
DIM BRC#(6)
DIM BRV#(6)
BRN# = 0
REM ------------------------------------------------------- shapes and dirt
REM Eight of the sixteen SSHAPE slots, spent once in DRAWPROTOS and never
REM again: six brick stamps and the two erasers. Nothing captures per frame
REM any more, so the pool never runs dry and GRAPHIC 5 is never called.
REM One scalar per row colour, and scalars rather than an array because
REM SSHAPE and GSHAPE used to read the leaf they were handed without
REM applying its subscript -- a string array element always resolved to
REM element zero, and every brick came out the same colour. That is fixed
REM and an array would work now; six scalars is what this listing has.
S0$ = ""
S1$ = ""
S2$ = ""
S3$ = ""
S4$ = ""
S5$ = ""
Z$ = ""
DHUD# = 1
DPLAY# = 1
BAN$ = ""
BANT# = 0
REM ------------------------------------------------------------- the font
ALPHA$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :-.!"
DIM FNS#(280)
DIM FNI#(41)
DIM FNC#(41)
REM Strokes waiting to be drawn: H is the HUD strip, P the field. Each
REM stroke carries the colour source it wants, so one pass over the list
REM draws a line in as many colours as it likes.
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
REM ------------------------------------------------- the text being built
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$ = ""
REM ------------------------------------------------------ the paddle bounce
REM Eight landing zones across the bar, each with a velocity of very
REM nearly the same length, so the ball changes direction without the
REM program ever needing a square root -- there is no SQR in this dialect.
DIM ZVX%(8)
DIM ZVY%(8)
REM --------------------------------------------------------------- scratch
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
RR1# = 0
RR2# = 0
HIT# = 0
SEED# = 1
RNMAX# = 2
RNVAL# = 0
REM The collision handler answers through these, so they are created out here
REM like everything else: a name first seen inside a GOSUB dies at its RETURN.
MB# = 0
AX# = 0
DP% = 0.0
SLX# = 0
SLY# = 0
SLI# = 0
REM The two eraser stamps. Declared here for exactly the same reason -- built
REM inside DRAWPROTOS and left undeclared, they were SHAPE:6 and SHAPE:7 inside
REM it and empty everywhere else.
BL$ = ""
HBL$ = ""
REM =====================================================================
REM Start up
REM =====================================================================
REM Tables before font, and the order is not a preference: READ walks one
REM cursor through every DATA item in the program in the order they appear
REM in the file, so whichever loader runs first gets the DATA that is
REM written first. The tables are written first.
REM The text layer repaints every row it owns, opaque, so it has to be moved
REM out of the way before anything drawn can be seen. Two rows at the bottom is
REM enough for the final score, and hands the other thirty-five to the drawing
REM verbs. Everything this game draws then simply stays there -- a drawing goes
REM into a layer the frame composites, so nothing here is captured into a sprite
REM and nothing is redrawn every frame.
WINDOW 0, 35, 49, 36
GOSUB LOADTABLES
GOSUB LOADFONT
SEED# = MOD(1 + TI# * 7919, 2147483648)
RNMAX# = 97
GOSUB NEXTRAND
SPRSAV "art/paddleBlu.png", 3
SPRSAV "art/paddleRed.png", 4
SPRSAV "art/ballBlue.png", 5
SPRSAV "art/ballGrey.png", 6
SPRSAV "art/ballGrey.png", 7
SPRSAV "art/element_red_polygon_glossy.png", 8
SPRITE 3, 0, 2
SPRITE 4, 0, 2
SPRITE 5, 0, 2
SPRITE 6, 0, 2
SPRITE 7, 0, 2
SPRITE 8, 0, 2
REM SOUND's frequency argument is a SID register value rather than hertz --
REM register * 1022730 / 16777216 is the pitch -- so the numbers below are
REM notes worked out once and written down: 17175 is C6, 12861 G5, 8579 C5,
REM 6431 G4, 4298 C4, 3609 A3, 2411 D3, 1804 A2.
REM
REM Three voices. Voice 1 is the ball hitting things, voice 2 is the gem
REM and the ball being lost, voice 3 is PLAY's fanfares -- kept apart so a
REM brick going does not cut a tune off mid-note.
VOL 8
ENVELOPE 0, 0, 6, 0, 4
TEMPO 12
COLLISION 2, BRICKHIT
REM The stamps, once. They used to be rebuilt whenever the SSHAPE pool ran dry,
REM because every frame's capture spent another slot; nothing captures now, so
REM eight slots are spent here and never again.
GOSUB DRAWPROTOS
GOSUB TITLESCREEN
REM =====================================================================
REM The frame loop
REM
REM At most one drawing job, then the game, which may take as long as it
REM likes. Nothing here has a deadline: a drawing that spans two host
REM batches simply arrives over two frames and the layer keeps both halves.
REM =====================================================================
LABEL FRAME
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
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
REM =====================================================================
REM One drawing job per frame, and only one
REM
REM The field is the expensive one -- sixty stamps and a border -- so it
REM never shares a frame with the HUD. Both are flagged rather than drawn
REM where the change happens, so a routine that changes three things costs
REM one drawing.
REM =====================================================================
REM Labels rather than BEGIN blocks here. When this was written it was not
REM a style choice -- a loop inside a block that was skipped left the
REM interpreter with no GOSUB to return from and stopped the program. That
REM is fixed; the shape is kept because it reads the same either way.
LABEL DRAWJOB
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
REM ---------------------------------------------------------------------
REM Six brick stamps, one per row colour. Filled two rules at a time so
REM the set costs about a hundred and thirty lines rather than the two
REM hundred and seventy a rule-at-a-time loop would take. PAINT would be
REM one statement instead of sixteen and is not an option: it costs nearly
REM four milliseconds a call, which is an eighth of a frame.
LABEL DRAWPROTOS
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#
REM A seventh stamp, in the background colour, is how a broken brick is taken
REM off the screen. There is no filled-rectangle verb and PAINT costs four
REM milliseconds a call, so erasing is a GSHAPE of something blank -- one line
REM instead of sixteen, and it reuses machinery that is already here.
COLOR 1, 1
FOR K# = 0 TO 15
DRAW 1, 0, 130 + K# TO 67, 130 + K#
NEXT K#
REM And an eighth the width of the HUD strip, for the same reason: the strip is
REM rewritten whenever a number in it changes, and the old digits have to go
REM somewhere first.
FOR K# = 0 TO 59
DRAW 1, 0, 160 + K# TO 799, 160 + K#
NEXT K#
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 Z$, 0, 130, 68, 146 : BL$ = Z$
SSHAPE Z$, 0, 160, 800, 220 : HBL$ = Z$
GRAPHIC 1, 1
DPLAY# = 1
DHUD# = 1
RETURN
REM Take one brick off the screen: stamp the blank over it. Called when a brick
REM breaks, so the field is never redrawn as a whole during play -- which is
REM what lets the whole live-list machinery go.
LABEL ERASEBRICK
Z$ = BL$
GSHAPE Z$, BRKX# + C# * 72, BRKY# + R# * 24
RETURN
REM ---------------------------------------------------------------------
REM The field: walls, whatever bricks are still standing, and whatever
REM lettering belongs on the field. Nothing here computes anything it
REM could have computed on an earlier frame.
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
REM Every brick still standing, stamped where it belongs. This runs when a
REM level is laid out and not again -- a broken brick is erased in place by
REM ERASEBRICK, so there is no reason to redraw sixty of them to remove one.
REM That is what took away the flattened live list, its four arrays, and the
REM run-per-row loop that walked it.
FOR R# = 0 TO 5
IF R# = 0 THEN Z$ = S0$
IF R# = 1 THEN Z$ = S1$
IF R# = 2 THEN Z$ = S2$
IF R# = 3 THEN Z$ = S3$
IF R# = 4 THEN Z$ = S4$
IF R# = 5 THEN Z$ = S5$
FOR C# = 0 TO 9
IF BRK#(R# * 10 + C#) > 0 THEN GSHAPE Z$, BRKX# + C# * 72, BRKY# + R# * 24
NEXT C#
NEXT R#
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
RETURN
REM ---------------------------------------------------------------------
REM The HUD strip. Same shape as DRAWPLAY: one pass over a stroke list
REM that was built on some earlier frame, when there was time.
LABEL DRAWHUD
GRAPHIC 1, 0
WIDTH 1
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
REM The old strip goes first. Nothing here clears the screen -- a drawing
REM stays, which is the whole point -- so the digits that were there have to be
REM stamped over before the new ones are drawn.
Z$ = HBL$
GSHAPE Z$, 0, 0
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
RETURN
REM =====================================================================
REM Input
REM
REM GET takes one keystroke per call from a ring the host fills, so the
REM loop empties it every frame. A held arrow arrives as about twenty six
REM repeats a second, which is nearly one a frame -- but the first repeat
REM is a quarter of a second behind the press, and a paddle that stalled
REM for a quarter of a second would be unusable. So a press buys seven
REM frames of travel (PDCO#) and a held key keeps renewing it.
REM =====================================================================
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
REM VOL rather than a test around every SOUND: one line here beats eleven
REM scattered through the game, and a silenced voice costs nothing to issue.
LABEL PRESSMUTE
SNDON# = 1 - SNDON#
IF SNDON# = 1 THEN VOL 8
IF SNDON# = 0 THEN VOL 0
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
REM =====================================================================
REM The states
REM =====================================================================
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
REM =====================================================================
REM Paddle
REM =====================================================================
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
REM The catcher mirrors the player, so it guards the side the paddle left.
CTX% = 0.0 - PDX% + WALLL# + WALLR# - 104
MOVSPR 4, CTX%, 470
RETURN
REM =====================================================================
REM Balls
REM =====================================================================
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 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
REM ---------------------------------------------------------------------
REM Brick collision, all of it.
REM
REM COLLISION 2 fires between source lines when a ball overlaps one of the
REM rectangles SOLID registered, and RCOLLISION says which and which way out.
REM That replaced a bounding-box narrowing that turned the ball's box into a
REM range of candidate cells, a nested DO walking them, and a cell test that
REM computed an overlap rectangle and compared its width to its height to pick
REM an axis -- about sixty lines.
REM
REM The handler cannot know which ball hit, so it asks each live one. Three is
REM not a loop worth writing.
REM The mask is by *sprite*, and the balls are sprites 5, 6 and 7 -- so their
REM bits are 16, 32 and 64, not 1, 2 and 4. Ball B# is sprite 5 + B#, which is
REM the same arithmetic MOVSPR and RCOLLISION use for them everywhere else.
LABEL BRICKHIT
MB# = BUMP(2)
IF MB# = 0 THEN RETURN
B# = 0
IF (MB# AND 16) <> 0 THEN GOSUB ONEBRICK
B# = 1
IF (MB# AND 32) <> 0 THEN GOSUB ONEBRICK
B# = 2
IF (MB# AND 64) <> 0 THEN GOSUB ONEBRICK
RETURN
REM One ball against whatever it hit. B# is which ball; sprite 5 + B# is its
REM slot, and RCOLLISION is keyed on the sprite number.
LABEL ONEBRICK
IF BLON#(B#) = 0 THEN RETURN
N# = RCOLLISION(5 + B#, 1)
IF N# < 1 THEN RETURN
N# = N# - 1
IF BRK#(N#) = 0 THEN RETURN
DP% = RCOLLISION(5 + B#, 4)
BLX%(B#) = BLX%(B#) + (RCOLLISION(5 + B#, 2) * DP%)
BLY%(B#) = BLY%(B#) + (RCOLLISION(5 + B#, 3) * DP%)
AX# = RCOLLISION(5 + B#, 7)
IF AX# = 1 THEN BLVX%(B#) = 0.0 - BLVX%(B#)
IF AX# = 2 THEN BLVY%(B#) = 0.0 - BLVY%(B#)
R# = N# / 10
C# = MOD(N#, 10)
BRK#(N#) = 0
SOLID N# + 1
BRN# = BRN# - 1
SCORE# = SCORE# + BRV#(R#)
REM The top row rings at C6 and each row down drops about a third, so the
REM wall plays itself down a scale as it comes apart.
SOUND 1, 17175 - R# * 2100, 4
BX1# = BRKX# + C# * 72
BY1# = BRKY# + R# * 24
GOSUB ERASEBRICK
GOSUB HUDTEXT
IF GMON# = 1 THEN RETURN
RNMAX# = 7
GOSUB NEXTRAND
IF RNVAL# = 0 THEN GOSUB SPAWNGEM
RETURN
REM ---------------------------------------------------------------------
REM The bars. T1# is the bar's left edge, T2# its top, T3# its width.
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
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
IF STKON# = 0 THEN RETURN
BLST#(B#) = 1
BLOFF# = BLX%(B#) - PDX%
RETURN
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
REM =====================================================================
REM The gem, and what catching one does
REM =====================================================================
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
SOUND 2, 10810, 3
SPRITE 8, 1, 2
MOVSPR 8, GMX%, GMY%
RETURN
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
REM The x-expand bit is the only scaling a sprite has, and doubling the
REM artwork is exactly what EXPAND wants.
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
REM Two more balls, thrown off the first one at slightly different angles
REM so they do not travel as a stack.
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
REM Bring every ball in play to the current speed without changing where it
REM is heading.
REM
REM Every velocity this program assigns is one of the eight unit vectors in
REM ZVX%/ZVY# times SPD%, and a bounce only ever flips a sign -- so a ball
REM is travelling at exactly the SPD% that was in force when it last left a
REM bar. BSPD% remembers what that was, which makes the rescale a ratio of
REM two speeds rather than a change of magnitude, and no square root is
REM needed. There isn't one in this dialect.
REM
REM Scaling by the vertical component instead, which is what this routine
REM did first, is not a slowdown at all: a shallow ball's small vertical
REM gets stretched up to the new speed and drags the large horizontal with
REM it. SLOW made the ball faster. Sixty degrees of the eight zones are
REM shallow enough to do it.
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
REM =====================================================================
REM Timers
REM =====================================================================
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
REM =====================================================================
REM Losing a ball, clearing a level, starting and ending a game
REM =====================================================================
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 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#
SOLID
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
LABEL LEVELDONE
PLAY "V3 T2 U8 O4 QC QE QG O5 HC"
STATE# = 4
STIMER# = 60
GMTYP# = 0
BAN$ = "CLEAR"
GOSUB SETBANNER
RETURN
LABEL TITLESCREEN
STATE# = 0
STIMER# = 0
GOSUB CLEARPOWERS
BLN# = 0
BRN# = 0
FOR I# = 0 TO 59
BRK#(I#) = 0
NEXT I#
SOLID
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
LABEL STARTGAME
PLAY "V3 T2 U8 O4 IC IE IG O5 IC"
SCORE# = 0
LIVES# = 3
LEVEL# = 1
GOSUB SETUPLEVEL
RETURN
REM ---------------------------------------------------------------------
REM Lay a level out. Every third one is a row shorter, so the field
REM changes shape as well as pace.
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#
REM Retire the previous level's rectangles *before* laying this one out, not
REM after. Put after FILLROW it throws away all sixty rectangles that were
REM just registered, which costs nothing visible and simply stops the ball
REM hitting anything.
SOLID
FOR R# = 0 TO 5
IF R# < T3# THEN GOSUB FILLROW
NEXT R#
GOSUB RESETBALL
GOSUB HUDTEXT
GMTYP# = 0
BAN$ = ""
GOSUB SETBANNER
STATE# = 1
DPLAY# = 1
RETURN
REM Again a subroutine rather than a block, for the same reason.
REM One row of bricks: the array, and the rectangle the interpreter collides
REM against. The id is the array index plus one, so RCOLLISION hands back a
REM number that indexes BRK#() with no lookup at all.
LABEL FILLROW
FOR C# = 0 TO 9
BRK#(R# * 10 + C#) = 1
BRN# = BRN# + 1
SLX# = BRKX# + C# * 72
SLY# = BRKY# + R# * 24
SLI# = (R# * 10 + C#) + 1
SOLID SLI#, SLX#, SLY#, SLX# + 68, SLY# + 16
NEXT C#
RETURN
LABEL LEVELSPEED
SPD% = 5.6 + 0.45 * LEVEL#
IF SPD% > 11.0 THEN SPD% = 11.0
RETURN
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
REM =====================================================================
REM Text
REM
REM The HUD line, rebuilt whenever a number in it changes. Four colours:
REM labels in cyan, the score in yellow, the lives in light green and the
REM level in light red. Colour is the whole reason the HUD is drawn
REM rather than printed -- the interpreter's text layer is white, and it
REM has no verb that changes that.
REM =====================================================================
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
REM The banner over the field: whatever BAN$ says, centred, in the colour
REM of the gem that put it there.
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
REM Sources 1 to 6 are cyan, yellow, purple, light red, light grey and
REM green; the five gems are red, yellow, green, blue and purple, so the
REM banner is as close to the gem as seven colour sources allow.
TX$ = BAN$
GOSUB BUILDTEXT
RETURN
REM ---------------------------------------------------------------------
REM Turn TX$ into strokes, into the HUD list when BUILDH# is 1 and the
REM field list when it is 0. This is the expensive routine in the program,
REM about sixteen lines a character, and it is deliberately nowhere near a
REM capture: it runs when a value changes, and the drawing that replays
REM its output happens on some later frame.
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
REM T4# as six digits with leading zeros, into NUM$.
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
REM =====================================================================
REM A generator, because this dialect has no RND
REM
REM The usual constants, kept inside 2^31 so the multiply stays inside a
REM 64 bit integer, and the answer taken from the middle bits because the
REM low ones of a power-of-two modulus barely move. A GOSUB rather than a
REM DEF on purpose: a multi-line DEF that assigns to a global spends a
REM value-pool slot on every call and never gives it back, which empties
REM the pool after a few thousand throws.
REM =====================================================================
LABEL NEXTRAND
SEED# = MOD(SEED# * 1103515245 + 12345, 2147483648)
RNVAL# = MOD(SHR(SEED#, 16), RNMAX#)
RETURN
REM =====================================================================
REM Tables
REM =====================================================================
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
REM Row colours, top to bottom, and what a brick in that row is worth.
DATA 3, 9, 8, 6, 4, 5
DATA 60, 50, 40, 30, 20, 10
REM The eight landing zones. Each pair is very nearly a unit vector, so
REM the ball leaves a bar at the speed it arrived and only the angle
REM changes.
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
REM =====================================================================
REM The stroke font
REM
REM One glyph per DATA line: how many strokes, then that many pairs of
REM points. A point is coded X*10+Y on a grid four wide and seven tall,
REM so 0 is the top left corner, 30 the top right and 36 the bottom
REM right. INSTR into ALPHA$ is what turns a character into a glyph
REM number, which is why the order of these lines matters.
REM =====================================================================
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
REM Its own subroutine rather than a BEGIN block: a FOR nested inside a
REM block that is itself inside a FOR does not find its own NEXT.
LABEL READGLYPH
FOR K# = 1 TO N# * 2
READ D#
FNS#(GX#) = D#
GX# = GX# + 1
NEXT K#
RETURN
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