Files
akbasic/examples/galaga/galaga.bas

120 lines
4.1 KiB
QBasic
Raw Normal View History

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
Unbreak the three example programs the RND merge left behind Adding native RND and ASC (ae2c702) made RND a function name, and a suffixed identifier that collides with one is refused -- "SYNTAX ERROR Reserved word in variable name". Three example programs held their PRNG output in a variable called RND#, or a host field called RND%, and none of them had run since: - examples/galaga/ bound RND% as a host field on both ENEMY and GAME. The BASIC-visible name is ROLL% now; the C member stays `rnd`. This one was caught by example_galaga and example_galaga_interop, which have been failing. - examples/breakout/characters/breakout.bas and examples/megademo/ megademo.bas both use RND# for their LCG output, renamed to ROLL#. Neither is in any test, so neither failure was visible. examples/breakout/sprites/breakout.bas was broken a second way: seven REM lines the reader refuses. Worth recording that the ceiling is not the one the message names -- src/sink_stdio.c fails when the read filled the buffer without seeing a terminator, so with AKBASIC_MAX_LINE_LENGTH at 80 the message says "79 character limit" and the real maximum is 78, because a 79-character line leaves no room for the newline. The sweeps that fixed the corpus and the megademo for this did not reach this file. The seven comments are reflowed. The prose went stale with the code. Chapter 21 said "there is no RND verb in this dialect; issue #16 tracks adding one", chapter 17's historical aside offered an LCG that no longer parses, and four REM blocks across the two games said the same thing. All of them now say RND exists, and say why these programs keep their own generator anyway: the sequence has to be reproducible for a headless run to be the same game on every machine, which is what lets interop_test.c assert exact counts. Chapter 21 also gains the rule that bit them, since a reader writing a host type will hit it: a host field name is a bare word and shares a namespace with every verb and function. None of this came from the submodule bump -- all three were already broken on main. It was found by running the tutorial games, which nothing else does; that gap is akbasic issue #58. Verified: all three run clean under the dummy drivers, and 114/114 default, 116/116 with akgl. Co-Authored-By: Andrew Kesterson <andrew@aklabs.net> Co-Authored-By: Claude Code (Claude Opus 5, claude-opus-5[1m]) <noreply@anthropic.com>
2026-08-05 23:26:33 -04:00
IF SELF@.ROLL% < 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%
Unbreak the three example programs the RND merge left behind Adding native RND and ASC (ae2c702) made RND a function name, and a suffixed identifier that collides with one is refused -- "SYNTAX ERROR Reserved word in variable name". Three example programs held their PRNG output in a variable called RND#, or a host field called RND%, and none of them had run since: - examples/galaga/ bound RND% as a host field on both ENEMY and GAME. The BASIC-visible name is ROLL% now; the C member stays `rnd`. This one was caught by example_galaga and example_galaga_interop, which have been failing. - examples/breakout/characters/breakout.bas and examples/megademo/ megademo.bas both use RND# for their LCG output, renamed to ROLL#. Neither is in any test, so neither failure was visible. examples/breakout/sprites/breakout.bas was broken a second way: seven REM lines the reader refuses. Worth recording that the ceiling is not the one the message names -- src/sink_stdio.c fails when the read filled the buffer without seeing a terminator, so with AKBASIC_MAX_LINE_LENGTH at 80 the message says "79 character limit" and the real maximum is 78, because a 79-character line leaves no room for the newline. The sweeps that fixed the corpus and the megademo for this did not reach this file. The seven comments are reflowed. The prose went stale with the code. Chapter 21 said "there is no RND verb in this dialect; issue #16 tracks adding one", chapter 17's historical aside offered an LCG that no longer parses, and four REM blocks across the two games said the same thing. All of them now say RND exists, and say why these programs keep their own generator anyway: the sequence has to be reproducible for a headless run to be the same game on every machine, which is what lets interop_test.c assert exact counts. Chapter 21 also gains the rule that bit them, since a reader writing a host type will hit it: a host field name is a bare word and shares a namespace with every verb and function. None of this came from the submodule bump -- all three were already broken on main. It was found by running the tutorial games, which nothing else does; that gap is akbasic issue #58. Verified: all three run clean under the dummy drivers, and 114/114 default, 116/116 with akgl. Co-Authored-By: Andrew Kesterson <andrew@aklabs.net> Co-Authored-By: Claude Code (Claude Opus 5, claude-opus-5[1m]) <noreply@anthropic.com>
2026-08-05 23:26:33 -04:00
IF SELF@.ROLL% < 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%
Unbreak the three example programs the RND merge left behind Adding native RND and ASC (ae2c702) made RND a function name, and a suffixed identifier that collides with one is refused -- "SYNTAX ERROR Reserved word in variable name". Three example programs held their PRNG output in a variable called RND#, or a host field called RND%, and none of them had run since: - examples/galaga/ bound RND% as a host field on both ENEMY and GAME. The BASIC-visible name is ROLL% now; the C member stays `rnd`. This one was caught by example_galaga and example_galaga_interop, which have been failing. - examples/breakout/characters/breakout.bas and examples/megademo/ megademo.bas both use RND# for their LCG output, renamed to ROLL#. Neither is in any test, so neither failure was visible. examples/breakout/sprites/breakout.bas was broken a second way: seven REM lines the reader refuses. Worth recording that the ceiling is not the one the message names -- src/sink_stdio.c fails when the read filled the buffer without seeing a terminator, so with AKBASIC_MAX_LINE_LENGTH at 80 the message says "79 character limit" and the real maximum is 78, because a 79-character line leaves no room for the newline. The sweeps that fixed the corpus and the megademo for this did not reach this file. The seven comments are reflowed. The prose went stale with the code. Chapter 21 said "there is no RND verb in this dialect; issue #16 tracks adding one", chapter 17's historical aside offered an LCG that no longer parses, and four REM blocks across the two games said the same thing. All of them now say RND exists, and say why these programs keep their own generator anyway: the sequence has to be reproducible for a headless run to be the same game on every machine, which is what lets interop_test.c assert exact counts. Chapter 21 also gains the rule that bit them, since a reader writing a host type will hit it: a host field name is a bare word and shares a namespace with every verb and function. None of this came from the submodule bump -- all three were already broken on main. It was found by running the tutorial games, which nothing else does; that gap is akbasic issue #58. Verified: all three run clean under the dummy drivers, and 114/114 default, 116/116 with akgl. Co-Authored-By: Andrew Kesterson <andrew@aklabs.net> Co-Authored-By: Claude Code (Claude Opus 5, claude-opus-5[1m]) <noreply@anthropic.com>
2026-08-05 23:26:33 -04:00
IF SELF@.ROLL% < 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%
Unbreak the three example programs the RND merge left behind Adding native RND and ASC (ae2c702) made RND a function name, and a suffixed identifier that collides with one is refused -- "SYNTAX ERROR Reserved word in variable name". Three example programs held their PRNG output in a variable called RND#, or a host field called RND%, and none of them had run since: - examples/galaga/ bound RND% as a host field on both ENEMY and GAME. The BASIC-visible name is ROLL% now; the C member stays `rnd`. This one was caught by example_galaga and example_galaga_interop, which have been failing. - examples/breakout/characters/breakout.bas and examples/megademo/ megademo.bas both use RND# for their LCG output, renamed to ROLL#. Neither is in any test, so neither failure was visible. examples/breakout/sprites/breakout.bas was broken a second way: seven REM lines the reader refuses. Worth recording that the ceiling is not the one the message names -- src/sink_stdio.c fails when the read filled the buffer without seeing a terminator, so with AKBASIC_MAX_LINE_LENGTH at 80 the message says "79 character limit" and the real maximum is 78, because a 79-character line leaves no room for the newline. The sweeps that fixed the corpus and the megademo for this did not reach this file. The seven comments are reflowed. The prose went stale with the code. Chapter 21 said "there is no RND verb in this dialect; issue #16 tracks adding one", chapter 17's historical aside offered an LCG that no longer parses, and four REM blocks across the two games said the same thing. All of them now say RND exists, and say why these programs keep their own generator anyway: the sequence has to be reproducible for a headless run to be the same game on every machine, which is what lets interop_test.c assert exact counts. Chapter 21 also gains the rule that bit them, since a reader writing a host type will hit it: a host field name is a bare word and shares a namespace with every verb and function. None of this came from the submodule bump -- all three were already broken on main. It was found by running the tutorial games, which nothing else does; that gap is akbasic issue #58. Verified: all three run clean under the dummy drivers, and 114/114 default, 116/116 with akgl. Co-Authored-By: Andrew Kesterson <andrew@aklabs.net> Co-Authored-By: Claude Code (Claude Opus 5, claude-opus-5[1m]) <noreply@anthropic.com>
2026-08-05 23:26:33 -04:00
IF SELF@.ROLL% < 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