Add two Breakout examples and the tutorials that build them
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 3m10s
akbasic CI Build / sanitizers (push) Failing after 4m5s
akbasic CI Build / coverage (push) Failing after 3m29s
akbasic CI Build / akgl_build (push) Failing after 21s
akbasic CI Build / mutation_test (push) Failing after 3m19s
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>
74
examples/breakout/characters/README.md
Normal file
@@ -0,0 +1,74 @@
|
||||
# Breakout
|
||||
|
||||
Breakout, written entirely in BASIC for the AKGL BASIC interpreter. The ball and the
|
||||
paddle are sprites defined from `DATA` in the listing; the bricks, the HUD and the
|
||||
messages are characters in the text grid.
|
||||
|
||||
```sh
|
||||
../../../build-akgl/basic breakout.bas
|
||||
```
|
||||
|
||||
It needs the SDL build. The stdio build has no graphics device, so `RGR` refuses on the
|
||||
fourth line and the game stops there — which is exactly what it should do.
|
||||
|
||||
| Key | Does |
|
||||
|---|---|
|
||||
| left / right | move the paddle |
|
||||
| space | start a game from the title screen, then launch the ball |
|
||||
| P | pause |
|
||||
| Q or escape | quit |
|
||||
|
||||
The title screen plays itself. Attract mode is a real feature and it is also how the game
|
||||
was tested without a hand on the keyboard.
|
||||
|
||||
Three lives, six rows of bricks worth 60 down to 10 points a row, three level layouts that
|
||||
repeat with a faster ball each time, and a high score that lasts as long as the process
|
||||
does.
|
||||
|
||||
## How it works, and how to write your own
|
||||
|
||||
**[Chapter 17 of the guide](../../../docs/17-tutorial-breakout.md)** builds this program a
|
||||
step at a time: what to make a sprite and what to make a character, why every name is
|
||||
declared before the game starts, how a text row has to be written whole, and the rest of
|
||||
the rules this listing never breaks. It is the tutorial; this is the finished thing.
|
||||
|
||||
[Chapter 18](../../../docs/18-tutorial-breakout-artwork.md) does the same for
|
||||
[`../sprites`](../sprites), which builds the same game out of loaded artwork and comes out
|
||||
a completely different program.
|
||||
|
||||
## Known warts, this game's own
|
||||
|
||||
- **The cell size is a constant.** `CW#` and `CH#` are 16, measured with the bundled
|
||||
C64_Pro_Mono at 16 points in the 800×600 window; the grid is 50×37. BASIC cannot ask —
|
||||
`WINDOW` knows, but the standalone frontend never offers it. Change the font or the
|
||||
window size and those two numbers have to change with them.
|
||||
- **Every character the game draws also goes to stdout**, because the frontend tees the
|
||||
text sink to the terminal. It made the whole game auditable from a log file during
|
||||
development and it is pure noise the rest of the time. Redirect it.
|
||||
- **Collision is against the cell the ball's leading edge is in**, so a brick clipped at
|
||||
the very corner can be missed by up to seven pixels' worth of ball. Testing the centre
|
||||
instead was worse: the ball sank four pixels into a brick before it turned.
|
||||
- **There is a stall watchdog.** Ten seconds without touching a brick and the ball gets a
|
||||
new angle — at the *next paddle bounce*, never in mid-air.
|
||||
- **The demo cannot lose**, it re-serves. It also cannot be paused; `P` is ignored until a
|
||||
real game starts.
|
||||
- **Sound is asked for once and then believed.** On a machine with no audio device the
|
||||
game is silent rather than dead.
|
||||
- **No high score on disk.** There is no disk in this interpreter.
|
||||
|
||||
The interpreter defects this game turned up are filed in `TODO.md` §6 item 30 and §9, each
|
||||
with a reduction that fits on a screen.
|
||||
|
||||
## How this was checked
|
||||
|
||||
- Parses clean under the stdio `basic` (it then stops at the first graphics verb, as it
|
||||
should).
|
||||
- Attract mode run for 145 seconds and again for 110: no runtime error, score climbing
|
||||
throughout, 39 bricks in the second run.
|
||||
- Level clear verified against a copy whose first layout is a single row: `LEVEL CLEARED`,
|
||||
layout 2 loaded, ball speed up, play continues.
|
||||
- Input driven with `xdotool` against the real window: paddle moves and clamps at both
|
||||
edges, ball launches, `P` pauses and unpauses, three lives drain to `GAME OVER`, space
|
||||
starts a new game, `Q` exits printing the final and high scores.
|
||||
- Every direction change logged for a whole run and read back: each one is a wall, a brick
|
||||
or the paddle. Nothing turns without a reason.
|
||||
843
examples/breakout/characters/breakout.bas
Normal file
@@ -0,0 +1,843 @@
|
||||
REM ####################################################################
|
||||
REM BREAKOUT
|
||||
REM
|
||||
REM One hundred percent BASIC, for the AKGL BASIC interpreter.
|
||||
REM ../akbasic/build-akgl/basic breakout.bas
|
||||
REM
|
||||
REM The ball and the paddle are sprites, defined from DATA in this listing.
|
||||
REM The bricks, the HUD and the messages are characters in the text grid.
|
||||
REM Nothing here loads an asset and nothing here uses DRAW, BOX or CIRCLE:
|
||||
REM the drawing verbs do not survive a frame in this interpreter, while the
|
||||
REM text grid and the sprites are both redrawn from persistent state every
|
||||
REM frame. See README.md for the whole list of things that shaped this.
|
||||
REM
|
||||
REM Three rules this listing never breaks, each one earned the hard way:
|
||||
REM * Every name is created once, in the pool block below -- variables,
|
||||
REM loop counters, all of it. A name first created inside a GOSUB or a
|
||||
REM FOR costs a value slot that is never handed back, and there are 4096
|
||||
REM of them for the whole run. A game that creates one name per tick is
|
||||
REM dead in half a minute. This one creates none after startup.
|
||||
REM * A row of text is written whole, from column 0. CHAR terminates the
|
||||
REM row where it stops, so writing at column N erases N+1 onwards -- and
|
||||
REM writing PAST the terminator of a short row draws nothing at all.
|
||||
REM * Mixed + and - are parenthesised. This parser reads a - b + c as
|
||||
REM a - (b + c), and matches only ONE unparenthesised AND or OR.
|
||||
REM
|
||||
REM Written by Claude Opus (1M)
|
||||
REM ####################################################################
|
||||
|
||||
LABEL SETUP
|
||||
SCNCLR
|
||||
|
||||
REM --- geometry -------------------------------------------------------
|
||||
REM The cell is 16x16 and the grid is 50x37, measured with the bundled
|
||||
REM C64_Pro_Mono at 16 points in the 800x600 window. BASIC cannot ask for
|
||||
REM the cell size -- WINDOW would tell us, but the standalone frontend's
|
||||
REM tee sink never offers it -- so these two are constants. Change them
|
||||
REM together with the font or the window and everything else follows.
|
||||
CW# = 16
|
||||
CH# = 16
|
||||
SCW# = RGR(1)
|
||||
SCH# = RGR(2)
|
||||
COLS# = SCW# / CW#
|
||||
ROWS# = SCH# / CH#
|
||||
|
||||
REM --- the brick wall, in cells ---------------------------------------
|
||||
BRW# = 4
|
||||
BCOLS# = 10
|
||||
BROWS# = 6
|
||||
BTOP# = 4
|
||||
BLEFT# = (COLS# - (BRW# * BCOLS#)) / 2
|
||||
|
||||
REM --- the play area, in pixels ---------------------------------------
|
||||
TOPY# = 2 * CH#
|
||||
MAXX# = SCW# - 8
|
||||
PW# = 144
|
||||
PY# = 528
|
||||
LOSEY# = PY# + 32
|
||||
MSGROW# = 35
|
||||
TITROW# = 20
|
||||
PSPD# = 10
|
||||
|
||||
REM --- pools ----------------------------------------------------------
|
||||
REM Every name in the program is created HERE, and this is load-bearing
|
||||
REM twice over.
|
||||
REM
|
||||
REM A GOSUB gets its own scope. Assignment walks up the chain and finds an
|
||||
REM outer variable, but a name first seen inside a subroutine is created in
|
||||
REM the subroutine and dies at RETURN -- so HIT# and BI# would mean nothing
|
||||
REM to the caller if they were not already out here.
|
||||
REM
|
||||
REM And a name that is created costs a value slot for good. The pool is a
|
||||
REM bump allocator with no free (../akbasic/src/value.c:142) and recycling a
|
||||
REM variable takes fresh slots, so 4096 creations ends the run whenever they
|
||||
REM happen. Declared once, the whole game then runs on nothing but stores.
|
||||
DIM BR#(60)
|
||||
DIM SB#(63)
|
||||
DIM SP#(63)
|
||||
DIM LAY$(6)
|
||||
DIM BSG$(6)
|
||||
|
||||
SCORE# = 0
|
||||
HIGH# = 0
|
||||
LIVES# = 3
|
||||
LEVEL# = 1
|
||||
LEFTN# = 0
|
||||
BSPD# = 4
|
||||
STATE# = 0
|
||||
DEMO# = 0
|
||||
PAUSED# = 0
|
||||
HELD# = 0
|
||||
PDIR# = 0
|
||||
PDEC# = 0
|
||||
PX# = 0
|
||||
BX# = 0
|
||||
BY# = 0
|
||||
BVX# = 0
|
||||
BVY# = 0
|
||||
OX# = 0
|
||||
OY# = 0
|
||||
K# = 0
|
||||
HIT# = 0
|
||||
PVX# = 0
|
||||
DOFF# = 0
|
||||
STALL# = 0
|
||||
NUDGE# = 0
|
||||
BI# = 0
|
||||
BR2# = 0
|
||||
RB# = 0
|
||||
CB# = 0
|
||||
RC# = 0
|
||||
CC2# = 0
|
||||
TX# = 0
|
||||
TY# = 0
|
||||
PTS# = 0
|
||||
V# = 0
|
||||
L# = 0
|
||||
C2# = 0
|
||||
D# = 0
|
||||
M# = 0
|
||||
X2# = 0
|
||||
X3# = 0
|
||||
Z# = 0
|
||||
BB# = 0
|
||||
RX# = 0
|
||||
N# = 0
|
||||
MROW# = 0
|
||||
RMAX# = 2
|
||||
RND# = 0
|
||||
SND# = 0
|
||||
P$ = ""
|
||||
H$ = ""
|
||||
S$ = ""
|
||||
T$ = ""
|
||||
MSG$ = ""
|
||||
BSEG$ = ""
|
||||
FRAME$ = ""
|
||||
I# = 0
|
||||
R# = 0
|
||||
R2# = 0
|
||||
C# = 0
|
||||
CC# = 0
|
||||
KI# = 0
|
||||
T# = 0
|
||||
|
||||
REM Brick faces, one per wall row. CHAR ignores its colour argument --
|
||||
REM the sink draws in one colour -- so the rows are told apart by shape.
|
||||
BSG$(0) = "[##]"
|
||||
BSG$(1) = "[##]"
|
||||
BSG$(2) = "[==]"
|
||||
BSG$(3) = "[==]"
|
||||
BSG$(4) = "[--]"
|
||||
BSG$(5) = "[--]"
|
||||
|
||||
REM --- the seed -------------------------------------------------------
|
||||
REM There is no RND in this dialect. TI# is jiffies off the host's clock
|
||||
REM and is host uptime rather than zero-based, which makes it a fine seed.
|
||||
SEED# = TI#
|
||||
|
||||
REM The ceiling, drawn once. TOPY# is the bottom edge of this row, so the
|
||||
REM ball turns against something the player can see rather than against an
|
||||
REM invisible line two rows down from the top of the window.
|
||||
FRAME$ = "=" * COLS#
|
||||
CHAR 1, 0, 1, FRAME$
|
||||
|
||||
GOSUB MKSPR
|
||||
GOSUB SNDPROBE
|
||||
GOTO TITLE
|
||||
|
||||
REM ####################################################################
|
||||
REM SPRITES
|
||||
REM ####################################################################
|
||||
|
||||
LABEL MKSPR
|
||||
FOR I# = 0 TO 62
|
||||
READ SB#(I#)
|
||||
NEXT I#
|
||||
FOR I# = 0 TO 62
|
||||
READ SP#(I#)
|
||||
NEXT I#
|
||||
SPRSAV SB#, 1
|
||||
SPRITE 1, 1, 2
|
||||
SPRSAV SP#, 2
|
||||
SPRSAV SP#, 3
|
||||
SPRSAV SP#, 4
|
||||
SPRITE 2, 1, 15, 0, 1, 0
|
||||
SPRITE 3, 1, 15, 0, 1, 0
|
||||
SPRITE 4, 1, 15, 0, 1, 0
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM SOUND
|
||||
REM
|
||||
REM SOUND refuses on a machine with no audio device, and an untrapped
|
||||
REM refusal would end the game. Ask once, remember the answer, and never
|
||||
REM ask again -- the game is silent where there is nothing to play on.
|
||||
REM ####################################################################
|
||||
|
||||
LABEL SNDPROBE
|
||||
SND# = 1
|
||||
TRAP NOAUDIO
|
||||
SOUND 1, 2000, 1
|
||||
TRAP
|
||||
RETURN
|
||||
|
||||
LABEL NOAUDIO
|
||||
SND# = 0
|
||||
RESUME NEXT
|
||||
|
||||
LABEL BEEPB
|
||||
IF SND# = 1 THEN SOUND 1, 12000, 2
|
||||
RETURN
|
||||
|
||||
LABEL BEEPP
|
||||
IF SND# = 1 THEN SOUND 2, 6000, 3
|
||||
RETURN
|
||||
|
||||
LABEL BEEPW
|
||||
IF SND# = 1 THEN SOUND 3, 9000, 1
|
||||
RETURN
|
||||
|
||||
LABEL BEEPL
|
||||
IF SND# = 1 THEN SOUND 1, 1500, 20
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM TITLE AND ATTRACT MODE
|
||||
REM
|
||||
REM The title screen plays itself. It is also how this game gets tested
|
||||
REM without a human at the keyboard.
|
||||
REM ####################################################################
|
||||
|
||||
LABEL TITLE
|
||||
DEMO# = 1
|
||||
SCORE# = 0
|
||||
LIVES# = 3
|
||||
LEVEL# = 1
|
||||
BSPD# = 4
|
||||
PAUSED# = 0
|
||||
GOSUB LOADLAY
|
||||
GOSUB BUILDW
|
||||
GOSUB DRAWW
|
||||
GOSUB DRAWHUD
|
||||
GOSUB TITTEXT
|
||||
GOSUB SERVE
|
||||
STATE# = 0
|
||||
GOTO TICK
|
||||
|
||||
LABEL TITTEXT
|
||||
MROW# = TITROW#
|
||||
MSG$ = "B R E A K O U T"
|
||||
GOSUB SHOWAT
|
||||
MROW# = TITROW# + 2
|
||||
MSG$ = "PRESS SPACE TO PLAY"
|
||||
GOSUB SHOWAT
|
||||
MROW# = TITROW# + 4
|
||||
MSG$ = "LEFT AND RIGHT MOVE P PAUSES Q QUITS"
|
||||
GOSUB SHOWAT
|
||||
MROW# = TITROW# + 6
|
||||
MSG$ = "ATTRACT MODE"
|
||||
GOSUB SHOWAT
|
||||
RETURN
|
||||
|
||||
LABEL CLRTIT
|
||||
MROW# = TITROW#
|
||||
GOSUB CLRAT
|
||||
MROW# = TITROW# + 2
|
||||
GOSUB CLRAT
|
||||
MROW# = TITROW# + 4
|
||||
GOSUB CLRAT
|
||||
MROW# = TITROW# + 6
|
||||
GOSUB CLRAT
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM GAME AND LEVEL SETUP
|
||||
REM ####################################################################
|
||||
|
||||
LABEL NEWGAME
|
||||
DEMO# = 0
|
||||
SCORE# = 0
|
||||
LIVES# = 3
|
||||
LEVEL# = 1
|
||||
BSPD# = 4
|
||||
PAUSED# = 0
|
||||
GOSUB CLRTIT
|
||||
GOSUB NEWLEV
|
||||
STATE# = 0
|
||||
GOTO TICK
|
||||
|
||||
LABEL NEWLEV
|
||||
GOSUB LOADLAY
|
||||
GOSUB BUILDW
|
||||
GOSUB DRAWW
|
||||
GOSUB DRAWHUD
|
||||
GOSUB SERVE
|
||||
MSG$ = "PRESS SPACE TO LAUNCH"
|
||||
GOSUB SHOWMSG
|
||||
RETURN
|
||||
|
||||
REM Three layouts, then round again with a faster ball. RESTORE takes a
|
||||
REM label -- it wants a line and a label evaluates to one -- so a layout is
|
||||
REM simply a named place in the DATA stream.
|
||||
LABEL LOADLAY
|
||||
N# = MOD((LEVEL# - 1), 3)
|
||||
IF N# = 0 THEN RESTORE LAY1
|
||||
IF N# = 1 THEN RESTORE LAY2
|
||||
IF N# = 2 THEN RESTORE LAY3
|
||||
FOR R# = 0 TO 5
|
||||
READ LAY$(R#)
|
||||
NEXT R#
|
||||
RETURN
|
||||
|
||||
LABEL BUILDW
|
||||
LEFTN# = 0
|
||||
FOR R# = 0 TO 5
|
||||
S$ = LAY$(R#)
|
||||
GOSUB BUILDR
|
||||
NEXT R#
|
||||
RETURN
|
||||
|
||||
LABEL BUILDR
|
||||
FOR C# = 0 TO 9
|
||||
T$ = MID(S$, C#, 1)
|
||||
I# = (R# * BCOLS#) + C#
|
||||
BR#(I#) = 0
|
||||
IF T$ = "1" THEN BR#(I#) = 1
|
||||
IF T$ = "1" THEN LEFTN# = LEFTN# + 1
|
||||
NEXT C#
|
||||
RETURN
|
||||
|
||||
LABEL DRAWW
|
||||
FOR R# = 0 TO 5
|
||||
BR2# = R#
|
||||
GOSUB DRAWBR
|
||||
NEXT R#
|
||||
RETURN
|
||||
|
||||
REM One brick row, rebuilt whole and written in a single CHAR. Anything
|
||||
REM else would truncate the row at the first character it wrote.
|
||||
LABEL DRAWBR
|
||||
S$ = ""
|
||||
IF BLEFT# > 0 THEN S$ = " " * BLEFT#
|
||||
BSEG$ = BSG$(BR2#)
|
||||
FOR CC# = 0 TO 9
|
||||
I# = (BR2# * BCOLS#) + CC#
|
||||
IF BR#(I#) = 1 THEN S$ = S$ + BSEG$
|
||||
IF BR#(I#) = 0 THEN S$ = S$ + " "
|
||||
NEXT CC#
|
||||
R2# = BTOP# + BR2#
|
||||
CHAR 1, 0, R2#, S$
|
||||
RETURN
|
||||
|
||||
REM Put the ball back on the paddle, pointing up, left or right at random.
|
||||
LABEL SERVE
|
||||
PX# = (SCW# - PW#) / 2
|
||||
HELD# = 1
|
||||
IF DEMO# = 1 THEN HELD# = 0
|
||||
BX# = PX# + ((PW# / 2) - 4)
|
||||
BY# = PY# - 10
|
||||
RMAX# = 2
|
||||
GOSUB RANDOM
|
||||
BVX# = BSPD#
|
||||
IF RND# = 0 THEN BVX# = 0 - BSPD#
|
||||
BVY# = 0 - BSPD#
|
||||
PDEC# = 0
|
||||
GOSUB SHOWSPR
|
||||
RETURN
|
||||
|
||||
REM A linear congruential generator, because this dialect has no RND.
|
||||
REM The multiply stays inside int64 for any seed under 2^31.
|
||||
LABEL RANDOM
|
||||
SEED# = MOD(((SEED# * 1103515245) + 12345), 2147483648)
|
||||
RND# = MOD((SEED# / 65536), RMAX#)
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM THE MAIN LOOP
|
||||
REM
|
||||
REM A GOTO loop rather than DO/LOOP on purpose: every loop and every
|
||||
REM GOSUB pushes one of 32 scopes, and a state change that jumped out of
|
||||
REM a DO would leak one every time. This loop pushes nothing.
|
||||
REM ####################################################################
|
||||
|
||||
LABEL TICK
|
||||
GOSUB READKEY
|
||||
IF STATE# <> 0 THEN GOTO DISPATCH
|
||||
IF PAUSED# = 1 THEN GOTO TICKEND
|
||||
GOSUB MOVEPAD
|
||||
IF HELD# = 1 THEN GOSUB HOLDBAL
|
||||
IF HELD# = 0 THEN GOSUB MOVEBAL
|
||||
GOSUB SHOWSPR
|
||||
LABEL TICKEND
|
||||
SLEEP 0.02
|
||||
GOTO TICK
|
||||
|
||||
LABEL DISPATCH
|
||||
IF STATE# = 1 THEN GOTO LOSTLIF
|
||||
IF STATE# = 2 THEN GOTO LEVELUP
|
||||
IF STATE# = 3 THEN GOTO BYE
|
||||
IF STATE# = 5 THEN GOTO NEWGAME
|
||||
STATE# = 0
|
||||
GOTO TICK
|
||||
|
||||
REM ####################################################################
|
||||
REM INPUT
|
||||
REM
|
||||
REM GET is the only non-blocking read there is, and there is no key-up
|
||||
REM event and no held-key state. What there IS is the host's own key
|
||||
REM repeat, which arrives as ordinary key-down events about once per tick.
|
||||
REM So a keystroke refills a countdown and the paddle coasts to a stop
|
||||
REM when the repeats stop: held reads as held, tapped reads as a nudge.
|
||||
REM ####################################################################
|
||||
|
||||
LABEL READKEY
|
||||
FOR KI# = 1 TO 8
|
||||
GET K#
|
||||
IF K# <> 0 THEN GOSUB HANDKEY
|
||||
NEXT KI#
|
||||
RETURN
|
||||
|
||||
LABEL HANDKEY
|
||||
IF K# = 1073741904 THEN PDIR# = 0 - 1
|
||||
IF K# = 1073741904 THEN PDEC# = 12
|
||||
IF K# = 1073741903 THEN PDIR# = 1
|
||||
IF K# = 1073741903 THEN PDEC# = 12
|
||||
IF K# = 32 THEN GOSUB KEYSPC
|
||||
IF K# = 112 THEN GOSUB KEYPAU
|
||||
IF K# = 113 THEN STATE# = 3
|
||||
IF K# = 27 THEN STATE# = 3
|
||||
RETURN
|
||||
|
||||
LABEL KEYSPC
|
||||
IF DEMO# = 1 THEN STATE# = 5
|
||||
IF DEMO# = 1 THEN RETURN
|
||||
IF HELD# = 0 THEN RETURN
|
||||
HELD# = 0
|
||||
GOSUB CLRMSG
|
||||
RETURN
|
||||
|
||||
LABEL KEYPAU
|
||||
IF DEMO# = 1 THEN RETURN
|
||||
PAUSED# = 1 - PAUSED#
|
||||
IF PAUSED# = 1 THEN MSG$ = "PAUSED"
|
||||
IF PAUSED# = 1 THEN GOSUB SHOWMSG
|
||||
IF PAUSED# = 0 THEN GOSUB CLRMSG
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM THE PADDLE
|
||||
REM ####################################################################
|
||||
|
||||
LABEL MOVEPAD
|
||||
IF DEMO# = 1 THEN GOSUB DEMOPAD
|
||||
IF DEMO# = 1 THEN GOTO PADCLMP
|
||||
IF PDEC# < 1 THEN GOTO PADCLMP
|
||||
PDEC# = PDEC# - 1
|
||||
PX# = PX# + (PDIR# * PSPD#)
|
||||
LABEL PADCLMP
|
||||
IF PX# < 0 THEN PX# = 0
|
||||
M# = SCW# - PW#
|
||||
IF PX# > M# THEN PX# = M#
|
||||
RETURN
|
||||
|
||||
LABEL DEMOPAD
|
||||
TX# = (BX# + 4) - (PW# / 2)
|
||||
TX# = TX# + DOFF#
|
||||
D# = TX# - PX#
|
||||
IF D# > PSPD# THEN D# = PSPD#
|
||||
M# = 0 - PSPD#
|
||||
IF D# < M# THEN D# = M#
|
||||
PX# = PX# + D#
|
||||
RETURN
|
||||
|
||||
LABEL HOLDBAL
|
||||
BX# = PX# + ((PW# / 2) - 4)
|
||||
BY# = PY# - 10
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM THE BALL
|
||||
REM
|
||||
REM X first and then Y, each move tested on its own, so a ball arriving
|
||||
REM at the corner of a brick reverses one axis rather than both.
|
||||
REM ####################################################################
|
||||
|
||||
LABEL MOVEBAL
|
||||
OX# = BX#
|
||||
BX# = BX# + BVX#
|
||||
IF BX# < 0 THEN GOSUB WALLL
|
||||
IF BX# > MAXX# THEN GOSUB WALLR
|
||||
GOSUB XBRICK
|
||||
OY# = BY#
|
||||
BY# = BY# + BVY#
|
||||
IF BY# < TOPY# THEN GOSUB WALLT
|
||||
GOSUB YBRICK
|
||||
GOSUB PADHIT
|
||||
IF BY# > LOSEY# THEN STATE# = 1
|
||||
STALL# = STALL# + 1
|
||||
IF STALL# > 500 THEN NUDGE# = 1
|
||||
RETURN
|
||||
|
||||
LABEL WALLL
|
||||
BX# = 0
|
||||
BVX# = 0 - BVX#
|
||||
GOSUB BEEPW
|
||||
RETURN
|
||||
|
||||
LABEL WALLR
|
||||
BX# = MAXX#
|
||||
BVX# = 0 - BVX#
|
||||
GOSUB BEEPW
|
||||
RETURN
|
||||
|
||||
LABEL WALLT
|
||||
BY# = TOPY#
|
||||
BVY# = 0 - BVY#
|
||||
GOSUB BEEPW
|
||||
RETURN
|
||||
|
||||
REM The leading edge, not the centre: a ball tested by its centre sinks
|
||||
REM four pixels into a brick before it turns, which looks like the brick
|
||||
REM was hit late and the one beside it was not hit at all.
|
||||
LABEL XBRICK
|
||||
TX# = BX#
|
||||
IF BVX# > 0 THEN TX# = BX# + 7
|
||||
TY# = BY# + 4
|
||||
GOSUB HITTEST
|
||||
IF HIT# = 0 THEN RETURN
|
||||
BX# = OX#
|
||||
BVX# = 0 - BVX#
|
||||
GOSUB KILLBR
|
||||
RETURN
|
||||
|
||||
LABEL YBRICK
|
||||
TX# = BX# + 4
|
||||
TY# = BY#
|
||||
IF BVY# > 0 THEN TY# = BY# + 7
|
||||
GOSUB HITTEST
|
||||
IF HIT# = 0 THEN RETURN
|
||||
BY# = OY#
|
||||
BVY# = 0 - BVY#
|
||||
GOSUB KILLBR
|
||||
RETURN
|
||||
|
||||
REM Which brick, if any, holds the pixel (TX#, TY#). This is the only
|
||||
REM place in the game that converts pixels into cells.
|
||||
LABEL HITTEST
|
||||
HIT# = 0
|
||||
RC# = TY# / CH#
|
||||
IF RC# < BTOP# THEN RETURN
|
||||
RB# = RC# - BTOP#
|
||||
IF RB# > (BROWS# - 1) THEN RETURN
|
||||
CC2# = TX# / CW#
|
||||
IF CC2# < BLEFT# THEN RETURN
|
||||
CB# = (CC2# - BLEFT#) / BRW#
|
||||
IF CB# > (BCOLS# - 1) THEN RETURN
|
||||
BI# = (RB# * BCOLS#) + CB#
|
||||
BR2# = RB#
|
||||
IF BR#(BI#) = 1 THEN HIT# = 1
|
||||
RETURN
|
||||
|
||||
LABEL KILLBR
|
||||
BR#(BI#) = 0
|
||||
LEFTN# = LEFTN# - 1
|
||||
STALL# = 0
|
||||
PTS# = (BROWS# - BR2#) * 10
|
||||
SCORE# = SCORE# + PTS#
|
||||
IF DEMO# = 0 THEN GOSUB HISCORE
|
||||
GOSUB DRAWBR
|
||||
GOSUB DRAWHUD
|
||||
GOSUB BEEPB
|
||||
IF LEFTN# < 1 THEN STATE# = 2
|
||||
RETURN
|
||||
|
||||
REM Paddle bounce. Five zones across the face, so where you catch it
|
||||
REM decides where it goes -- the whole game, really.
|
||||
LABEL PADHIT
|
||||
IF BVY# < 1 THEN RETURN
|
||||
BB# = BY# + 8
|
||||
IF BB# < PY# THEN RETURN
|
||||
IF BY# > (PY# + 10) THEN RETURN
|
||||
RX# = PX# + PW#
|
||||
IF (BX# + 8) < PX# THEN RETURN
|
||||
IF BX# > RX# THEN RETURN
|
||||
BY# = PY# - 8
|
||||
BVY# = 0 - BSPD#
|
||||
Z# = ((BX# + 4) - PX#) / (PW# / 5)
|
||||
IF Z# < 0 THEN Z# = 0
|
||||
IF Z# > 4 THEN Z# = 4
|
||||
PVX# = BVX#
|
||||
BVX# = (Z# - 2) * 3
|
||||
IF BVX# <> 0 THEN GOTO PADAIM
|
||||
REM Dead centre. Keep the direction it came in on and give it a shallow
|
||||
REM angle: a ball with no sideways speed at all rises and falls down one
|
||||
REM column for ever, which is how attract mode used to hang itself.
|
||||
BVX# = 2
|
||||
IF PVX# < 0 THEN BVX# = 0 - 2
|
||||
LABEL PADAIM
|
||||
IF NUDGE# = 1 THEN GOSUB UNSTICK
|
||||
IF DEMO# = 1 THEN GOSUB DEMOAIM
|
||||
GOSUB BEEPP
|
||||
RETURN
|
||||
|
||||
REM Ten seconds without touching a brick means the ball has found an orbit
|
||||
REM that misses the wall -- straight up and straight back down the same
|
||||
REM empty column, usually. Give it a different angle, but do it HERE, on a
|
||||
REM bounce the player is already watching, rather than in mid-flight where
|
||||
REM it would look like the ball hit something that was not there.
|
||||
LABEL UNSTICK
|
||||
NUDGE# = 0
|
||||
STALL# = 0
|
||||
RMAX# = 4
|
||||
GOSUB RANDOM
|
||||
BVX# = (RND# * 3) - 6
|
||||
IF BVX# = 0 THEN BVX# = 3
|
||||
RETURN
|
||||
|
||||
REM Attract mode aims off-centre by a random amount, so the machine plays
|
||||
REM like something with a hand on the paddle rather than a mirror.
|
||||
LABEL DEMOAIM
|
||||
RMAX# = 81
|
||||
GOSUB RANDOM
|
||||
DOFF# = RND# - 40
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM DRAWING
|
||||
REM ####################################################################
|
||||
|
||||
REM The sprite position is its top-left corner in device pixels, and the
|
||||
REM paddle is three 48-wide segments laid end to end. Each coordinate is
|
||||
REM worked out into a variable first: MOVSPR reads a leading sign as
|
||||
REM "move by this much" rather than "move to here".
|
||||
LABEL SHOWSPR
|
||||
MOVSPR 1, BX#, BY#
|
||||
MOVSPR 2, PX#, PY#
|
||||
X2# = PX# + 48
|
||||
MOVSPR 3, X2#, PY#
|
||||
X3# = PX# + 96
|
||||
MOVSPR 4, X3#, PY#
|
||||
RETURN
|
||||
|
||||
LABEL DRAWHUD
|
||||
V# = SCORE#
|
||||
GOSUB PAD6
|
||||
H$ = " SCORE " + P$
|
||||
H$ = (H$ + " LIVES ") + LIVES#
|
||||
H$ = (H$ + " LEVEL ") + LEVEL#
|
||||
V# = HIGH#
|
||||
GOSUB PAD6
|
||||
H$ = (H$ + " HIGH ") + P$
|
||||
CHAR 1, 0, 0, H$
|
||||
RETURN
|
||||
|
||||
LABEL PAD6
|
||||
P$ = "" + V#
|
||||
DO WHILE LEN(P$) < 6
|
||||
P$ = "0" + P$
|
||||
LOOP
|
||||
RETURN
|
||||
|
||||
LABEL SHOWMSG
|
||||
MROW# = MSGROW#
|
||||
GOSUB SHOWAT
|
||||
RETURN
|
||||
|
||||
LABEL CLRMSG
|
||||
MROW# = MSGROW#
|
||||
GOSUB CLRAT
|
||||
RETURN
|
||||
|
||||
REM Centre MSG$ on row MROW#. No padding on the right is needed: CHAR
|
||||
REM terminates the row where the message ends, which erases the rest.
|
||||
LABEL SHOWAT
|
||||
L# = LEN(MSG$)
|
||||
C2# = (COLS# - L#) / 2
|
||||
S$ = MSG$
|
||||
IF C2# > 0 THEN S$ = (" " * C2#) + MSG$
|
||||
CHAR 1, 0, MROW#, S$
|
||||
RETURN
|
||||
|
||||
LABEL CLRAT
|
||||
CHAR 1, 0, MROW#, " "
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM WHAT HAPPENS WHEN THE BALL GETS PAST YOU
|
||||
REM ####################################################################
|
||||
|
||||
LABEL LOSTLIF
|
||||
GOSUB BEEPL
|
||||
IF DEMO# = 1 THEN GOTO DEMOSRV
|
||||
LIVES# = LIVES# - 1
|
||||
GOSUB DRAWHUD
|
||||
IF LIVES# < 1 THEN GOTO GAMEOVR
|
||||
GOSUB SERVE
|
||||
MSG$ = "BALL LOST -- PRESS SPACE"
|
||||
GOSUB SHOWMSG
|
||||
STATE# = 0
|
||||
GOTO TICK
|
||||
|
||||
LABEL DEMOSRV
|
||||
GOSUB SERVE
|
||||
HELD# = 0
|
||||
STATE# = 0
|
||||
GOTO TICK
|
||||
|
||||
LABEL LEVELUP
|
||||
MSG$ = "LEVEL CLEARED"
|
||||
GOSUB SHOWMSG
|
||||
SLEEP 1.5
|
||||
LEVEL# = LEVEL# + 1
|
||||
IF BSPD# < 7 THEN BSPD# = BSPD# + 1
|
||||
GOSUB NEWLEV
|
||||
IF DEMO# = 1 THEN GOSUB CLRMSG
|
||||
STATE# = 0
|
||||
GOTO TICK
|
||||
|
||||
LABEL GAMEOVR
|
||||
MSG$ = "GAME OVER -- SPACE PLAYS AGAIN, Q QUITS"
|
||||
GOSUB SHOWMSG
|
||||
LABEL GOWAIT
|
||||
GET K#
|
||||
IF K# = 32 THEN GOTO NEWGAME
|
||||
IF K# = 113 THEN GOTO BYE
|
||||
IF K# = 27 THEN GOTO BYE
|
||||
SLEEP 0.05
|
||||
GOTO GOWAIT
|
||||
|
||||
LABEL BYE
|
||||
SPRITE 1, 0
|
||||
SPRITE 2, 0
|
||||
SPRITE 3, 0
|
||||
SPRITE 4, 0
|
||||
SCNCLR
|
||||
PRINT "BREAKOUT"
|
||||
PRINT "FINAL SCORE " + SCORE#
|
||||
PRINT "HIGH SCORE " + HIGH#
|
||||
SLEEP 2
|
||||
QUIT
|
||||
|
||||
REM The machine does not get on the scoreboard: attract mode keeps score
|
||||
REM so the wall behaves, but only a player's score is ever the high one.
|
||||
LABEL HISCORE
|
||||
IF SCORE# > HIGH# THEN HIGH# = SCORE#
|
||||
RETURN
|
||||
|
||||
REM ####################################################################
|
||||
REM SPRITE PATTERNS
|
||||
REM
|
||||
REM 63 bytes each: three per row, twenty-one rows, most significant bit
|
||||
REM leftmost. A clear bit is transparent, which is what lets an 8x8 ball
|
||||
REM live in the corner of a 24x21 pattern with nothing drawn around it.
|
||||
REM
|
||||
REM byte 0 byte 1 byte 2
|
||||
REM 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0
|
||||
REM ^ column 0 ^ column 23
|
||||
REM ####################################################################
|
||||
|
||||
REM Ball: an 8x8 disc in the top-left corner, so its pixel rectangle is
|
||||
REM the sprite position and eight pixels each way. No offset arithmetic.
|
||||
REM . . # # # # . . 60
|
||||
REM . # # # # # # . 126
|
||||
REM # # # # # # # # 255
|
||||
DATA 60, 0, 0
|
||||
DATA 126, 0, 0
|
||||
DATA 255, 0, 0
|
||||
DATA 255, 0, 0
|
||||
DATA 255, 0, 0
|
||||
DATA 255, 0, 0
|
||||
DATA 126, 0, 0
|
||||
DATA 60, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
|
||||
REM Paddle segment: the full 24 wide and 6 deep, expanded to 48 wide by
|
||||
REM SPRITE's xexpand bit. Three of them make one 144 pixel paddle.
|
||||
DATA 255, 255, 255
|
||||
DATA 255, 255, 255
|
||||
DATA 255, 255, 255
|
||||
DATA 255, 255, 255
|
||||
DATA 255, 255, 255
|
||||
DATA 255, 255, 255
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
DATA 0, 0, 0
|
||||
|
||||
REM ####################################################################
|
||||
REM LEVEL LAYOUTS
|
||||
REM
|
||||
REM Six rows of ten bricks. 1 is a brick, 0 is a hole. RESTORE picks the
|
||||
REM layout by line number, so adding a fourth is a block and one IF.
|
||||
REM ####################################################################
|
||||
|
||||
LABEL LAY1
|
||||
REM Level 1: the whole wall.
|
||||
DATA "1111111111"
|
||||
DATA "1111111111"
|
||||
DATA "1111111111"
|
||||
DATA "1111111111"
|
||||
DATA "1111111111"
|
||||
DATA "1111111111"
|
||||
|
||||
LABEL LAY2
|
||||
REM Level 2: a barrel, open at the corners.
|
||||
DATA "0011111100"
|
||||
DATA "0111111110"
|
||||
DATA "1111111111"
|
||||
DATA "1111111111"
|
||||
DATA "0111111110"
|
||||
DATA "0011111100"
|
||||
|
||||
LABEL LAY3
|
||||
REM Level 3: gaps to shoot through.
|
||||
DATA "1010101010"
|
||||
DATA "0101010101"
|
||||
DATA "1111001111"
|
||||
DATA "1100110011"
|
||||
DATA "1011111101"
|
||||
DATA "0110110110"
|
||||
86
examples/breakout/sprites/README.md
Normal file
@@ -0,0 +1,86 @@
|
||||
# Breakout, with artwork
|
||||
|
||||
Breakout, written entirely in BASIC for the AKGL BASIC interpreter, using downloaded CC0
|
||||
sprite artwork for the paddles, the balls and the powerup gems. The bricks, the HUD and
|
||||
the lettering are drawn, captured with `SSHAPE` and installed as sprites — which is the
|
||||
only way a program in this interpreter can put a picture on the screen.
|
||||
|
||||
```sh
|
||||
../../../build-akgl/basic breakout.bas
|
||||
```
|
||||
|
||||
It needs the SDL build. Without a graphics device the first `SPRSAV` refuses by name and
|
||||
the program stops there, which is the right thing for it to do.
|
||||
|
||||
| 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 |
|
||||
|
||||
Three lives, six rows of bricks worth 60 down to 10 points a row, and a field that cycles
|
||||
six rows, then five, then four while the ball gets faster every level up to a ceiling. A
|
||||
gem is worth 50 on top of whatever it does.
|
||||
|
||||
## The powerups
|
||||
|
||||
A broken brick drops a gem about one time in seven, and only ever one at a time. Catch it
|
||||
with the paddle. The colour of the gem is what tells you which it is; the banner that
|
||||
flashes over the field names it as well.
|
||||
|
||||
| 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 |
|
||||
|
||||
The second bar **mirrors** the paddle rather than following it, so it covers the side you
|
||||
just left. 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.
|
||||
|
||||
## How it works, and how to write your own
|
||||
|
||||
**[Chapter 18 of the guide](../../../docs/18-tutorial-breakout-artwork.md)** builds this
|
||||
program a step at a time: budgeting the eight sprite slots, capturing a drawing into one,
|
||||
finding the host's frame boundary with `TI#`, moving everything that is not drawing out of
|
||||
the deadline, the stroke font, and the five things in this dialect that do not do what
|
||||
they look like.
|
||||
|
||||
[Chapter 17](../../../docs/17-tutorial-breakout.md) does the same for
|
||||
[`../characters`](../characters), which builds the same game out of the text grid and two
|
||||
`DATA` sprites — a completely different program, and the shorter one to read first.
|
||||
|
||||
## Where the artwork came from
|
||||
|
||||
Kenney's [Puzzle Pack 1](https://kenney.nl/assets/puzzle-pack-1), released under
|
||||
[CC0](http://creativecommons.org/publicdomain/zero/1.0/). The files in `art/` are the
|
||||
pack's own `PNG/Default/` versions, unmodified, with the pack's licence file beside them.
|
||||
`art/PROVENANCE.md` says which file is used for what and why the double-size set is not.
|
||||
|
||||
Crediting Kenney is not required by CC0. It is here because it should be.
|
||||
|
||||
## What is not in it
|
||||
|
||||
Named honestly rather than left to be discovered:
|
||||
|
||||
- **No music under the game**, only event sounds and two four-note stings. There is room in
|
||||
the third voice for it; there is not much room anywhere else. 121 of the interpreter's
|
||||
128 variables are spoken for, four more are the ones it makes itself, and the label table
|
||||
is at 61 of 64.
|
||||
- **One gem at a time.** There is one sprite slot for it. A brick broken while a gem is
|
||||
falling drops nothing.
|
||||
- **Sticky and multiball share one offset.** Two balls stuck to the paddle at once sit on
|
||||
top of each other. It is rare enough that fixing it would cost a variable I do not have.
|
||||
- **The score lags a frame behind the bricks.** Only one capture happens per frame and the
|
||||
field goes first, so a brick disappears one frame before the score that counts it. At
|
||||
thirty frames a second nobody can see it, and it is a deliberate trade rather than an
|
||||
oversight.
|
||||
- **No high score on disk.** There is no disk.
|
||||
|
||||
The eight interpreter defects this game turned up are filed in `TODO.md` §9, each with a
|
||||
reduction that fits on a screen, the file and line of the cause, and what a fix would
|
||||
touch.
|
||||
21
examples/breakout/sprites/art/License.txt
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
|
||||
Puzzle Pack (1.1)
|
||||
|
||||
Created/distributed by Kenney (www.kenney.nl)
|
||||
|
||||
------------------------------
|
||||
|
||||
License: (Creative Commons Zero, CC0)
|
||||
http://creativecommons.org/publicdomain/zero/1.0/
|
||||
|
||||
This content is free to use in personal, educational and commercial projects.
|
||||
Support us by crediting Kenney or www.kenney.nl (this is not mandatory)
|
||||
|
||||
------------------------------
|
||||
|
||||
Donate: http://support.kenney.nl
|
||||
Patreon: http://patreon.com/kenney/
|
||||
|
||||
Follow on Twitter for updates:
|
||||
http://twitter.com/KenneyNL
|
||||
35
examples/breakout/sprites/art/PROVENANCE.md
Normal file
@@ -0,0 +1,35 @@
|
||||
# Where this art came from
|
||||
|
||||
Every PNG in this directory is from **Kenney's Puzzle Pack 1**, released into the public
|
||||
domain under [Creative Commons Zero](http://creativecommons.org/publicdomain/zero/1.0/).
|
||||
`License.txt` is the pack's own licence file, copied here unedited.
|
||||
|
||||
* Source: <https://kenney.nl/assets/puzzle-pack-1>
|
||||
* Downloaded: 2026-08-01, `kenney_puzzle-pack-1.zip`
|
||||
* Author: Kenney (<https://www.kenney.nl>)
|
||||
* Licence: CC0 1.0. Crediting is not required; it is here because it should be.
|
||||
|
||||
The files are the pack's `PNG/Default/` versions, byte for byte — nothing is resized,
|
||||
recoloured or re-encoded, so the checksum of any of them still matches the distributed
|
||||
archive.
|
||||
|
||||
| File | Size | Used for |
|
||||
|---|---|---|
|
||||
| `paddleBlu.png` | 104x24 | the player's paddle |
|
||||
| `paddleRed.png` | 104x24 | the second paddle, from the CATCHER powerup |
|
||||
| `ballBlue.png` | 22x22 | the ball |
|
||||
| `ballGrey.png` | 22x22 | the extra balls from MULTIBALL |
|
||||
| `element_red_polygon_glossy.png` | 48x46 | the EXPAND powerup |
|
||||
| `element_yellow_polygon_glossy.png` | 48x46 | the MULTIBALL powerup |
|
||||
| `element_green_polygon_glossy.png` | 48x46 | the SLOW powerup |
|
||||
| `element_blue_polygon_glossy.png` | 48x46 | the STICKY powerup |
|
||||
| `element_purple_polygon_glossy.png` | 48x46 | the CATCHER powerup |
|
||||
|
||||
The pack's `PNG/Double/` versions are twice these sizes and are the wrong scale for an
|
||||
800x600 field. `SPRITE`'s x- and y-expand bits would double them again, which is the only
|
||||
scaling this interpreter has — there is no way to make a sprite *smaller* — so the
|
||||
`Default` set is the one that fits.
|
||||
|
||||
Everything else on screen — the bricks, the walls, the HUD lettering, the title — is drawn
|
||||
by the program with `DRAW` and `BOX`. See `README-sprites.md` for why only eight things in
|
||||
this game can be made of artwork.
|
||||
BIN
examples/breakout/sprites/art/ballBlue.png
Normal file
|
After Width: | Height: | Size: 513 B |
BIN
examples/breakout/sprites/art/ballGrey.png
Normal file
|
After Width: | Height: | Size: 495 B |
BIN
examples/breakout/sprites/art/element_blue_polygon_glossy.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/breakout/sprites/art/element_green_polygon_glossy.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/breakout/sprites/art/element_purple_polygon_glossy.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
examples/breakout/sprites/art/element_red_polygon_glossy.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
examples/breakout/sprites/art/element_yellow_polygon_glossy.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
examples/breakout/sprites/art/paddleBlu.png
Normal file
|
After Width: | Height: | Size: 762 B |
BIN
examples/breakout/sprites/art/paddleRed.png
Normal file
|
After Width: | Height: | Size: 578 B |