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
1358 lines
38 KiB
QBasic
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
|