Chapter 18 still taught the machinery the listing lost when it moved onto SOLID and the persistent drawing layer: the frame-boundary PACE routine, the flattened live list, the SSHAPE pool accounting, and the sixty lines of BALLBRICKS/TESTCELL that computed a minimum translation axis by hand. Step 8 now registers each brick with SOLID, arms COLLISION 2 and reads the contact back with RCOLLISION, including the point that costs an evening if it is missed: the BUMP mask is by sprite, and the balls are sprites 5, 6 and 7, so their bits are 16, 32 and 64. The FOR-with-equal-bounds rule that every one-item case in the chapter depends on is now stated where it is first used rather than referred to from a step that no longer exists. The listing loses six declarations orphaned by the conversion and the comment blocks that still described the captures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
1344 lines
37 KiB
QBasic
1344 lines
37 KiB
QBasic
REM =====================================================================
|
|
REM BREAKOUT, for the AKGL BASIC interpreter, using sprite artwork.
|
|
REM
|
|
REM Run it with the SDL build: build-akgl/basic breakout-sprites.bas
|
|
REM
|
|
REM 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 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 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 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
|
|
|