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
REM 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
REM 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
REM goes
REM into a layer the frame composites, so nothing here is captured into a
REM 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
REM 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
REM 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
REM 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
REM 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 GOTO PRESSPAUSE2
STATE# = 6 : GMTYP# = 0 : BAN$ = "PAUSED" : GOSUB SETBANNER
RETURN
LABEL PRESSPAUSE2
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
