Close the remaining cold-read gaps: bind, labels, menus and main's shape
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 5m32s
akbasic CI Build / sanitizers (push) Failing after 15m58s
akbasic CI Build / coverage (push) Failing after 20m7s
akbasic CI Build / mutation_test (push) Failing after 4m29s
akbasic CI Build / akgl_build (push) Failing after 12m28s
Some checks failed
akbasic CI Build / cmake_build (push) Failing after 5m32s
akbasic CI Build / sanitizers (push) Failing after 15m58s
akbasic CI Build / coverage (push) Failing after 20m7s
akbasic CI Build / mutation_test (push) Failing after 4m29s
akbasic CI Build / akgl_build (push) Failing after 12m28s
Three more Haiku-class cold reads of the chapters, each against the amended text. What each surfaced is now shown rather than described: the akbasic_host_register_type()/akbasic_host_bind() boot calls, the declare_play() label listing, the akgl_UiMenu static and its handle_event signature, one control-handler pair, and main()'s ATTEMPT/HANDLE_DEFAULT/FINISH_NORETURN shape with the CATCH-inside- ATTEMPT rule stated. By the fourth read the generated player.c and enemies.c compiled untouched and every remaining guess was a tuning value the chapters deliberately leave open. Co-authored-by: andrew <andrew@aklabs.net> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XiGgpHuXUm2mR4Wzndw3dc
This commit is contained in:
@@ -125,7 +125,38 @@ writes `x` and `y` directly is the mover, and in this game that will be BASIC.
|
||||
**Error handling is the house protocol.** Every function returns
|
||||
`akerr_ErrorContext *`, `PASS` propagates, `ATTEMPT`/`CATCH`/`CLEANUP` brackets
|
||||
anything that must unwind. libakgl's docs/04-errors.md teaches it; this chapter
|
||||
just uses it. The status codes this game raises are `AKERR_NULLPOINTER`,
|
||||
just uses it, with two rules that keep the fragments compiling: **`CATCH` is
|
||||
only legal inside an `ATTEMPT` block, and `PASS` everywhere else** — swap them
|
||||
and the compiler objects about a stray `break` — and `main()` alone ends its
|
||||
block with `FINISH_NORETURN(errctx)` instead of `FINISH`, because `FINISH`
|
||||
expands a `return` of the context that an `int`-returning function cannot
|
||||
compile:
|
||||
|
||||
```c wrap=galagatypes requires=akgl
|
||||
static int FAILED = 0;
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
(void)argc; (void)argv;
|
||||
ATTEMPT {
|
||||
/* CATCH each stage in order: startup, assets, the script boot,
|
||||
* the spawns, then the frame loop. */
|
||||
} CLEANUP {
|
||||
/* ...teardown, every call wrapped in IGNORE()... */
|
||||
} PROCESS(errctx) {
|
||||
} HANDLE_DEFAULT(errctx) {
|
||||
LOG_ERROR_WITH_MESSAGE(errctx, "galaga could not run");
|
||||
/* Set a flag rather than returning: leaving a HANDLE block early
|
||||
* skips FINISH's release and leaks the context's pool slot. */
|
||||
FAILED = 1;
|
||||
} FINISH_NORETURN(errctx);
|
||||
return FAILED;
|
||||
}
|
||||
```
|
||||
|
||||
The status codes this game raises are `AKERR_NULLPOINTER`,
|
||||
`AKERR_VALUE`, `AKERR_KEY`, `AKERR_IO`, `AKERR_OUTOFBOUNDS`, `AKGL_ERR_SDL`
|
||||
and `AKGL_ERR_HEAP` — there is no code this tutorial invents.
|
||||
|
||||
@@ -285,8 +316,32 @@ Each of the four lines under the comment closes a trap:
|
||||
invisibly. This one line cost this example its first screenshot.
|
||||
|
||||
Input goes through a control map: push a control per key with handlers that set
|
||||
flags, and let the actor's update hook read the flags. The full recipe is in
|
||||
`examples/galaga/player.c` and libakgl docs/16-input.md; the shape is:
|
||||
flags, and let the actor's update hook read the flags. A handler receives the
|
||||
map's target actor and the event, and returns through the error protocol like
|
||||
everything else — this pair is the whole pattern, repeated per key:
|
||||
|
||||
```c wrap=galagagame requires=akgl
|
||||
static bool MOVELEFT = false;
|
||||
|
||||
akerr_ErrorContext *left_on(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
(void)obj; (void)event;
|
||||
MOVELEFT = true;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
|
||||
akerr_ErrorContext *left_off(akgl_Actor *obj, SDL_Event *event)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
(void)obj; (void)event;
|
||||
MOVELEFT = false;
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
```
|
||||
|
||||
(The example keeps the flags in its `galaga_Game` struct rather than statics;
|
||||
either works.) The bindings themselves are pushes onto map 0:
|
||||
|
||||
```c wrap=galagagame requires=akgl
|
||||
static akerr_ErrorContext *galaga_player_controls(void)
|
||||
@@ -673,10 +728,53 @@ case GALAGA_SCREEN_VICTORY:
|
||||
CATCH(errctx, akgl_ui_frame_end(akgl_renderer));
|
||||
```
|
||||
|
||||
The playing screen is two `akgl_ui_label()` calls — score top-left, lives and
|
||||
wave top-right — formatted into `static` buffers, because the UI borrows label
|
||||
The playing screen is two `akgl_ui_label()` calls — a widget call per label,
|
||||
not a struct — formatted into `static` buffers, because the UI borrows label
|
||||
text until `frame_end` and a local buffer would be dangling by the time it
|
||||
draws. The title and end screens are an `akgl_ui_menu()` at the center.
|
||||
draws:
|
||||
|
||||
```c wrap=galagagame requires=akgl
|
||||
static char HUD_SCORE[64];
|
||||
static char HUD_LIVES[64];
|
||||
|
||||
static akerr_ErrorContext *declare_play(void)
|
||||
{
|
||||
int count = 0;
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
PASS(errctx, aksl_snprintf(&count, HUD_SCORE, sizeof(HUD_SCORE),
|
||||
"SCORE %06d", galaga_game.score));
|
||||
PASS(errctx, aksl_snprintf(&count, HUD_LIVES, sizeof(HUD_LIVES),
|
||||
"LIVES %d WAVE %d", galaga_game.lives, galaga_shared.wave));
|
||||
PASS(errctx, akgl_ui_label("score", HUD_SCORE, AKGL_UI_ANCHOR_TOP_LEFT, NULL));
|
||||
PASS(errctx, akgl_ui_label("lives", HUD_LIVES, AKGL_UI_ANCHOR_TOP_RIGHT, NULL));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
```
|
||||
|
||||
The title and end screens are an `akgl_ui_menu()` at the center, fed an
|
||||
`akgl_UiMenu` that lives in a `static` for the same borrowing reason. The
|
||||
struct is an id, the item strings, a count, the selected index, the
|
||||
`activated` output flag, and a style (`NULL` for the default):
|
||||
|
||||
```c wrap=galagatypes requires=akgl
|
||||
static akgl_UiMenu TITLE_MENU = {
|
||||
"titlemenu", { "START", "QUIT" }, 2, 0, false, NULL
|
||||
};
|
||||
|
||||
static akerr_ErrorContext *declare_title(void)
|
||||
{
|
||||
PREPARE_ERROR(errctx);
|
||||
|
||||
PASS(errctx, akgl_ui_menu(&TITLE_MENU));
|
||||
SUCCEED_RETURN(errctx);
|
||||
}
|
||||
```
|
||||
|
||||
Route events to the menu with
|
||||
`akgl_ui_menu_handle_event(&TITLE_MENU, event, &consumed)` — the menu for
|
||||
whichever screen is up, a `bool` out-parameter reporting whether the event was
|
||||
taken. Up and down move `selected`, return sets `activated`.
|
||||
|
||||
The big **GALAGA** headline is direct text rather than a label:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user