From 39d1d0c80c2372454b6d4f6421735337f9ca842b Mon Sep 17 00:00:00 2001 From: Andrew Kesterson Date: Sun, 2 Aug 2026 12:58:08 -0400 Subject: [PATCH] Stop the artwork breakout drawing its screen into two sprites It captured the HUD strip into sprite 1 and the whole play field into sprite 2, and its own header called them "the screen". That was never a choice: a drawing lasted one frame, so a sprite was the only thing the interpreter would redraw for nothing. Both slots are free now, and five things went with them. **`WINDOW 0, 35, 49, 36` is the first executable line.** The text layer repaints every row it owns and by default owns the whole window; moved to two rows at the bottom, it leaves the other thirty-five to the drawing verbs, and what the program draws stays. **`PACE` is gone.** It spun on `TI#` until a batch boundary so an `SSHAPE` capture would not be cut in half by a present. Nothing is captured, so a drawing spanning two batches simply arrives over two frames and the layer keeps both halves. The 256-line drawing deadline that shaped every draw routine in this listing does not apply to it any more. **The flattened live list is gone** -- `BUILDLIVE`, `BUILDROW`, `STAMPROW` and the four arrays behind them. They existed so a full-field redraw could be one stamp and an advance per brick, and a full-field redraw existed because removing one brick meant rebuilding all sixty. A broken brick is now erased in place with a blank stamp, so the field is drawn once per level and not again. **`BALLBRICKS` and `TESTCELL` are gone** -- about sixty lines that turned the ball's box into a range of candidate cells, walked them with a nested `DO`, and computed an overlap rectangle to pick an axis. `SOLID` registers each brick as the level is laid out, `COLLISION 2` fires, and `RCOLLISION` says which brick, which way out, how far, and which axis to reverse. **The `SSHAPE` pool accounting is gone.** `SHN#` counted slots because every frame's capture spent another and `GRAPHIC 5` had to throw them all away periodically; eight are now spent once and never again. Three mistakes worth recording, because each was silent: - **The bricks were registered and then immediately thrown away.** `GOSUB BUILDLIVE` sat *after* the fill loop in `SETUPLEVEL` and meant "rebuild the list of what is standing"; replaced in place by a bare `SOLID`, it meant "retire everything" and ran after all sixty had been registered. Nothing failed -- the ball simply passed through the wall. - **The handler tested the wrong bits.** `BUMP` is a mask by *sprite*, and the balls are sprites 5, 6 and 7, so their bits are 16, 32 and 64. Testing 1, 2 and 4 asked about the HUD and field sprites that no longer exist. - **The eraser stamps were undeclared**, so `SSHAPE` filled them inside `DRAWPROTOS` and they were empty everywhere else -- exactly the scope trap chapter 17 Step 3 is about, found by printing the handle inside the routine and again outside it. Verified by driving it: a copy with a tracking paddle and auto-relaunch breaks thirteen bricks in sixty seconds with no error line, and the shipped listing runs clean. Both suites green. Chapter 18 still describes the architecture this commit removed; that is the rest of section 6 item 39. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL --- examples/breakout/sprites/README.md | 10 +- examples/breakout/sprites/breakout.bas | 349 ++++++++++++------------- 2 files changed, 171 insertions(+), 188 deletions(-) diff --git a/examples/breakout/sprites/README.md b/examples/breakout/sprites/README.md index e3c2457..0d9973d 100644 --- a/examples/breakout/sprites/README.md +++ b/examples/breakout/sprites/README.md @@ -2,11 +2,11 @@ Breakout, written entirely in BASIC for the AKGL BASIC interpreter, using downloaded CC0 sprite artwork for the paddles, the balls and the powerup gems. The bricks, the HUD and -the lettering are drawn, captured with `SSHAPE` and installed as sprites — because a -sprite is the one thing this interpreter puts on the screen for nothing. The text layer -owns every row of the window unless a program takes some back with `WINDOW`, and even -then a drawing has to be re-issued every frame; a sprite is drawn from state the -interpreter keeps. +the lettering are drawn — `WINDOW` moves the text layer to two rows at the bottom, and +everything the program draws then stays on the screen without being redrawn or captured. + +The bricks are also **collidable without being sprites**: `SOLID` registers a rectangle +the interpreter collides against, so sixty of them cost none of the eight slots. ```sh ../../../build-akgl/basic breakout.bas diff --git a/examples/breakout/sprites/breakout.bas b/examples/breakout/sprites/breakout.bas index 37b80be..bc6d1fa 100644 --- a/examples/breakout/sprites/breakout.bas +++ b/examples/breakout/sprites/breakout.bas @@ -3,43 +3,45 @@ 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 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 of the window opaque black after -REM the program's steps have run, so by default anything DRAW or BOX -REM put on the screen is wiped before it is ever presented. Sprites are -REM drawn after the text layer, so a sprite is the one thing that is -REM seen for nothing. Everything drawn here is therefore captured with -REM SSHAPE and installed as a sprite with SPRSAV -- that is what -REM sprites 1 and 2 are. They are the screen. +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 (WINDOW can shrink the text area and hand the rest of the window to -REM the drawing verbs; it could not be reached from this build when -REM this was written. It would not help here anyway: a drawing has to -REM be re-issued every frame and fit inside one batch, and a sprite has -REM neither constraint.) +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. 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 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 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 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 5. Five things in this dialect did not do what they looked like they +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: @@ -138,13 +140,8 @@ 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 @@ -165,7 +162,6 @@ 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$ = "" @@ -244,6 +240,19 @@ 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 @@ -252,6 +261,14 @@ 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) @@ -283,6 +300,11 @@ 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 ===================================================================== @@ -292,8 +314,11 @@ 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 PACE GOSUB DRAWJOB GOSUB READKEYS IF STATE# = 0 THEN GOSUB TITLETICK @@ -312,30 +337,6 @@ 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 @@ -346,10 +347,10 @@ 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 -IF SHN# < 14 THEN GOTO DRAWJOB2 -GOSUB DRAWPROTOS -RETURN LABEL DRAWJOB2 IF DPLAY# = 0 THEN GOTO DRAWJOB3 GOSUB DRAWPLAY @@ -368,10 +369,8 @@ 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 @@ -380,17 +379,41 @@ FOR R# = 0 TO 5 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$ -SHN# = 6 +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 @@ -401,53 +424,43 @@ 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 skipped BEGIN block holding a FOR used to break the RETURN at the end -REM of the routine holding it, so this loop is guarded by a GOTO instead. -REM Fixed since; left alone because a GOTO guard costs nothing. +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 -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 +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 @@ -455,11 +468,6 @@ 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 ===================================================================== @@ -580,7 +588,6 @@ IF BLST#(B#) = 0 THEN BEGIN 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#) @@ -610,70 +617,58 @@ 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 +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 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# +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 -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#) +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 -HIT# = 1 -GOSUB BUILDLIVE +BX1# = BRKX# + C# * 72 +BY1# = BRKY# + R# * 24 +GOSUB ERASEBRICK GOSUB HUDTEXT -DPLAY# = 1 IF GMON# = 1 THEN RETURN RNMAX# = 7 GOSUB NEXTRAND @@ -915,7 +910,7 @@ BRN# = 0 FOR I# = 0 TO 59 BRK#(I#) = 0 NEXT I# -GOSUB BUILDLIVE +SOLID SPRITE 3, 0 SPRITE 5, 0 PN# = 0 @@ -962,7 +957,7 @@ BRN# = 0 FOR I# = 0 TO 59 BRK#(I#) = 0 NEXT I# -GOSUB BUILDLIVE +SOLID SPRITE 3, 0 SPRITE 5, 0 SPRITE 6, 0 @@ -1006,10 +1001,15 @@ 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 BUILDLIVE GOSUB RESETBALL GOSUB HUDTEXT GMTYP# = 0 @@ -1020,10 +1020,17 @@ 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 @@ -1032,30 +1039,6 @@ 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