Reflow the approved reference cases, shorten local fixture comments, and split the executable docs line so the 80-byte input buffer can read every source line. Co-authored-by: andrew <andrew@aklabs.net>
2190 lines
60 KiB
Markdown
2190 lines
60 KiB
Markdown
# 18. Tutorial: Breakout with artwork
|
||
|
||
This chapter builds Breakout a second time, out of loaded PNG artwork, with a drawn
|
||
brick field, five powerups, three voices of sound and a HUD in colour. It is a bigger
|
||
program than [Chapter 17](17-tutorial-breakout.md)'s and it uses a completely different set
|
||
of verbs: everything here is drawn or loaded, and nothing is written into the text grid.
|
||
|
||
This is what you are building:
|
||
|
||

|
||
|
||
The finished listing is
|
||
[`examples/breakout/sprites/breakout.bas`](../examples/breakout/sprites/breakout.bas).
|
||
|
||
Read Chapter 17 first if you have not. The rules it teaches — declare every name up front,
|
||
loop with `GOTO`, parenthesise mixed `+` and `-`, build a random-number generator — all
|
||
apply here too, and are not repeated.
|
||
|
||
One of them is worth repeating, because this chapter leans on it harder than Chapter 17
|
||
does. **`#` is an integer, `%` is a float and `$` is a string.** That is the opposite way
|
||
round from Commodore BASIC, and it is why ball positions and velocities here are `BLX%` and
|
||
`BLVX%` while counters and flags are `BLN#` and `BLON#`. Get one wrong and the ball moves in
|
||
whole pixels, or stops.
|
||
|
||
[Chapter 3](03-the-language.md) is the language reference for the rest — `MOD`, `INSTR`,
|
||
`MID`, `LEN` — and [Chapter 4](04-control-flow.md) is `BEGIN`/`BEND`, `DO ... LOOP UNTIL`
|
||
and `GOSUB`.
|
||
|
||
```sh norun
|
||
$ ./build-akgl/basic examples/breakout/sprites/breakout.bas
|
||
```
|
||
|
||
| Key | Does |
|
||
|---|---|
|
||
| left / right | move the paddle |
|
||
| space | start a game, launch the ball, release a stuck ball |
|
||
| P | pause |
|
||
| S | sound on and off |
|
||
| Q or escape | quit |
|
||
|
||
A broken brick drops a gem about one time in seven. Catch it with the paddle; the colour
|
||
tells you which it is.
|
||
|
||
| Gem | Name | Does | For |
|
||
|---|---|---|---|
|
||
| red | EXPAND | doubles the paddle's width | 20 seconds |
|
||
| yellow | MULTI | throws two more balls off the one in play | until they are lost |
|
||
| green | SLOW | drops the ball's speed to about two thirds | 16 seconds |
|
||
| blue | STICKY | the ball sticks where it lands; space fires it | 18 seconds |
|
||
| purple | CATCH | a second bar appears higher up the field | 24 seconds |
|
||
|
||
## What you will do
|
||
|
||
- **[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-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-draw-at-most-one-thing-per-frame)** — draw at most one thing per
|
||
frame, chosen by dirty flags
|
||
- **[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 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 11](#step-11-the-states)** — the frame loop's states: title, serve, play, lost,
|
||
cleared
|
||
- **[Step 12](#step-12-put-the-program-together)** — put the pieces in one file, in the
|
||
right order
|
||
|
||
---
|
||
|
||
## Step 1: Put artwork on the screen
|
||
|
||
**Goal: a picture from a file, on the screen, at its own size.**
|
||
|
||
`SPRSAV` takes an image file path as well as an array of pattern bytes, and a sprite
|
||
loaded from a file **keeps the image's own size** rather than being squeezed into 24 by
|
||
21. That is the whole of it:
|
||
|
||
```basic requires=akgl setup=breakout_art screenshot=breakout-artwork
|
||
I# = 0
|
||
SPRSAV "art/paddleBlu.png", 3
|
||
SPRSAV "art/paddleRed.png", 4
|
||
SPRSAV "art/ballBlue.png", 5
|
||
SPRSAV "art/element_red_polygon_glossy.png", 6
|
||
SPRSAV "art/element_green_polygon_glossy.png", 7
|
||
SPRSAV "art/element_purple_polygon_glossy.png", 8
|
||
FOR I# = 3 TO 8
|
||
SPRITE I#, 1, 2
|
||
NEXT I#
|
||
MOVSPR 3, 20, 20
|
||
MOVSPR 4, 20, 60
|
||
MOVSPR 5, 160, 30
|
||
MOVSPR 6, 30, 120
|
||
MOVSPR 7, 130, 120
|
||
MOVSPR 8, 230, 120
|
||
```
|
||
|
||

|
||
|
||
That is the whole game's cast: two bars, a ball and five gems.
|
||
|
||
**Write down the sizes now**, because every collision test later in this chapter is built
|
||
out of them:
|
||
|
||
| Artwork | Is | Shows up later as |
|
||
|---|---|---|
|
||
| `paddleBlu.png`, `paddleRed.png` | 104 by 24 | `PDW# = 104`, and the bar's height in `HITBAR` |
|
||
| `ballBlue.png`, `ballGrey.png` | 22 by 22 | `+ 21` on every edge of the ball's box |
|
||
| the five gems | 48 by 46 | the `+ 48` and `+ 46` in the catch test |
|
||
|
||
Three more things to note.
|
||
|
||
**The path is tried against the working directory first, then against the directory the
|
||
program was loaded from.** A `.bas` stored beside its own `art/` folder therefore runs from
|
||
anywhere.
|
||
|
||
**`SPRITE n, 1, 2` turns sprite `n` on in colour 2.** A sprite's colour *multiplies* the
|
||
artwork rather than replacing it, so colour 2 — white — is the one that leaves loaded
|
||
artwork looking like itself. Any other colour tints it.
|
||
|
||
**Put the art where the licence lets you.** The artwork here is Kenney's
|
||
[Puzzle Pack 1](https://kenney.nl/assets/puzzle-pack-1), released under
|
||
[CC0](http://creativecommons.org/publicdomain/zero/1.0/), and
|
||
`examples/breakout/sprites/art/PROVENANCE.md` records which file is used for what. CC0
|
||
does not require crediting Kenney. Do it anyway.
|
||
|
||
## Step 2: Budget the eight sprite slots
|
||
|
||
**Goal: know what all eight sprites are before you write the program.**
|
||
|
||
There are eight sprite slots and no more. That is not a limit you will design your way
|
||
around later, so spend them on paper first. This game spends them like this:
|
||
|
||
| Slot | Is |
|
||
|---|---|
|
||
| 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 |
|
||
|
||
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`
|
||
cannot stamp a sprite and `SPRSAV` cannot read one back out.
|
||
- **Only one gem falls at a time.** There is one slot for it, so a brick broken while a gem
|
||
is already falling drops nothing.
|
||
|
||
Deciding this first is what stops a feature costing an afternoon before it is abandoned.
|
||
|
||
## Step 3: Make room for what you draw
|
||
|
||
**Goal: something you drew, still on the screen on the next frame.**
|
||
|
||
Two things have to be true before a drawing is any use, and only one of them is automatic.
|
||
|
||
**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
|
||
WINDOW 0, 35, 49, 36
|
||
```
|
||
|
||
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.
|
||
|
||
That is the whole of it. Draw your field once and it stays; draw your HUD once and it
|
||
stays.
|
||
|
||
**Two consequences shape the rest of this chapter, and both are things you no longer have
|
||
to do.**
|
||
|
||
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.
|
||
|
||
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
|
||
|
||
**Goal: six coloured bricks, and a field stamped out of them.**
|
||
|
||
Draw one brick per row colour, capture the six of them, and then stamp them wherever a
|
||
brick belongs with `GSHAPE`. Drawing six things and stamping sixty is far cheaper than
|
||
drawing sixty.
|
||
|
||
```basic requires=akgl screenshot=breakout-stamps size=100x130
|
||
DIM BRC#(6)
|
||
I# = 0
|
||
R# = 0
|
||
K# = 0
|
||
T1# = 0
|
||
T2# = 0
|
||
FOR I# = 0 TO 5
|
||
READ BRC#(I#)
|
||
NEXT I#
|
||
GRAPHIC 1, 1
|
||
WIDTH 1
|
||
FOR R# = 0 TO 5
|
||
COLOR 1, BRC#(R#)
|
||
T1# = R# * 20
|
||
T2# = T1# + 8
|
||
FOR K# = 0 TO 7
|
||
DRAW 1, 0, T1# + K# TO 67, T1# + K# : DRAW 1, 0, T2# + K# TO 67, T2# + K#
|
||
NEXT K#
|
||
NEXT R#
|
||
|
||
DATA 3, 9, 8, 6, 4, 5
|
||
```
|
||
|
||

|
||
|
||
Reading that from the top: `GRAPHIC 1, 1` selects the graphics mode and clears it.
|
||
`COLOR 1, BRC#(R#)` sets **colour source 1** to the palette entry that row wants — a
|
||
drawing verb names a source, not a colour, and there are seven sources. `DRAW 1, x1, y1 TO
|
||
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.
|
||
|
||
Capturing the six is six `SSHAPE`s:
|
||
|
||
```basic norun
|
||
SSHAPE Z$, 0, 0, 68, 16 : S0$ = Z$
|
||
SSHAPE Z$, 0, 20, 68, 36 : S1$ = Z$
|
||
SSHAPE Z$, 0, 40, 68, 56 : S2$ = Z$
|
||
SSHAPE Z$, 0, 60, 68, 76 : S3$ = Z$
|
||
SSHAPE Z$, 0, 80, 68, 96 : S4$ = Z$
|
||
SSHAPE Z$, 0, 100, 68, 116 : S5$ = Z$
|
||
```
|
||
|
||
**`SSHAPE` 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 1, 1
|
||
WIDTH 1
|
||
```
|
||
|
||
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.
|
||
|
||
Now the whole screen — walls, the brick field stamped out of the six, and the artwork on
|
||
top:
|
||
|
||
```basic requires=akgl setup=breakout_art screenshot=breakout-screen size=800x600
|
||
DIM BRC#(6)
|
||
I# = 0
|
||
R# = 0
|
||
C# = 0
|
||
K# = 0
|
||
T1# = 0
|
||
T2# = 0
|
||
Z$ = ""
|
||
FOR I# = 0 TO 5
|
||
READ BRC#(I#)
|
||
NEXT I#
|
||
GRAPHIC 1, 1
|
||
WIDTH 1
|
||
FOR R# = 0 TO 5
|
||
COLOR 1, BRC#(R#)
|
||
T1# = R# * 20
|
||
T2# = T1# + 8
|
||
FOR K# = 0 TO 7
|
||
DRAW 1, 0, T1# + K# TO 67, T1# + K# : DRAW 1, 0, T2# + K# TO 67, T2# + K#
|
||
NEXT K#
|
||
NEXT R#
|
||
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$
|
||
GRAPHIC 1, 1
|
||
WIDTH 2
|
||
COLOR 5, 16 : COLOR 1, 4
|
||
BOX 5, 2, 62, 797, 597
|
||
BOX 1, 6, 66, 793, 593
|
||
FOR C# = 0 TO 9
|
||
Z$ = S0$ : GSHAPE Z$, 42 + C# * 72, 108
|
||
Z$ = S1$ : GSHAPE Z$, 42 + C# * 72, 132
|
||
Z$ = S2$ : GSHAPE Z$, 42 + C# * 72, 156
|
||
Z$ = S3$ : GSHAPE Z$, 42 + C# * 72, 180
|
||
Z$ = S4$ : GSHAPE Z$, 42 + C# * 72, 204
|
||
Z$ = S5$ : GSHAPE Z$, 42 + C# * 72, 228
|
||
NEXT C#
|
||
SSHAPE Z$, 0, 60, 800, 600
|
||
SPRSAV Z$, 2
|
||
SPRITE 2, 1, 2
|
||
MOVSPR 2, 0, 60
|
||
SPRSAV "art/paddleBlu.png", 3
|
||
SPRSAV "art/ballBlue.png", 5
|
||
SPRITE 3, 1, 2
|
||
SPRITE 5, 1, 2
|
||
MOVSPR 3, 348, 540
|
||
MOVSPR 5, 389, 517
|
||
|
||
DATA 3, 9, 8, 6, 4, 5
|
||
```
|
||
|
||

|
||
|
||
Everything above the `SSHAPE Z$, 0, 60, 800, 600` is a drawing nobody would ever see.
|
||
Those four lines are what make it the screen.
|
||
|
||
A brick is 68 by 16 on a 72 by 24 pitch, ten columns by six rows, with the field's top-left
|
||
corner at (42, 108). Those numbers are worth writing down once; they come back in Steps 7
|
||
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: 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 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
|
||
```
|
||
|
||
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 DPLAY# = 0 THEN GOTO DRAWJOB3
|
||
GOSUB DRAWPLAY
|
||
DPLAY# = 0
|
||
RETURN
|
||
LABEL DRAWJOB3
|
||
IF DHUD# = 0 THEN RETURN
|
||
GOSUB DRAWHUD
|
||
DHUD# = 0
|
||
RETURN
|
||
```
|
||
|
||
The rest of the game never draws. It sets `DPLAY# = 1` or `DHUD# = 1` when something has
|
||
changed and gets on with its frame. One consequence is visible and deliberate: **the score
|
||
lags the bricks by one frame**, because the field is drawn first. At thirty frames a second
|
||
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 6: Erase one brick, not the whole field
|
||
|
||
**Goal: take a broken brick off the screen without redrawing the other fifty-nine.**
|
||
|
||
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
|
||
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$
|
||
```
|
||
|
||
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
|
||
```
|
||
|
||
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. It is the same code with a bigger rectangle:
|
||
|
||
```basic norun
|
||
FOR K# = 0 TO 59
|
||
DRAW 1, 0, 160 + K# TO 799, 160 + K#
|
||
NEXT K#
|
||
SSHAPE Z$, 0, 160, 800, 220 : HBL$ = Z$
|
||
```
|
||
|
||
Both blocks belong at the end of `DRAWPROTOS` in Step 4, drawn below the six brick
|
||
prototypes and captured with them, so the eight `SSHAPE` slots are all spent in one place.
|
||
`HBL$` is stamped at the top of the HUD redraw and `BL$` by `ERASEBRICK`.
|
||
|
||
**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.
|
||
|
||
## Step 7: Draw lettering with a stroke font
|
||
|
||
**Goal: a HUD in more than one colour.**
|
||
|
||
The text grid draws in one colour and has no verb that changes it — `CHAR` accepts a
|
||
colour argument and ignores it. A coloured HUD therefore has to be *drawn*, which means
|
||
carrying a font.
|
||
|
||
The font here is four units wide and seven tall, one glyph per `DATA` line: **how many
|
||
strokes, then that many pairs of points**. A point is coded `X * 10 + Y`, so 0 is the
|
||
top-left corner, 30 the top right, 6 the bottom left and 36 the bottom right.
|
||
|
||
```basic requires=akgl screenshot=breakout-lettering size=260x110
|
||
DIM FNC#(5)
|
||
DIM FNI#(5)
|
||
DIM FNS#(60)
|
||
I# = 0
|
||
K# = 0
|
||
N# = 0
|
||
D# = 0
|
||
GP# = 0
|
||
GX# = 0
|
||
GX2# = 0
|
||
P1# = 0
|
||
P2# = 0
|
||
X1# = 0
|
||
Y1# = 0
|
||
X2# = 0
|
||
Y2# = 0
|
||
FOR I# = 0 TO 4
|
||
READ N#
|
||
FNC#(I#) = N#
|
||
FNI#(I#) = GX#
|
||
GOSUB READGLYPH
|
||
NEXT I#
|
||
GRAPHIC 1, 1
|
||
WIDTH 2
|
||
COLOR 1, 8
|
||
SZ# = 9
|
||
FOR I# = 0 TO 4
|
||
GOSUB DRAWGLYPH
|
||
NEXT I#
|
||
END
|
||
|
||
LABEL READGLYPH
|
||
FOR K# = 1 TO N# * 2
|
||
READ D#
|
||
FNS#(GX#) = D#
|
||
GX# = GX# + 1
|
||
NEXT K#
|
||
RETURN
|
||
|
||
LABEL DRAWGLYPH
|
||
GP# = FNI#(I#)
|
||
GX2# = 20 + I# * SZ# * 5
|
||
FOR K# = 1 TO FNC#(I#)
|
||
P1# = FNS#(GP#)
|
||
P2# = FNS#(GP# + 1)
|
||
GP# = GP# + 2
|
||
X1# = GX2# + (P1# / 10) * SZ#
|
||
Y1# = 20 + MOD(P1#, 10) * SZ#
|
||
X2# = GX2# + (P2# / 10) * SZ#
|
||
Y2# = 20 + MOD(P2#, 10) * SZ#
|
||
DRAW 1, X1#, Y1# TO X2#, Y2#
|
||
NEXT K#
|
||
RETURN
|
||
|
||
REM S
|
||
DATA 5, 0,30, 0,3, 3,33, 33,36, 6,36
|
||
REM C
|
||
DATA 3, 0,30, 0,6, 6,36
|
||
REM O
|
||
DATA 4, 0,30, 6,36, 0,6, 30,36
|
||
REM R
|
||
DATA 5, 0,6, 0,30, 3,33, 30,33, 13,36
|
||
REM E
|
||
DATA 4, 0,6, 0,30, 3,33, 6,36
|
||
```
|
||
|
||

|
||
|
||
`SZ#` is the scale, so the same table draws a 12-unit `BREAKOUT` on the title screen and a
|
||
4-unit `SCORE` in the HUD.
|
||
|
||
Turning a character into a glyph number is one call: `INSTR(ALPHA$, MID(TX$, TXI#, 1))`
|
||
over an alphabet string. **`MID` and `INSTR` both count from zero in this dialect**, which
|
||
is the opposite of Commodore BASIC and the opposite of most other BASICs — `MID(A$, 0, 1)`
|
||
is the first character, and `INSTR` answers 0 for a match at the front and −1 for no match
|
||
at all. That is what makes the glyph number an array index with no adjustment, and it is
|
||
also why `MID("0123456789", D#, 1)` turns a digit straight into its character. That is why the order of the `DATA` lines matters — they have to
|
||
match the order of the characters in `ALPHA$`. The finished game uses a 41-character
|
||
alphabet covering `A` to `Z`, `0` to `9`, space, colon, dash, full stop and exclamation
|
||
mark.
|
||
|
||
**Building the strokes and drawing them are separate jobs, on different frames.** Turning a
|
||
string into strokes costs about sixteen lines a character and happens when a number
|
||
changes; the draw routine merely replays the list at two lines a stroke, and it is the one
|
||
with the deadline:
|
||
|
||
```basic norun
|
||
LABEL DRAWHUD
|
||
GRAPHIC 1, 0
|
||
WIDTH 1
|
||
COLOR 0, 1 : COLOR 1, 4 : COLOR 2, 8 : COLOR 3, 5
|
||
COLOR 4, 11 : COLOR 5, 16 : COLOR 6, 6
|
||
Z$ = HBL$
|
||
GSHAPE Z$, 0, 0
|
||
BOX 5, 0, 56, 799, 57
|
||
IF HN# = 1 THEN DRAW HC#(0), HX1#(0), HY1#(0) TO HX2#(0), HY2#(0)
|
||
IF HN# < 2 THEN GOTO HUDONE
|
||
FOR I# = 0 TO HN# - 1
|
||
DRAW HC#(I#), HX1#(I#), HY1#(I#) TO HX2#(I#), HY2#(I#)
|
||
NEXT I#
|
||
LABEL HUDONE
|
||
RETURN
|
||
```
|
||
|
||
**`GRAPHIC 1, 0` selects the graphics mode without clearing it**, and that is the whole
|
||
difference between this routine and the ones that draw the field. Clearing here would take
|
||
the field down with the old digits, so the strip is covered by Step 6's `HBL$` eraser at
|
||
`0, 0` instead — the drawing stays, so the only way to remove something is to draw over it.
|
||
`GRAPHIC 1, 1` is right in a routine that is about to redraw everything anyway.
|
||
|
||
**`HUDTEXT` builds the stroke list and `DRAWHUD` replays it**, on different frames. Call
|
||
`HUDTEXT` wherever a number in the HUD changes — level setup, a broken brick, a lost life,
|
||
a caught gem — and it sets `DHUD# = 1`, which is all it takes to get the strip drawn.
|
||
|
||
`HX1#()` to `HY2#()` are the stroke endpoints and `HC#()` is the colour source each stroke
|
||
wants, so one pass over the list draws in as many colours as it likes: labels cyan, the
|
||
score yellow, the lives green, the level red. **Seven colour sources is the ceiling**,
|
||
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. **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 8: Move and bounce the ball
|
||
|
||
**Goal: bounces at sensible angles, computed without a square root.**
|
||
|
||
There is no `SQR` in this dialect, so never compute a magnitude. Instead, tabulate eight
|
||
landing zones across the bar, each holding **very nearly a unit vector**, and make every
|
||
velocity one of them times the current speed:
|
||
|
||
```basic norun
|
||
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
|
||
```
|
||
|
||
Read those into `ZVX%()` and `ZVY%()`. A bar bounce is then: work out which zone the ball
|
||
landed in, and assign.
|
||
|
||
```basic norun
|
||
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
|
||
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 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 9 adds two more lines before that `RETURN`, for the sticky gem.
|
||
|
||
### Which type a variable is decides the arithmetic
|
||
|
||
Two rules matter here and they matter a lot, because breaking either produces **no error at
|
||
all** — the program computes something else and carries on.
|
||
|
||
**A ball's velocity must be a float.** `BLVX%` has a `%` suffix; a `#` variable holds
|
||
`-0.85 * 6.0` as `-5`, and a ball whose velocity is quantised to whole pixels bleeds speed
|
||
away at every bounce.
|
||
|
||
**The left operand of an expression decides whether it is done in integers or floats.**
|
||
Put the float on the left:
|
||
|
||
```basic
|
||
LEVEL# = 4
|
||
SPD% = 5.6 + LEVEL# * 0.45
|
||
PRINT "INTEGER FIRST " + SPD%
|
||
SPD% = 5.6 + 0.45 * LEVEL#
|
||
PRINT "FLOAT FIRST " + SPD%
|
||
V% = 6.4
|
||
PRINT "0 - V% " + (0 - V%)
|
||
PRINT "0.0 - V% " + (0.0 - V%)
|
||
```
|
||
|
||
```output
|
||
INTEGER FIRST 5.600000
|
||
FLOAT FIRST 7.400000
|
||
0 - V% -6
|
||
0.0 - V% -6.400000
|
||
```
|
||
|
||
`LEVEL# * 0.45` is zero because `LEVEL#` is an integer, so reversing a ball is
|
||
`0.0 - BLVX%(B#)` and never `0 - BLVX%(B#)`. This is a decision of the dialect rather than
|
||
a defect — [Chapter 3](03-the-language.md#the-left-operand-decides-whether-the-arithmetic-is-integer-or-float)
|
||
has the two rules that keep you out of it, and
|
||
[Chapter 13](13-differences.md) names it as the difference from BASIC 7.0 most likely to
|
||
turn a working listing into a quietly wrong one.
|
||
|
||
### Bricks
|
||
|
||
A bounce off a wall or a brick only ever flips a sign, so a ball is always travelling at
|
||
exactly the `SPD%` that was in force when it last left a bar. That makes changing the speed
|
||
a **ratio of two speeds** rather than a change of magnitude, which is what the SLOW gem
|
||
needs:
|
||
|
||
```basic norun
|
||
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
|
||
```
|
||
|
||
`RAT%` is a float on purpose: an integer one holds the 0.75 of a slowdown as 0 and stops
|
||
the ball dead.
|
||
|
||
Scaling by the *vertical component* instead — setting `BLVY%` to the new speed and taking
|
||
the horizontal along with it — is not a slowdown at all. A shallow ball's small vertical
|
||
gets stretched up and drags the large horizontal with it, so SLOW makes the ball faster. The
|
||
table above is why: the outermost zones keep only 0.53 of their speed in the vertical, so
|
||
scaling that up to the new speed multiplies everything by 1/0.53, and the ball ends up
|
||
nearly twice as fast as intended. Every zone inflates; only the middle two are close enough
|
||
to vertical for it not to show.
|
||
|
||
### 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 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
|
||
```
|
||
|
||
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.
|
||
|
||
**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
|
||
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. Sprite *n* sets bit 2 to the power of *n* − 1: sprite 1 is 1,
|
||
sprite 2 is 2, sprite 3 is 4, sprite 4 is 8, sprite 5 is 16, and so on to sprite 8 at 128. 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
|
||
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
|
||
BX1# = BRKX# + C# * 72
|
||
BY1# = BRKY# + R# * 24
|
||
GOSUB ERASEBRICK
|
||
GOSUB HUDTEXT
|
||
IF GMON# = 1 THEN RETURN
|
||
RNMAX# = 7
|
||
GOSUB NEXTRAND
|
||
IF RNVAL# = 0 THEN GOSUB SPAWNGEM
|
||
RETURN
|
||
```
|
||
|
||
`BX1#` and `BY1#` are the broken brick's top-left corner in pixels, and they are set here
|
||
because this is the only routine that knows it — Step 9's `SPAWNGEM` starts the gem falling
|
||
from that spot. The last four lines are the whole of "a broken brick drops a gem about one
|
||
time in seven": one roll, and `IF GMON# = 1 THEN RETURN` first because there is one gem
|
||
sprite and a gem already falling means no roll at all.
|
||
|
||
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.
|
||
|
||
`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.
|
||
|
||
**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.**
|
||
|
||
A gem is one sprite, one type number and two timers. The type picks the artwork, the
|
||
banner colour and what catching it does:
|
||
|
||
```basic norun
|
||
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
|
||
SPRITE 8, 1, 2
|
||
MOVSPR 8, GMX%, GMY%
|
||
RETURN
|
||
```
|
||
|
||
**Reloading slot 8 is how one sprite becomes five gems.** `SPRSAV` over a live slot replaces
|
||
its artwork, so there is no need for a slot per gem — which is just as well, because Step 2
|
||
did not leave one.
|
||
|
||
Catching one is one routine with five arms. Each sets a timer, sets the banner text, and
|
||
does whatever that gem does:
|
||
|
||
```basic norun
|
||
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
|
||
```
|
||
|
||
EXPAND is `SPRITE 3, 1, 2, 0, 1, 0` — the x-expand flag, which is the only scaling a sprite
|
||
has, and doubling the artwork is exactly what it wants. `PDW#` is the width the collision
|
||
test uses, so it has to be doubled with it.
|
||
|
||
MULTI throws two more balls off the one in play, at slightly different angles so they do
|
||
not travel as a stack:
|
||
|
||
```basic norun
|
||
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
|
||
```
|
||
|
||
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
|
||
IF STKON# = 0 THEN RETURN
|
||
BLST#(B#) = 1
|
||
BLOFF# = BLX%(B#) - PDX%
|
||
RETURN
|
||
```
|
||
|
||
and space releases it, straight up the middle:
|
||
|
||
```basic norun
|
||
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
|
||
```
|
||
|
||
The CATCH bar **mirrors** the paddle rather than following it — the line is in `MOVEPADDLE`
|
||
in Step 12:
|
||
|
||
```basic norun
|
||
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 8.
|
||
|
||
### Every timer in one place
|
||
|
||
A timer is a frame count decremented in one routine, so an expiry is exactly where the
|
||
effect is undone and there is only one place to look:
|
||
|
||
```basic norun
|
||
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
|
||
```
|
||
|
||
Each arm is the same shape: count down, and on the frame it reaches zero, put back what the
|
||
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 10: Three voices and a mute
|
||
|
||
**Goal: sound that does not cut itself off, and a mute that costs one line.**
|
||
|
||
Give each kind of sound its own voice. Voice 1 is the ball hitting things, voice 2 is the
|
||
gem and the ball being lost, and voice 3 is reserved for `PLAY` — so a brick going cannot
|
||
cut a tune off mid-note.
|
||
|
||
**`SOUND`'s frequency argument is a SID register value, not hertz.** The pitch is
|
||
`register * 1022730 / 16777216` — see [Chapter 7](07-sound.md#sound) — so work the notes
|
||
out once and write them down: 17175 is C6, 8579 C5, 4298 C4, 3609 A3.
|
||
|
||
Pitch the brick tone by the row it came from and the wall plays itself down a scale as it
|
||
comes apart:
|
||
|
||
```basic norun
|
||
SOUND 1, 17175 - R# * 2100, 4
|
||
```
|
||
|
||
Mute with `VOL 0` rather than a flag tested at every call site:
|
||
|
||
```basic norun
|
||
LABEL PRESSMUTE
|
||
SNDON# = 1 - SNDON#
|
||
IF SNDON# = 1 THEN VOL 8
|
||
IF SNDON# = 0 THEN VOL 0
|
||
RETURN
|
||
```
|
||
|
||
A silenced voice costs nothing to issue, so one line here beats eleven scattered through
|
||
the game.
|
||
|
||
Set up the envelope and tempo once at startup:
|
||
|
||
```basic norun
|
||
VOL 8
|
||
ENVELOPE 0, 0, 6, 0, 4
|
||
TEMPO 12
|
||
```
|
||
|
||
`PLAY` takes a string of notes, which is what the fanfares are:
|
||
|
||
```basic norun
|
||
PLAY "V3 T2 U8 O4 QC QE QG O5 HC"
|
||
```
|
||
|
||
## Step 11: The states
|
||
|
||
**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
|
||
loop to reach it.
|
||
|
||
There are seven state numbers and only five tick routines. **5 is game over and 6 is
|
||
paused, and neither has one on purpose**: the frame loop's `IF STATE# = n THEN GOSUB` list
|
||
simply has no line for them, so the frame draws whatever is on the screen, reads the
|
||
keyboard and does nothing else. That is exactly what a pause is, and writing an empty
|
||
subroutine to say so would cost a label.
|
||
|
||
```basic norun
|
||
LABEL TITLETICK
|
||