Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m10s
akbasic CI Build / sanitizers (push) Failing after 4m5s
akbasic CI Build / coverage (push) Failing after 3m29s
akbasic CI Build / akgl_build (push) Failing after 21s
akbasic CI Build / mutation_test (push) Failing after 3m19s
Two complete games in `examples/breakout/`, both 100% BASIC: `characters/` draws its wall in the text grid with two `DATA` sprites for the ball and paddle, and `sprites/` loads CC0 artwork and captures its whole screen with `SSHAPE`/`SPRSAV`. They take opposite shapes for reasons that are entirely this interpreter's, which is what the chapters are for. `docs/17-tutorial-breakout.md` and `docs/18-tutorial-breakout-artwork.md` build each one a step at a time, and end in a checklist of the rules a real program runs into: create every name before the loop starts, write a text row whole, loop with `GOTO` rather than `DO`, put the float on the left. Every trap is a runnable block with its own output rather than a claim -- the value pool dying at four thousand names, the skipped `BEGIN` block that breaks its caller's `RETURN`, `SSHAPE` ignoring a subscript, `READ`'s single cursor. Five figures, generated from the listings beside them by `docs_screenshots`, and a `breakout_art` setup so the ones that load artwork load the example's own. The character game's wall cannot be photographed -- the screenshot host omits the text layer on purpose -- so it is shown as compared output instead. `docs/07-sound.md` never said `SOUND`'s frequency is a SID register value rather than hertz, which both games depend on. It says so now, with the conversion from `src/audio_tables.c:84`. `TODO.md` gains the thirteen defects the two games turned up -- §6 items 30 to 33 and all of §9 -- each with a reduction that fits on a screen, the file and line of the cause, and what a fix would touch. Verified: `docs_examples` passes in both build configurations, `docs_screenshots --check` re-renders all thirteen figures and byte-compares them, the full 109-test suite passes in both builds, every quoted fragment was checked to appear verbatim in the listing it came from, and every relative link and anchor resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1361 lines
36 KiB
QBasic
1361 lines
36 KiB
QBasic
REM =====================================================================
|
|
REM BREAKOUT, for the AKGL BASIC interpreter, using sprite artwork.
|
|
REM
|
|
REM Run it with the SDL build: build-akgl/basic breakout-sprites.bas
|
|
REM
|
|
REM Five things about this interpreter shape everything below, and they
|
|
REM are worth knowing before the first line stops making sense.
|
|
REM
|
|
REM 1. The text layer repaints every row of the window opaque black after
|
|
REM the program's steps have run, so anything DRAW or BOX put on the
|
|
REM screen is wiped before it is ever presented. Sprites are drawn
|
|
REM after the text layer, so a sprite is the only thing that can be
|
|
REM seen. Everything drawn here is therefore captured with SSHAPE and
|
|
REM installed as a sprite with SPRSAV -- that is what sprites 1 and 2
|
|
REM are. They are the screen.
|
|
REM
|
|
REM 2. The host runs 256 source lines and then presents the frame, and a
|
|
REM present throws the drawing buffer away. So all the drawing between
|
|
REM a GRAPHIC 1, 1 and its SSHAPE has to fit inside one of those
|
|
REM batches or the capture comes back half empty. Waiting for TI# to
|
|
REM tick lands us on a batch boundary -- see PACE -- and every draw
|
|
REM routine below is written to stay under about two hundred lines.
|
|
REM Arithmetic is free; only drawing has a deadline. That is why the
|
|
REM lettering is turned into a list of strokes when a number changes
|
|
REM and merely replayed when it is time to draw.
|
|
REM
|
|
REM 3. There are eight sprites and no more. Two are the screen, five are
|
|
REM the artwork, one is the gem. That is the whole budget, and it is
|
|
REM why the bricks are drawn rather than made of art.
|
|
REM
|
|
REM 4. There are 128 variables and 64 labels, and this program is close
|
|
REM enough to both that a constant is spelled out rather than named
|
|
REM and several routines that would read better as subroutines are
|
|
REM BEGIN blocks instead.
|
|
REM
|
|
REM 5. Five things in this dialect do not do what they look like they do,
|
|
REM and each one cost an evening. They are noted where they bite, and
|
|
REM collected here so nobody has to find them twice:
|
|
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 * A FOR inside a BEGIN block does not survive the RETURN of the
|
|
REM routine holding it; the interpreter loses the GOSUB. Loops in
|
|
REM this program are guarded by GOTO rather than wrapped in a block.
|
|
REM * SSHAPE and GSHAPE ignore the subscript on a string array, so
|
|
REM every element resolves to element zero. The six brick stamps are
|
|
REM therefore six separate scalars.
|
|
REM * READ walks one cursor through every DATA item in the file in the
|
|
REM order they were written, no matter which routine is reading.
|
|
REM
|
|
REM Artwork: Kenney's Puzzle Pack 1, CC0. See art/PROVENANCE.md.
|
|
REM Written by Claude Opus (1M)
|
|
REM =====================================================================
|
|
|
|
REM --------------------------------------------------------------- state
|
|
REM 0 title, 1 waiting to serve, 2 playing, 3 ball lost, 4 level cleared,
|
|
REM 5 game over, 6 paused.
|
|
STATE# = 0
|
|
RUNNING# = 1
|
|
SCORE# = 0
|
|
LIVES# = 3
|
|
LEVEL# = 1
|
|
HISCORE# = 0
|
|
STIMER# = 0
|
|
LASTT# = 0
|
|
KEYV# = 0
|
|
SNDON# = 1
|
|
|
|
REM ------------------------------------------------------------- geometry
|
|
REM WALLL and WALLR are the inside faces of the side walls; BRKX and BRKY
|
|
REM are the top left corner of the brick field.
|
|
WALLL# = 12
|
|
WALLR# = 788
|
|
BRKX# = 42
|
|
BRKY# = 108
|
|
REM The rest of the layout is written out where it is used rather than
|
|
REM named. A brick is 68 by 16 on a 72 by 24 pitch, ten columns by six
|
|
REM rows; the ceiling is at 72 and a ball below 596 is gone. This program
|
|
REM sits a handful of variables under the interpreter's ceiling of 128 and
|
|
REM a name costs one whether it holds a constant or not.
|
|
|
|
REM ---------------------------------------------------------------- paddle
|
|
PDX% = 348
|
|
PDY# = 540
|
|
PDW# = 104
|
|
PDDIR# = 0
|
|
PDCO# = 0
|
|
PDEXP# = 0
|
|
CTON# = 0
|
|
CTX% = 348
|
|
CTTM# = 0
|
|
|
|
REM ----------------------------------------------------------------- balls
|
|
DIM BLON#(3)
|
|
DIM BLX%(3)
|
|
DIM BLY%(3)
|
|
DIM BLVX%(3)
|
|
DIM BLVY%(3)
|
|
DIM BLST#(3)
|
|
BLN# = 0
|
|
BLOFF# = 41
|
|
SPD% = 6.0
|
|
REM What the balls on screen are actually travelling at, which is only the
|
|
REM same as SPD% between a change and the RESCALE that answers it. RAT% is
|
|
REM the ratio between the two, and it is a float because an integer one
|
|
REM would hold the 0.75 of a slowdown as 0 and stop the ball dead.
|
|
BSPD% = 6.0
|
|
RAT% = 1.0
|
|
SLOWT# = 0
|
|
STKON# = 0
|
|
STKT# = 0
|
|
|
|
REM ------------------------------------------------------------------ gem
|
|
GMON# = 0
|
|
GMTYP# = 0
|
|
GMX% = 0
|
|
GMY% = 0
|
|
|
|
REM --------------------------------------------------------------- bricks
|
|
DIM BRK#(60)
|
|
DIM LX#(60)
|
|
DIM LY#(60)
|
|
DIM RS#(6)
|
|
DIM RC#(6)
|
|
DIM BRC#(6)
|
|
DIM BRV#(6)
|
|
LN# = 0
|
|
BRN# = 0
|
|
|
|
REM ------------------------------------------------------- shapes and dirt
|
|
REM SHN# counts the SSHAPE slots spent. There are sixteen, nothing gives
|
|
REM one back, and GRAPHIC 5 gives back all of them at once -- so the brick
|
|
REM stamps are thrown away and rebuilt every time the pool runs dry.
|
|
REM One scalar per row colour, and scalars rather than an array on
|
|
REM purpose: SSHAPE and GSHAPE read the leaf they are handed without
|
|
REM applying its subscript, so a string array element always resolves to
|
|
REM element zero and every brick comes out the same colour.
|
|
S0$ = ""
|
|
S1$ = ""
|
|
S2$ = ""
|
|
S3$ = ""
|
|
S4$ = ""
|
|
S5$ = ""
|
|
Z$ = ""
|
|
REM 99 means the stamps are gone and have to be rebuilt before anything
|
|
REM can be drawn with them.
|
|
SHN# = 99
|
|
DHUD# = 1
|
|
DPLAY# = 1
|
|
BAN$ = ""
|
|
BANT# = 0
|
|
|
|
REM ------------------------------------------------------------- the font
|
|
ALPHA$ = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :-.!"
|
|
DIM FNS#(280)
|
|
DIM FNI#(41)
|
|
DIM FNC#(41)
|
|
|
|
REM Strokes waiting to be drawn: H is the HUD strip, P the field. Each
|
|
REM stroke carries the colour source it wants, so one pass over the list
|
|
REM draws a line in as many colours as it likes.
|
|
DIM HX1#(120)
|
|
DIM HY1#(120)
|
|
DIM HX2#(120)
|
|
DIM HY2#(120)
|
|
DIM HC#(120)
|
|
HN# = 0
|
|
DIM PX1#(110)
|
|
DIM PY1#(110)
|
|
DIM PX2#(110)
|
|
DIM PY2#(110)
|
|
DIM PC#(110)
|
|
PN# = 0
|
|
BUILDH# = 0
|
|
|
|
REM ------------------------------------------------- the text being built
|
|
TX$ = ""
|
|
TXX# = 0
|
|
TXY# = 0
|
|
TXS# = 3
|
|
TXC# = 1
|
|
TLEN# = 0
|
|
TXI# = 0
|
|
GC# = 0
|
|
GN# = 0
|
|
GP# = 0
|
|
GX# = 0
|
|
GK# = 0
|
|
P1# = 0
|
|
P2# = 0
|
|
NUM$ = ""
|
|
|
|
REM ------------------------------------------------------ the paddle bounce
|
|
REM Eight landing zones across the bar, each with a velocity of very
|
|
REM nearly the same length, so the ball changes direction without the
|
|
REM program ever needing a square root -- there is no SQR in this dialect.
|
|
DIM ZVX%(8)
|
|
DIM ZVY%(8)
|
|
|
|
REM --------------------------------------------------------------- scratch
|
|
I# = 0
|
|
K# = 0
|
|
N# = 0
|
|
R# = 0
|
|
C# = 0
|
|
D# = 0
|
|
B# = 0
|
|
T1# = 0
|
|
T2# = 0
|
|
T3# = 0
|
|
T4# = 0
|
|
BX1# = 0
|
|
BY1# = 0
|
|
LFT# = 0
|
|
TOP# = 0
|
|
RGT# = 0
|
|
BOT# = 0
|
|
CC1# = 0
|
|
CC2# = 0
|
|
RR1# = 0
|
|
RR2# = 0
|
|
HIT# = 0
|
|
SEED# = 1
|
|
RNMAX# = 2
|
|
RNVAL# = 0
|
|
|
|
REM =====================================================================
|
|
REM Start up
|
|
REM =====================================================================
|
|
REM Tables before font, and the order is not a preference: READ walks one
|
|
REM cursor through every DATA item in the program in the order they appear
|
|
REM in the file, so whichever loader runs first gets the DATA that is
|
|
REM written first. The tables are written first.
|
|
GOSUB LOADTABLES
|
|
GOSUB LOADFONT
|
|
SEED# = MOD(1 + TI# * 7919, 2147483648)
|
|
RNMAX# = 97
|
|
GOSUB NEXTRAND
|
|
|
|
SPRSAV "art/paddleBlu.png", 3
|
|
SPRSAV "art/paddleRed.png", 4
|
|
SPRSAV "art/ballBlue.png", 5
|
|
SPRSAV "art/ballGrey.png", 6
|
|
SPRSAV "art/ballGrey.png", 7
|
|
SPRSAV "art/element_red_polygon_glossy.png", 8
|
|
SPRITE 3, 0, 2
|
|
SPRITE 4, 0, 2
|
|
SPRITE 5, 0, 2
|
|
SPRITE 6, 0, 2
|
|
SPRITE 7, 0, 2
|
|
SPRITE 8, 0, 2
|
|
|
|
REM SOUND's frequency argument is a SID register value rather than hertz --
|
|
REM register * 1022730 / 16777216 is the pitch -- so the numbers below are
|
|
REM notes worked out once and written down: 17175 is C6, 12861 G5, 8579 C5,
|
|
REM 6431 G4, 4298 C4, 3609 A3, 2411 D3, 1804 A2.
|
|
REM
|
|
REM Three voices. Voice 1 is the ball hitting things, voice 2 is the gem
|
|
REM and the ball being lost, voice 3 is PLAY's fanfares -- kept apart so a
|
|
REM brick going does not cut a tune off mid-note.
|
|
VOL 8
|
|
ENVELOPE 0, 0, 6, 0, 4
|
|
TEMPO 12
|
|
|
|
GOSUB TITLESCREEN
|
|
|
|
REM =====================================================================
|
|
REM The frame loop
|
|
REM
|
|
REM PACE first, because it is what puts the drawing at the top of a host
|
|
REM batch; then at most one capture, because a capture is the only thing
|
|
REM with a deadline; then the game, which may take as long as it likes.
|
|
REM =====================================================================
|
|
LABEL FRAME
|
|
GOSUB PACE
|
|
GOSUB DRAWJOB
|
|
GOSUB READKEYS
|
|
IF STATE# = 0 THEN GOSUB TITLETICK
|
|
IF STATE# = 1 THEN GOSUB SERVETICK
|
|
IF STATE# = 2 THEN GOSUB PLAYTICK
|
|
IF STATE# = 3 THEN GOSUB LOSTTICK
|
|
IF STATE# = 4 THEN GOSUB CLEARTICK
|
|
IF RUNNING# = 0 THEN GOTO SHUTDOWN
|
|
GOTO FRAME
|
|
|
|
LABEL SHUTDOWN
|
|
FOR I# = 1 TO 8
|
|
SPRITE I#, 0
|
|
NEXT I#
|
|
IF SCORE# > HISCORE# THEN HISCORE# = SCORE#
|
|
PRINT "FINAL SCORE " + SCORE# + " BEST " + HISCORE#
|
|
END
|
|
|
|
REM =====================================================================
|
|
REM Pacing, and the batch boundary it buys
|
|
REM
|
|
REM TI# is refreshed from the host's clock once per batch, so the step on
|
|
REM which it changes is the first step of a batch. Spinning here until it
|
|
REM moves is the only way a program in this dialect can find out where a
|
|
REM frame boundary is, and a capture that starts anywhere else risks being
|
|
REM cut in half by a present. Two jiffies is thirty frames a second.
|
|
REM
|
|
REM **LASTT# is sampled here rather than carried over from the last
|
|
REM frame**, and that is the whole correctness of it. Carried over, a
|
|
REM frame whose work ran long would find two jiffies already gone and
|
|
REM return on whatever step it happened to be on -- somewhere in the
|
|
REM middle of a batch -- and the capture that followed would come back
|
|
REM holding half of the frame before it. Sampling here means the loop
|
|
REM always sees TI# change under it, and a change is only ever seen on
|
|
REM the first step of a batch.
|
|
REM =====================================================================
|
|
LABEL PACE
|
|
LASTT# = TI#
|
|
LABEL PACEEDGE
|
|
IF TI# - LASTT# < 2 THEN GOTO PACEEDGE
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM One capture per frame, and only one
|
|
REM
|
|
REM Rebuilding the brick stamps comes first, because everything else
|
|
REM draws with them and GRAPHIC 5 has just thrown them away.
|
|
REM =====================================================================
|
|
REM Labels rather than BEGIN blocks here, and it is not a style choice: a
|
|
REM RETURN inside a block leaves the interpreter with no GOSUB to return
|
|
REM from and stops the program.
|
|
LABEL DRAWJOB
|
|
IF SHN# < 14 THEN GOTO DRAWJOB2
|
|
GOSUB DRAWPROTOS
|
|
RETURN
|
|
LABEL DRAWJOB2
|
|
IF DPLAY# = 0 THEN GOTO DRAWJOB3
|
|
GOSUB DRAWPLAY
|
|
DPLAY# = 0
|
|
RETURN
|
|
LABEL DRAWJOB3
|
|
IF DHUD# = 0 THEN RETURN
|
|
GOSUB DRAWHUD
|
|
DHUD# = 0
|
|
RETURN
|
|
|
|
REM ---------------------------------------------------------------------
|
|
REM Six brick stamps, one per row colour. Filled two rules at a time so
|
|
REM the set costs about a hundred and thirty lines rather than the two
|
|
REM hundred and seventy a rule-at-a-time loop would take. PAINT would be
|
|
REM one statement instead of sixteen and is not an option: it costs nearly
|
|
REM four milliseconds a call, which is an eighth of a frame.
|
|
LABEL DRAWPROTOS
|
|
GRAPHIC 5
|
|
GRAPHIC 1, 1
|
|
WIDTH 1
|
|
SHN# = 99
|
|
FOR R# = 0 TO 5
|
|
COLOR 1, BRC#(R#)
|
|
T1# = R# * 20
|
|
T2# = T1# + 8
|
|
FOR K# = 0 TO 7
|
|
DRAW 1, 0, T1# + K# TO 67, T1# + K# : DRAW 1, 0, T2# + K# TO 67, T2# + K#
|
|
NEXT K#
|
|
NEXT R#
|
|
SSHAPE Z$, 0, 0, 68, 16 : S0$ = Z$
|
|
SSHAPE Z$, 0, 20, 68, 36 : S1$ = Z$
|
|
SSHAPE Z$, 0, 40, 68, 56 : S2$ = Z$
|
|
SSHAPE Z$, 0, 60, 68, 76 : S3$ = Z$
|
|
SSHAPE Z$, 0, 80, 68, 96 : S4$ = Z$
|
|
SSHAPE Z$, 0, 100, 68, 116 : S5$ = Z$
|
|
SHN# = 6
|
|
DPLAY# = 1
|
|
DHUD# = 1
|
|
RETURN
|
|
|
|
REM ---------------------------------------------------------------------
|
|
REM The field: walls, whatever bricks are still standing, and whatever
|
|
REM lettering belongs on the field. Nothing here computes anything it
|
|
REM could have computed on an earlier frame.
|
|
LABEL DRAWPLAY
|
|
GRAPHIC 1, 1
|
|
WIDTH 2
|
|
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
|
|
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
|
|
BOX 5, 2, 62, 797, 597 : BOX 1, 6, 66, 793, 593
|
|
REM Six runs of stamps, one per row colour, because picking the stamp per
|
|
REM brick would cost a line per brick inside the one routine in this
|
|
REM program that has a deadline. The live list is built row by row, so a
|
|
REM row is a contiguous run of it: RS# is where the run starts and RC# how
|
|
REM long it is.
|
|
Z$ = S0$ : T1# = RS#(0) : T2# = RC#(0) : GOSUB STAMPROW
|
|
Z$ = S1$ : T1# = RS#(1) : T2# = RC#(1) : GOSUB STAMPROW
|
|
Z$ = S2$ : T1# = RS#(2) : T2# = RC#(2) : GOSUB STAMPROW
|
|
Z$ = S3$ : T1# = RS#(3) : T2# = RC#(3) : GOSUB STAMPROW
|
|
Z$ = S4$ : T1# = RS#(4) : T2# = RC#(4) : GOSUB STAMPROW
|
|
Z$ = S5$ : T1# = RS#(5) : T2# = RC#(5) : GOSUB STAMPROW
|
|
REM A FOR inside a BEGIN block does not survive the RETURN at the end of
|
|
REM the routine that holds it, so this loop is guarded by a GOTO instead.
|
|
IF PN# = 1 THEN DRAW PC#(0), PX1#(0), PY1#(0) TO PX2#(0), PY2#(0)
|
|
IF PN# < 2 THEN GOTO PLDONE
|
|
FOR I# = 0 TO PN# - 1
|
|
DRAW PC#(I#), PX1#(I#), PY1#(I#) TO PX2#(I#), PY2#(I#)
|
|
NEXT I#
|
|
LABEL PLDONE
|
|
SSHAPE Z$, 0, 60, 800, 600
|
|
SPRSAV Z$, 2
|
|
SPRITE 2, 1, 2
|
|
MOVSPR 2, 0, 60
|
|
SHN# = SHN# + 1
|
|
RETURN
|
|
|
|
REM Stamp T2# bricks starting at T1# with the shape in Z$. The count of
|
|
REM one is spelled out because a FOR whose bounds are equal does not run
|
|
REM its body in this dialect, and one brick left in a row is exactly that.
|
|
LABEL STAMPROW
|
|
IF T2# < 1 THEN RETURN
|
|
IF T2# = 1 THEN GSHAPE Z$, LX#(T1#), LY#(T1#)
|
|
IF T2# < 2 THEN RETURN
|
|
FOR I# = T1# TO T1# + T2# - 1
|
|
GSHAPE Z$, LX#(I#), LY#(I#)
|
|
NEXT I#
|
|
RETURN
|
|
|
|
REM ---------------------------------------------------------------------
|
|
REM The HUD strip. Same shape as DRAWPLAY: one pass over a stroke list
|
|
REM that was built on some earlier frame, when there was time.
|
|
LABEL DRAWHUD
|
|
GRAPHIC 1, 1
|
|
WIDTH 1
|
|
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
|
|
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
|
|
BOX 5, 0, 56, 799, 57
|
|
IF HN# = 1 THEN DRAW HC#(0), HX1#(0), HY1#(0) TO HX2#(0), HY2#(0)
|
|
IF HN# < 2 THEN GOTO HUDONE
|
|
FOR I# = 0 TO HN# - 1
|
|
DRAW HC#(I#), HX1#(I#), HY1#(I#) TO HX2#(I#), HY2#(I#)
|
|
NEXT I#
|
|
LABEL HUDONE
|
|
SSHAPE Z$, 0, 0, 800, 60
|
|
SPRSAV Z$, 1
|
|
SPRITE 1, 1, 2
|
|
MOVSPR 1, 0, 0
|
|
SHN# = SHN# + 1
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM Input
|
|
REM
|
|
REM GET takes one keystroke per call from a ring the host fills, so the
|
|
REM loop empties it every frame. A held arrow arrives as about twenty six
|
|
REM repeats a second, which is nearly one a frame -- but the first repeat
|
|
REM is a quarter of a second behind the press, and a paddle that stalled
|
|
REM for a quarter of a second would be unusable. So a press buys seven
|
|
REM frames of travel (PDCO#) and a held key keeps renewing it.
|
|
REM =====================================================================
|
|
LABEL READKEYS
|
|
GET KEYV#
|
|
IF KEYV# = 0 THEN RETURN
|
|
IF KEYV# = 1073741904 THEN PDDIR# = 0 - 1 : PDCO# = 7
|
|
IF KEYV# = 1073741903 THEN PDDIR# = 1 : PDCO# = 7
|
|
IF KEYV# = 32 THEN GOSUB PRESSFIRE
|
|
IF KEYV# = 112 THEN GOSUB PRESSPAUSE
|
|
IF KEYV# = 115 THEN GOSUB PRESSMUTE
|
|
IF KEYV# = 113 THEN RUNNING# = 0
|
|
IF KEYV# = 27 THEN RUNNING# = 0
|
|
GOTO READKEYS
|
|
|
|
LABEL PRESSFIRE
|
|
IF STATE# = 0 THEN GOSUB STARTGAME : RETURN
|
|
IF STATE# = 5 THEN GOSUB TITLESCREEN : RETURN
|
|
IF STATE# = 1 THEN GOSUB LAUNCH : RETURN
|
|
IF STATE# = 2 THEN GOSUB UNSTICK
|
|
RETURN
|
|
|
|
REM VOL rather than a test around every SOUND: one line here beats eleven
|
|
REM scattered through the game, and a silenced voice costs nothing to issue.
|
|
LABEL PRESSMUTE
|
|
SNDON# = 1 - SNDON#
|
|
IF SNDON# = 1 THEN VOL 8
|
|
IF SNDON# = 0 THEN VOL 0
|
|
RETURN
|
|
|
|
LABEL PRESSPAUSE
|
|
IF STATE# = 2 THEN STATE# = 6 : GMTYP# = 0 : BAN$ = "PAUSED" : GOSUB SETBANNER : RETURN
|
|
IF STATE# = 6 THEN STATE# = 2 : BAN$ = "" : GOSUB SETBANNER
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM The states
|
|
REM =====================================================================
|
|
LABEL TITLETICK
|
|
STIMER# = STIMER# + 1
|
|
RETURN
|
|
|
|
LABEL SERVETICK
|
|
GOSUB MOVEPADDLE
|
|
BLX%(0) = PDX% + BLOFF#
|
|
BLY%(0) = PDY# - 23
|
|
MOVSPR 5, BLX%(0), BLY%(0)
|
|
RETURN
|
|
|
|
LABEL PLAYTICK
|
|
GOSUB MOVEPADDLE
|
|
GOSUB MOVEBALLS
|
|
GOSUB MOVEGEM
|
|
GOSUB TIMERS
|
|
IF BRN# = 0 THEN GOSUB LEVELDONE
|
|
IF BLN# = 0 AND STATE# = 2 THEN GOSUB BALLGONE
|
|
RETURN
|
|
|
|
LABEL LOSTTICK
|
|
STIMER# = STIMER# - 1
|
|
IF STIMER# > 0 THEN RETURN
|
|
IF LIVES# > 0 THEN GOSUB SERVEAGAIN
|
|
IF LIVES# < 1 THEN GOSUB GAMEISOVER
|
|
RETURN
|
|
|
|
LABEL CLEARTICK
|
|
STIMER# = STIMER# - 1
|
|
IF STIMER# > 0 THEN RETURN
|
|
LEVEL# = LEVEL# + 1
|
|
GOSUB SETUPLEVEL
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM Paddle
|
|
REM =====================================================================
|
|
LABEL MOVEPADDLE
|
|
IF PDCO# > 0 THEN BEGIN
|
|
PDCO# = PDCO# - 1
|
|
PDX% = PDX% + PDDIR# * 9
|
|
BEND
|
|
IF PDCO# < 1 THEN PDDIR# = 0
|
|
IF PDX% < WALLL# THEN PDX% = WALLL#
|
|
IF PDX% > WALLR# - PDW# THEN PDX% = WALLR# - PDW#
|
|
MOVSPR 3, PDX%, PDY#
|
|
IF CTON# = 0 THEN RETURN
|
|
REM The catcher mirrors the player, so it guards the side the paddle left.
|
|
CTX% = 0.0 - PDX% + WALLL# + WALLR# - 104
|
|
MOVSPR 4, CTX%, 470
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM Balls
|
|
REM =====================================================================
|
|
LABEL MOVEBALLS
|
|
FOR B# = 0 TO 2
|
|
IF BLON#(B#) = 1 THEN GOSUB MOVEONE
|
|
NEXT B#
|
|
RETURN
|
|
|
|
LABEL MOVEONE
|
|
IF BLST#(B#) = 1 THEN BEGIN
|
|
BLX%(B#) = PDX% + BLOFF#
|
|
BLY%(B#) = PDY# - 23
|
|
BEND
|
|
IF BLST#(B#) = 0 THEN BEGIN
|
|
BLX%(B#) = BLX%(B#) + BLVX%(B#)
|
|
BLY%(B#) = BLY%(B#) + BLVY%(B#)
|
|
GOSUB BALLWALLS
|
|
BEND
|
|
IF BLON#(B#) = 0 THEN RETURN
|
|
IF BLST#(B#) = 0 THEN BEGIN
|
|
GOSUB BALLBRICKS
|
|
GOSUB BALLPADDLE
|
|
BEND
|
|
MOVSPR 5 + B#, BLX%(B#), BLY%(B#)
|
|
RETURN
|
|
|
|
LABEL BALLWALLS
|
|
IF BLX%(B#) < WALLL# THEN BEGIN
|
|
BLX%(B#) = WALLL#
|
|
BLVX%(B#) = 0.0 - BLVX%(B#)
|
|
SOUND 1, 3609, 2
|
|
BEND
|
|
IF BLX%(B#) > WALLR# - 22 THEN BEGIN
|
|
BLX%(B#) = WALLR# - 22
|
|
BLVX%(B#) = 0.0 - BLVX%(B#)
|
|
SOUND 1, 3609, 2
|
|
BEND
|
|
IF BLY%(B#) < 72 THEN BEGIN
|
|
BLY%(B#) = 72
|
|
BLVY%(B#) = 0.0 - BLVY%(B#)
|
|
SOUND 1, 3609, 2
|
|
BEND
|
|
IF BLY%(B#) < 596 THEN RETURN
|
|
BLON#(B#) = 0
|
|
BLN# = BLN# - 1
|
|
SOUND 2, 2411, 6
|
|
SPRITE 5 + B#, 0
|
|
RETURN
|
|
|
|
REM ---------------------------------------------------------------------
|
|
REM Brick collision. A ball can only be over four cells at once, so the
|
|
REM cells are worked out from its box rather than by walking sixty
|
|
REM bricks. DO rather than FOR throughout: a FOR whose bounds are equal
|
|
REM does not run its body in this dialect, and a ball inside one column is
|
|
REM exactly that case.
|
|
LABEL BALLBRICKS
|
|
IF BRN# = 0 THEN RETURN
|
|
LFT# = BLX%(B#)
|
|
TOP# = BLY%(B#)
|
|
RGT# = LFT# + 21
|
|
BOT# = TOP# + 21
|
|
IF BOT# < BRKY# THEN RETURN
|
|
IF TOP# > BRKY# + 6 * 24 THEN RETURN
|
|
CC1# = (LFT# - BRKX#) / 72
|
|
CC2# = (RGT# - BRKX#) / 72
|
|
RR1# = (TOP# - BRKY#) / 24
|
|
RR2# = (BOT# - BRKY#) / 24
|
|
IF CC1# < 0 THEN CC1# = 0
|
|
IF CC2# > 9 THEN CC2# = 9
|
|
IF RR1# < 0 THEN RR1# = 0
|
|
IF RR2# > 5 THEN RR2# = 5
|
|
IF CC2# < CC1# THEN RETURN
|
|
IF RR2# < RR1# THEN RETURN
|
|
HIT# = 0
|
|
R# = RR1#
|
|
DO
|
|
C# = CC1#
|
|
DO
|
|
IF HIT# = 0 THEN GOSUB TESTCELL
|
|
C# = C# + 1
|
|
LOOP UNTIL C# > CC2# OR HIT# = 1
|
|
R# = R# + 1
|
|
LOOP UNTIL R# > RR2# OR HIT# = 1
|
|
RETURN
|
|
|
|
REM Reflect off whichever face the ball has less of itself past: the
|
|
REM standard box resolution, and the reason a ball clipping the end of a
|
|
REM row goes sideways instead of straight back down. T1# to T4# are the
|
|
REM overlapping rectangle; its width against its height is the whole test.
|
|
LABEL TESTCELL
|
|
N# = R# * 10 + C#
|
|
IF BRK#(N#) = 0 THEN RETURN
|
|
BX1# = BRKX# + C# * 72
|
|
BY1# = BRKY# + R# * 24
|
|
IF RGT# < BX1# THEN RETURN
|
|
IF LFT# > BX1# + 67 THEN RETURN
|
|
IF BOT# < BY1# THEN RETURN
|
|
IF TOP# > BY1# + 15 THEN RETURN
|
|
T1# = RGT# : IF BX1# + 67 < T1# THEN T1# = BX1# + 67
|
|
T2# = LFT# : IF BX1# > T2# THEN T2# = BX1#
|
|
T3# = BOT# : IF BY1# + 15 < T3# THEN T3# = BY1# + 15
|
|
T4# = TOP# : IF BY1# > T4# THEN T4# = BY1#
|
|
IF T1# - T2# < T3# - T4# THEN BLVX%(B#) = 0.0 - BLVX%(B#)
|
|
IF T1# - T2# >= T3# - T4# THEN BLVY%(B#) = 0.0 - BLVY%(B#)
|
|
BRK#(N#) = 0
|
|
BRN# = BRN# - 1
|
|
SCORE# = SCORE# + BRV#(R#)
|
|
REM The top row rings at C6 and each row down drops about a third, so the
|
|
REM wall plays itself down a scale as it comes apart.
|
|
SOUND 1, 17175 - R# * 2100, 4
|
|
HIT# = 1
|
|
GOSUB BUILDLIVE
|
|
GOSUB HUDTEXT
|
|
DPLAY# = 1
|
|
IF GMON# = 1 THEN RETURN
|
|
RNMAX# = 7
|
|
GOSUB NEXTRAND
|
|
IF RNVAL# = 0 THEN GOSUB SPAWNGEM
|
|
RETURN
|
|
|
|
REM ---------------------------------------------------------------------
|
|
REM The bars. T1# is the bar's left edge, T2# its top, T3# its width.
|
|
LABEL BALLPADDLE
|
|
IF BLVY%(B#) < 0 THEN RETURN
|
|
HIT# = 0
|
|
T1# = PDX% : T2# = PDY# : T3# = PDW#
|
|
GOSUB HITBAR
|
|
IF CTON# = 0 THEN RETURN
|
|
IF HIT# = 1 THEN RETURN
|
|
T1# = CTX% : T2# = 470 : T3# = 104
|
|
GOSUB HITBAR
|
|
RETURN
|
|
|
|
LABEL HITBAR
|
|
IF BLY%(B#) + 21 < T2# THEN RETURN
|
|
IF BLY%(B#) > T2# + 23 THEN RETURN
|
|
IF BLX%(B#) + 21 < T1# THEN RETURN
|
|
IF BLX%(B#) > T1# + T3# THEN RETURN
|
|
T4# = (BLX%(B#) + 11 - T1#) * 8 / T3#
|
|
IF T4# < 0 THEN T4# = 0
|
|
IF T4# > 7 THEN T4# = 7
|
|
BLVX%(B#) = ZVX%(T4#) * SPD%
|
|
BLVY%(B#) = ZVY%(T4#) * SPD%
|
|
BLY%(B#) = T2# - 23
|
|
SOUND 1, 4298, 5
|
|
HIT# = 1
|
|
IF STKON# = 0 THEN RETURN
|
|
BLST#(B#) = 1
|
|
BLOFF# = BLX%(B#) - PDX%
|
|
RETURN
|
|
|
|
LABEL UNSTICK
|
|
FOR B# = 0 TO 2
|
|
IF BLST#(B#) = 1 THEN BEGIN
|
|
BLST#(B#) = 0
|
|
BLVX%(B#) = ZVX%(4) * SPD%
|
|
BLVY%(B#) = ZVY%(4) * SPD%
|
|
BEND
|
|
NEXT B#
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM The gem, and what catching one does
|
|
REM =====================================================================
|
|
LABEL SPAWNGEM
|
|
RNMAX# = 5
|
|
GOSUB NEXTRAND
|
|
GMTYP# = RNVAL# + 1
|
|
GMX% = BX1# + 10
|
|
GMY% = BY1#
|
|
IF GMTYP# = 1 THEN SPRSAV "art/element_red_polygon_glossy.png", 8
|
|
IF GMTYP# = 2 THEN SPRSAV "art/element_yellow_polygon_glossy.png", 8
|
|
IF GMTYP# = 3 THEN SPRSAV "art/element_green_polygon_glossy.png", 8
|
|
IF GMTYP# = 4 THEN SPRSAV "art/element_blue_polygon_glossy.png", 8
|
|
IF GMTYP# = 5 THEN SPRSAV "art/element_purple_polygon_glossy.png", 8
|
|
GMON# = 1
|
|
SOUND 2, 10810, 3
|
|
SPRITE 8, 1, 2
|
|
MOVSPR 8, GMX%, GMY%
|
|
RETURN
|
|
|
|
LABEL MOVEGEM
|
|
IF GMON# = 0 THEN RETURN
|
|
GMY% = GMY% + 3.2
|
|
MOVSPR 8, GMX%, GMY%
|
|
HIT# = 0
|
|
IF GMY% > 596 THEN HIT# = 1
|
|
IF GMY% + 46 > PDY# AND GMY% < PDY# + 24 THEN GOSUB CATCHGEM
|
|
IF HIT# = 0 THEN RETURN
|
|
GMON# = 0
|
|
SPRITE 8, 0
|
|
RETURN
|
|
|
|
LABEL CATCHGEM
|
|
IF GMX% + 48 < PDX% THEN RETURN
|
|
IF GMX% > PDX% + PDW# THEN RETURN
|
|
GOSUB TAKEGEM
|
|
HIT# = 1
|
|
RETURN
|
|
|
|
REM The x-expand bit is the only scaling a sprite has, and doubling the
|
|
REM artwork is exactly what EXPAND wants.
|
|
LABEL TAKEGEM
|
|
SOUND 2, 8579, 14, 1, 17175, 400
|
|
SCORE# = SCORE# + 50
|
|
GOSUB HUDTEXT
|
|
IF GMTYP# = 1 THEN BEGIN
|
|
PDW# = 208
|
|
PDEXP# = 600
|
|
SPRITE 3, 1, 2, 0, 1, 0
|
|
BAN$ = "EXPAND"
|
|
BEND
|
|
IF GMTYP# = 2 THEN BEGIN
|
|
BAN$ = "MULTI"
|
|
GOSUB SPLITBALLS
|
|
BEND
|
|
IF GMTYP# = 3 THEN BEGIN
|
|
SLOWT# = 480
|
|
SPD% = 4.2
|
|
GOSUB RESCALE
|
|
BAN$ = "SLOW"
|
|
BEND
|
|
IF GMTYP# = 4 THEN BEGIN
|
|
STKON# = 1
|
|
STKT# = 540
|
|
BAN$ = "STICKY"
|
|
BEND
|
|
IF GMTYP# = 5 THEN BEGIN
|
|
CTON# = 1
|
|
CTTM# = 720
|
|
SPRITE 4, 1, 2
|
|
BAN$ = "CATCH"
|
|
BEND
|
|
BANT# = 90
|
|
GOSUB SETBANNER
|
|
RETURN
|
|
|
|
REM Two more balls, thrown off the first one at slightly different angles
|
|
REM so they do not travel as a stack.
|
|
LABEL SPLITBALLS
|
|
IF BLN# > 2 THEN RETURN
|
|
IF BLON#(0) = 0 THEN RETURN
|
|
FOR B# = 1 TO 2
|
|
IF BLON#(B#) = 0 THEN BEGIN
|
|
BLON#(B#) = 1
|
|
BLST#(B#) = 0
|
|
BLX%(B#) = BLX%(0)
|
|
BLY%(B#) = BLY%(0)
|
|
BLVX%(B#) = ZVX%(B# * 5 - 4) * SPD%
|
|
BLVY%(B#) = ZVY%(B# * 5 - 4) * SPD%
|
|
BLN# = BLN# + 1
|
|
SPRITE 5 + B#, 1, 2
|
|
BEND
|
|
NEXT B#
|
|
RETURN
|
|
|
|
REM Bring every ball in play to the current speed without changing where it
|
|
REM is heading.
|
|
REM
|
|
REM Every velocity this program assigns is one of the eight unit vectors in
|
|
REM ZVX%/ZVY# times SPD%, and a bounce only ever flips a sign -- so a ball
|
|
REM is travelling at exactly the SPD% that was in force when it last left a
|
|
REM bar. BSPD% remembers what that was, which makes the rescale a ratio of
|
|
REM two speeds rather than a change of magnitude, and no square root is
|
|
REM needed. There isn't one in this dialect.
|
|
REM
|
|
REM Scaling by the vertical component instead, which is what this routine
|
|
REM did first, is not a slowdown at all: a shallow ball's small vertical
|
|
REM gets stretched up to the new speed and drags the large horizontal with
|
|
REM it. SLOW made the ball faster. Sixty degrees of the eight zones are
|
|
REM shallow enough to do it.
|
|
LABEL RESCALE
|
|
IF BSPD% < 0.1 THEN BSPD% = SPD%
|
|
RAT% = SPD% / BSPD%
|
|
BSPD% = SPD%
|
|
FOR B# = 0 TO 2
|
|
IF BLON#(B#) = 1 THEN BEGIN
|
|
BLVX%(B#) = BLVX%(B#) * RAT%
|
|
BLVY%(B#) = BLVY%(B#) * RAT%
|
|
BEND
|
|
NEXT B#
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM Timers
|
|
REM =====================================================================
|
|
LABEL TIMERS
|
|
IF BANT# > 0 THEN BEGIN
|
|
BANT# = BANT# - 1
|
|
IF BANT# = 0 THEN BEGIN
|
|
BAN$ = ""
|
|
GOSUB SETBANNER
|
|
BEND
|
|
BEND
|
|
IF PDEXP# > 0 THEN BEGIN
|
|
PDEXP# = PDEXP# - 1
|
|
IF PDEXP# = 0 THEN BEGIN
|
|
PDW# = 104
|
|
SPRITE 3, 1, 2, 0, 0, 0
|
|
BEND
|
|
BEND
|
|
IF SLOWT# > 0 THEN BEGIN
|
|
SLOWT# = SLOWT# - 1
|
|
IF SLOWT# = 0 THEN BEGIN
|
|
GOSUB LEVELSPEED
|
|
GOSUB RESCALE
|
|
BEND
|
|
BEND
|
|
IF STKT# > 0 THEN BEGIN
|
|
STKT# = STKT# - 1
|
|
IF STKT# = 0 THEN BEGIN
|
|
STKON# = 0
|
|
GOSUB UNSTICK
|
|
BEND
|
|
BEND
|
|
IF CTTM# > 0 THEN BEGIN
|
|
CTTM# = CTTM# - 1
|
|
IF CTTM# = 0 THEN BEGIN
|
|
CTON# = 0
|
|
SPRITE 4, 0
|
|
BEND
|
|
BEND
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM Losing a ball, clearing a level, starting and ending a game
|
|
REM =====================================================================
|
|
LABEL BALLGONE
|
|
SOUND 2, 7218, 30, 2, 1804, 220
|
|
LIVES# = LIVES# - 1
|
|
STATE# = 3
|
|
STIMER# = 45
|
|
GMTYP# = 0
|
|
BAN$ = "MISS"
|
|
GOSUB SETBANNER
|
|
GOSUB HUDTEXT
|
|
RETURN
|
|
|
|
LABEL SERVEAGAIN
|
|
BAN$ = ""
|
|
GOSUB SETBANNER
|
|
GOSUB CLEARPOWERS
|
|
GOSUB RESETBALL
|
|
STATE# = 1
|
|
RETURN
|
|
|
|
LABEL GAMEISOVER
|
|
PLAY "V3 T1 U8 O4 QG QE QC O3 HG"
|
|
IF SCORE# > HISCORE# THEN HISCORE# = SCORE#
|
|
STATE# = 5
|
|
GOSUB CLEARPOWERS
|
|
BRN# = 0
|
|
FOR I# = 0 TO 59
|
|
BRK#(I#) = 0
|
|
NEXT I#
|
|
GOSUB BUILDLIVE
|
|
SPRITE 3, 0
|
|
SPRITE 5, 0
|
|
PN# = 0
|
|
TXS# = 9 : TXC# = 4 : TXX# = 148 : TXY# = 230
|
|
TX$ = "GAME OVER"
|
|
BUILDH# = 0
|
|
GOSUB BUILDTEXT
|
|
TXS# = 5 : TXC# = 2 : TXX# = 200 : TXY# = 380
|
|
T4# = SCORE#
|
|
GOSUB FMTNUM
|
|
TX$ = "SCORE " + NUM$
|
|
BUILDH# = 0
|
|
GOSUB BUILDTEXT
|
|
HN# = 0
|
|
TXS# = 3 : TXC# = 5 : TXY# = 20 : TXX# = 40
|
|
T4# = HISCORE#
|
|
GOSUB FMTNUM
|
|
TX$ = "BEST " + NUM$
|
|
BUILDH# = 1
|
|
GOSUB BUILDTEXT
|
|
TXC# = 6 : TXX# = 400
|
|
TX$ = "SPACE FOR TITLE"
|
|
BUILDH# = 1
|
|
GOSUB BUILDTEXT
|
|
DPLAY# = 1
|
|
DHUD# = 1
|
|
RETURN
|
|
|
|
LABEL LEVELDONE
|
|
PLAY "V3 T2 U8 O4 QC QE QG O5 HC"
|
|
STATE# = 4
|
|
STIMER# = 60
|
|
GMTYP# = 0
|
|
BAN$ = "CLEAR"
|
|
GOSUB SETBANNER
|
|
RETURN
|
|
|
|
LABEL TITLESCREEN
|
|
STATE# = 0
|
|
STIMER# = 0
|
|
GOSUB CLEARPOWERS
|
|
BLN# = 0
|
|
BRN# = 0
|
|
FOR I# = 0 TO 59
|
|
BRK#(I#) = 0
|
|
NEXT I#
|
|
GOSUB BUILDLIVE
|
|
SPRITE 3, 0
|
|
SPRITE 5, 0
|
|
SPRITE 6, 0
|
|
SPRITE 7, 0
|
|
PN# = 0
|
|
TXS# = 12 : TXC# = 2 : TXX# = 120 : TXY# = 170
|
|
TX$ = "BREAKOUT"
|
|
BUILDH# = 0
|
|
GOSUB BUILDTEXT
|
|
TXS# = 4 : TXC# = 1 : TXX# = 220 : TXY# = 370
|
|
TX$ = "SPACE TO START"
|
|
BUILDH# = 0
|
|
GOSUB BUILDTEXT
|
|
HN# = 0
|
|
TXS# = 3 : TXC# = 6 : TXX# = 40 : TXY# = 20
|
|
TX$ = "ARROWS MOVE P PAUSE Q QUIT"
|
|
BUILDH# = 1
|
|
GOSUB BUILDTEXT
|
|
DPLAY# = 1
|
|
DHUD# = 1
|
|
RETURN
|
|
|
|
LABEL STARTGAME
|
|
PLAY "V3 T2 U8 O4 IC IE IG O5 IC"
|
|
SCORE# = 0
|
|
LIVES# = 3
|
|
LEVEL# = 1
|
|
GOSUB SETUPLEVEL
|
|
RETURN
|
|
|
|
REM ---------------------------------------------------------------------
|
|
REM Lay a level out. Every third one is a row shorter, so the field
|
|
REM changes shape as well as pace.
|
|
LABEL SETUPLEVEL
|
|
GOSUB CLEARPOWERS
|
|
GOSUB LEVELSPEED
|
|
T3# = 6
|
|
IF MOD(LEVEL#, 3) = 2 THEN T3# = 5
|
|
IF MOD(LEVEL#, 3) = 0 THEN T3# = 4
|
|
BRN# = 0
|
|
FOR I# = 0 TO 59
|
|
BRK#(I#) = 0
|
|
NEXT I#
|
|
FOR R# = 0 TO 5
|
|
IF R# < T3# THEN GOSUB FILLROW
|
|
NEXT R#
|
|
GOSUB BUILDLIVE
|
|
GOSUB RESETBALL
|
|
GOSUB HUDTEXT
|
|
GMTYP# = 0
|
|
BAN$ = ""
|
|
GOSUB SETBANNER
|
|
STATE# = 1
|
|
DPLAY# = 1
|
|
RETURN
|
|
|
|
REM Again a subroutine rather than a block, for the same reason.
|
|
LABEL FILLROW
|
|
FOR C# = 0 TO 9
|
|
BRK#(R# * 10 + C#) = 1
|
|
BRN# = BRN# + 1
|
|
NEXT C#
|
|
RETURN
|
|
|
|
LABEL LEVELSPEED
|
|
SPD% = 5.6 + 0.45 * LEVEL#
|
|
IF SPD% > 11.0 THEN SPD% = 11.0
|
|
RETURN
|
|
|
|
REM Flatten the brick grid into the list DRAWPLAY walks. Doing it here
|
|
REM rather than in the draw routine is the whole trick: this costs four
|
|
REM lines a brick and has all the time in the world, the draw costs two
|
|
REM and has to finish before the host presents.
|
|
LABEL BUILDLIVE
|
|
LN# = 0
|
|
FOR R# = 0 TO 5
|
|
RS#(R#) = LN#
|
|
RC#(R#) = 0
|
|
GOSUB BUILDROW
|
|
NEXT R#
|
|
RETURN
|
|
|
|
LABEL BUILDROW
|
|
FOR C# = 0 TO 9
|
|
IF BRK#(R# * 10 + C#) > 0 THEN BEGIN
|
|
LX#(LN#) = BRKX# + C# * 72
|
|
LY#(LN#) = BRKY# + R# * 24
|
|
LN# = LN# + 1
|
|
RC#(R#) = RC#(R#) + 1
|
|
BEND
|
|
NEXT C#
|
|
RETURN
|
|
|
|
LABEL RESETBALL
|
|
FOR B# = 0 TO 2
|
|
BLON#(B#) = 0
|
|
BLST#(B#) = 0
|
|
NEXT B#
|
|
SPRITE 6, 0
|
|
SPRITE 7, 0
|
|
BLON#(0) = 1
|
|
BLN# = 1
|
|
BSPD% = SPD%
|
|
PDX% = 348
|
|
PDW# = 104
|
|
BLOFF# = 41
|
|
SPRITE 3, 1, 2, 0, 0, 0
|
|
SPRITE 5, 1, 2
|
|
MOVSPR 3, PDX%, PDY#
|
|
BLX%(0) = PDX% + BLOFF#
|
|
BLY%(0) = PDY# - 23
|
|
MOVSPR 5, BLX%(0), BLY%(0)
|
|
RETURN
|
|
|
|
LABEL LAUNCH
|
|
SOUND 1, 6431, 3
|
|
STATE# = 2
|
|
BLST#(0) = 0
|
|
BLVX%(0) = ZVX%(5) * SPD%
|
|
BLVY%(0) = ZVY%(5) * SPD%
|
|
RETURN
|
|
|
|
LABEL CLEARPOWERS
|
|
GOSUB LEVELSPEED
|
|
PDW# = 104
|
|
PDEXP# = 0
|
|
SLOWT# = 0
|
|
STKON# = 0
|
|
STKT# = 0
|
|
CTON# = 0
|
|
CTTM# = 0
|
|
GMON# = 0
|
|
BANT# = 0
|
|
BLOFF# = 41
|
|
SPRITE 4, 0
|
|
SPRITE 8, 0
|
|
SPRITE 3, 1, 2, 0, 0, 0
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM Text
|
|
REM
|
|
REM The HUD line, rebuilt whenever a number in it changes. Four colours:
|
|
REM labels in cyan, the score in yellow, the lives in light green and the
|
|
REM level in light red. Colour is the whole reason the HUD is drawn
|
|
REM rather than printed -- the interpreter's text layer is white, and it
|
|
REM has no verb that changes that.
|
|
REM =====================================================================
|
|
LABEL HUDTEXT
|
|
HN# = 0
|
|
BUILDH# = 1
|
|
TXS# = 4
|
|
TXY# = 14
|
|
TXC# = 1 : TXX# = 24
|
|
TX$ = "SCORE"
|
|
GOSUB BUILDTEXT
|
|
TXC# = 2 : TXX# = 144
|
|
T4# = SCORE#
|
|
GOSUB FMTNUM
|
|
TX$ = NUM$
|
|
GOSUB BUILDTEXT
|
|
TXC# = 1 : TXX# = 316
|
|
TX$ = "LIVES"
|
|
GOSUB BUILDTEXT
|
|
TXC# = 6 : TXX# = 436
|
|
TX$ = "" + LIVES#
|
|
GOSUB BUILDTEXT
|
|
TXC# = 1 : TXX# = 500
|
|
TX$ = "LEVEL"
|
|
GOSUB BUILDTEXT
|
|
TXC# = 4 : TXX# = 620
|
|
TX$ = "" + LEVEL#
|
|
GOSUB BUILDTEXT
|
|
DHUD# = 1
|
|
RETURN
|
|
|
|
REM The banner over the field: whatever BAN$ says, centred, in the colour
|
|
REM of the gem that put it there.
|
|
LABEL SETBANNER
|
|
PN# = 0
|
|
DPLAY# = 1
|
|
IF BAN$ = "" THEN RETURN
|
|
BUILDH# = 0
|
|
TXS# = 8
|
|
TXY# = 430
|
|
TXX# = 400 - LEN(BAN$) * 20
|
|
TXC# = 5
|
|
IF GMTYP# = 1 THEN TXC# = 4
|
|
IF GMTYP# = 2 THEN TXC# = 2
|
|
IF GMTYP# = 3 THEN TXC# = 6
|
|
IF GMTYP# = 4 THEN TXC# = 1
|
|
IF GMTYP# = 5 THEN TXC# = 3
|
|
REM Sources 1 to 6 are cyan, yellow, purple, light red, light grey and
|
|
REM green; the five gems are red, yellow, green, blue and purple, so the
|
|
REM banner is as close to the gem as seven colour sources allow.
|
|
TX$ = BAN$
|
|
GOSUB BUILDTEXT
|
|
RETURN
|
|
|
|
REM ---------------------------------------------------------------------
|
|
REM Turn TX$ into strokes, into the HUD list when BUILDH# is 1 and the
|
|
REM field list when it is 0. This is the expensive routine in the program,
|
|
REM about sixteen lines a character, and it is deliberately nowhere near a
|
|
REM capture: it runs when a value changes, and the drawing that replays
|
|
REM its output happens on some later frame.
|
|
LABEL BUILDTEXT
|
|
TLEN# = LEN(TX$)
|
|
IF TLEN# = 0 THEN RETURN
|
|
TXI# = 0
|
|
DO
|
|
GC# = INSTR(ALPHA$, MID(TX$, TXI#, 1))
|
|
IF GC# > 0 - 1 THEN GOSUB BUILDGLYPH
|
|
TXI# = TXI# + 1
|
|
LOOP UNTIL TXI# >= TLEN#
|
|
RETURN
|
|
|
|
LABEL BUILDGLYPH
|
|
GN# = FNC#(GC#)
|
|
IF GN# = 0 THEN RETURN
|
|
GP# = FNI#(GC#)
|
|
GX# = TXX# + TXI# * TXS# * 5
|
|
GK# = 0
|
|
DO
|
|
P1# = FNS#(GP#) : P2# = FNS#(GP# + 1) : GP# = GP# + 2
|
|
IF BUILDH# = 1 AND HN# < 120 THEN BEGIN
|
|
HX1#(HN#) = GX# + (P1# / 10) * TXS#
|
|
HY1#(HN#) = TXY# + MOD(P1#, 10) * TXS#
|
|
HX2#(HN#) = GX# + (P2# / 10) * TXS#
|
|
HY2#(HN#) = TXY# + MOD(P2#, 10) * TXS#
|
|
HC#(HN#) = TXC#
|
|
HN# = HN# + 1
|
|
BEND
|
|
IF BUILDH# = 0 AND PN# < 110 THEN BEGIN
|
|
PX1#(PN#) = GX# + (P1# / 10) * TXS#
|
|
PY1#(PN#) = TXY# + MOD(P1#, 10) * TXS#
|
|
PX2#(PN#) = GX# + (P2# / 10) * TXS#
|
|
PY2#(PN#) = TXY# + MOD(P2#, 10) * TXS#
|
|
PC#(PN#) = TXC#
|
|
PN# = PN# + 1
|
|
BEND
|
|
GK# = GK# + 1
|
|
LOOP UNTIL GK# >= GN#
|
|
RETURN
|
|
|
|
REM T4# as six digits with leading zeros, into NUM$.
|
|
LABEL FMTNUM
|
|
NUM$ = ""
|
|
T2# = T4#
|
|
FOR I# = 1 TO 6
|
|
D# = MOD(T2#, 10)
|
|
NUM$ = MID("0123456789", D#, 1) + NUM$
|
|
T2# = T2# / 10
|
|
NEXT I#
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM A generator, because this dialect has no RND
|
|
REM
|
|
REM The usual constants, kept inside 2^31 so the multiply stays inside a
|
|
REM 64 bit integer, and the answer taken from the middle bits because the
|
|
REM low ones of a power-of-two modulus barely move. A GOSUB rather than a
|
|
REM DEF on purpose: a multi-line DEF that assigns to a global spends a
|
|
REM value-pool slot on every call and never gives it back, which empties
|
|
REM the pool after a few thousand throws.
|
|
REM =====================================================================
|
|
LABEL NEXTRAND
|
|
SEED# = MOD(SEED# * 1103515245 + 12345, 2147483648)
|
|
RNVAL# = MOD(SHR(SEED#, 16), RNMAX#)
|
|
RETURN
|
|
|
|
REM =====================================================================
|
|
REM Tables
|
|
REM =====================================================================
|
|
LABEL LOADTABLES
|
|
FOR I# = 0 TO 5
|
|
READ BRC#(I#)
|
|
NEXT I#
|
|
FOR I# = 0 TO 5
|
|
READ BRV#(I#)
|
|
NEXT I#
|
|
FOR I# = 0 TO 7
|
|
READ ZVX%(I#)
|
|
NEXT I#
|
|
FOR I# = 0 TO 7
|
|
READ ZVY%(I#)
|
|
NEXT I#
|
|
RETURN
|
|
|
|
REM Row colours, top to bottom, and what a brick in that row is worth.
|
|
DATA 3, 9, 8, 6, 4, 5
|
|
DATA 60, 50, 40, 30, 20, 10
|
|
|
|
REM The eight landing zones. Each pair is very nearly a unit vector, so
|
|
REM the ball leaves a bar at the speed it arrived and only the angle
|
|
REM changes.
|
|
DATA -0.85, -0.62, -0.40, -0.18, 0.18, 0.40, 0.62, 0.85
|
|
DATA -0.53, -0.78, -0.92, -0.98, -0.98, -0.92, -0.78, -0.53
|
|
|
|
REM =====================================================================
|
|
REM The stroke font
|
|
REM
|
|
REM One glyph per DATA line: how many strokes, then that many pairs of
|
|
REM points. A point is coded X*10+Y on a grid four wide and seven tall,
|
|
REM so 0 is the top left corner, 30 the top right and 36 the bottom
|
|
REM right. INSTR into ALPHA$ is what turns a character into a glyph
|
|
REM number, which is why the order of these lines matters.
|
|
REM =====================================================================
|
|
LABEL LOADFONT
|
|
GX# = 0
|
|
FOR I# = 0 TO 40
|
|
READ N#
|
|
FNC#(I#) = N#
|
|
FNI#(I#) = GX#
|
|
IF N# > 0 THEN GOSUB READGLYPH
|
|
NEXT I#
|
|
RETURN
|
|
|
|
REM Its own subroutine rather than a BEGIN block: a FOR nested inside a
|
|
REM block that is itself inside a FOR does not find its own NEXT.
|
|
LABEL READGLYPH
|
|
FOR K# = 1 TO N# * 2
|
|
READ D#
|
|
FNS#(GX#) = D#
|
|
GX# = GX# + 1
|
|
NEXT K#
|
|
RETURN
|
|
|
|
REM A
|
|
DATA 4, 0,30, 0,6, 30,36, 3,33
|
|
REM B
|
|
DATA 6, 0,6, 0,30, 3,33, 6,36, 30,33, 33,36
|
|
REM C
|
|
DATA 3, 0,30, 0,6, 6,36
|
|
REM D
|
|
DATA 6, 0,6, 0,20, 20,31, 31,35, 35,26, 26,6
|
|
REM E
|
|
DATA 4, 0,6, 0,30, 3,33, 6,36
|
|
REM F
|
|
DATA 3, 0,6, 0,30, 3,33
|
|
REM G
|
|
DATA 5, 0,30, 0,6, 6,36, 33,36, 13,33
|
|
REM H
|
|
DATA 3, 0,6, 30,36, 3,33
|
|
REM I
|
|
DATA 1, 10,16
|
|
REM J
|
|
DATA 3, 30,36, 6,36, 3,6
|
|
REM K
|
|
DATA 3, 0,6, 30,3, 3,36
|
|
REM L
|
|
DATA 2, 0,6, 6,36
|
|
REM M
|
|
DATA 4, 0,6, 30,36, 0,13, 13,30
|
|
REM N
|
|
DATA 3, 0,6, 30,36, 0,36
|
|
REM O
|
|
DATA 4, 0,30, 6,36, 0,6, 30,36
|
|
REM P
|
|
DATA 4, 0,6, 0,30, 3,33, 30,33
|
|
REM Q
|
|
DATA 5, 0,30, 6,36, 0,6, 30,36, 24,36
|
|
REM R
|
|
DATA 5, 0,6, 0,30, 3,33, 30,33, 13,36
|
|
REM S
|
|
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
|
|
REM T
|
|
DATA 2, 0,30, 10,16
|
|
REM U
|
|
DATA 3, 0,6, 30,36, 6,36
|
|
REM V
|
|
DATA 2, 0,16, 16,30
|
|
REM W
|
|
DATA 4, 0,6, 30,36, 6,13, 13,36
|
|
REM X
|
|
DATA 2, 0,36, 30,6
|
|
REM Y
|
|
DATA 3, 0,13, 30,13, 13,16
|
|
REM Z
|
|
DATA 3, 0,30, 30,6, 6,36
|
|
REM 0
|
|
DATA 5, 0,30, 6,36, 0,6, 30,36, 30,6
|
|
REM 1
|
|
DATA 2, 10,16, 1,10
|
|
REM 2
|
|
DATA 5, 0,30, 30,33, 3,33, 3,6, 6,36
|
|
REM 3
|
|
DATA 4, 0,30, 30,36, 3,33, 6,36
|
|
REM 4
|
|
DATA 3, 0,3, 3,33, 30,36
|
|
REM 5
|
|
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
|
|
REM 6
|
|
DATA 5, 0,30, 0,6, 3,33, 33,36, 6,36
|
|
REM 7
|
|
DATA 2, 0,30, 30,36
|
|
REM 8
|
|
DATA 5, 0,30, 6,36, 0,6, 30,36, 3,33
|
|
REM 9
|
|
DATA 5, 0,30, 0,3, 3,33, 30,36, 6,36
|
|
REM space
|
|
DATA 0
|
|
REM colon
|
|
DATA 2, 12,13, 14,15
|
|
REM dash
|
|
DATA 1, 3,33
|
|
REM full stop
|
|
DATA 1, 15,16
|
|
REM exclamation mark
|
|
DATA 2, 10,14, 15,16
|