Add the galaga example: a C engine with akbasic as its enemy brain
A GALAGA-style fixed shooter whose engine is C on libakgl (null physics) with the interpreter embedded as the scripting engine that owns every enemy's behavior. One DEF-only script is called per enemy per frame through a custom akgl_Actor update hook; SELF@, ACTOR@ and GAME@ are host bindings, so the script reads and writes the engine's real memory -- the boss even swaps its own damage sprite by raising an actor state bit from BASIC. Bullets, collision, scoring and screens stay C. Structure arguments were measured and rejected for the per-frame path: each pointer parameter spends a value-pool slot the pool never reclaims, 1,015 calls to exhaustion against an unbounded rebind (issue #36). Built when AKBASIC_WITH_AKGL=ON. Two CTest entries: a 600-frame headless autoplay run under the dummy SDL drivers, and an interop round-trip test that links the real script.c and galaga.bas and pins the four boundary claims, 24,000 sustained calls among them. docs_galaga_figures regenerates the two checked-in figures. Art is Kenney CC0, byte for byte, with provenance. 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:
119
examples/galaga/galaga.bas
Normal file
119
examples/galaga/galaga.bas
Normal file
@@ -0,0 +1,119 @@
|
||||
REM GALAGA enemy behavior. The C engine loads this file, runs it once so the
|
||||
REM definitions exist, and then calls one UPDATE function per enemy per frame.
|
||||
REM There is no top-level code: definitions, then END.
|
||||
REM
|
||||
REM Names the engine binds before every call:
|
||||
REM SELF@ - this enemy's record (ENEMY): the state machine's memory
|
||||
REM ACTOR@ - the engine's live actor (ACTOR): position is the real thing
|
||||
REM GAME@ - shared frame state (GAME): player position, wave, randomness
|
||||
REM
|
||||
REM SELF@.STATE# bits: 1 = entering 2 = in formation 4 = diving
|
||||
REM
|
||||
REM Two rules of this dialect that bite here, both from docs/03:
|
||||
REM - the LEFT operand decides integer or float arithmetic, so a float
|
||||
REM always goes first: SELF@.T% * 150 + 260, never 260 + 150 * SELF@.T%
|
||||
REM - RETURN at the start of a line ends the DEF body, so every early
|
||||
REM return rides an IF ... THEN, and only the last RETURN starts a line
|
||||
|
||||
REM Ease toward the formation slot, with a little entry swirl.
|
||||
REM Answers 1 once the slot is reached, else 0.
|
||||
DEF GLIDEHOME(DT%)
|
||||
DX% = SELF@.HOMEX% - ACTOR@.X%
|
||||
DY% = SELF@.HOMEY% - ACTOR@.Y%
|
||||
K% = DT% * 4.5
|
||||
IF K% > 1 THEN K% = 1
|
||||
ACTOR@.X% = ACTOR@.X% + DX% * K% + SIN(SELF@.T% * 6) * 90 * DT%
|
||||
ACTOR@.Y% = ACTOR@.Y% + DY% * K%
|
||||
IF ABS(DX%) < 3 AND ABS(DY%) < 3 THEN RETURN 1
|
||||
RETURN 0
|
||||
|
||||
REM One frame of a dive: accelerate downward, weave, lean toward the
|
||||
REM player's column, and glide back in from the top after falling out.
|
||||
DEF DIVESTEP(DT%, WEAVE%, LEAD%)
|
||||
SPD% = SELF@.T% * 150 + 260
|
||||
ACTOR@.Y% = ACTOR@.Y% + SPD% * DT%
|
||||
ACTOR@.X% = ACTOR@.X% + SIN(SELF@.T% * 4) * WEAVE% * DT%
|
||||
DX% = GAME@.PLAYERX% - ACTOR@.X%
|
||||
IF DX% > 220 THEN DX% = 220
|
||||
IF DX% < -220 THEN DX% = -220
|
||||
ACTOR@.X% = ACTOR@.X% + DX% * LEAD% * DT%
|
||||
IF ACTOR@.Y% > 1040 THEN BEGIN
|
||||
ACTOR@.Y% = 0.0 - 90
|
||||
SELF@.STATE# = 1
|
||||
SELF@.T% = 0
|
||||
BEND
|
||||
RETURN 0
|
||||
|
||||
REM Raise the fire flag when diving roughly above the player. The engine
|
||||
REM consumes FIRE# and does the spawning; the script only wishes.
|
||||
DEF DECIDEFIRE(DT%)
|
||||
DX% = GAME@.PLAYERX% - ACTOR@.X%
|
||||
IF ABS(DX%) > 140 THEN RETURN 0
|
||||
IF ACTOR@.Y% > GAME@.PLAYERY% THEN RETURN 0
|
||||
IF SELF@.RND% < DT% * 1.5 THEN SELF@.FIRE# = 1
|
||||
RETURN 0
|
||||
|
||||
REM Bee: enter, breathe in formation, occasionally dive nearly straight.
|
||||
DEF UPDATEBEE(DT%)
|
||||
SELF@.T% = SELF@.T% + DT%
|
||||
IF SELF@.T% < 0 THEN RETURN 0
|
||||
S# = SELF@.STATE#
|
||||
IF (S# AND 1) > 0 THEN BEGIN
|
||||
R# = GLIDEHOME(DT%)
|
||||
IF R# = 1 THEN SELF@.STATE# = 2 : SELF@.T% = 0
|
||||
BEND
|
||||
IF (S# AND 2) > 0 THEN BEGIN
|
||||
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 1.7) * 16
|
||||
ACTOR@.Y% = SELF@.HOMEY%
|
||||
IF SELF@.RND% < DT% * 0.04 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
||||
BEND
|
||||
IF (S# AND 4) > 0 THEN BEGIN
|
||||
R# = DIVESTEP(DT%, 130, 0.2)
|
||||
R# = DECIDEFIRE(DT%)
|
||||
BEND
|
||||
RETURN 0
|
||||
|
||||
REM Butterfly: the same machine with a wide lateral weave on the dive.
|
||||
DEF UPDATEBFLY(DT%)
|
||||
SELF@.T% = SELF@.T% + DT%
|
||||
IF SELF@.T% < 0 THEN RETURN 0
|
||||
S# = SELF@.STATE#
|
||||
IF (S# AND 1) > 0 THEN BEGIN
|
||||
R# = GLIDEHOME(DT%)
|
||||
IF R# = 1 THEN SELF@.STATE# = 2 : SELF@.T% = 0
|
||||
BEND
|
||||
IF (S# AND 2) > 0 THEN BEGIN
|
||||
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 2.1) * 24
|
||||
ACTOR@.Y% = SELF@.HOMEY%
|
||||
IF SELF@.RND% < DT% * 0.05 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
||||
BEND
|
||||
IF (S# AND 4) > 0 THEN BEGIN
|
||||
R# = DIVESTEP(DT%, 260, 0.1)
|
||||
R# = DECIDEFIRE(DT%)
|
||||
BEND
|
||||
RETURN 0
|
||||
|
||||
REM Boss: two hit points, a slow sway, and a dive that leads the player.
|
||||
REM At one hit point it raises actor state bit 13 (8192), and the engine's
|
||||
REM character mapping swaps the sprite - the boundary crossed the other way.
|
||||
DEF UPDATEBOSS(DT%)
|
||||
SELF@.T% = SELF@.T% + DT%
|
||||
IF SELF@.T% < 0 THEN RETURN 0
|
||||
IF SELF@.HP# = 1 THEN ACTOR@.STATE# = ACTOR@.STATE# OR 8192
|
||||
S# = SELF@.STATE#
|
||||
IF (S# AND 1) > 0 THEN BEGIN
|
||||
R# = GLIDEHOME(DT%)
|
||||
IF R# = 1 THEN SELF@.STATE# = 2 : SELF@.T% = 0
|
||||
BEND
|
||||
IF (S# AND 2) > 0 THEN BEGIN
|
||||
ACTOR@.X% = SELF@.HOMEX% + SIN(SELF@.T% * 1.1) * 10
|
||||
ACTOR@.Y% = SELF@.HOMEY%
|
||||
IF SELF@.RND% < DT% * 0.03 THEN SELF@.STATE# = 4 : SELF@.T% = 0
|
||||
BEND
|
||||
IF (S# AND 4) > 0 THEN BEGIN
|
||||
R# = DIVESTEP(DT%, 60, 0.9)
|
||||
R# = DECIDEFIRE(DT%)
|
||||
BEND
|
||||
RETURN 0
|
||||
|
||||
END
|
||||
Reference in New Issue
Block a user