Add two Breakout examples and the tutorials that build them
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>
2026-08-01 22:54:42 -04:00
|
|
|
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
|
Stop the listings and their chapters stating fixed defects as fact
You were right that this was still everywhere. The two `.bas` files carried
their workarounds as present-tense statements about the interpreter -- "a name
first created inside a GOSUB costs a value slot that is never handed back",
"writing PAST the terminator of a short row draws nothing at all", "SSHAPE and
GSHAPE ignore the subscript on a string array", "a FOR inside a BEGIN block does
not survive the RETURN" -- and every one of those is now false. A comment that
lies is worse than no comment, and these were the first thing anybody opening
the files would read.
**The characters game takes the geometry fix.** `CW# = 16` becomes `CW# =
RGR(3)`, and the grid comes from `RWINDOW`, so the game fits whatever window and
font the host gives it. That was the one thing in the listing that would break on
a different font, and `RGR(3)` and `RWINDOW` were added to the interpreter
because of it -- leaving the constant in place while Chapter 17 teaches the
function was the inconsistency worth closing. `RGR(1)` still comes first so a
build with no graphics device refuses by naming the device that is missing.
Everything else in both listings keeps its shape and says why. Declaring every
name up front, guarding loops with `GOTO`, six scalars for six brick stamps: none
is forced any more and none costs anything, so they stay, with the comments
marking which rules stand and which are history. The sprites header's five traps
are marked FIXED where they are fixed.
The chapters follow: Chapter 17 Step 2 no longer says "the listing still has
`CW# = 16`", and Chapter 18's trap section is "five things that did not do what
they looked like" with the two that still stand named up front rather than left
to be counted.
Verified: Chapter 17 Step 2's block is once again verbatim from the listing, so
every quoted fragment in both chapters matches its source with no exceptions;
both games run 40 seconds on the SDL frontend with no error line; both suites
green in both configurations; `docs_examples` and `docs_screenshots --check`
pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:32:48 -04:00
|
|
|
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
|
|
|
|
|
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.)
|
Add two Breakout examples and the tutorials that build them
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>
2026-08-01 22:54:42 -04:00
|
|
|
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
|
Stop the listings and their chapters stating fixed defects as fact
You were right that this was still everywhere. The two `.bas` files carried
their workarounds as present-tense statements about the interpreter -- "a name
first created inside a GOSUB costs a value slot that is never handed back",
"writing PAST the terminator of a short row draws nothing at all", "SSHAPE and
GSHAPE ignore the subscript on a string array", "a FOR inside a BEGIN block does
not survive the RETURN" -- and every one of those is now false. A comment that
lies is worse than no comment, and these were the first thing anybody opening
the files would read.
**The characters game takes the geometry fix.** `CW# = 16` becomes `CW# =
RGR(3)`, and the grid comes from `RWINDOW`, so the game fits whatever window and
font the host gives it. That was the one thing in the listing that would break on
a different font, and `RGR(3)` and `RWINDOW` were added to the interpreter
because of it -- leaving the constant in place while Chapter 17 teaches the
function was the inconsistency worth closing. `RGR(1)` still comes first so a
build with no graphics device refuses by naming the device that is missing.
Everything else in both listings keeps its shape and says why. Declaring every
name up front, guarding loops with `GOTO`, six scalars for six brick stamps: none
is forced any more and none costs anything, so they stay, with the comments
marking which rules stand and which are history. The sprites header's five traps
are marked FIXED where they are fixed.
The chapters follow: Chapter 17 Step 2 no longer says "the listing still has
`CW# = 16`", and Chapter 18's trap section is "five things that did not do what
they looked like" with the two that still stand named up front rather than left
to be counted.
Verified: Chapter 17 Step 2's block is once again verbatim from the listing, so
every quoted fragment in both chapters matches its source with no exceptions;
both games run 40 seconds on the SDL frontend with no error line; both suites
green in both configurations; `docs_examples` and `docs_screenshots --check`
pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:32:48 -04:00
|
|
|
REM 5. 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:
|
Add two Breakout examples and the tutorials that build them
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>
2026-08-01 22:54:42 -04:00
|
|
|
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.
|
Stop the listings and their chapters stating fixed defects as fact
You were right that this was still everywhere. The two `.bas` files carried
their workarounds as present-tense statements about the interpreter -- "a name
first created inside a GOSUB costs a value slot that is never handed back",
"writing PAST the terminator of a short row draws nothing at all", "SSHAPE and
GSHAPE ignore the subscript on a string array", "a FOR inside a BEGIN block does
not survive the RETURN" -- and every one of those is now false. A comment that
lies is worse than no comment, and these were the first thing anybody opening
the files would read.
**The characters game takes the geometry fix.** `CW# = 16` becomes `CW# =
RGR(3)`, and the grid comes from `RWINDOW`, so the game fits whatever window and
font the host gives it. That was the one thing in the listing that would break on
a different font, and `RGR(3)` and `RWINDOW` were added to the interpreter
because of it -- leaving the constant in place while Chapter 17 teaches the
function was the inconsistency worth closing. `RGR(1)` still comes first so a
build with no graphics device refuses by naming the device that is missing.
Everything else in both listings keeps its shape and says why. Declaring every
name up front, guarding loops with `GOTO`, six scalars for six brick stamps: none
is forced any more and none costs anything, so they stay, with the comments
marking which rules stand and which are history. The sprites header's five traps
are marked FIXED where they are fixed.
The chapters follow: Chapter 17 Step 2 no longer says "the listing still has
`CW# = 16`", and Chapter 18's trap section is "five things that did not do what
they looked like" with the two that still stand named up front rather than left
to be counted.
Verified: Chapter 17 Step 2's block is once again verbatim from the listing, so
every quoted fragment in both chapters matches its source with no exceptions;
both games run 40 seconds on the SDL frontend with no error line; both suites
green in both configurations; `docs_examples` and `docs_screenshots --check`
pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:32:48 -04:00
|
|
|
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.
|
Add two Breakout examples and the tutorials that build them
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>
2026-08-01 22:54:42 -04:00
|
|
|
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.
|
Stop the listings and their chapters stating fixed defects as fact
You were right that this was still everywhere. The two `.bas` files carried
their workarounds as present-tense statements about the interpreter -- "a name
first created inside a GOSUB costs a value slot that is never handed back",
"writing PAST the terminator of a short row draws nothing at all", "SSHAPE and
GSHAPE ignore the subscript on a string array", "a FOR inside a BEGIN block does
not survive the RETURN" -- and every one of those is now false. A comment that
lies is worse than no comment, and these were the first thing anybody opening
the files would read.
**The characters game takes the geometry fix.** `CW# = 16` becomes `CW# =
RGR(3)`, and the grid comes from `RWINDOW`, so the game fits whatever window and
font the host gives it. That was the one thing in the listing that would break on
a different font, and `RGR(3)` and `RWINDOW` were added to the interpreter
because of it -- leaving the constant in place while Chapter 17 teaches the
function was the inconsistency worth closing. `RGR(1)` still comes first so a
build with no graphics device refuses by naming the device that is missing.
Everything else in both listings keeps its shape and says why. Declaring every
name up front, guarding loops with `GOTO`, six scalars for six brick stamps: none
is forced any more and none costs anything, so they stay, with the comments
marking which rules stand and which are history. The sprites header's five traps
are marked FIXED where they are fixed.
The chapters follow: Chapter 17 Step 2 no longer says "the listing still has
`CW# = 16`", and Chapter 18's trap section is "five things that did not do what
they looked like" with the two that still stand named up front rather than left
to be counted.
Verified: Chapter 17 Step 2's block is once again verbatim from the listing, so
every quoted fragment in both chapters matches its source with no exceptions;
both games run 40 seconds on the SDL frontend with no error line; both suites
green in both configurations; `docs_examples` and `docs_screenshots --check`
pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:32:48 -04:00
|
|
|
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.
|
Add two Breakout examples and the tutorials that build them
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>
2026-08-01 22:54:42 -04:00
|
|
|
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 =====================================================================
|
Stop the listings and their chapters stating fixed defects as fact
You were right that this was still everywhere. The two `.bas` files carried
their workarounds as present-tense statements about the interpreter -- "a name
first created inside a GOSUB costs a value slot that is never handed back",
"writing PAST the terminator of a short row draws nothing at all", "SSHAPE and
GSHAPE ignore the subscript on a string array", "a FOR inside a BEGIN block does
not survive the RETURN" -- and every one of those is now false. A comment that
lies is worse than no comment, and these were the first thing anybody opening
the files would read.
**The characters game takes the geometry fix.** `CW# = 16` becomes `CW# =
RGR(3)`, and the grid comes from `RWINDOW`, so the game fits whatever window and
font the host gives it. That was the one thing in the listing that would break on
a different font, and `RGR(3)` and `RWINDOW` were added to the interpreter
because of it -- leaving the constant in place while Chapter 17 teaches the
function was the inconsistency worth closing. `RGR(1)` still comes first so a
build with no graphics device refuses by naming the device that is missing.
Everything else in both listings keeps its shape and says why. Declaring every
name up front, guarding loops with `GOTO`, six scalars for six brick stamps: none
is forced any more and none costs anything, so they stay, with the comments
marking which rules stand and which are history. The sprites header's five traps
are marked FIXED where they are fixed.
The chapters follow: Chapter 17 Step 2 no longer says "the listing still has
`CW# = 16`", and Chapter 18's trap section is "five things that did not do what
they looked like" with the two that still stand named up front rather than left
to be counted.
Verified: Chapter 17 Step 2's block is once again verbatim from the listing, so
every quoted fragment in both chapters matches its source with no exceptions;
both games run 40 seconds on the SDL frontend with no error line; both suites
green in both configurations; `docs_examples` and `docs_screenshots --check`
pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:32:48 -04:00
|
|
|
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.
|
Add two Breakout examples and the tutorials that build them
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>
2026-08-01 22:54:42 -04:00
|
|
|
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
|
Stop the listings and their chapters stating fixed defects as fact
You were right that this was still everywhere. The two `.bas` files carried
their workarounds as present-tense statements about the interpreter -- "a name
first created inside a GOSUB costs a value slot that is never handed back",
"writing PAST the terminator of a short row draws nothing at all", "SSHAPE and
GSHAPE ignore the subscript on a string array", "a FOR inside a BEGIN block does
not survive the RETURN" -- and every one of those is now false. A comment that
lies is worse than no comment, and these were the first thing anybody opening
the files would read.
**The characters game takes the geometry fix.** `CW# = 16` becomes `CW# =
RGR(3)`, and the grid comes from `RWINDOW`, so the game fits whatever window and
font the host gives it. That was the one thing in the listing that would break on
a different font, and `RGR(3)` and `RWINDOW` were added to the interpreter
because of it -- leaving the constant in place while Chapter 17 teaches the
function was the inconsistency worth closing. `RGR(1)` still comes first so a
build with no graphics device refuses by naming the device that is missing.
Everything else in both listings keeps its shape and says why. Declaring every
name up front, guarding loops with `GOTO`, six scalars for six brick stamps: none
is forced any more and none costs anything, so they stay, with the comments
marking which rules stand and which are history. The sprites header's five traps
are marked FIXED where they are fixed.
The chapters follow: Chapter 17 Step 2 no longer says "the listing still has
`CW# = 16`", and Chapter 18's trap section is "five things that did not do what
they looked like" with the two that still stand named up front rather than left
to be counted.
Verified: Chapter 17 Step 2's block is once again verbatim from the listing, so
every quoted fragment in both chapters matches its source with no exceptions;
both games run 40 seconds on the SDL frontend with no error line; both suites
green in both configurations; `docs_examples` and `docs_screenshots --check`
pass.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-02 06:32:48 -04:00
|
|
|
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.
|
Add two Breakout examples and the tutorials that build them
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>
2026-08-01 22:54:42 -04:00
|
|
|
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
|