A reader building chapter 17 from the text alone had to infer that COLLISION 2, BRICKHIT belongs in the setup block rather than the frame loop, and met SCNCLR and QUIT for the first time in the assembly step. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwxGB6TdoVvZ11KQQME9cL Co-Authored-By: Andrew Kesterson <andrew@aklabs.net>
50 KiB
17. Tutorial: Breakout
This chapter builds a complete game from an empty file: three lives, six rows of bricks, three level layouts, a score, a high score, sound, and a title screen that plays itself. Nothing is loaded from disk — the ball and the paddle are drawn from numbers in the listing, and the wall, the HUD and the messages are characters written into the text grid.
This is what you are building:
The finished listing is
examples/breakout/characters/breakout.bas.
You do not need it to follow along, but it is the same program assembled, and it is worth
opening once you have your own running.
Run it with the SDL build:
$ ./build-akgl/basic examples/breakout/characters/breakout.bas
| Key | Does |
|---|---|
| left / right | move the paddle |
| space | start a game, then launch the ball |
| P | pause |
| Q or escape | quit |
What you will do
- Step 1 — get a program onto the screen, and find out which build you need
- Step 2 — measure the screen and work the geometry out from what you measured
- Step 3 — declare every name the program will use, before anything uses it
- Step 4 — make the ball and the paddle out of sprite data
- Step 5 — build the brick wall out of characters, and keep a grid saying which bricks are still there
- Step 6 — write the main loop and the frame pacing
- Step 7 — read the keyboard without blocking
- Step 8 — move the paddle and keep it on the screen
- Step 9 — move the ball and bounce it off the walls
- Step 10 — find out which brick the ball hit, and break it
- Step 11 — bounce off the paddle at an angle the player chooses
- Step 12 — draw the HUD and the messages
- Step 13 — handle lives, levels and game over
- Step 14 — add sound, on a machine that may not have any
- Step 15 — add a title screen that plays itself
- Step 16 — put the pieces in one file, in the right order
Each step is a piece you can type in and run. Work through them in order, and Step 16 is where they become one program.
Step 1: Get a program onto the screen
Goal: a program that runs, and a build that can draw.
A BASIC program here is a text file. Line numbers are optional, so a file is just statements, one per line:
PRINT "HELLO"
END
HELLO
Save that as first.bas and run it:
$ ./build/basic first.bas
There are two builds and the difference matters for this whole chapter. ./build/basic is
the plain one: it reads and writes text and has no graphics device, no sprites and no
sound. ./build-akgl/basic opens a window and has all three. A game needs the second.
Ask for something that needs a device and you get a refusal naming what is missing rather than a crash:
PRINT "BEFORE"
SPRITE 1, 1, 2
PRINT "AFTER"
BEFORE
? 2 : RUNTIME ERROR SPRITE needs a sprite device and this runtime has none
That is worth seeing once, because it is how the finished game behaves if you start it with the wrong build: it stops at the first graphics verb and tells you why.
For the rest of this chapter, run everything with ./build-akgl/basic.
Two more things to know before writing anything longer.
Every variable carries a type suffix. A# is an integer, A% is a float, A$ is a
string. There is no such thing as a plain A — a bare word is a label. This is different
from Commodore BASIC, where A% is the integer.
+ joins a string to a number. "SCORE " + 40 is "SCORE 40", and "" + N# is how
you turn a number into a string. There is no STR$.
N# = 40
PRINT "SCORE " + N#
END
SCORE 40
Step 2: Measure the screen
Goal: every position in the game derived from the window's real size, so the game fits whatever window and font the player has.
Do not guess the size of anything. Four functions tell you what you have:
| Call | Gives |
|---|---|
RGR(1) |
the window's width in pixels |
RGR(2) |
the window's height in pixels |
RGR(3) |
the width of one character cell, in pixels |
RGR(4) |
the height of one character cell |
RWINDOW(0) |
the text grid's height, in rows |
RWINDOW(1) |
the text grid's width, in columns |
Put them at the very top of the program:
SCW# = RGR(1)
SCH# = RGR(2)
CW# = RGR(3)
CH# = RGR(4)
COLS# = RWINDOW(1)
ROWS# = RWINDOW(0)
RGR(1) is first on purpose. It is the first statement in the program that needs a
graphics device, so on the wrong build this is the line that refuses, and it refuses
before anything else has happened.
Now derive the layout from those numbers rather than writing pixel positions down. The wall is measured in character cells, because it is made of characters:
BRW# = 4
