Teach the artwork tutorial the converted listing
Chapter 18 still taught the machinery the listing lost when it moved onto SOLID and the persistent drawing layer: the frame-boundary PACE routine, the flattened live list, the SSHAPE pool accounting, and the sixty lines of BALLBRICKS/TESTCELL that computed a minimum translation axis by hand. Step 8 now registers each brick with SOLID, arms COLLISION 2 and reads the contact back with RCOLLISION, including the point that costs an evening if it is missed: the BUMP mask is by sprite, and the balls are sprites 5, 6 and 7, so their bits are 16, 32 and 64. The FOR-with-equal-bounds rule that every one-item case in the chapter depends on is now stated where it is first used rather than referred to from a step that no longer exists. The listing loses six declarations orphaned by the conversion and the comment blocks that still described the captures. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL
This commit is contained in:
@@ -54,26 +54,25 @@ tells you which it is.
|
||||
- **[Step 1](#step-1-put-artwork-on-the-screen)** — load PNG artwork into sprites
|
||||
- **[Step 2](#step-2-budget-the-eight-sprite-slots)** — decide what each of the eight
|
||||
sprites is, before writing anything else
|
||||
- **[Step 3](#step-3-turn-a-drawing-into-a-sprite)** — draw something and turn it into a
|
||||
sprite so it stays on the screen
|
||||
- **[Step 3](#step-3-make-room-for-what-you-draw)** — take the text layer out of the way,
|
||||
so what you draw can be seen and stays seen
|
||||
- **[Step 4](#step-4-make-the-brick-stamps)** — make one brick per colour and stamp the
|
||||
field out of them
|
||||
- **[Step 5](#step-5-find-the-frame-boundary)** — find the host's frame boundary, so a
|
||||
drawing survives being captured
|
||||
- **[Step 6](#step-6-draw-at-most-one-thing-per-frame)** — draw at most one thing per
|
||||
- **[Step 5](#step-5-draw-at-most-one-thing-per-frame)** — draw at most one thing per
|
||||
frame, chosen by dirty flags
|
||||
- **[Step 7](#step-7-keep-a-flattened-list-of-live-bricks)** — keep a flattened list of the
|
||||
bricks still standing, so the draw routine decides nothing
|
||||
- **[Step 8](#step-8-draw-lettering-with-a-stroke-font)** — draw lettering with a stroke
|
||||
- **[Step 6](#step-6-erase-one-brick-not-the-whole-field)** — erase a broken brick in
|
||||
place, so the field is drawn once and not rebuilt
|
||||
- **[Step 7](#step-7-draw-lettering-with-a-stroke-font)** — draw lettering with a stroke
|
||||
font, because text has no colour
|
||||
- **[Step 9](#step-9-move-and-bounce-the-ball)** — move the ball and bounce it off bars and
|
||||
bricks without a square root
|
||||
- **[Step 10](#step-10-gems-and-powerups)** — drop gems and apply what catching one does
|
||||
- **[Step 11](#step-11-three-voices-and-a-mute)** — three voices of sound and a mute that
|
||||
- **[Step 8](#step-8-move-and-bounce-the-ball)** — move the ball, bounce it off the bars
|
||||
without a square root, and register the bricks as collision geometry so the interpreter
|
||||
finds the hits
|
||||
- **[Step 9](#step-9-gems-and-powerups)** — drop gems and apply what catching one does
|
||||
- **[Step 10](#step-10-three-voices-and-a-mute)** — three voices of sound and a mute that
|
||||
costs nothing
|
||||
- **[Step 12](#step-12-the-states)** — the frame loop's states: title, serve, play, lost,
|
||||
- **[Step 11](#step-11-the-states)** — the frame loop's states: title, serve, play, lost,
|
||||
cleared
|
||||
- **[Step 13](#step-13-put-the-program-together)** — put the pieces in one file, in the
|
||||
- **[Step 12](#step-12-put-the-program-together)** — put the pieces in one file, in the
|
||||
right order
|
||||
|
||||
---
|
||||
@@ -143,15 +142,15 @@ around later, so spend them on paper first. This game spends them like this:
|
||||
|
||||
| Slot | Is |
|
||||
|---|---|
|
||||
| 1 | the HUD strip — a captured drawing |
|
||||
| 2 | the playing field — a captured drawing |
|
||||
| 1 | free |
|
||||
| 2 | free |
|
||||
| 3 | the paddle |
|
||||
| 4 | the catcher bar (the purple gem) |
|
||||
| 5, 6, 7 | up to three balls |
|
||||
| 8 | the falling gem |
|
||||
|
||||
Two of the eight are **the screen itself**, and Step 3 explains why. That leaves six for
|
||||
everything that moves, and two consequences fall straight out of it:
|
||||
Six are spent and **two are left over**, which is more room than the program needs. Two
|
||||
consequences fall straight out of the six that are spent:
|
||||
|
||||
- **The bricks are drawn rather than made of artwork.** Sixty bricks will not fit in six
|
||||
slots, and a sprite is the only way to get an image file onto the screen — `GSHAPE`
|
||||
@@ -161,44 +160,41 @@ everything that moves, and two consequences fall straight out of it:
|
||||
|
||||
Deciding this first is what stops a feature costing an afternoon before it is abandoned.
|
||||
|
||||
## Step 3: Turn a drawing into a sprite
|
||||
## Step 3: Make room for what you draw
|
||||
|
||||
**Goal: something you drew, still on the screen on the next frame.**
|
||||
|
||||
Two layers are redrawn for you every frame from state the interpreter keeps: the text grid
|
||||
and the sprites. **The drawing verbs are not one of them.** `DRAW`, `BOX`, `CIRCLE` and
|
||||
`PAINT` write straight into the renderer's buffer, and by default the text layer owns
|
||||
every row of the window and repaints over them before the frame is shown.
|
||||
Two things have to be true before a drawing is any use, and only one of them is automatic.
|
||||
|
||||
So a drawing has to be turned into something that persists. The sequence is: draw it,
|
||||
capture it with `SSHAPE`, install the capture into a sprite with `SPRSAV`.
|
||||
**A drawing persists.** `DRAW`, `BOX`, `CIRCLE` and `PAINT` render into a layer the frame
|
||||
composites underneath the text and the sprites, so a picture you draw once is there on
|
||||
every frame after. You do not redraw it and you do not have to keep it anywhere.
|
||||
|
||||
**But the text layer covers it.** It repaints every row it owns, opaque, every frame — and
|
||||
by default it owns the whole window. So the first executable line of this program is:
|
||||
|
||||
```basic norun
|
||||
SSHAPE Z$, 0, 60, 800, 600
|
||||
SPRSAV Z$, 2
|
||||
SPRITE 2, 1, 2
|
||||
MOVSPR 2, 0, 60
|
||||
WINDOW 0, 35, 49, 36
|
||||
```
|
||||
|
||||
Four lines, and they are the last four lines of every draw routine in this game. `SSHAPE`
|
||||
captures the rectangle from (0, 60) to (800, 600) into `Z$`; `SPRSAV` installs it into
|
||||
slot 2; `SPRITE` turns the slot on; `MOVSPR` puts it back where it was drawn.
|
||||
Two rows at the bottom, which is enough for the final score, and the other thirty-five
|
||||
belong to the drawing verbs. `WINDOW l, t, r, b` takes character cells, and
|
||||
`RWINDOW(0)` and `RWINDOW(1)` report what you ended up with.
|
||||
|
||||
`Z$` holds a **handle**, not pixels — see
|
||||
[Chapter 6](06-graphics.md#saving-and-stamping-regions). You can pass it to `GSHAPE` and
|
||||
`SPRSAV`. You cannot print it, save it or take its `LEN`.
|
||||
That is the whole of it. Draw your field once and it stays; draw your HUD once and it
|
||||
stays.
|
||||
|
||||
Once it is a sprite it stays on the screen for nothing, and that is the point: a sprite is
|
||||
drawn from state the interpreter keeps, so it costs the program no work per frame at all.
|
||||
**Two consequences shape the rest of this chapter, and both are things you no longer have
|
||||
to do.**
|
||||
|
||||
`WINDOW 0, 0, 49, 1` would shrink the text area and hand the rest of the window to the
|
||||
drawing verbs, which is the other way to make a drawing visible. This program does not use
|
||||
it, because a drawing kept that way still has to be re-issued every frame *and* fit inside
|
||||
one batch (Step 5), and a sprite has neither constraint. Making the drawing verbs visible
|
||||
without a `WINDOW` call is [`TODO.md` §9 item 3](../TODO.md).
|
||||
There is no drawing deadline. The host runs a fixed number of source lines and then
|
||||
presents the frame, and a drawing longer than one batch used to be a problem — it does not
|
||||
matter here, because a drawing that spans two batches simply arrives over two frames and
|
||||
the layer keeps both halves.
|
||||
|
||||
The figures in this chapter are rendered by a tool that draws no text layer, which is why
|
||||
they can show a bare drawing at all.
|
||||
And there is nothing to redraw. If you change one brick, erase that brick; the other
|
||||
fifty-nine are still on the screen. Which is why this chapter has no routine that walks a
|
||||
list of everything still standing.
|
||||
|
||||
## Step 4: Make the brick stamps
|
||||
|
||||
@@ -242,7 +238,7 @@ x2, y2` draws a line using source 1.
|
||||
Each brick is 68 by 16 and is filled with horizontal lines. The inner loop draws **two
|
||||
scan lines per pass**, `T1# + K#` and `T2# + K#`, so eight passes fill sixteen rows. That
|
||||
costs about a hundred and thirty lines for the whole set instead of the two hundred and
|
||||
seventy a line-at-a-time loop would take, and Step 5 explains why that number matters.
|
||||
seventy a line-at-a-time loop would take.
|
||||
|
||||
Capturing the six is six `SSHAPE`s:
|
||||
|
||||
@@ -255,19 +251,18 @@ SSHAPE Z$, 0, 80, 68, 96 : S4$ = Z$
|
||||
SSHAPE Z$, 0, 100, 68, 116 : S5$ = Z$
|
||||
```
|
||||
|
||||
**`SSHAPE` has sixteen slots and nothing gives one back.** `GRAPHIC 5` gives back all of
|
||||
them at once, so count what you have spent and rebuild the stamps when the pool runs dry:
|
||||
**`SSHAPE` has sixteen slots and nothing gives one back**, and this program spends eight of
|
||||
them: six brick colours and the two erasers in Step 6. Spent once, at startup, and never
|
||||
again — nothing here captures per frame, so there is no pool to run dry and no rebuild:
|
||||
|
||||
```basic norun
|
||||
LABEL DRAWPROTOS
|
||||
GRAPHIC 5
|
||||
GRAPHIC 1, 1
|
||||
WIDTH 1
|
||||
SHN# = 99
|
||||
```
|
||||
|
||||
with the six `DRAW` loops and six `SSHAPE`s after it, and `SHN# = 6` at the end. Every
|
||||
routine that captures adds one to `SHN#`, and the frame loop rebuilds when it reaches 14.
|
||||
with the six `DRAW` loops, Step 6's two erasers, and eight `SSHAPE`s after it. Called once,
|
||||
from the setup block.
|
||||
|
||||
**`PAINT` would be one statement instead of sixteen, and is not usable here.** It costs
|
||||
nearly four milliseconds a call; sixty of those is seven frames' worth of time.
|
||||
@@ -342,55 +337,12 @@ and 9.
|
||||
The finished game keeps its six stamps in six separate scalars — `S0$` to `S5$` — rather
|
||||
than in an array. An array works too.
|
||||
|
||||
## Step 5: Find the frame boundary
|
||||
|
||||
**Goal: a drawing that is not cut in half by the host presenting the frame.**
|
||||
|
||||
The host runs **256 source lines and then presents the frame**, and presenting throws the
|
||||
drawing buffer away. So all the drawing between a `GRAPHIC 1, 1` and its `SSHAPE` has to
|
||||
happen inside one of those batches. Draw more than fits and the capture comes back holding
|
||||
only what was issued since the present, over whatever the frame before it left behind —
|
||||
which looks exactly like a ghost.
|
||||
|
||||
`TI#` is the host's clock in sixtieths of a second, and it is refreshed **once per batch**.
|
||||
So the step on which `TI#` changes is the first step of a batch, and spinning until it
|
||||
changes is how a program finds a frame boundary:
|
||||
|
||||
```basic norun
|
||||
LABEL PACE
|
||||
LASTT# = TI#
|
||||
LABEL PACEEDGE
|
||||
IF TI# - LASTT# < 2 THEN GOTO PACEEDGE
|
||||
RETURN
|
||||
```
|
||||
|
||||
Two jiffies is thirty frames a second.
|
||||
|
||||
**`LASTT#` is sampled on entry rather than carried over from the last frame, and that is
|
||||
the whole correctness of the routine.** Carried over, a frame whose work ran long would
|
||||
find the time already spent, return immediately from somewhere in the middle of a batch,
|
||||
and ruin the capture that followed. Sampling here means the loop always sees `TI#` change
|
||||
under it, and a change is only ever seen on the first step of a batch.
|
||||
|
||||
Measured on one machine: after a jiffy edge, 220 lines of drawing survive the capture
|
||||
intact and 250 do not. Write every draw routine to stay near 200 and you have margin.
|
||||
|
||||
**Arithmetic is free.** A routine that computes for two thousand steps costs frame rate and
|
||||
nothing else — only *drawing* has a deadline. That single fact is what Steps 7 and 8 are
|
||||
built on: move everything that is not a drawing verb out of the routine that draws.
|
||||
|
||||
This is the host's budget rather than the interpreter's, so the pacing routine is the
|
||||
answer rather than a workaround for something being fixed;
|
||||
[`TODO.md` §9 item 5](../TODO.md) records the measurements and why no fix belongs in the
|
||||
library.
|
||||
|
||||
## Step 6: Draw at most one thing per frame
|
||||
## Step 5: Draw at most one thing per frame
|
||||
|
||||
**Goal: a frame loop that paces, draws one thing, and then plays the game.**
|
||||
|
||||
```basic norun
|
||||
LABEL FRAME
|
||||
GOSUB PACE
|
||||
GOSUB DRAWJOB
|
||||
GOSUB READKEYS
|
||||
IF STATE# = 0 THEN GOSUB TITLETICK
|
||||
@@ -402,19 +354,16 @@ IF RUNNING# = 0 THEN GOTO SHUTDOWN
|
||||
GOTO FRAME
|
||||
```
|
||||
|
||||
Pace first, because that is what puts the drawing at the top of a batch. Then at most one
|
||||
capture, because a capture is the only thing with a deadline. Then the game, which may
|
||||
take as long as it likes.
|
||||
At most one drawing job a frame, then the game. Nothing here has a deadline any more — a
|
||||
drawing that spans two batches arrives over two frames — but a queue of one still keeps the
|
||||
work even, and it keeps the *decision* about what needs redrawing in one place instead of
|
||||
scattered through the game.
|
||||
|
||||
`DRAWJOB` is a queue of one, chosen by dirty flags — stamps first, because everything else
|
||||
draws with them, then the field, then the HUD:
|
||||
|
||||
```basic norun
|
||||
LABEL DRAWJOB
|
||||
IF SHN# < 14 THEN GOTO DRAWJOB2
|
||||
GOSUB DRAWPROTOS
|
||||
RETURN
|
||||
LABEL DRAWJOB2
|
||||
IF DPLAY# = 0 THEN GOTO DRAWJOB3
|
||||
GOSUB DRAWPLAY
|
||||
DPLAY# = 0
|
||||
@@ -434,105 +383,43 @@ nobody can see it.
|
||||
Those are `LABEL`s and `GOTO`s rather than `BEGIN` blocks. Either works; the labels keep
|
||||
each arm to one `RETURN` and read the same.
|
||||
|
||||
## Step 7: Keep a flattened list of live bricks
|
||||
## Step 6: Erase one brick, not the whole field
|
||||
|
||||
**Goal: a draw routine that decides nothing.**
|
||||
**Goal: take a broken brick off the screen without redrawing the other fifty-nine.**
|
||||
|
||||
The draw routine has a deadline and the rest of the program does not, so any decision that
|
||||
can be made earlier should be. When a brick breaks, walk the grid once and write out a
|
||||
**list of the bricks still standing**, row by row — so a row becomes a contiguous run of
|
||||
that list, and drawing it is a stamp and an advance:
|
||||
Because a drawing stays, removing something means covering it up. There is no
|
||||
filled-rectangle verb — `BOX` outlines and `PAINT` costs about four milliseconds a call —
|
||||
so the cheapest way to blank a region is a stamp of something blank:
|
||||
|
||||
```basic norun
|
||||
LABEL BUILDLIVE
|
||||
LN# = 0
|
||||
FOR R# = 0 TO 5
|
||||
RS#(R#) = LN#
|
||||
RC#(R#) = 0
|
||||
GOSUB BUILDROW
|
||||
NEXT R#
|
||||
RETURN
|
||||
COLOR 1, 1
|
||||
FOR K# = 0 TO 15
|
||||
DRAW 1, 0, 130 + K# TO 67, 130 + K#
|
||||
NEXT K#
|
||||
SSHAPE Z$, 0, 130, 68, 146 : BL$ = Z$
|
||||
```
|
||||
|
||||
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#
|
||||
The same sixteen lines the brick stamps are made of, in the background colour. Erasing is
|
||||
then one statement:
|
||||
|
||||
```basic norun
|
||||
LABEL ERASEBRICK
|
||||
Z$ = BL$
|
||||
GSHAPE Z$, BRKX# + C# * 72, BRKY# + R# * 24
|
||||
RETURN
|
||||
```
|
||||
|
||||
`LX#()` and `LY#()` are the pixel positions of the bricks still alive. `RS#(R#)` is where
|
||||
row `R#`'s run starts in that list and `RC#(R#)` is how long it is.
|
||||
Make a second one the width of the HUD strip while you are here, for the same reason: the
|
||||
strip is rewritten whenever a number in it changes, and the old digits have to go
|
||||
somewhere before the new ones are drawn.
|
||||
|
||||
Drawing a row is then one loop with no tests in it:
|
||||
**This is what makes the whole field a draw-once job.** Draw the walls and all sixty bricks
|
||||
when a level is laid out, and after that touch only what changes. A program that had to
|
||||
redraw the field to remove one brick would need a list of what is still standing, kept in
|
||||
step with the array, walked in runs per row — and none of that is here, because none of it
|
||||
is needed.
|
||||
|
||||
```basic norun
|
||||
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
|
||||
```
|
||||
|
||||
and `DRAWPLAY` calls it six times, once per stamp:
|
||||
|
||||
```basic norun
|
||||
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
|
||||
```
|
||||
|
||||
Building the list costs about four lines a brick and has all the time in the world.
|
||||
Drawing costs two and has a deadline. **That trade is the spine of this program**, and it
|
||||
comes back in Step 8 for the lettering.
|
||||
|
||||
### Write the one-item case out beside every loop
|
||||
|
||||
Notice the two lines before the `FOR` in `STAMPROW`. **A `FOR` whose bounds are equal does
|
||||
not run its body**, and the last brick left in a row is exactly that case:
|
||||
|
||||
```basic
|
||||
FOR I# = 0 TO 0
|
||||
PRINT "THE BODY RAN"
|
||||
NEXT I#
|
||||
PRINT "AFTER THE LOOP"
|
||||
```
|
||||
|
||||
```output
|
||||
AFTER THE LOOP
|
||||
```
|
||||
|
||||
So a loop over a list of unknown length needs its count of one written out beside it, as
|
||||
`STAMPROW` does. The alternative is a `DO ... LOOP UNTIL`, which tests at the bottom and
|
||||
therefore always runs once:
|
||||
|
||||
```basic
|
||||
I# = 0
|
||||
DO
|
||||
PRINT "PASS " + I#
|
||||
I# = I# + 1
|
||||
LOOP UNTIL I# >= 1
|
||||
```
|
||||
|
||||
```output
|
||||
PASS 0
|
||||
```
|
||||
|
||||
Use whichever reads better. This is [`TODO.md` §6 item 19](../TODO.md), which is waiting on
|
||||
a decision about a checked-in acceptance file rather than on the work; when it lands,
|
||||
`FOR I# = 0 TO 0` will run once and both shapes above will still be correct.
|
||||
|
||||
## Step 8: Draw lettering with a stroke font
|
||||
## Step 7: Draw lettering with a stroke font
|
||||
|
||||
**Goal: a HUD in more than one colour.**
|
||||
|
||||
@@ -640,11 +527,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
|
||||
```
|
||||
|
||||
@@ -654,11 +536,14 @@ score yellow, the lives green, the level red. **Seven colour sources is the ceil
|
||||
which is why the purple gem's banner is the closest purple the palette has rather than the
|
||||
gem's own.
|
||||
|
||||
Note the one-stroke case written out beside the loop, for the reason in Step 7. And note
|
||||
`SHN# = SHN# + 1`: every routine that captures counts the slot it spent, which is what
|
||||
Step 4's rebuild-when-dry test reads.
|
||||
Note the one-stroke case written out beside the loop. **A `FOR` with equal bounds does not
|
||||
run its body at all in this dialect**, so `FOR I# = 0 TO HN# - 1` with one stroke in the
|
||||
list draws nothing. Write the one-item case out beside the loop with a `GOTO` past the
|
||||
loop, and do it wherever a list can hold exactly one thing — a one-character word, a
|
||||
one-stroke glyph, a field with one brick left. [Chapter 13](13-differences.md) records the
|
||||
rule; `TODO.md` §6 item 19 records why it stands.
|
||||
|
||||
## Step 9: Move and bounce the ball
|
||||
## Step 8: Move and bounce the ball
|
||||
|
||||
**Goal: bounces at sensible angles, computed without a square root.**
|
||||
|
||||
@@ -692,12 +577,12 @@ RETURN
|
||||
```
|
||||
|
||||
`T1#` is the bar's left edge, `T2#` its top and `T3#` its width, so the same routine serves
|
||||
the paddle and the catcher bar — `BALLPADDLE` in Step 13 calls it twice with different
|
||||
the paddle and the catcher bar — `BALLPADDLE` in Step 12 calls it twice with different
|
||||
values and reads `HIT#` to find out whether the first call already caught the ball. The
|
||||
four tests at the top are the overlap; each can bail out, and reaching the fifth line means
|
||||
the ball is on the bar.
|
||||
|
||||
Step 10 adds two more lines before that `RETURN`, for the sticky gem.
|
||||
Step 9 adds two more lines before that `RETURN`, for the sticky gem.
|
||||
|
||||
### Which type a variable is decides the arithmetic
|
||||
|
||||
@@ -768,92 +653,97 @@ scaling that up to the new speed multiplies everything by 1/0.53, and the ball e
|
||||
nearly twice as fast as intended. Every zone inflates; only the middle two are close enough
|
||||
to vertical for it not to show.
|
||||
|
||||
A ball can only be over four cells at once, so work out **which** cells from its bounding
|
||||
box rather than walking all sixty bricks:
|
||||
### Bricks are collision geometry, not arithmetic
|
||||
|
||||
A brick is a rectangle the interpreter knows about. Register one per brick as the level is
|
||||
laid out:
|
||||
|
||||
```basic norun
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
`LFT#`, `TOP#`, `RGT#` and `BOT#` are the ball's own box — the artwork is 22 pixels across,
|
||||
so the ball's right edge is its left plus 21. `CC1#` to `RR2#` are the range of cells that
|
||||
box can touch, clamped to the field. Both loops are `DO ... LOOP UNTIL` rather than `FOR`,
|
||||
because a ball entirely inside one column gives `CC1#` and `CC2#` the same value and a
|
||||
`FOR` would not run at all.
|
||||
The id is the array index plus one, so what comes back out indexes `BRK#()` with no lookup.
|
||||
Compute it into `SLI#` first rather than writing the expression inline — a parenthesised
|
||||
first argument to a verb is worth avoiding, and it reads better anyway.
|
||||
|
||||
One brick at a time, then. A collision reflects off whichever face the ball has less of
|
||||
itself past. `T1#` to `T4#` are the **overlapping** rectangle, and its width against its
|
||||
height is the whole test:
|
||||
**Retire the previous level's rectangles before laying the next one out**, not after. That
|
||||
is one line in `SETUPLEVEL`, and putting it in the wrong place is silent: registering all
|
||||
sixty and then clearing them leaves a wall the ball passes straight through, with no error
|
||||
and nothing to see.
|
||||
|
||||
Then arm a handler and let it do the work:
|
||||
|
||||
```basic norun
|
||||
LABEL TESTCELL
|
||||
N# = R# * 10 + C#
|
||||
COLLISION 2, BRICKHIT
|
||||
```
|
||||
|
||||
```basic norun
|
||||
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
|
||||
```
|
||||
|
||||
**The mask is by sprite, and the balls are sprites 5, 6 and 7** — so their bits are 16, 32
|
||||
and 64, not 1, 2 and 4. That is the same `5 + B#` arithmetic `MOVSPR` uses for them
|
||||
everywhere else, and getting it wrong costs nothing visible: the handler runs, tests bits
|
||||
nothing sets, and returns.
|
||||
|
||||
```basic norun
|
||||
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#)
|
||||
SOUND 1, 17175 - R# * 2100, 4
|
||||
HIT# = 1
|
||||
GOSUB BUILDLIVE
|
||||
GOSUB ERASEBRICK
|
||||
GOSUB HUDTEXT
|
||||
DPLAY# = 1
|
||||
IF GMON# = 1 THEN RETURN
|
||||
RNMAX# = 7
|
||||
GOSUB NEXTRAND
|
||||
IF RNVAL# = 0 THEN GOSUB SPAWNGEM
|
||||
RETURN
|
||||
```
|
||||
|
||||
That is the standard box resolution, and it is the reason a ball clipping the end of a row
|
||||
goes sideways instead of straight back down.
|
||||
Fields 2, 3 and 4 push the ball exactly clear along the contact normal. Field 7 is which
|
||||
axis to reverse — **the minimum translation axis**, which is why a ball clipping the end of
|
||||
a row goes sideways rather than straight back down. Working that out by hand means building
|
||||
an overlap rectangle and comparing its width to its height; it is one field here.
|
||||
|
||||
Everything after `BRK#(N#) = 0` is the consequences of the hit, in one place: score it,
|
||||
sound it, rebuild the live list from Step 7, rebuild the HUD text, mark the field dirty so
|
||||
Step 6's queue redraws it, and roll one chance in seven for a gem — but only if there is not
|
||||
one already falling, because Step 2 left exactly one slot for it. `BX1#` and `BY1#` are the
|
||||
broken brick's own corner, which is where the gem will appear.
|
||||
`SOLID N# + 1` retires the rectangle in the same statement that clears the array, so the
|
||||
next frame cannot hit a brick that is no longer drawn. `GOSUB ERASEBRICK` takes it off the
|
||||
screen with Step 6's blank stamp.
|
||||
|
||||
## Step 10: Gems and powerups
|
||||
**Both records exist on purpose.** `BRK#()` is the program's — it is what "is the level
|
||||
clear" counts and what the field redraw reads. The rectangles are the interpreter's. Keeping
|
||||
them in step is two statements and the ids line up, which is the whole reason the id is the
|
||||
index plus one.
|
||||
|
||||
## Step 9: Gems and powerups
|
||||
|
||||
**Goal: one falling gem that can be any of five things.**
|
||||
|
||||
@@ -948,7 +838,7 @@ NEXT B#
|
||||
RETURN
|
||||
```
|
||||
|
||||
STICKY is three lines at the end of `HITBAR` in Step 9, replacing its final `RETURN` — the
|
||||
STICKY is three lines at the end of `HITBAR` in Step 8, replacing its final `RETURN` — the
|
||||
ball stops where it landed and remembers its offset along the bar:
|
||||
|
||||
```basic norun
|
||||
@@ -973,7 +863,7 @@ RETURN
|
||||
```
|
||||
|
||||
The CATCH bar **mirrors** the paddle rather than following it — the line is in `MOVEPADDLE`
|
||||
in Step 13:
|
||||
in Step 12:
|
||||
|
||||
```basic norun
|
||||
CTX% = 0.0 - PDX% + WALLL# + WALLR# - 104
|
||||
@@ -981,7 +871,7 @@ CTX% = 0.0 - PDX% + WALLL# + WALLR# - 104
|
||||
|
||||
A second bar directly above the first is worth nothing; a mirrored one turns a dive across
|
||||
the field into a save at both ends. Note the leading `0.0` rather than `0`, for the reason
|
||||
in Step 9.
|
||||
in Step 8.
|
||||
|
||||
### Every timer in one place
|
||||
|
||||
@@ -1032,7 +922,7 @@ Each arm is the same shape: count down, and on the frame it reaches zero, put ba
|
||||
gem changed. SLOW expiring calls `LEVELSPEED` rather than assigning a number, so the speed
|
||||
it returns to is the speed the current level should be running at.
|
||||
|
||||
## Step 11: Three voices and a mute
|
||||
## Step 10: Three voices and a mute
|
||||
|
||||
**Goal: sound that does not cut itself off, and a mute that costs one line.**
|
||||
|
||||
@@ -1078,9 +968,9 @@ TEMPO 12
|
||||
PLAY "V3 T2 U8 O4 QC QE QG O5 HC"
|
||||
```
|
||||
|
||||
## Step 12: The states
|
||||
## Step 11: The states
|
||||
|
||||
**Goal: the five states the frame loop in Step 6 dispatches to.**
|
||||
**Goal: the five states the frame loop in Step 5 dispatches to.**
|
||||
|
||||
Each is a subroutine called once a frame while that state is current. This is a different
|
||||
shape from Chapter 17's branch targets, and it works here because nothing jumps out of a
|
||||
@@ -1139,10 +1029,10 @@ BRN# = 0
|
||||
FOR I# = 0 TO 59
|
||||
BRK#(I#) = 0
|
||||
NEXT I#
|
||||
SOLID
|
||||
FOR R# = 0 TO 5
|
||||
IF R# < T3# THEN GOSUB FILLROW
|
||||
NEXT R#
|
||||
GOSUB BUILDLIVE
|
||||
GOSUB RESETBALL
|
||||
GOSUB HUDTEXT
|
||||
STATE# = 1
|
||||
@@ -1159,7 +1049,7 @@ RETURN
|
||||
called from level setup, from a re-serve and from the title screen — so no state can leak
|
||||
from one game into the next.
|
||||
|
||||
## Step 13: Put the program together
|
||||
## Step 12: Put the program together
|
||||
|
||||
**Goal: one file, in an order that runs.**
|
||||
|
||||
@@ -1167,18 +1057,21 @@ The file runs from the top, so three things about the order matter and nothing e
|
||||
setup first, `DATA` in the order it will be read, and every `LABEL` present somewhere.
|
||||
|
||||
```text
|
||||
WINDOW 0, 35, 49, 36 the text layer, out of the way -- Step 3
|
||||
the declaration block, below
|
||||
GOSUB LOADTABLES the row colours, values and bounce zones
|
||||
GOSUB LOADFONT the stroke font
|
||||
SEED# = MOD(1 + TI# * 7919, 2147483648)
|
||||
the artwork from Step 1
|
||||
VOL 8 / ENVELOPE / TEMPO from Step 11
|
||||
VOL 8 / ENVELOPE / TEMPO from Step 10
|
||||
COLLISION 2, BRICKHIT the brick handler -- Step 8
|
||||
GOSUB DRAWPROTOS the stamps, once -- Steps 4 and 6
|
||||
GOSUB TITLESCREEN
|
||||
|
||||
LABEL FRAME the frame loop from Step 6
|
||||
LABEL FRAME the frame loop from Step 5
|
||||
|
||||
the pacing, the draw jobs, the input, the states, the ball,
|
||||
the gems, the text builders, the generator
|
||||
the draw jobs, the input, the states, the ball, the gems,
|
||||
the text builders, the generator
|
||||
--- in any order ---
|
||||
|
||||
the tables as DATA read by LOADTABLES
|
||||
@@ -1241,19 +1134,14 @@ GMX% = 0
|
||||
GMY% = 0
|
||||
|
||||
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
|
||||
|
||||
S0$ = "" : S1$ = "" : S2$ = ""
|
||||
S3$ = "" : S4$ = "" : S5$ = ""
|
||||
BL$ = "" : HBL$ = ""
|
||||
Z$ = ""
|
||||
SHN# = 99
|
||||
DHUD# = 1
|
||||
DPLAY# = 1
|
||||
BAN$ = ""
|
||||
@@ -1294,10 +1182,18 @@ LFT# = 0 : TOP# = 0 : RGT# = 0 : BOT# = 0
|
||||
CC1# = 0 : CC2# = 0 : RR1# = 0 : RR2# = 0
|
||||
HIT# = 0
|
||||
SEED# = 1 : RNMAX# = 2 : RNVAL# = 0
|
||||
MB# = 0 : AX# = 0 : DP% = 0.0
|
||||
SLX# = 0 : SLY# = 0 : SLI# = 0
|
||||
```
|
||||
|
||||
`SHN# = 99` means "the brick stamps do not exist", so the frame loop's first job is to
|
||||
build them. `DPLAY#` and `DHUD#` start at 1 so the first frame draws both.
|
||||
**`BL$` and `HBL$` are in that list for a reason worth learning from.** They are the two
|
||||
eraser stamps, and they are built inside `DRAWPROTOS` — so left undeclared they were
|
||||
created *in that routine's scope*, held a perfectly good handle while it ran, and were
|
||||
empty everywhere else. Nothing failed; the first `GSHAPE` that used one simply refused with
|
||||
"was given a string that did not come from SSHAPE", several routines away from the cause.
|
||||
Chapter 17 Step 3 is about exactly this.
|
||||
|
||||
`DPLAY#` and `DHUD#` start at 1 so the first frame draws both.
|
||||
|
||||
That block is 121 of the interpreter's 128 variables, which is why several constants in the
|
||||
geometry are spelled out where they are used rather than given names: a name costs a slot
|
||||
@@ -1334,7 +1230,7 @@ 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
|
||||
```
|
||||
|
||||
Row colours, what a brick in each row is worth, and the eight bounce zones from Step 9.
|
||||
Row colours, what a brick in each row is worth, and the eight bounce zones from Step 8.
|
||||
|
||||
`NEXTRAND` is a `GOSUB` over globals rather than a `DEF` function. Set `RNMAX#` to the
|
||||
number of answers you want and read `RNVAL#`.
|
||||
@@ -1527,7 +1423,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#)
|
||||
@@ -1570,9 +1465,9 @@ RETURN
|
||||
|
||||
Balls 0, 1 and 2 live in sprites 5, 6 and 7, which is why `MOVSPR 5 + B#` works.
|
||||
`BLST#(B#)` is 1 while a ball is stuck to the paddle. Note the `0.0 -` on every sign flip,
|
||||
for the reason in Step 9.
|
||||
for the reason in Step 8.
|
||||
|
||||
`BALLBRICKS` and `TESTCELL` are in Step 9.
|
||||
Brick collision is in Step 8.
|
||||
|
||||
### The gem's fall
|
||||
|
||||
@@ -1597,7 +1492,7 @@ HIT# = 1
|
||||
RETURN
|
||||
```
|
||||
|
||||
`TAKEGEM` is in Step 10. `HIT#` is doing double duty here as "this gem is finished with" —
|
||||
`TAKEGEM` is in Step 9. `HIT#` is doing double duty here as "this gem is finished with" —
|
||||
it is set both by the gem going off the bottom and by it being caught.
|
||||
|
||||
### Turning text into strokes
|
||||
@@ -1771,7 +1666,7 @@ RETURN
|
||||
colour rather than the last gem's.
|
||||
|
||||
`TITLESCREEN` and `GAMEISOVER` are longer only because both draw a screenful of lettering.
|
||||
Both clear the brick array and call `BUILDLIVE` so the field draws empty, turn the paddle
|
||||
Both clear the brick array and retire every rectangle with a bare `SOLID`, turn the paddle
|
||||
and ball sprites off, reset `PN#`, and then build their text at a large `TXS#`:
|
||||
|
||||
```basic norun
|
||||
@@ -1784,7 +1679,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
|
||||
@@ -1886,10 +1781,8 @@ actually calls, with those pieces in place.
|
||||
|
||||
```basic norun
|
||||
LABEL DRAWPROTOS
|
||||
GRAPHIC 5
|
||||
GRAPHIC 1, 1
|
||||
WIDTH 1
|
||||
SHN# = 99
|
||||
FOR R# = 0 TO 5
|
||||
COLOR 1, BRC#(R#)
|
||||
T1# = R# * 20
|
||||
@@ -1904,15 +1797,11 @@ 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
|
||||
```
|
||||
|
||||
`GRAPHIC 5` hands all sixteen `SSHAPE` slots back, which is the only way to get any of them
|
||||
back — so `SHN#` goes to 99 first and to 6 at the end, and both flags are set because the
|
||||
field and the HUD were drawn with stamps that no longer exist.
|
||||
plus Step 6's two erasers, and then `DPLAY# = 1` and `DHUD# = 1` so the first frame draws
|
||||
both. Called once from the setup block: nothing captures per frame, so eight of the sixteen
|
||||
`SSHAPE` slots are spent here and never again, and `GRAPHIC 5` is never called at all.
|
||||
|
||||
```basic norun
|
||||
LABEL DRAWPLAY
|
||||
@@ -1921,29 +1810,29 @@ 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
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
Same shape as `DRAWHUD` in Step 8: the walls, then the bricks, then the field's own stroke
|
||||
list with its one-item case beside the loop, then Step 3's four lines that turn all of it
|
||||
into sprite 2.
|
||||
Same shape as `DRAWHUD` in Step 7: the walls, then the bricks, then the field's own stroke
|
||||
list with its one-item case beside the loop. Nothing at the end — what is drawn is on the
|
||||
screen.
|
||||
|
||||
**A label is one bare word.** `PLDONE` and `HUDONE` have no underscore in them, and cannot:
|
||||
underscores are not part of an identifier here.
|
||||
@@ -1963,7 +1852,7 @@ BRN# = 0
|
||||
FOR I# = 0 TO 59
|
||||
BRK#(I#) = 0
|
||||
NEXT I#
|
||||
GOSUB BUILDLIVE
|
||||
SOLID
|
||||
SPRITE 3, 0
|
||||
SPRITE 5, 0
|
||||
PN# = 0
|
||||
@@ -2021,10 +1910,10 @@ a feature costing an afternoon before it is abandoned.
|
||||
|
||||
| Resource | There are | This game uses |
|
||||
|---|---|---|
|
||||
| Sprites | 8 | 8 |
|
||||
| Variables | 128 | 121, plus 4 the interpreter makes |
|
||||
| Labels | 64 | 61 |
|
||||
| `SSHAPE` slots | 16, none reclaimed except by `GRAPHIC 5` | 6 stamps plus 1 capture a frame |
|
||||
| Sprites | 8 | 6 |
|
||||
| Variables | 128 | 123, plus 4 the interpreter makes |
|
||||
| Labels | 64 | 57 |
|
||||
| `SSHAPE` slots | 16, none reclaimed except by `GRAPHIC 5` | 8, spent once at startup |
|
||||
| Colour sources | 7 | 7 |
|
||||
| Scopes | 32 | 6 deep at most |
|
||||
| Tokens on a line | 32, and the 33rd stops the interpreter rather than raising | short lines, temporaries instead of long conditions |
|
||||
@@ -2043,7 +1932,7 @@ Step 1's artwork, with the numbers filled in by hand and no frame loop, so it dr
|
||||
frame and stops.
|
||||
|
||||
The font here carries only the fourteen glyphs the HUD needs, in the order they appear in
|
||||
`ALPHA$` — which is the ordering rule from Step 8, shown small enough to check by eye:
|
||||
`ALPHA$` — which is the ordering rule from Step 7, shown small enough to check by eye:
|
||||
|
||||
```basic requires=akgl setup=breakout_art screenshot=breakout-game-artwork size=800x600
|
||||
DIM BRC#(6)
|
||||
|
||||
Reference in New Issue
Block a user