Generate the documentation's figures from the listings they illustrate
Chapters 6 and 8 described what a verb draws in prose. Eight figures now show it, and each one is produced by running the BASIC listing printed immediately above it -- so a picture cannot drift away from the code beside it, which is the way a screenshot goes wrong and the way nothing notices. tools/screenshot.c is a second SDL host, much smaller than the frontend: dummy video driver, software renderer, run to completion, read the target back, write a PNG. It draws no text layer on purpose, so a READY in the corner is not noise in a figure about BOX and no font has to be resolved. tools/docs_screenshots.sh reads the new screenshot=NAME fence tag straight out of the markdown. size=WxH is the second tag, and SCALE's figure uses it: the point being made is a 320x200 listing filling a larger window, which cannot be made on a 320x200 surface. Two gates, answering different questions. docs_examples fails a tagged block with no image, in both configurations, so a figure cannot be added and forgotten. docs_screenshots -- a CTest, AKGL build only -- re-renders every figure and compares byte for byte, so a listing edited without regenerating fails. Only the second catches a stale picture. The PNGs are checked in because a reader on the forge has no build tree, and docs/images/README.md says loudly that they are generated. Regenerating is never part of a build: the target is run deliberately, so a make cannot put eight binary diffs in front of whoever ran it. Drawing the BOX figure caught a defect in TODO.md itself. Deviation 16 claimed in bold that BOX fills on a negative angle while its own paragraph said the fill was filed rather than implemented. BOX cannot fill, and filled_rect is reached by no verb as a result. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@@ -65,43 +65,86 @@ small number that is not a colour.
|
||||
`GRAPHIC mode` chooses a screen mode; `GRAPHIC CLR` clears it. Mode 0 is text and
|
||||
refuses to draw.
|
||||
|
||||
Every picture in this chapter is generated by running the listing above it; see
|
||||
`MAINTENANCE.md` if you are editing one.
|
||||
|
||||
### DRAW
|
||||
|
||||
```basic requires=akgl
|
||||
10 DRAW 1, 10, 20
|
||||
20 DRAW 1, 0, 0 TO 100, 100 TO 200, 0
|
||||
```basic requires=akgl screenshot=draw
|
||||
10 COLOR 1, 8
|
||||
20 DRAW 1, 20, 180 TO 90, 40 TO 160, 150 TO 230, 20 TO 300, 120
|
||||
30 COLOR 2, 6
|
||||
40 DRAW 2, 20, 190 TO 300, 190
|
||||
50 LOCATE 160, 100
|
||||
60 COLOR 3, 3
|
||||
70 DRAW 3
|
||||
```
|
||||
|
||||

|
||||
|
||||
One coordinate pair plots a point. Two or more, separated by `TO`, draw a polyline. A
|
||||
bare `DRAW 1` plots wherever `LOCATE` left the pixel cursor.
|
||||
bare `DRAW 3` — the last line above, and the single red pixel in the middle of the
|
||||
picture — plots wherever `LOCATE` left the pixel cursor.
|
||||
|
||||
### BOX
|
||||
|
||||
```basic requires=akgl
|
||||
10 BOX 1, 10, 10, 40, 40
|
||||
```basic requires=akgl screenshot=box
|
||||
10 COLOR 1, 8
|
||||
20 BOX 1, 20, 30, 130, 140
|
||||
30 COLOR 2, 6
|
||||
40 BOX 2, 180, 30, 290, 140, 30
|
||||
50 COLOR 3, 3
|
||||
60 LOCATE 300, 190
|
||||
70 BOX 3, 20, 160
|
||||
```
|
||||
|
||||
Corners, and an optional rotation angle. An unrotated `BOX` outlines rather than fills.
|
||||

|
||||
|
||||
Corners, and an optional rotation angle — the green box is the same box turned 30
|
||||
degrees about its own centre. Two coordinates instead of four take the other corner
|
||||
from the pixel cursor, which is the red box.
|
||||
|
||||
**`BOX` always outlines; it cannot fill.** BASIC 7.0 selects fill with a seventh
|
||||
argument and that is not implemented here — see Chapter 13. `PAINT` is the fill you
|
||||
have.
|
||||
|
||||
### CIRCLE
|
||||
|
||||
```basic requires=akgl
|
||||
10 CIRCLE 1, 160, 100, 50, 30
|
||||
```basic requires=akgl screenshot=circle
|
||||
10 COLOR 1, 8
|
||||
20 CIRCLE 1, 80, 70, 60, 60
|
||||
30 COLOR 2, 6
|
||||
40 CIRCLE 2, 230, 70, 75, 45
|
||||
50 COLOR 3, 3
|
||||
60 CIRCLE 3, 160, 140, 130, 50, 90, 270
|
||||
```
|
||||
|
||||
Source, centre, then the two radii — so it draws ellipses. Further arguments give a
|
||||
start angle, an end angle, a rotation and the degree increment, which is what makes it
|
||||
an arc or a polygon.
|
||||

|
||||
|
||||
Source, centre, then the two radii — equal radii give a circle and unequal ones an
|
||||
ellipse. Two further arguments are a start and an end angle, which is what makes the
|
||||
red arc: 90 to 270 is the bottom half, because angles here are degrees clockwise from
|
||||
straight up, the same convention `MOVSPR` uses. Beyond those come a rotation and the
|
||||
degree increment, and a large increment is what turns a circle into a polygon.
|
||||
|
||||
### PAINT
|
||||
|
||||
```basic requires=akgl
|
||||
10 PAINT 1, 160, 100
|
||||
```basic requires=akgl screenshot=paint
|
||||
10 COLOR 1, 8
|
||||
20 CIRCLE 1, 100, 100, 70, 70
|
||||
30 BOX 1, 180, 50, 290, 150
|
||||
40 COLOR 2, 6
|
||||
50 PAINT 2, 100, 100
|
||||
60 COLOR 3, 3
|
||||
70 PAINT 3, 230, 100
|
||||
```
|
||||
|
||||
Flood-fills the region containing a point. If the region is too large for the fill's
|
||||
own working space it stops and reports rather than leaving a half-painted screen with
|
||||
no explanation.
|
||||

|
||||
|
||||
Flood-fills the region containing a point, stopping at whatever is already drawn — so
|
||||
the outline you fill inside can come from any verb. If the region is too large for the
|
||||
fill's own working space it stops and reports rather than leaving a half-painted screen
|
||||
with no explanation.
|
||||
|
||||
### LOCATE
|
||||
|
||||
@@ -110,10 +153,23 @@ coordinates finishes.
|
||||
|
||||
### SCALE
|
||||
|
||||
```basic requires=akgl
|
||||
10 SCALE 1, 1023, 1023
|
||||
```basic requires=akgl screenshot=scale size=640x400
|
||||
10 COLOR 1, 3
|
||||
20 BOX 1, 0, 0, 319, 199
|
||||
30 SCALE 1, 319, 199
|
||||
40 COLOR 2, 6
|
||||
50 BOX 2, 0, 0, 319, 199
|
||||
60 DRAW 2, 0, 0 TO 319, 199
|
||||
```
|
||||
|
||||

|
||||
|
||||
That picture is 640 by 400, and both boxes name the same four numbers. The red one is
|
||||
drawn with `SCALE` off, so its coordinates are pixels and it covers exactly the
|
||||
top-left 320 by 200 of the window — which is what a C128 listing does here. The green
|
||||
one is drawn after `SCALE 1, 319, 199` and fills the window, and the diagonal confirms
|
||||
that 319, 199 reaches the last pixel rather than stopping one short of it.
|
||||
|
||||
Turns on user coordinates and gives their maxima. With it on, your coordinates are
|
||||
mapped onto the drawing surface: 0 is the first pixel and the maximum you gave is the
|
||||
*last* one, so `DRAW 1, 1023, 1023` above reaches the bottom-right corner rather than
|
||||
@@ -137,12 +193,21 @@ parallel passes; see Chapter 13.
|
||||
|
||||
`SSHAPE` copies a rectangle off the screen and `GSHAPE` stamps it back:
|
||||
|
||||
```basic requires=akgl
|
||||
10 BOX 1, 0, 0, 20, 20
|
||||
20 SSHAPE A$, 0, 0, 20, 20
|
||||
30 GSHAPE A$, 100, 100
|
||||
```basic requires=akgl screenshot=shapes
|
||||
10 COLOR 1, 8
|
||||
20 CIRCLE 1, 40, 40, 30, 30
|
||||
30 COLOR 2, 6
|
||||
40 PAINT 2, 40, 40
|
||||
50 SSHAPE A$, 8, 8, 72, 72
|
||||
60 GSHAPE A$, 120, 20
|
||||
70 GSHAPE A$, 200, 60
|
||||
80 GSHAPE A$, 120, 120
|
||||
```
|
||||
|
||||

|
||||
|
||||
One disc drawn, captured, and stamped three times.
|
||||
|
||||
**`A$` holds a handle, not the pixels.** On a C128 the string holds the bitmap, so a
|
||||
program could save it to disk or take its `LEN`. Here a string is a fixed 255 bytes and
|
||||
the region is a device surface, so what goes in the string is a reference to it —
|
||||
|
||||
@@ -58,12 +58,33 @@ operation.
|
||||
|
||||
## Showing and moving
|
||||
|
||||
```basic requires=akgl setup=ship
|
||||
10 SPRSAV "ship.png", 1
|
||||
20 SPRITE 1, 1, 3
|
||||
30 MOVSPR 1, 100, 50
|
||||
```basic requires=akgl screenshot=sprites
|
||||
10 COLOR 1, 2
|
||||
20 CIRCLE 1, 20, 20, 18, 18
|
||||
30 PAINT 1, 20, 20
|
||||
40 SSHAPE A$, 0, 0, 40, 40
|
||||
50 GRAPHIC 1, 1
|
||||
60 FOR N# = 1 TO 3
|
||||
70 SPRSAV A$, N#
|
||||
80 NEXT N#
|
||||
90 SPRITE 1, 1
|
||||
100 SPRITE 2, 1, 6
|
||||
110 SPRITE 3, 1, 8, 0, 1, 1
|
||||
120 MOVSPR 1, 30, 80
|
||||
130 MOVSPR 2, 110, 80
|
||||
140 MOVSPR 3, 190, 60
|
||||
```
|
||||
|
||||

|
||||
|
||||
Three sprites from one drawing: the first in the default colour, the second in colour
|
||||
6, and the third in colour 8 with both expansion bits set, which is what makes it twice
|
||||
the size. `GRAPHIC 1, 1` on line 50 wipes the drawing the sprites were captured from —
|
||||
the picture would otherwise still show the original disc in the top-left corner.
|
||||
|
||||
**A sprite's colour multiplies the artwork rather than replacing it**, so a white disc
|
||||
takes the colour cleanly and a coloured one comes out darker than you asked for.
|
||||
|
||||
`SPRITE n [,on] [,colour] [,priority] [,xexpand] [,yexpand] [,multicolour]`. Only the
|
||||
number is required and **an argument you leave out is left alone**, so `SPRITE 1, 1`
|
||||
turns sprite 1 on without disturbing its colour.
|
||||
@@ -113,6 +134,27 @@ Only type 1, sprite-to-sprite, is implemented. Types 2 and 3 are refused by name
|
||||
Collision is by bounding box, not by pixel: two sprites whose boxes overlap but whose
|
||||
artwork does not are reported as colliding.
|
||||
|
||||
```basic requires=akgl screenshot=sprite-collision
|
||||
10 COLOR 1, 2
|
||||
20 CIRCLE 1, 20, 20, 14, 14
|
||||
30 PAINT 1, 20, 20
|
||||
40 BOX 1, 0, 0, 40, 40
|
||||
50 SSHAPE A$, 0, 0, 40, 40
|
||||
60 GRAPHIC 1, 1
|
||||
70 SPRSAV A$, 1
|
||||
80 SPRSAV A$, 2
|
||||
90 SPRITE 1, 1, 6
|
||||
100 SPRITE 2, 1, 3
|
||||
110 MOVSPR 1, 110, 55
|
||||
120 MOVSPR 2, 145, 90
|
||||
```
|
||||
|
||||

|
||||
|
||||
The artwork here includes a border around the whole 40 by 40 sprite, so each sprite
|
||||
draws its own bounding box. Those boxes overlap at one corner and the two discs are
|
||||
nowhere near each other — and `BUMP(1)` reports a collision.
|
||||
|
||||
## Reading state back
|
||||
|
||||
| Function | Gives |
|
||||
|
||||
21
docs/images/README.md
Normal file
@@ -0,0 +1,21 @@
|
||||
# Generated figures — do not edit
|
||||
|
||||
**Every PNG in this directory is output, not an asset.** Each one is produced by running
|
||||
the BASIC listing shown immediately above it in the chapter, through
|
||||
`tools/screenshot.c`, and it is checked in only because a reader on the forge has no
|
||||
build tree to generate it from.
|
||||
|
||||
To change a picture, **change the listing** and regenerate:
|
||||
|
||||
```sh
|
||||
cmake --build build-akgl --target docs_screenshots
|
||||
```
|
||||
|
||||
Editing a PNG here by hand puts the picture out of step with the code it illustrates,
|
||||
which is the exact failure this arrangement exists to prevent — and the `docs_screenshots`
|
||||
test will fail on it, because it re-renders every figure and compares byte for byte.
|
||||
|
||||
The tag that ties a figure to its listing is `screenshot=NAME` in the fence info string,
|
||||
and `MAINTENANCE.md` documents it along with `size=WxH` for a figure that needs a surface
|
||||
other than 320 by 200. A tagged block with no image here fails `docs_examples` in both
|
||||
build configurations, so a figure cannot be added to a chapter and then forgotten.
|
||||
BIN
docs/images/box.png
Normal file
|
After Width: | Height: | Size: 1.5 KiB |
BIN
docs/images/circle.png
Normal file
|
After Width: | Height: | Size: 1.8 KiB |
BIN
docs/images/draw.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
docs/images/paint.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
BIN
docs/images/scale.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
docs/images/shapes.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
BIN
docs/images/sprite-collision.png
Normal file
|
After Width: | Height: | Size: 739 B |
BIN
docs/images/sprites.png
Normal file
|
After Width: | Height: | Size: 904 B |