romdevtools 0.27.0 → 0.29.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +56 -44
- package/CHANGELOG.md +355 -0
- package/README.md +4 -4
- package/examples/README.md +7 -7
- package/examples/atari2600/templates/platformer.asm +1227 -325
- package/examples/atari2600/templates/puzzle.asm +1056 -0
- package/examples/atari2600/templates/racing.asm +909 -257
- package/examples/atari2600/templates/shmup.asm +1035 -218
- package/examples/atari2600/templates/sports.asm +1143 -229
- package/examples/atari7800/templates/hello_sprite.c +8 -4
- package/examples/atari7800/templates/platformer.c +991 -152
- package/examples/atari7800/templates/puzzle.c +1091 -145
- package/examples/atari7800/templates/racing.c +949 -118
- package/examples/atari7800/templates/shmup.c +812 -130
- package/examples/atari7800/templates/sports.c +820 -181
- package/examples/c64/templates/platformer.c +876 -157
- package/examples/c64/templates/puzzle.c +881 -143
- package/examples/c64/templates/racing.c +873 -88
- package/examples/c64/templates/shmup.c +762 -154
- package/examples/c64/templates/sports.c +755 -95
- package/examples/gb/templates/platformer.c +841 -175
- package/examples/gb/templates/puzzle.c +1094 -176
- package/examples/gb/templates/racing.c +761 -169
- package/examples/gb/templates/shmup.c +679 -169
- package/examples/gb/templates/sports.c +790 -153
- package/examples/gba/templates/platformer.c +624 -169
- package/examples/gba/templates/puzzle.c +535 -207
- package/examples/gba/templates/racing.c +513 -196
- package/examples/gba/templates/shmup.c +565 -168
- package/examples/gba/templates/sports.c +454 -162
- package/examples/gbc/templates/platformer.c +944 -176
- package/examples/gbc/templates/puzzle.c +1131 -177
- package/examples/gbc/templates/racing.c +891 -175
- package/examples/gbc/templates/shmup.c +827 -179
- package/examples/gbc/templates/sports.c +870 -156
- package/examples/genesis/templates/platformer.c +747 -129
- package/examples/genesis/templates/puzzle.c +702 -208
- package/examples/genesis/templates/racing.c +728 -193
- package/examples/genesis/templates/shmup.c +535 -142
- package/examples/genesis/templates/shmup_2p.c +13 -1
- package/examples/genesis/templates/sports.c +495 -158
- package/examples/gg/templates/platformer.c +883 -214
- package/examples/gg/templates/puzzle.c +906 -181
- package/examples/gg/templates/racing.c +919 -160
- package/examples/gg/templates/shmup.c +716 -177
- package/examples/gg/templates/sports.c +735 -128
- package/examples/lynx/templates/platformer.c +604 -50
- package/examples/lynx/templates/puzzle.c +533 -130
- package/examples/lynx/templates/racing.c +538 -102
- package/examples/lynx/templates/shmup.c +461 -122
- package/examples/lynx/templates/sports.c +496 -69
- package/examples/msx/platformer/main.c +648 -159
- package/examples/msx/puzzle/main.c +750 -185
- package/examples/msx/racing/main.c +669 -177
- package/examples/msx/shmup/main.c +460 -177
- package/examples/msx/sports/main.c +591 -124
- package/examples/nes/templates/platformer.c +586 -160
- package/examples/nes/templates/puzzle.c +603 -222
- package/examples/nes/templates/racing.c +505 -197
- package/examples/nes/templates/shmup.c +339 -144
- package/examples/nes/templates/sports.c +341 -182
- package/examples/pce/platformer/main.c +875 -204
- package/examples/pce/puzzle/main.c +797 -216
- package/examples/pce/racing/main.c +782 -206
- package/examples/pce/shmup/main.c +638 -211
- package/examples/pce/sports/main.c +585 -167
- package/examples/porting-across-platforms/README.md +1 -1
- package/examples/sms/templates/platformer.c +765 -176
- package/examples/sms/templates/puzzle.c +783 -177
- package/examples/sms/templates/racing.c +812 -133
- package/examples/sms/templates/shmup.c +601 -148
- package/examples/sms/templates/shmup_2p.c +17 -1
- package/examples/sms/templates/sports.c +633 -121
- package/examples/snes/templates/music_demo.c +7 -0
- package/examples/snes/templates/platformer-data.asm +123 -24
- package/examples/snes/templates/platformer-hdr.asm +57 -0
- package/examples/snes/templates/platformer.c +587 -149
- package/examples/snes/templates/puzzle-data.asm +116 -21
- package/examples/snes/templates/puzzle-hdr.asm +57 -0
- package/examples/snes/templates/puzzle.c +632 -185
- package/examples/snes/templates/racing-data.asm +390 -32
- package/examples/snes/templates/racing-hdr.asm +57 -0
- package/examples/snes/templates/racing.c +807 -177
- package/examples/snes/templates/shmup-data.asm +87 -29
- package/examples/snes/templates/shmup-hdr.asm +57 -0
- package/examples/snes/templates/shmup.c +459 -180
- package/examples/snes/templates/sports-data.asm +48 -2
- package/examples/snes/templates/sports-hdr.asm +57 -0
- package/examples/snes/templates/sports.c +414 -156
- package/package.json +12 -12
- package/src/cores/wasm/bluemsx_libretro.js +1 -1
- package/src/cores/wasm/bluemsx_libretro.wasm +0 -0
- package/src/cores/wasm/fceumm_libretro.js +1 -1
- package/src/cores/wasm/fceumm_libretro.wasm +0 -0
- package/src/cores/wasm/gambatte_libretro.js +1 -1
- package/src/cores/wasm/gambatte_libretro.wasm +0 -0
- package/src/cores/wasm/geargrafx_libretro.js +1 -1
- package/src/cores/wasm/geargrafx_libretro.wasm +0 -0
- package/src/cores/wasm/genesis_plus_gx_libretro.js +1 -1
- package/src/cores/wasm/genesis_plus_gx_libretro.wasm +0 -0
- package/src/cores/wasm/handy_libretro.js +1 -1
- package/src/cores/wasm/handy_libretro.wasm +0 -0
- package/src/cores/wasm/mgba_libretro.js +1 -1
- package/src/cores/wasm/mgba_libretro.wasm +0 -0
- package/src/cores/wasm/prosystem_libretro.js +1 -1
- package/src/cores/wasm/prosystem_libretro.wasm +0 -0
- package/src/cores/wasm/snes9x_libretro.js +1 -1
- package/src/cores/wasm/snes9x_libretro.wasm +0 -0
- package/src/cores/wasm/stella2014_libretro.js +1 -1
- package/src/cores/wasm/stella2014_libretro.wasm +0 -0
- package/src/cores/wasm/vice_x64_libretro.js +1 -1
- package/src/cores/wasm/vice_x64_libretro.wasm +0 -0
- package/src/host/LibretroHost.js +304 -11
- package/src/http/tool-registry.js +11 -11
- package/src/mcp/server.js +6 -0
- package/src/mcp/tools/cheats.js +2 -1
- package/src/mcp/tools/disasm-rebuild.js +315 -65
- package/src/mcp/tools/disasm.js +149 -28
- package/src/mcp/tools/find-references.js +216 -51
- package/src/mcp/tools/frame.js +14 -6
- package/src/mcp/tools/index.js +18 -4
- package/src/mcp/tools/input.js +31 -7
- package/src/mcp/tools/lifecycle.js +6 -4
- package/src/mcp/tools/memory.js +208 -39
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/playtest.js +56 -4
- package/src/mcp/tools/preview-tile.js +6 -2
- package/src/mcp/tools/project.js +1114 -120
- package/src/mcp/tools/rom-id.js +5 -1
- package/src/mcp/tools/run-until.js +4 -2
- package/src/mcp/tools/snippets.js +6 -6
- package/src/mcp/tools/sprite-pipeline.js +14 -2
- package/src/mcp/tools/state.js +2 -1
- package/src/mcp/tools/tile-inspect.js +8 -1
- package/src/mcp/tools/toolchain.js +55 -11
- package/src/mcp/tools/watch-memory.js +145 -27
- package/src/observer/bus.js +73 -0
- package/src/observer/livestream.html +4 -2
- package/src/observer/tool-wrap.js +17 -14
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +64 -17
- package/src/platforms/atari2600/MENTAL_MODEL.md +5 -1
- package/src/platforms/atari2600/TROUBLESHOOTING.md +40 -0
- package/src/platforms/atari7800/MENTAL_MODEL.md +32 -11
- package/src/platforms/atari7800/TROUBLESHOOTING.md +5 -5
- package/src/platforms/c64/MENTAL_MODEL.md +11 -4
- package/src/platforms/c64/TROUBLESHOOTING.md +13 -0
- package/src/platforms/gb/MENTAL_MODEL.md +19 -4
- package/src/platforms/gb/TROUBLESHOOTING.md +101 -6
- package/src/platforms/gb/lib/c/README.md +10 -11
- package/src/platforms/gb/lib/c/gb_crt0.s +27 -3
- package/src/platforms/gb/lib/c/patch-header.js +19 -6
- package/src/platforms/gba/MENTAL_MODEL.md +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +3 -3
- package/src/platforms/gba/lib/c/gba_sfx.c +40 -0
- package/src/platforms/gba/lib/c/gba_sfx.h +10 -0
- package/src/platforms/gbc/MENTAL_MODEL.md +16 -4
- package/src/platforms/gbc/TROUBLESHOOTING.md +24 -3
- package/src/platforms/gbc/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gbc/lib/c/README.md +10 -11
- package/src/platforms/gbc/lib/c/font.h +43 -0
- package/src/platforms/gbc/lib/c/gb_crt0.s +26 -3
- package/src/platforms/gbc/lib/c/patch-header.js +19 -6
- package/src/platforms/genesis/MENTAL_MODEL.md +43 -9
- package/src/platforms/genesis/TROUBLESHOOTING.md +2 -2
- package/src/platforms/genesis/lib/c/genesis_sfx.c +37 -0
- package/src/platforms/genesis/lib/c/genesis_sfx.h +1 -0
- package/src/platforms/gg/MENTAL_MODEL.md +4 -4
- package/src/platforms/gg/TROUBLESHOOTING.md +14 -18
- package/src/platforms/gg/UPSTREAM_SOURCES.md +1 -1
- package/src/platforms/gg/lib/c/gg_crt0.s +14 -2
- package/src/platforms/gg/lib/c/joypad_read.c +29 -0
- package/src/platforms/lynx/MENTAL_MODEL.md +1 -1
- package/src/platforms/lynx/TROUBLESHOOTING.md +3 -3
- package/src/platforms/lynx/lib/c/lynx_sfx.c +38 -2
- package/src/platforms/lynx/lib/c/lynx_sfx.h +1 -0
- package/src/platforms/msx/MENTAL_MODEL.md +11 -5
- package/src/platforms/msx/TROUBLESHOOTING.md +21 -0
- package/src/platforms/msx/lib/c/msx_crt0.s +27 -0
- package/src/platforms/msx/lib/c/msx_hw.h +3 -0
- package/src/platforms/msx/lib/c/msx_vdp.c +70 -0
- package/src/platforms/nes/MENTAL_MODEL.md +12 -5
- package/src/platforms/nes/lib/c/nes_runtime.c +190 -34
- package/src/platforms/nes/lib/c/nes_runtime.h +35 -0
- package/src/platforms/pce/MENTAL_MODEL.md +14 -5
- package/src/platforms/pce/TROUBLESHOOTING.md +9 -0
- package/src/platforms/pce/lib/c/pce_hw.h +13 -1
- package/src/platforms/pce/lib/c/pce_sound.c +22 -0
- package/src/platforms/pce/lib/c/pce_video.c +32 -0
- package/src/platforms/sms/MENTAL_MODEL.md +11 -6
- package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
- package/src/platforms/sms/lib/c/sms_crt0.s +14 -2
- package/src/platforms/snes/MENTAL_MODEL.md +7 -2
- package/src/platforms/snes/TROUBLESHOOTING.md +40 -1
- package/src/playtest/playtest.js +73 -3
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +13 -8
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.crt0.s +58 -5
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +52 -3
- package/src/toolchains/cc65/presets/pce/rom32k.cfg +52 -0
- package/src/toolchains/index.js +64 -19
|
@@ -1,27 +1,36 @@
|
|
|
1
|
-
/* ── shmup.c — SNES
|
|
1
|
+
/* ── shmup.c — SNES vertical shooter (complete example game) ──────────────────
|
|
2
2
|
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - 6 bullet slots (OAM 1..6), 6 enemy slots (OAM 7..12)
|
|
7
|
-
* - Wave spawner: one enemy from the top every ~28 frames
|
|
8
|
-
* - Linear movement, AABB collision (8×8 vs 8×8)
|
|
3
|
+
* A COMPLETE, working game — title screen, 1P and 2P SIMULTANEOUS co-op,
|
|
4
|
+
* shared lives, score + persistent hi-score (battery SRAM), SPC music + SFX,
|
|
5
|
+
* and a scrolling Mode 1 starfield under a rock-steady text HUD.
|
|
9
6
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
7
|
+
* THIS FILE IS MEANT TO BE FORKED AND MODIFIED into your own game — even a
|
|
8
|
+
* very different one. The markers tell you what's what:
|
|
9
|
+
* HARDWARE IDIOM (load-bearing) — dodges a documented SNES footgun; reshape
|
|
10
|
+
* your gameplay around it (see TROUBLESHOOTING before changing).
|
|
11
|
+
* GAME LOGIC (clay) — enemy patterns, scoring, tuning, art: reshape freely.
|
|
13
12
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
13
|
+
* What depends on what:
|
|
14
|
+
* data.asm — font + sprite tiles + starfield tiles (rodata), and
|
|
15
|
+
* sram_read16/write16 (battery SRAM needs 24-bit addressing that tcc
|
|
16
|
+
* C pointers don't emit). Load-bearing.
|
|
17
|
+
* hdr.asm — THIS PROJECT OVERRIDES the stock header to declare battery
|
|
18
|
+
* SRAM (CARTRIDGETYPE $02 + SRAMSIZE $01). Delete that file and saves
|
|
19
|
+
* silently stop existing — the build still succeeds.
|
|
20
|
+
* snes_sfx.{h,c} + snes_sfx_data.asm + apu_blob.bin — the SPC700 sound
|
|
21
|
+
* driver (music + 2 one-shot samples). #include'd, not separately built.
|
|
16
22
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
23
|
+
* Why the HUD never shears (read this if you come from the NES): the SNES
|
|
24
|
+
* Mode 1 gives you THREE independent background layers, each with its own
|
|
25
|
+
* scroll registers. The starfield lives on BG1 and scrolls; the text HUD
|
|
26
|
+
* lives on BG0 and simply never gets a scroll write. No sprite-0 splits, no
|
|
27
|
+
* mid-frame raster tricks — layer separation IS the SNES way. (When one
|
|
28
|
+
* layer must be two things — a fixed strip over a moving field on the SAME
|
|
29
|
+
* BG — that's when you reach for HDMA; see the Mode 7 racing example.)
|
|
30
|
+
*
|
|
31
|
+
* VRAM BUDGET (word addresses):
|
|
32
|
+
* $0000- OBJ tiles, $2000- BG1 starfield tiles, $3000- BG0 console font,
|
|
33
|
+
* $4000- BG1 map, $6800- BG0 text map.
|
|
25
34
|
*/
|
|
26
35
|
|
|
27
36
|
#include <snes.h>
|
|
@@ -29,194 +38,464 @@
|
|
|
29
38
|
* inline rather than linked separately. */
|
|
30
39
|
#include "snes_sfx.c"
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
41
|
+
/* The title screen renders this — examples({op:'fork'}) stamps your game's
|
|
42
|
+
* name here automatically. Keep it ≤16 chars of A-Z 0-9 space dash. */
|
|
43
|
+
#define GAME_TITLE "SOLAR BULWARK"
|
|
44
|
+
|
|
45
|
+
extern char tilfont, palfont; /* HUD font + text palette (data.asm) */
|
|
46
|
+
extern char tilsprite, palsprite; /* ship/bullet/enemy tiles + OBJ pal */
|
|
47
|
+
extern char tilbg, palbg; /* 4 starfield tiles + BG palette */
|
|
35
48
|
|
|
36
49
|
/* consoleVblank() copies the dirty text tilemap to VRAM during VBlank.
|
|
37
50
|
* No public prototype in console.h, so declare it; call once per frame. */
|
|
38
51
|
extern void consoleVblank(void);
|
|
39
52
|
|
|
40
|
-
/*
|
|
53
|
+
/* data.asm exports — battery SRAM accessors (long addressing to $70:0000). */
|
|
54
|
+
extern u16 sram_read16(u16 offset);
|
|
55
|
+
extern void sram_write16(u16 offset, u16 value);
|
|
56
|
+
|
|
57
|
+
/* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
|
|
58
|
+
* oamSet's FIRST arg is a BYTE OFFSET into OAM, not a slot number. Each
|
|
59
|
+
* sprite is 4 bytes, so sprite slot N lives at offset N*4. Passing a plain
|
|
60
|
+
* slot number interleaves/corrupts entries — always go through SPR(). */
|
|
41
61
|
#define SPR(slot) ((slot) << 2)
|
|
42
62
|
|
|
43
|
-
/*
|
|
44
|
-
*
|
|
45
|
-
|
|
63
|
+
/* ── GAME LOGIC (clay — reshape freely) ──────────────────────────────────────
|
|
64
|
+
* Object pools — fixed slots, no allocation. OAM slot layout:
|
|
65
|
+
* 0..1 = ships (P1, P2), 2..9 = bullets, 10..15 = enemies. */
|
|
66
|
+
#define MAX_BULLETS 8
|
|
67
|
+
#define MAX_ENEMIES 6
|
|
68
|
+
#define OAM_SHIP 0
|
|
69
|
+
#define OAM_BULLET 2
|
|
70
|
+
#define OAM_ENEMY 10
|
|
71
|
+
#define OAM_COUNT 16
|
|
72
|
+
#define TILE_SHIP 0 /* tile indexes into tilsprite (data.asm) */
|
|
73
|
+
#define TILE_BULLET 1
|
|
74
|
+
#define TILE_ENEMY 2
|
|
75
|
+
#define START_LIVES 3
|
|
76
|
+
#define HUD_Y 24 /* playfield starts below the text HUD row */
|
|
77
|
+
#define FIELD_BOT 208
|
|
78
|
+
#define INV_FRAMES 90 /* post-hit invulnerability (blink) */
|
|
46
79
|
|
|
47
|
-
|
|
48
|
-
|
|
80
|
+
/* SRAM layout: [0]=magic "SB", [2]=hi-score, [4]=hiscore ^ 0xA5C3.
|
|
81
|
+
* Magic is written LAST in hiscore_save so a torn write never validates. */
|
|
82
|
+
#define SRAM_MAGIC 0x4253u
|
|
83
|
+
|
|
84
|
+
/* Game states — the shell every example shares: title → play → game over. */
|
|
85
|
+
#define ST_TITLE 0
|
|
86
|
+
#define ST_PLAY 1
|
|
87
|
+
#define ST_OVER 2
|
|
49
88
|
|
|
50
89
|
typedef struct { s16 x, y; u8 alive; } Obj;
|
|
51
90
|
|
|
52
|
-
static
|
|
91
|
+
static u8 state;
|
|
92
|
+
static u8 two_player; /* mode chosen on the title screen */
|
|
93
|
+
static u8 sound_ok;
|
|
94
|
+
static Obj ships[2];
|
|
95
|
+
static u8 ship_inv[2]; /* invulnerability frames after a hit */
|
|
96
|
+
static u8 fire_cd[2];
|
|
53
97
|
static Obj bullets[MAX_BULLETS];
|
|
54
98
|
static Obj enemies[MAX_ENEMIES];
|
|
55
|
-
static
|
|
99
|
+
static u8 lives; /* SHARED pool in co-op (arcade style) */
|
|
100
|
+
static u16 score, hiscore;
|
|
101
|
+
static u8 hud_dirty;
|
|
56
102
|
static u16 spawn_timer;
|
|
103
|
+
static u16 frame_ct;
|
|
104
|
+
static u16 star_v; /* BG1 vertical scroll (starfield drift) */
|
|
105
|
+
static u16 prev_pad0;
|
|
106
|
+
static char nbuf[8]; /* 5-digit number formatter output */
|
|
107
|
+
|
|
108
|
+
/* BG1 starfield map: 32×32 entries, composed once at boot then scrolled in
|
|
109
|
+
* hardware forever. Static (not a local): >255 bytes of locals overflows
|
|
110
|
+
* tcc's 8-bit stack-relative addressing. */
|
|
111
|
+
static u16 bg_map[32 * 32];
|
|
112
|
+
|
|
113
|
+
/* Headless-test telemetry — written once per frame; a test harness finds it
|
|
114
|
+
* by scanning WRAM for the "SB"+0xB7 signature, then plays the game from
|
|
115
|
+
* real state instead of parsing pixels. Costs ~30 byte-writes; delete freely. */
|
|
116
|
+
static u8 telem[32];
|
|
117
|
+
|
|
118
|
+
/* ── GAME LOGIC (clay) — Galois LFSR (taps $B8), period 255 ────────────────── */
|
|
119
|
+
static u8 rng_state = 0xA5;
|
|
120
|
+
static u8 rand8(void) {
|
|
121
|
+
u8 lsb = (u8)(rng_state & 1);
|
|
122
|
+
rng_state >>= 1;
|
|
123
|
+
if (lsb) rng_state ^= 0xB8;
|
|
124
|
+
return rng_state;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* ── GAME LOGIC (clay) — SRAM hi-score (see sram_* in data.asm) ────────────── */
|
|
128
|
+
static u16 hiscore_load(void) {
|
|
129
|
+
u16 v;
|
|
130
|
+
if (sram_read16(0) != SRAM_MAGIC) return 0;
|
|
131
|
+
v = sram_read16(2);
|
|
132
|
+
if (sram_read16(4) != (u16)(v ^ 0xA5C3u)) return 0;
|
|
133
|
+
return v;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
static void hiscore_save(u16 v) {
|
|
137
|
+
sram_write16(2, v);
|
|
138
|
+
sram_write16(4, (u16)(v ^ 0xA5C3u));
|
|
139
|
+
sram_write16(0, SRAM_MAGIC); /* magic LAST — torn write = no record */
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/* ── GAME LOGIC (clay) — text helpers ──────────────────────────────────────── */
|
|
143
|
+
static void fmt5(u16 v) { /* u16 → "00000" into nbuf */
|
|
144
|
+
s8 i;
|
|
145
|
+
for (i = 4; i >= 0; i--) { nbuf[i] = (char)('0' + v % 10); v /= 10; }
|
|
146
|
+
nbuf[5] = 0;
|
|
147
|
+
}
|
|
57
148
|
|
|
58
|
-
static
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
static void
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
149
|
+
static void clear_rows(u16 a, u16 b) {
|
|
150
|
+
u16 y;
|
|
151
|
+
for (y = a; y <= b; y++)
|
|
152
|
+
consoleDrawText(0, y, " ");
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static void draw_hud(void) {
|
|
156
|
+
fmt5(score); consoleDrawText(3, 1, nbuf);
|
|
157
|
+
fmt5(hiscore); consoleDrawText(13, 1, nbuf);
|
|
158
|
+
nbuf[0] = (char)('0' + lives); nbuf[1] = 0;
|
|
159
|
+
consoleDrawText(23, 1, nbuf);
|
|
160
|
+
hud_dirty = 0;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/* ── GAME LOGIC (clay) — firing + spawning ─────────────────────────────────── */
|
|
164
|
+
static void fire_bullet(u8 p) {
|
|
165
|
+
u8 i;
|
|
166
|
+
for (i = 0; i < MAX_BULLETS; i++) {
|
|
167
|
+
if (!bullets[i].alive) {
|
|
168
|
+
bullets[i].x = ships[p].x;
|
|
169
|
+
bullets[i].y = ships[p].y - 8;
|
|
170
|
+
bullets[i].alive = 1;
|
|
171
|
+
if (sound_ok) sfx_play(1); /* pew (voice 0 one-shot) */
|
|
172
|
+
return;
|
|
82
173
|
}
|
|
174
|
+
}
|
|
83
175
|
}
|
|
84
176
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
177
|
+
static void spawn_enemy(void) {
|
|
178
|
+
u8 i;
|
|
179
|
+
for (i = 0; i < MAX_ENEMIES; i++) {
|
|
180
|
+
if (!enemies[i].alive) {
|
|
181
|
+
enemies[i].x = (s16)(rand8() % 224) + 8;
|
|
182
|
+
enemies[i].y = HUD_Y;
|
|
183
|
+
enemies[i].alive = 1;
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/* AABB, both boxes 8×8. */
|
|
190
|
+
static u8 hits(Obj *a, Obj *b) {
|
|
191
|
+
return a->x < b->x + 8 && a->x + 8 > b->x
|
|
192
|
+
&& a->y < b->y + 8 && a->y + 8 > b->y;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/* ── HARDWARE IDIOM (load-bearing — reshape gameplay around this; see TROUBLESHOOTING) ──
|
|
196
|
+
* Stage every OAM slot every frame, then ONE oamUpdate(). Inactive objects
|
|
197
|
+
* park at Y=240 (below the 224-line display) — that's how you "hide" a
|
|
198
|
+
* sprite without touching the OAM high table; oamInitGfxSet leaves slots
|
|
199
|
+
* shown. The invulnerability blink also parks the ship every few frames.
|
|
200
|
+
* CHANNEL BUDGET NOTE: oamUpdate only marks the shadow table; PVSnesLib's
|
|
201
|
+
* VBlank ISR DMAs it on CHANNEL 7 every frame, and ch 0 carries the console
|
|
202
|
+
* text upload. If you add HDMA effects (gradient sky, per-line scroll —
|
|
203
|
+
* see the Mode 7 racing example) park them on channels 2-6: a channel can't
|
|
204
|
+
* serve HDMA and that GP-DMA in the same frame, and the ISR silently
|
|
205
|
+
* rewrites ch 7's params each NMI. */
|
|
90
206
|
static void stage_frame(void) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
207
|
+
u8 i, hide;
|
|
208
|
+
s16 y;
|
|
209
|
+
for (i = 0; i < 2; i++) {
|
|
210
|
+
hide = (u8)(!ships[i].alive || (ship_inv[i] & 4));
|
|
211
|
+
y = hide ? 240 : ships[i].y;
|
|
212
|
+
/* P2 = same tile, OBJ palette 1 (oamSet's LAST arg; CGRAM entry 145
|
|
213
|
+
* is recoloured green in main). */
|
|
214
|
+
oamSet(SPR(OAM_SHIP + i), ships[i].x, y, 3, 0, 0, TILE_SHIP, i);
|
|
215
|
+
}
|
|
216
|
+
for (i = 0; i < MAX_BULLETS; i++) {
|
|
217
|
+
y = bullets[i].alive ? bullets[i].y : 240;
|
|
218
|
+
oamSet(SPR(OAM_BULLET + i), bullets[i].x, y, 3, 0, 0, TILE_BULLET, 0);
|
|
219
|
+
}
|
|
220
|
+
for (i = 0; i < MAX_ENEMIES; i++) {
|
|
221
|
+
y = enemies[i].alive ? enemies[i].y : 240;
|
|
222
|
+
oamSet(SPR(OAM_ENEMY + i), enemies[i].x, y, 3, 0, 0, TILE_ENEMY, 0);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/* ── GAME LOGIC (clay) — state entries ─────────────────────────────────────── */
|
|
227
|
+
static void clear_pools(void) {
|
|
228
|
+
u8 i;
|
|
229
|
+
for (i = 0; i < MAX_BULLETS; i++) bullets[i].alive = 0;
|
|
230
|
+
for (i = 0; i < MAX_ENEMIES; i++) enemies[i].alive = 0;
|
|
231
|
+
ships[0].alive = ships[1].alive = 0;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
static void title_enter(void) {
|
|
235
|
+
clear_pools();
|
|
236
|
+
clear_rows(0, 27);
|
|
237
|
+
consoleDrawText((32 - sizeof(GAME_TITLE) + 1) / 2, 6, GAME_TITLE);
|
|
238
|
+
consoleDrawText(12, 9, "HI");
|
|
239
|
+
fmt5(hiscore); consoleDrawText(15, 9, nbuf);
|
|
240
|
+
consoleDrawText(10, 12, "A - 1P START");
|
|
241
|
+
consoleDrawText(10, 14, "B - 2P CO-OP");
|
|
242
|
+
consoleDrawText(7, 20, "D-PAD MOVE B FIRE");
|
|
243
|
+
prev_pad0 = 0xFFFF; /* swallow the press that ENTERED this state — without
|
|
244
|
+
* this, the START that left the game-over screen
|
|
245
|
+
* instantly restarts (classic edge-detect reuse bug) */
|
|
246
|
+
state = ST_TITLE;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
static void respawn_ship(u8 p) {
|
|
250
|
+
ships[p].x = p ? 144 : (two_player ? 96 : 124);
|
|
251
|
+
ships[p].y = 200;
|
|
252
|
+
fire_cd[p] = 0;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
static void play_enter(u8 players) {
|
|
256
|
+
two_player = players;
|
|
257
|
+
clear_pools();
|
|
258
|
+
ships[0].alive = 1;
|
|
259
|
+
ships[1].alive = two_player;
|
|
260
|
+
respawn_ship(0);
|
|
261
|
+
respawn_ship(1);
|
|
262
|
+
ship_inv[0] = ship_inv[1] = 0;
|
|
263
|
+
lives = START_LIVES;
|
|
264
|
+
score = 0;
|
|
265
|
+
spawn_timer = 0;
|
|
266
|
+
clear_rows(0, 27);
|
|
267
|
+
consoleDrawText(0, 1, "SC");
|
|
268
|
+
consoleDrawText(10, 1, "HI");
|
|
269
|
+
consoleDrawText(20, 1, "LV");
|
|
270
|
+
draw_hud();
|
|
271
|
+
state = ST_PLAY;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
static void game_over(void) {
|
|
275
|
+
u8 newhi = 0;
|
|
276
|
+
if (score > hiscore) {
|
|
277
|
+
hiscore = score;
|
|
278
|
+
/* ── HARDWARE IDIOM (load-bearing) — persists via battery SRAM at
|
|
279
|
+
* $70:0000; works because hdr.asm declares CARTRIDGETYPE $02 +
|
|
280
|
+
* SRAMSIZE $01. Magic+checksum layout, magic written last. ── */
|
|
281
|
+
hiscore_save(hiscore);
|
|
282
|
+
newhi = 1;
|
|
283
|
+
hud_dirty = 1;
|
|
284
|
+
}
|
|
285
|
+
consoleDrawText(11, 13, "GAME OVER");
|
|
286
|
+
if (newhi) consoleDrawText(10, 15, "NEW HI SCORE");
|
|
287
|
+
consoleDrawText(10, 17, "PRESS START");
|
|
288
|
+
if (sound_ok) sfx_play(2);
|
|
289
|
+
prev_pad0 = 0xFFFF; /* swallow the held pad into ST_OVER */
|
|
290
|
+
state = ST_OVER;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/* ── GAME LOGIC (clay) — per-player update. THE 2P wiring is one line:
|
|
294
|
+
* padsCurrent(p) reads controller port p (0 = pad 1, 1 = pad 2). ──────────── */
|
|
295
|
+
static void update_ship(u8 p) {
|
|
296
|
+
u16 pad = padsCurrent(p);
|
|
297
|
+
if (!ships[p].alive) return;
|
|
298
|
+
if ((pad & KEY_LEFT) && ships[p].x > 8) ships[p].x -= 2;
|
|
299
|
+
if ((pad & KEY_RIGHT) && ships[p].x < 240) ships[p].x += 2;
|
|
300
|
+
if ((pad & KEY_UP) && ships[p].y > HUD_Y + 8) ships[p].y -= 2;
|
|
301
|
+
if ((pad & KEY_DOWN) && ships[p].y < FIELD_BOT) ships[p].y += 2;
|
|
302
|
+
if ((pad & KEY_B) && fire_cd[p] == 0) {
|
|
303
|
+
fire_bullet(p);
|
|
304
|
+
fire_cd[p] = 10;
|
|
305
|
+
}
|
|
306
|
+
if (fire_cd[p]) --fire_cd[p];
|
|
307
|
+
if (ship_inv[p]) --ship_inv[p];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/* ── GAME LOGIC (clay) — the playfield tick ────────────────────────────────── */
|
|
311
|
+
static void play_update(void) {
|
|
312
|
+
u8 i, j;
|
|
313
|
+
u16 interval;
|
|
314
|
+
|
|
315
|
+
update_ship(0);
|
|
316
|
+
if (two_player) update_ship(1);
|
|
317
|
+
|
|
318
|
+
for (i = 0; i < MAX_BULLETS; i++) {
|
|
319
|
+
if (!bullets[i].alive) continue;
|
|
320
|
+
bullets[i].y -= 4;
|
|
321
|
+
if (bullets[i].y < HUD_Y) bullets[i].alive = 0;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
for (i = 0; i < MAX_ENEMIES; i++) {
|
|
325
|
+
if (!enemies[i].alive) continue;
|
|
326
|
+
enemies[i].y += 1;
|
|
327
|
+
if (enemies[i].y >= 224) enemies[i].alive = 0; /* escaped — no penalty */
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/* difficulty ramp: spawn faster as the score grows */
|
|
331
|
+
interval = score >> 6;
|
|
332
|
+
interval = (interval >= 20) ? 12 : (32 - interval);
|
|
333
|
+
if (++spawn_timer >= interval) { spawn_timer = 0; spawn_enemy(); }
|
|
334
|
+
|
|
335
|
+
/* bullets ↔ enemies */
|
|
336
|
+
for (i = 0; i < MAX_BULLETS; i++) {
|
|
337
|
+
if (!bullets[i].alive) continue;
|
|
338
|
+
for (j = 0; j < MAX_ENEMIES; j++) {
|
|
339
|
+
if (!enemies[j].alive) continue;
|
|
340
|
+
if (hits(&bullets[i], &enemies[j])) {
|
|
341
|
+
bullets[i].alive = 0;
|
|
342
|
+
enemies[j].alive = 0;
|
|
343
|
+
if (score < 65500) score += 10;
|
|
344
|
+
hud_dirty = 1;
|
|
345
|
+
if (sound_ok) sfx_play(2); /* boom */
|
|
346
|
+
break;
|
|
347
|
+
}
|
|
96
348
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/* enemies ↔ ships: SHARED life pool (arcade co-op) + invulnerability
|
|
352
|
+
* blink, so one bad wave can't drain every life in a single overlap */
|
|
353
|
+
for (j = 0; j < MAX_ENEMIES; j++) {
|
|
354
|
+
if (!enemies[j].alive) continue;
|
|
355
|
+
for (i = 0; i < 2; i++) {
|
|
356
|
+
if (!ships[i].alive || ship_inv[i]) continue;
|
|
357
|
+
if (hits(&enemies[j], &ships[i])) {
|
|
358
|
+
enemies[j].alive = 0;
|
|
359
|
+
if (sound_ok) sfx_play(2);
|
|
360
|
+
if (lives) --lives;
|
|
361
|
+
hud_dirty = 1;
|
|
362
|
+
if (lives == 0) { game_over(); return; }
|
|
363
|
+
respawn_ship(i);
|
|
364
|
+
ship_inv[i] = INV_FRAMES;
|
|
365
|
+
}
|
|
100
366
|
}
|
|
367
|
+
}
|
|
101
368
|
}
|
|
102
369
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
370
|
+
/* ── GAME LOGIC (clay) — boot-time starfield composition ─────────────────────
|
|
371
|
+
* Two space tones in a checker (so no single colour ever dominates the
|
|
372
|
+
* screen) + LFSR-scattered star tiles. Map entries: tile index | 0x0400 =
|
|
373
|
+
* palette block 1 (bits 10-12), keeping the console font palette (block 0)
|
|
374
|
+
* untouched — HUD text stays white/legible. */
|
|
375
|
+
static void build_starfield(void) {
|
|
376
|
+
u16 r, c, e;
|
|
377
|
+
u8 v;
|
|
378
|
+
for (r = 0; r < 32; r++) {
|
|
379
|
+
for (c = 0; c < 32; c++) {
|
|
380
|
+
e = ((r ^ c) & 1) ? 1 : 0; /* space A / space B checker */
|
|
381
|
+
v = rand8();
|
|
382
|
+
if ((v & 0x1F) == 0) e = 2; /* bright star (on tone A) */
|
|
383
|
+
else if ((v & 0x1F) == 1) e = 3; /* gold star (on tone B) */
|
|
384
|
+
bg_map[(r << 5) + c] = (u16)(0x0400 | e);
|
|
112
385
|
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
static void telem_update(void) {
|
|
390
|
+
u8 i;
|
|
391
|
+
telem[0] = 'S'; telem[1] = 'B'; telem[2] = 0xB7;
|
|
392
|
+
telem[3] = state;
|
|
393
|
+
telem[4] = lives;
|
|
394
|
+
telem[5] = (u8)((sound_ok << 7) | (two_player << 1));
|
|
395
|
+
telem[6] = (u8)score; telem[7] = (u8)(score >> 8);
|
|
396
|
+
telem[8] = (u8)hiscore; telem[9] = (u8)(hiscore >> 8);
|
|
397
|
+
telem[10] = (u8)ships[0].x; telem[11] = (u8)ships[0].y;
|
|
398
|
+
telem[12] = (u8)ships[1].x; telem[13] = (u8)ships[1].y;
|
|
399
|
+
telem[14] = (u8)(ships[0].alive | (ships[1].alive << 1)
|
|
400
|
+
| ((ship_inv[0] != 0) << 2) | ((ship_inv[1] != 0) << 3));
|
|
401
|
+
for (i = 0; i < MAX_ENEMIES; i++) {
|
|
402
|
+
telem[15 + (i << 1)] = enemies[i].alive ? (u8)enemies[i].x : 0xFF;
|
|
403
|
+
telem[16 + (i << 1)] = enemies[i].alive ? (u8)enemies[i].y : 0xFF;
|
|
404
|
+
}
|
|
405
|
+
telem[27] = (u8)frame_ct;
|
|
113
406
|
}
|
|
114
407
|
|
|
115
408
|
int main(void) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
409
|
+
u16 pad;
|
|
410
|
+
|
|
411
|
+
/* ── HARDWARE IDIOM (load-bearing — see TROUBLESHOOTING) ──
|
|
412
|
+
* Init order: console text pointers FIRST, then mode, then per-BG base
|
|
413
|
+
* registers, then VRAM uploads — all while the screen is still off.
|
|
414
|
+
* consoleInitText DMAs the font but does NOT set the PPU BG base
|
|
415
|
+
* registers; bgSetGfxPtr/bgSetMapPtr for BG0 must repeat the same
|
|
416
|
+
* addresses or the HUD renders garbage. */
|
|
417
|
+
consoleSetTextMapPtr(0x6800);
|
|
418
|
+
consoleSetTextGfxPtr(0x3000);
|
|
419
|
+
consoleSetTextOffset(0x0000); /* tile index = (char - 0x20) */
|
|
420
|
+
consoleInitText(0, 16 * 2, &tilfont, &palfont);
|
|
421
|
+
setMode(BG_MODE1, 0);
|
|
422
|
+
bgSetGfxPtr(0, 0x3000);
|
|
423
|
+
bgSetMapPtr(0, 0x6800, SC_32x32);
|
|
424
|
+
|
|
425
|
+
/* BG1 = the scrolling starfield. 4 tiles → VRAM $2000, map → $4000
|
|
426
|
+
* (clear of sprites $0000, the console font $3000 and map $6800). */
|
|
427
|
+
bgInitTileSet(1, (u8 *)&tilbg, (u8 *)&palbg,
|
|
428
|
+
1, /* palbg → CGRAM palette block 1 */
|
|
429
|
+
4 * 32, 32, BG_16COLORS, 0x2000);
|
|
430
|
+
build_starfield();
|
|
431
|
+
bgInitMapSet(1, (u8 *)bg_map, sizeof(bg_map), SC_32x32, 0x4000);
|
|
432
|
+
bgSetEnable(1);
|
|
433
|
+
bgSetDisable(2); /* BG2 carries garbage in mode 1 — off */
|
|
434
|
+
|
|
435
|
+
setPaletteColor(0, RGB5(0, 0, 3)); /* backdrop: near-black space */
|
|
436
|
+
/* P2's ship: OBJ palette 1 (CGRAM 144+), colour 1 recoloured green.
|
|
437
|
+
* Same tile as P1 — only oamSet's palette argument differs. */
|
|
438
|
+
setPaletteColor(145, RGB5(6, 28, 10));
|
|
439
|
+
|
|
440
|
+
/* 3 sprite tiles (ship/bullet/enemy) × 32 bytes = 96 bytes. */
|
|
441
|
+
oamInitGfxSet(&tilsprite, 96, &palsprite, 32, 0, 0x0000, OBJ_SIZE8_L16);
|
|
442
|
+
|
|
443
|
+
/* ── HARDWARE IDIOM (load-bearing) — stage + flush OAM BEFORE the screen
|
|
444
|
+
* turns on, so frame 1 shows the game (not power-on OAM garbage). ── */
|
|
445
|
+
clear_pools();
|
|
446
|
+
stage_frame();
|
|
447
|
+
oamUpdate();
|
|
448
|
+
|
|
449
|
+
setScreenOn();
|
|
450
|
+
|
|
451
|
+
/* ── HARDWARE IDIOM (load-bearing) — sfx_init AFTER setScreenOn, and CHECK
|
|
452
|
+
* the return: a wedged SPC700 must not take the video down with it. ── */
|
|
453
|
+
sound_ok = (sfx_init() == 0);
|
|
454
|
+
/* ── HARDWARE IDIOM (load-bearing) — one frame between init and the first
|
|
455
|
+
* command. sfx_init returns the instant the SPC echoes the jump command,
|
|
456
|
+
* but the driver then spends ~50 port writes initialising the DSP BEFORE
|
|
457
|
+
* it seeds its command edge-detector from $2140. Send a command in that
|
|
458
|
+
* window and the seed swallows it — music silently never starts. A
|
|
459
|
+
* WaitForVBlank is thousands of SPC cycles — deterministic cure. ── */
|
|
460
|
+
WaitForVBlank();
|
|
461
|
+
if (sound_ok) sfx_music_play();
|
|
462
|
+
|
|
463
|
+
hiscore = hiscore_load(); /* battery SRAM — 0 on first boot */
|
|
464
|
+
star_v = 0;
|
|
465
|
+
frame_ct = 0;
|
|
466
|
+
title_enter();
|
|
467
|
+
|
|
468
|
+
while (1) {
|
|
469
|
+
pad = padsCurrent(0);
|
|
470
|
+
|
|
471
|
+
if (state == ST_TITLE) {
|
|
472
|
+
/* ── GAME LOGIC (clay) — title: A/START = 1P, B = 2P co-op ── */
|
|
473
|
+
if ((pad & KEY_A && !(prev_pad0 & KEY_A)) ||
|
|
474
|
+
(pad & KEY_START && !(prev_pad0 & KEY_START))) {
|
|
475
|
+
play_enter(0);
|
|
476
|
+
} else if (pad & KEY_B && !(prev_pad0 & KEY_B)) {
|
|
477
|
+
play_enter(1);
|
|
478
|
+
}
|
|
479
|
+
} else if (state == ST_PLAY) {
|
|
480
|
+
play_update();
|
|
481
|
+
} else { /* ST_OVER — field frozen, stars keep drifting */
|
|
482
|
+
if (pad & KEY_START && !(prev_pad0 & KEY_START)) title_enter();
|
|
483
|
+
}
|
|
484
|
+
prev_pad0 = pad;
|
|
485
|
+
|
|
486
|
+
/* starfield drift: ~0.5 px/frame downward. VOFS decreasing moves BG
|
|
487
|
+
* content DOWN the screen; the 256-px map wraps in hardware. */
|
|
488
|
+
if (frame_ct & 1) --star_v;
|
|
489
|
+
frame_ct++;
|
|
490
|
+
|
|
491
|
+
telem_update();
|
|
170
492
|
stage_frame();
|
|
171
493
|
oamUpdate();
|
|
494
|
+
if (hud_dirty) draw_hud();
|
|
172
495
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
while (1) {
|
|
179
|
-
pad = padsCurrent(0);
|
|
180
|
-
|
|
181
|
-
if (pad & KEY_LEFT && player.x > 8) player.x -= 2;
|
|
182
|
-
if (pad & KEY_RIGHT && player.x < 256 - 16) player.x += 2;
|
|
183
|
-
if (pad & KEY_UP && player.y > 16) player.y -= 2;
|
|
184
|
-
if (pad & KEY_DOWN && player.y < 224 - 16) player.y += 2;
|
|
185
|
-
if ((pad & KEY_B) && !(prev & KEY_B)) { fire(); sfx_play(1); }
|
|
186
|
-
prev = pad;
|
|
187
|
-
|
|
188
|
-
for (i = 0; i < MAX_BULLETS; i++) {
|
|
189
|
-
if (!bullets[i].alive) continue;
|
|
190
|
-
bullets[i].y -= 4;
|
|
191
|
-
if (bullets[i].y < 0 || bullets[i].y > 230) bullets[i].alive = 0;
|
|
192
|
-
}
|
|
193
|
-
for (i = 0; i < MAX_ENEMIES; i++) {
|
|
194
|
-
if (!enemies[i].alive) continue;
|
|
195
|
-
enemies[i].y += 1;
|
|
196
|
-
if (enemies[i].y >= 224) enemies[i].alive = 0;
|
|
197
|
-
}
|
|
198
|
-
if (++spawn_timer >= 28) { spawn_timer = 0; spawn(); }
|
|
199
|
-
|
|
200
|
-
for (i = 0; i < MAX_BULLETS; i++) {
|
|
201
|
-
if (!bullets[i].alive) continue;
|
|
202
|
-
for (j = 0; j < MAX_ENEMIES; j++) {
|
|
203
|
-
if (!enemies[j].alive) continue;
|
|
204
|
-
if (aabb(&bullets[i], &enemies[j])) {
|
|
205
|
-
bullets[i].alive = 0;
|
|
206
|
-
enemies[j].alive = 0;
|
|
207
|
-
if (score < 65500) score += 10;
|
|
208
|
-
sfx_play(2);
|
|
209
|
-
break;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
stage_frame();
|
|
215
|
-
oamUpdate();
|
|
216
|
-
|
|
217
|
-
render_score();
|
|
218
|
-
WaitForVBlank();
|
|
219
|
-
consoleVblank();
|
|
220
|
-
}
|
|
221
|
-
return 0;
|
|
496
|
+
WaitForVBlank();
|
|
497
|
+
bgSetScroll(1, 0, star_v); /* scroll regs: write inside vblank */
|
|
498
|
+
consoleVblank();
|
|
499
|
+
}
|
|
500
|
+
return 0;
|
|
222
501
|
}
|