Files
akbasic/examples/breakout/sprites/breakout.bas
Andrew Kesterson 39d1d0c80c Stop the artwork breakout drawing its screen into two sprites
It captured the HUD strip into sprite 1 and the whole play field into sprite 2,
and its own header called them "the screen". That was never a choice: a drawing
lasted one frame, so a sprite was the only thing the interpreter would redraw for
nothing. Both slots are free now, and five things went with them.

**`WINDOW 0, 35, 49, 36` is the first executable line.** The text layer repaints
every row it owns and by default owns the whole window; moved to two rows at the
bottom, it leaves the other thirty-five to the drawing verbs, and what the
program draws stays.

**`PACE` is gone.** It spun on `TI#` until a batch boundary so an `SSHAPE`
capture would not be cut in half by a present. Nothing is captured, so a drawing
spanning two batches simply arrives over two frames and the layer keeps both
halves. The 256-line drawing deadline that shaped every draw routine in this
listing does not apply to it any more.

**The flattened live list is gone** -- `BUILDLIVE`, `BUILDROW`, `STAMPROW` and
the four arrays behind them. They existed so a full-field redraw could be one
stamp and an advance per brick, and a full-field redraw existed because removing
one brick meant rebuilding all sixty. A broken brick is now erased in place with
a blank stamp, so the field is drawn once per level and not again.

**`BALLBRICKS` and `TESTCELL` are gone** -- about sixty lines that turned the
ball's box into a range of candidate cells, walked them with a nested `DO`, and
computed an overlap rectangle to pick an axis. `SOLID` registers each brick as
the level is laid out, `COLLISION 2` fires, and `RCOLLISION` says which brick,
which way out, how far, and which axis to reverse.

**The `SSHAPE` pool accounting is gone.** `SHN#` counted slots because every
frame's capture spent another and `GRAPHIC 5` had to throw them all away
periodically; eight are now spent once and never again.

Three mistakes worth recording, because each was silent:

- **The bricks were registered and then immediately thrown away.**
  `GOSUB BUILDLIVE` sat *after* the fill loop in `SETUPLEVEL` and meant "rebuild
  the list of what is standing"; replaced in place by a bare `SOLID`, it meant
  "retire everything" and ran after all sixty had been registered. Nothing
  failed -- the ball simply passed through the wall.
- **The handler tested the wrong bits.** `BUMP` is a mask by *sprite*, and the
  balls are sprites 5, 6 and 7, so their bits are 16, 32 and 64. Testing 1, 2 and
  4 asked about the HUD and field sprites that no longer exist.
- **The eraser stamps were undeclared**, so `SSHAPE` filled them inside
  `DRAWPROTOS` and they were empty everywhere else -- exactly the scope trap
  chapter 17 Step 3 is about, found by printing the handle inside the routine and
  again outside it.

Verified by driving it: a copy with a tracking paddle and auto-relaunch breaks
thirteen bricks in sixty seconds with no error line, and the shipped listing runs
clean. Both suites green.

Chapter 18 still describes the architecture this commit removed; that is the rest
of section 6 item 39.

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

1358 lines
38 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 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.
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 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 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 =====================================================================
REM No pacing. PACE spun on TI# until a batch boundary so that an SSHAPE
REM capture would not be cut in half by a present; nothing is captured now, so
REM a drawing that spans two batches simply arrives over two frames and the
REM layer keeps both halves.
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 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.
REM The stamps are built once now rather than counted and rebuilt. Nothing
REM captures per frame any more, so the sixteen SSHAPE slots are spent once and
REM stay spent -- eight of them -- and GRAPHIC 5 is never called.
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. This is where GOSUB BUILDLIVE used to sit, and it meant something
REM different: rebuild the list of what is standing. Left in place, the clear
REM ran after FILLROW had registered all sixty and threw them all away again --
REM which cost nothing visible and simply stopped the ball 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