Files
akbasic/examples/breakout/sprites/breakout.bas
Andrew Kesterson 0679c3042b
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m21s
akbasic CI Build / sanitizers (push) Failing after 4m30s
akbasic CI Build / coverage (push) Failing after 3m40s
akbasic CI Build / akgl_build (push) Failing after 21s
akbasic CI Build / mutation_test (push) Failing after 3m27s
Stop the listings and their chapters stating fixed defects as fact
You were right that this was still everywhere. The two `.bas` files carried
their workarounds as present-tense statements about the interpreter -- "a name
first created inside a GOSUB costs a value slot that is never handed back",
"writing PAST the terminator of a short row draws nothing at all", "SSHAPE and
GSHAPE ignore the subscript on a string array", "a FOR inside a BEGIN block does
not survive the RETURN" -- and every one of those is now false. A comment that
lies is worse than no comment, and these were the first thing anybody opening
the files would read.

**The characters game takes the geometry fix.** `CW# = 16` becomes `CW# =
RGR(3)`, and the grid comes from `RWINDOW`, so the game fits whatever window and
font the host gives it. That was the one thing in the listing that would break on
a different font, and `RGR(3)` and `RWINDOW` were added to the interpreter
because of it -- leaving the constant in place while Chapter 17 teaches the
function was the inconsistency worth closing. `RGR(1)` still comes first so a
build with no graphics device refuses by naming the device that is missing.

Everything else in both listings keeps its shape and says why. Declaring every
name up front, guarding loops with `GOTO`, six scalars for six brick stamps: none
is forced any more and none costs anything, so they stay, with the comments
marking which rules stand and which are history. The sprites header's five traps
are marked FIXED where they are fixed.

The chapters follow: Chapter 17 Step 2 no longer says "the listing still has
`CW# = 16`", and Chapter 18's trap section is "five things that did not do what
they looked like" with the two that still stand named up front rather than left
to be counted.

Verified: Chapter 17 Step 2's block is once again verbatim from the listing, so
every quoted fragment in both chapters matches its source with no exceptions;
both games run 40 seconds on the SDL frontend with no error line; both suites
green in both configurations; `docs_examples` and `docs_screenshots --check`
pass.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:32:48 -04:00

1375 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 Five things about this interpreter shape everything below, and they
REM are worth knowing before the first line stops making sense.
REM
REM 1. The text layer repaints every row of the window opaque black after
REM the program's steps have run, so by default anything DRAW or BOX
REM put on the screen is wiped before it is ever presented. Sprites are
REM drawn after the text layer, so a sprite is the one thing that is
REM seen for nothing. Everything drawn here is therefore captured with
REM SSHAPE and installed as a sprite with SPRSAV -- that is what
REM sprites 1 and 2 are. They are the screen.
REM
REM (WINDOW can shrink the text area and hand the rest of the window to
REM the drawing verbs; it could not be reached from this build when
REM this was written. It would not help here anyway: a drawing has to
REM be re-issued every frame and fit inside one batch, and a sprite has
REM neither constraint.)
REM
REM 2. The host runs 256 source lines and then presents the frame, and a
REM present throws the drawing buffer away. So all the drawing between
REM a GRAPHIC 1, 1 and its SSHAPE has to fit inside one of those
REM batches or the capture comes back half empty. Waiting for TI# to
REM tick lands us on a batch boundary -- see PACE -- and every draw
REM routine below is written to stay under about two hundred lines.
REM Arithmetic is free; only drawing has a deadline. That is why the
REM lettering is turned into a list of strokes when a number changes
REM and merely replayed when it is time to draw.
REM
REM 3. There are eight sprites and no more. Two are the screen, five are
REM the artwork, one is the gem. That is the whole budget, and it is
REM why the bricks are drawn rather than made of art.
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 5. 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 LX#(60)
DIM LY#(60)
DIM RS#(6)
DIM RC#(6)
DIM BRC#(6)
DIM BRV#(6)
LN# = 0
BRN# = 0
REM ------------------------------------------------------- shapes and dirt
REM SHN# counts the SSHAPE slots spent. There are sixteen, nothing gives
REM one back, and GRAPHIC 5 gives back all of them at once -- so the brick
REM stamps are thrown away and rebuilt every time the pool runs dry.
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$ = ""
REM 99 means the stamps are gone and have to be rebuilt before anything
REM can be drawn with them.
SHN# = 99
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
LFT# = 0
TOP# = 0
RGT# = 0
BOT# = 0
CC1# = 0
CC2# = 0
RR1# = 0
RR2# = 0
HIT# = 0
SEED# = 1
RNMAX# = 2
RNVAL# = 0
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.
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
GOSUB TITLESCREEN
REM =====================================================================
REM The frame loop
REM
REM PACE first, because it is what puts the drawing at the top of a host
REM batch; then at most one capture, because a capture is the only thing
REM with a deadline; then the game, which may take as long as it likes.
REM =====================================================================
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
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 Pacing, and the batch boundary it buys
REM
REM TI# is refreshed from the host's clock once per batch, so the step on
REM which it changes is the first step of a batch. Spinning here until it
REM moves is the only way a program in this dialect can find out where a
REM frame boundary is, and a capture that starts anywhere else risks being
REM cut in half by a present. Two jiffies is thirty frames a second.
REM
REM **LASTT# is sampled here rather than carried over from the last
REM frame**, and that is the whole correctness of it. Carried over, a
REM frame whose work ran long would find two jiffies already gone and
REM return on whatever step it happened to be on -- somewhere in the
REM middle of a batch -- and the capture that followed would come back
REM holding half of the frame before it. Sampling here means the loop
REM always sees TI# change under it, and a change is only ever seen on
REM the first step of a batch.
REM =====================================================================
LABEL PACE
LASTT# = TI#
LABEL PACEEDGE
IF TI# - LASTT# < 2 THEN GOTO PACEEDGE
RETURN
REM =====================================================================
REM One capture per frame, and only one
REM
REM Rebuilding the brick stamps comes first, because everything else
REM draws with them and GRAPHIC 5 has just thrown them away.
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
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
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 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
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 Six runs of stamps, one per row colour, because picking the stamp per
REM brick would cost a line per brick inside the one routine in this
REM program that has a deadline. The live list is built row by row, so a
REM row is a contiguous run of it: RS# is where the run starts and RC# how
REM long it is.
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
REM A skipped BEGIN block holding a FOR used to break the RETURN at the end
REM of the routine holding it, so this loop is guarded by a GOTO instead.
REM Fixed since; left alone because a GOTO guard costs nothing.
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
REM Stamp T2# bricks starting at T1# with the shape in Z$. The count of
REM one is spelled out because a FOR whose bounds are equal does not run
REM its body in this dialect, and one brick left in a row is exactly that.
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
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, 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
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 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
REM ---------------------------------------------------------------------
REM Brick collision. A ball can only be over four cells at once, so the
REM cells are worked out from its box rather than by walking sixty
REM bricks. DO rather than FOR throughout: a FOR whose bounds are equal
REM does not run its body in this dialect, and a ball inside one column is
REM exactly that case.
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
REM Reflect off whichever face the ball has less of itself past: the
REM standard box resolution, and the reason a ball clipping the end of a
REM row goes sideways instead of straight back down. T1# to T4# are the
REM overlapping rectangle; its width against its height is the whole test.
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#)
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
HIT# = 1
GOSUB BUILDLIVE
GOSUB HUDTEXT
DPLAY# = 1
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#
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
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#
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
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#
FOR R# = 0 TO 5
IF R# < T3# THEN GOSUB FILLROW
NEXT R#
GOSUB BUILDLIVE
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.
LABEL FILLROW
FOR C# = 0 TO 9
BRK#(R# * 10 + C#) = 1
BRN# = BRN# + 1
NEXT C#
RETURN
LABEL LEVELSPEED
SPD% = 5.6 + 0.45 * LEVEL#
IF SPD% > 11.0 THEN SPD% = 11.0
RETURN
REM Flatten the brick grid into the list DRAWPLAY walks. Doing it here
REM rather than in the draw routine is the whole trick: this costs four
REM lines a brick and has all the time in the world, the draw costs two
REM and has to finish before the host presents.
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
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