romdevtools 0.16.0 → 0.22.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 +75 -16
- package/CHANGELOG.md +316 -0
- package/examples/README.md +2 -0
- package/examples/atari2600/templates/platformer.asm +460 -0
- package/examples/atari2600/templates/racing.asm +463 -0
- package/examples/atari2600/templates/shmup.asm +386 -0
- package/examples/atari2600/templates/sports.asm +362 -0
- package/examples/atari7800/templates/default.c +49 -5
- package/examples/atari7800/templates/hello_sprite.c +48 -4
- package/examples/atari7800/templates/music_demo.c +47 -2
- package/examples/atari7800/templates/platformer.c +43 -4
- package/examples/atari7800/templates/puzzle.c +39 -4
- package/examples/atari7800/templates/racing.c +39 -4
- package/examples/atari7800/templates/shmup.c +40 -2
- package/examples/atari7800/templates/sports.c +36 -5
- package/examples/c64/templates/platformer.c +19 -5
- package/examples/c64/templates/puzzle.c +32 -2
- package/examples/c64/templates/shmup.c +28 -2
- package/examples/c64/templates/sports.c +30 -2
- package/examples/c64/templates/tile_engine.c +77 -27
- package/examples/gb/templates/default.c +110 -16
- package/examples/gb/templates/hello_sprite.c +15 -6
- package/examples/gb/templates/music_demo.c +36 -0
- package/examples/gb/templates/platformer.c +28 -6
- package/examples/gb/templates/puzzle.c +35 -4
- package/examples/gb/templates/racing.c +75 -10
- package/examples/gb/templates/shmup.c +41 -3
- package/examples/gb/templates/sports.c +51 -3
- package/examples/gb/templates/tile_engine.c +3 -2
- package/examples/gba/templates/gba_hello.c +29 -11
- package/examples/gba/templates/maxmod_demo.c +36 -2
- package/examples/gba/templates/platformer.c +3 -1
- package/examples/gba/templates/puzzle.c +15 -3
- package/examples/gba/templates/racing.c +65 -3
- package/examples/gba/templates/shmup.c +41 -4
- package/examples/gba/templates/sports.c +36 -2
- package/examples/gba/templates/tonc_hello.c +41 -5
- package/examples/gba/templates/tonc_hello_sprite.c +35 -1
- package/examples/gbc/templates/default.c +103 -26
- package/examples/gbc/templates/hello_sprite.c +12 -3
- package/examples/gbc/templates/music_demo.c +56 -12
- package/examples/gbc/templates/platformer.c +28 -6
- package/examples/gbc/templates/puzzle.c +35 -4
- package/examples/gbc/templates/racing.c +88 -21
- package/examples/gbc/templates/shmup.c +37 -3
- package/examples/gbc/templates/sports.c +48 -3
- package/examples/gbc/templates/tile_engine.c +3 -2
- package/examples/genesis/main.s +53 -1
- package/examples/genesis/templates/hello_sprite.c +25 -3
- package/examples/genesis/templates/puzzle.c +37 -3
- package/examples/genesis/templates/racing.c +44 -11
- package/examples/genesis/templates/sgdk_hello.c +34 -1
- package/examples/genesis/templates/shmup.c +31 -1
- package/examples/genesis/templates/shmup_2p.c +31 -0
- package/examples/genesis/templates/xgm2_demo.c +20 -0
- package/examples/gg/templates/default.c +56 -18
- package/examples/gg/templates/hello_sprite.c +25 -2
- package/examples/gg/templates/music_demo.c +24 -2
- package/examples/gg/templates/platformer.c +18 -12
- package/examples/gg/templates/puzzle.c +38 -7
- package/examples/gg/templates/racing.c +58 -9
- package/examples/gg/templates/shmup.c +47 -3
- package/examples/gg/templates/sports.c +57 -16
- package/examples/gg/templates/tile_engine.c +12 -6
- package/examples/lynx/templates/default.c +39 -8
- package/examples/lynx/templates/hello_sprite.c +15 -1
- package/examples/lynx/templates/music_demo.c +13 -1
- package/examples/lynx/templates/puzzle.c +28 -1
- package/examples/lynx/templates/racing.c +34 -7
- package/examples/lynx/templates/shmup.c +42 -3
- package/examples/lynx/templates/sports.c +29 -2
- package/examples/msx/platformer/main.c +213 -0
- package/examples/msx/puzzle/main.c +250 -0
- package/examples/msx/racing/main.c +249 -0
- package/examples/msx/shmup/main.c +288 -0
- package/examples/msx/sports/main.c +182 -0
- package/examples/nes/templates/default.c +67 -19
- package/examples/nes/templates/hello_sprite.c +35 -0
- package/examples/nes/templates/music_demo.c +40 -0
- package/examples/nes/templates/platformer.c +65 -6
- package/examples/nes/templates/puzzle.c +67 -6
- package/examples/nes/templates/racing.c +45 -13
- package/examples/nes/templates/shmup.c +51 -2
- package/examples/nes/templates/sports.c +51 -6
- package/examples/pce/catch_game/main.c +22 -3
- package/examples/pce/music_sfx/main.c +28 -1
- package/examples/pce/platformer/main.c +283 -0
- package/examples/pce/puzzle/main.c +304 -0
- package/examples/pce/racing/main.c +304 -0
- package/examples/pce/shmup/main.c +346 -0
- package/examples/pce/sports/main.c +254 -0
- package/examples/pce/sprite_move/main.c +7 -2
- package/examples/sms/main.c +35 -6
- package/examples/sms/templates/hello_sprite.c +29 -3
- package/examples/sms/templates/music_demo.c +18 -4
- package/examples/sms/templates/puzzle.c +34 -5
- package/examples/sms/templates/racing.c +39 -2
- package/examples/sms/templates/shmup.c +41 -2
- package/examples/sms/templates/shmup_2p.c +24 -1
- package/examples/sms/templates/sports.c +47 -4
- package/examples/snes/main.asm +108 -17
- package/examples/snes/templates/c-hello-data.asm +23 -0
- package/examples/snes/templates/c-hello.c +18 -1
- package/examples/snes/templates/default.c +50 -28
- package/examples/snes/templates/hello_sprite-data.asm +23 -0
- package/examples/snes/templates/hello_sprite.c +17 -1
- package/examples/snes/templates/music_demo-data.asm +23 -0
- package/examples/snes/templates/music_demo.c +22 -4
- package/examples/snes/templates/platformer-data.asm +22 -0
- package/examples/snes/templates/platformer.c +20 -2
- package/examples/snes/templates/puzzle-data.asm +22 -0
- package/examples/snes/templates/puzzle.c +21 -2
- package/examples/snes/templates/racing-data.asm +22 -0
- package/examples/snes/templates/racing.c +17 -1
- package/examples/snes/templates/shmup-data.asm +22 -0
- package/examples/snes/templates/shmup.c +20 -1
- package/examples/snes/templates/sports-data.asm +22 -0
- package/examples/snes/templates/sports.c +16 -1
- package/package.json +1 -1
- package/src/cheats/gamegenie.js +0 -1
- package/src/cli/smoke.js +1 -3
- 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 +191 -16
- package/src/host/callbacks.js +9 -1
- package/src/host/chafa-render.js +2 -0
- package/src/host/dsp-state.js +2 -2
- package/src/host/gpgx-state.js +4 -0
- package/src/host/types.js +15 -8
- package/src/http/routes.js +1 -1
- package/src/http/tool-registry.js +26 -1
- package/src/mcp/server.js +1 -1
- package/src/mcp/state.js +36 -0
- package/src/mcp/tools/address-to-symbol.js +0 -1
- package/src/mcp/tools/art-loaders.js +1 -1
- package/src/mcp/tools/cart-parts.js +75 -4
- package/src/mcp/tools/classify-region.js +1 -1
- package/src/mcp/tools/diff-roms.js +1 -1
- package/src/mcp/tools/disasm-rebuild.js +507 -0
- package/src/mcp/tools/disasm.js +97 -9
- package/src/mcp/tools/find-references.js +1 -2
- package/src/mcp/tools/font-map.js +1 -1
- package/src/mcp/tools/frame.js +168 -3
- package/src/mcp/tools/index.js +0 -49
- package/src/mcp/tools/input-layout.js +0 -1
- package/src/mcp/tools/input.js +33 -3
- package/src/mcp/tools/lifecycle.js +18 -4
- package/src/mcp/tools/lospec.js +0 -19
- package/src/mcp/tools/platform-docs.js +1 -1
- package/src/mcp/tools/platform-tools.js +4 -4
- package/src/mcp/tools/project.js +54 -11
- package/src/mcp/tools/reinject.js +0 -1
- package/src/mcp/tools/rom-id.js +2 -2
- package/src/mcp/tools/snippets.js +2 -2
- package/src/mcp/tools/sprite-pipeline.js +1 -2
- package/src/mcp/tools/state.js +201 -14
- package/src/mcp/tools/tile-inspect.js +1 -1
- package/src/mcp/tools/toolchain.js +105 -12
- package/src/mcp/tools/watch-memory.js +137 -16
- package/src/platforms/_guides/ROMHACKING_PLAYBOOK.md +34 -0
- package/src/platforms/atari2600/TROUBLESHOOTING.md +6 -0
- package/src/platforms/atari7800/TROUBLESHOOTING.md +6 -0
- package/src/platforms/c64/MENTAL_MODEL.md +45 -1
- package/src/platforms/c64/TROUBLESHOOTING.md +6 -0
- package/src/platforms/c64/d64.js +280 -0
- package/src/platforms/c64/sid.js +0 -2
- package/src/platforms/common/metasprite-adapters.js +1 -1
- package/src/platforms/common/metasprite-codegen.js +3 -3
- package/src/platforms/common/registers.js +5 -3
- package/src/platforms/gb/MENTAL_MODEL.md +10 -0
- package/src/platforms/gb/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gb/lib/c/gb_runtime.c +4 -4
- package/src/platforms/gba/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gbc/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gbc/lib/c/gb_runtime.c +4 -4
- package/src/platforms/genesis/TROUBLESHOOTING.md +6 -0
- package/src/platforms/gg/TROUBLESHOOTING.md +6 -0
- package/src/platforms/lynx/TROUBLESHOOTING.md +6 -0
- package/src/platforms/msx/MENTAL_MODEL.md +10 -6
- package/src/platforms/msx/TROUBLESHOOTING.md +6 -0
- package/src/platforms/nes/MENTAL_MODEL.md +63 -2
- package/src/platforms/nes/TROUBLESHOOTING.md +6 -0
- package/src/platforms/nes/image-to-tilemap.js +3 -0
- package/src/platforms/nes/lib/asm/famitone2.s +5 -1
- package/src/platforms/pce/MENTAL_MODEL.md +9 -4
- package/src/platforms/pce/TROUBLESHOOTING.md +6 -0
- package/src/platforms/pce/lib/c/pce_video.c +1 -1
- package/src/platforms/sms/TROUBLESHOOTING.md +6 -0
- package/src/platforms/snes/TROUBLESHOOTING.md +6 -0
- package/src/platforms/snes/brr.js +0 -2
- package/src/playtest/playtest.js +0 -7
- package/src/rom-id/identifier.js +15 -0
- package/src/toolchains/asar/asar.js +0 -9
- package/src/toolchains/assemble-snippet.js +30 -12
- package/src/toolchains/cc65/ines.js +145 -0
- package/src/toolchains/cc65/presets/nes/chr-ram-runtime.cfg +14 -1
- package/src/toolchains/cc65/presets/nes/chr-rom.cfg +83 -0
- package/src/toolchains/cc65/presets/nes/chr-rom.crt0.s +153 -0
- package/src/toolchains/common/reassemble.js +10 -3
- package/src/toolchains/common/sdk-cache.js +1 -1
- package/src/toolchains/genesis-c/genesis-c.js +5 -3
- package/src/toolchains/index.js +27 -3
- package/src/toolchains/parse-errors.js +78 -1
- package/src/toolchains/sdcc/preflight-lint.js +5 -1
- package/src/toolchains/sdcc/sdcc.js +1 -1
- package/src/toolchains/sjasm/sjasm.js +1 -1
- package/src/toolchains/snes-c/snes-c.js +2 -2
- package/src/toolchains/vasm68k/vasm68k.js +2 -4
- package/src/toolchains/wladx/wladx.js +1 -1
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PC Engine "racing" — a top-down lane-racer scaffold.
|
|
3
|
+
*
|
|
4
|
+
* Drive a car at the bottom of the screen up a 3-lane road. LEFT/RIGHT switch
|
|
5
|
+
* lanes; obstacle cars spawn at the top and slide down toward you. Dodge them —
|
|
6
|
+
* a collision freezes the game for a beat, then auto-resets. The road scrolls
|
|
7
|
+
* (dashed lane stripes animate via the BG Y-scroll register) and speed grows
|
|
8
|
+
* with your distance score. Mirrors the NES/Genesis/SNES/GB/SMS racing
|
|
9
|
+
* scaffolds, translated to the PCE helper API.
|
|
10
|
+
*
|
|
11
|
+
* Cars are hardware sprites; the road (grey lanes between green shoulders with
|
|
12
|
+
* animated dashed lane lines) is the BG tilemap, so the screen is clearly a
|
|
13
|
+
* road scene (clears the verify gate).
|
|
14
|
+
*
|
|
15
|
+
* PCE notes (see pce_hw.h / MENTAL_MODEL.md):
|
|
16
|
+
* - disp_enable() turns on BG + sprites + the VBlank IRQ (waitvsync needs it).
|
|
17
|
+
* - the road scroll is BG Y-scroll: vdc_set_reg(VDC_BYR, scroll).
|
|
18
|
+
* - .bss must be non-empty (pce_video.c's _pce_keep[] covers it).
|
|
19
|
+
*
|
|
20
|
+
* cc65 is C89 — declare locals at the top of a block.
|
|
21
|
+
*/
|
|
22
|
+
#include <pce.h>
|
|
23
|
+
#include <stdint.h> /* int16_t for the per-frame speed step */
|
|
24
|
+
#include "pce_hw.h"
|
|
25
|
+
|
|
26
|
+
/* ---- VRAM layout (word addresses) --------------------------------------- */
|
|
27
|
+
#define BAT_VRAM 0x0000
|
|
28
|
+
#define FONT_VRAM 0x1000
|
|
29
|
+
#define GRASS_VRAM 0x1400 /* shoulder grass (colour 1) */
|
|
30
|
+
#define ROAD_VRAM 0x1410 /* plain road (colour 2) */
|
|
31
|
+
#define DASH_VRAM 0x1420 /* road with a lane dash (colour 3) */
|
|
32
|
+
#define PLAYER_VRAM 0x1800 /* 16x16 player car */
|
|
33
|
+
#define ENEMY_VRAM 0x1840 /* 16x16 enemy car */
|
|
34
|
+
|
|
35
|
+
#define BAT_ENTRY(pal, vram) ((u16)(((pal) << 12) | ((vram) >> 4)))
|
|
36
|
+
|
|
37
|
+
#define LANE_L_X 76
|
|
38
|
+
#define LANE_M_X 120
|
|
39
|
+
#define LANE_R_X 164
|
|
40
|
+
#define PLAYER_Y 176
|
|
41
|
+
#define MAX_OBST 4
|
|
42
|
+
|
|
43
|
+
/* ---- font (digits only) ------------------------------------------------- */
|
|
44
|
+
#define NUM_GLYPHS 10
|
|
45
|
+
static const u8 FONT5x7[NUM_GLYPHS][7] = {
|
|
46
|
+
{0x0E,0x11,0x13,0x15,0x19,0x11,0x0E},
|
|
47
|
+
{0x04,0x0C,0x04,0x04,0x04,0x04,0x0E},
|
|
48
|
+
{0x0E,0x11,0x01,0x02,0x04,0x08,0x1F},
|
|
49
|
+
{0x1F,0x02,0x04,0x02,0x01,0x11,0x0E},
|
|
50
|
+
{0x02,0x06,0x0A,0x12,0x1F,0x02,0x02},
|
|
51
|
+
{0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E},
|
|
52
|
+
{0x06,0x08,0x10,0x1E,0x11,0x11,0x0E},
|
|
53
|
+
{0x1F,0x01,0x02,0x04,0x08,0x08,0x08},
|
|
54
|
+
{0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E},
|
|
55
|
+
{0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C}
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/* ---- state -------------------------------------------------------------- */
|
|
59
|
+
typedef struct { u16 x, y; u8 alive; } Car;
|
|
60
|
+
|
|
61
|
+
static Car player;
|
|
62
|
+
static Car obst[MAX_OBST];
|
|
63
|
+
static u16 score;
|
|
64
|
+
static u8 spawn_timer;
|
|
65
|
+
static u8 crash_timer;
|
|
66
|
+
static u8 player_lane;
|
|
67
|
+
static u8 road_scroll;
|
|
68
|
+
static u16 rng;
|
|
69
|
+
static u8 pad, prev_pad;
|
|
70
|
+
static u8 sfx_timer;
|
|
71
|
+
static u16 tile_buf[16];
|
|
72
|
+
static u16 spr_buf[64];
|
|
73
|
+
|
|
74
|
+
static const u16 lane_x[3] = { LANE_L_X, LANE_M_X, LANE_R_X };
|
|
75
|
+
|
|
76
|
+
static void make_solid_tile(u16 *t, u8 ci) {
|
|
77
|
+
u8 r;
|
|
78
|
+
u8 p0 = (ci & 1) ? 0xFF : 0x00;
|
|
79
|
+
u8 p1 = (ci & 2) ? 0xFF : 0x00;
|
|
80
|
+
u8 p2 = (ci & 4) ? 0xFF : 0x00;
|
|
81
|
+
u8 p3 = (ci & 8) ? 0xFF : 0x00;
|
|
82
|
+
for (r = 0; r < 8; ++r) {
|
|
83
|
+
t[r] = (u16)(p0 | (p1 << 8));
|
|
84
|
+
t[r + 8] = (u16)(p2 | (p3 << 8));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/* road tile with a centred lane dash in colour 3 (top half of the tile) */
|
|
89
|
+
static void make_dash_tile(u16 *t) {
|
|
90
|
+
u8 r;
|
|
91
|
+
make_solid_tile(t, 2); /* base road (colour 2) */
|
|
92
|
+
for (r = 0; r < 4; ++r) {
|
|
93
|
+
/* centre 4px (mask 0x18) -> colour 3 (planes 0+1): add plane0 bits */
|
|
94
|
+
t[r] = (u16)((t[r] & 0xFF00) | 0x18 | (t[r] & 0x00FF));
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
static void make_car_sprite(u16 vram, u8 ci) {
|
|
99
|
+
static const u16 car[16] = {
|
|
100
|
+
0x0660, 0x0660, 0x3FFC, 0x7FFE, 0x7FFE, 0x7FFE, 0x6FF6, 0x6FF6,
|
|
101
|
+
0x7FFE, 0x7FFE, 0x6FF6, 0x6FF6, 0x7FFE, 0x3FFC, 0x6006, 0x6006
|
|
102
|
+
};
|
|
103
|
+
u8 r;
|
|
104
|
+
for (r = 0; r < 64; ++r) spr_buf[r] = 0;
|
|
105
|
+
for (r = 0; r < 16; ++r) {
|
|
106
|
+
if (ci & 1) spr_buf[r] = car[r];
|
|
107
|
+
if (ci & 2) spr_buf[r + 16] = car[r];
|
|
108
|
+
if (ci & 4) spr_buf[r + 32] = car[r];
|
|
109
|
+
}
|
|
110
|
+
load_tiles(vram, spr_buf, 64);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
static void upload_font(void) {
|
|
114
|
+
u8 g, row, bits, plane0;
|
|
115
|
+
for (g = 0; g < NUM_GLYPHS; ++g) {
|
|
116
|
+
for (row = 0; row < 16; ++row) tile_buf[row] = 0;
|
|
117
|
+
for (row = 0; row < 7; ++row) {
|
|
118
|
+
bits = FONT5x7[g][row];
|
|
119
|
+
plane0 = 0;
|
|
120
|
+
if (bits & 0x10) plane0 |= 0x40;
|
|
121
|
+
if (bits & 0x08) plane0 |= 0x20;
|
|
122
|
+
if (bits & 0x04) plane0 |= 0x10;
|
|
123
|
+
if (bits & 0x02) plane0 |= 0x08;
|
|
124
|
+
if (bits & 0x01) plane0 |= 0x04;
|
|
125
|
+
tile_buf[row] = (u16)plane0;
|
|
126
|
+
}
|
|
127
|
+
load_tiles((u16)(FONT_VRAM + g * 16), tile_buf, 16);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/* Paint the road: grass shoulders, grey road in the middle, dashed lane lines
|
|
132
|
+
* between the three lanes. Player X spans ~76..164 -> BAT cols ~9..22. */
|
|
133
|
+
static void draw_road(void) {
|
|
134
|
+
u8 r, c;
|
|
135
|
+
u16 grass = BAT_ENTRY(0, GRASS_VRAM);
|
|
136
|
+
u16 road = BAT_ENTRY(0, ROAD_VRAM);
|
|
137
|
+
u16 dash = BAT_ENTRY(0, DASH_VRAM);
|
|
138
|
+
u16 e;
|
|
139
|
+
for (r = 0; r < 32; ++r) {
|
|
140
|
+
vram_set_write_addr((u16)(BAT_VRAM + r * 32));
|
|
141
|
+
for (c = 0; c < 32; ++c) {
|
|
142
|
+
if (c < 8 || c > 23) {
|
|
143
|
+
e = grass;
|
|
144
|
+
} else if ((c == 12 || c == 17) && (r & 1)) {
|
|
145
|
+
e = dash; /* dashed lane dividers, every other row */
|
|
146
|
+
} else {
|
|
147
|
+
e = road;
|
|
148
|
+
}
|
|
149
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
150
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
static void put_glyph(u8 col, u8 row, u8 digit) {
|
|
156
|
+
u16 e = BAT_ENTRY(0, (u16)(FONT_VRAM + digit * 16));
|
|
157
|
+
vram_set_write_addr((u16)(BAT_VRAM + row * 32 + col));
|
|
158
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
159
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
static void draw_score(void) {
|
|
163
|
+
u16 v = score;
|
|
164
|
+
u8 d0, d1, d2, d3;
|
|
165
|
+
d3 = (u8)(v % 10); v /= 10;
|
|
166
|
+
d2 = (u8)(v % 10); v /= 10;
|
|
167
|
+
d1 = (u8)(v % 10); v /= 10;
|
|
168
|
+
d0 = (u8)(v % 10);
|
|
169
|
+
put_glyph(1, 1, d0);
|
|
170
|
+
put_glyph(2, 1, d1);
|
|
171
|
+
put_glyph(3, 1, d2);
|
|
172
|
+
put_glyph(4, 1, d3);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
static u8 aabb(Car *a, Car *b) {
|
|
176
|
+
return (u8)(a->x < b->x + 14 && a->x + 14 > b->x &&
|
|
177
|
+
a->y < b->y + 14 && a->y + 14 > b->y);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static u16 next_rand(void) {
|
|
181
|
+
rng = (u16)(rng * 25173u + 13849u);
|
|
182
|
+
return rng;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
static void reset_run(void) {
|
|
186
|
+
u8 i;
|
|
187
|
+
player_lane = 1;
|
|
188
|
+
player.x = lane_x[1];
|
|
189
|
+
player.y = PLAYER_Y;
|
|
190
|
+
player.alive = 1;
|
|
191
|
+
for (i = 0; i < MAX_OBST; ++i) obst[i].alive = 0;
|
|
192
|
+
score = 0;
|
|
193
|
+
spawn_timer = 0;
|
|
194
|
+
crash_timer = 0;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static void spawn_obst(void) {
|
|
198
|
+
u8 i;
|
|
199
|
+
for (i = 0; i < MAX_OBST; ++i) {
|
|
200
|
+
if (!obst[i].alive) {
|
|
201
|
+
obst[i].x = lane_x[(next_rand() >> 9) % 3];
|
|
202
|
+
obst[i].y = 0;
|
|
203
|
+
obst[i].alive = 1;
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
void main(void) {
|
|
210
|
+
u8 i;
|
|
211
|
+
|
|
212
|
+
_pce_keep[0] = 0;
|
|
213
|
+
|
|
214
|
+
/* palette */
|
|
215
|
+
vce_set_color(0, PCE_RGB(0, 1, 0)); /* backdrop dark green */
|
|
216
|
+
vce_set_color(1, PCE_RGB(1, 5, 1)); /* BG c1: grass */
|
|
217
|
+
vce_set_color(2, PCE_RGB(2, 2, 2)); /* BG c2: road grey */
|
|
218
|
+
vce_set_color(3, PCE_RGB(7, 7, 1)); /* BG c3: yellow lane dash */
|
|
219
|
+
vce_set_color(256, PCE_RGB(0, 0, 0)); /* spr pal0 transparent */
|
|
220
|
+
vce_set_color(257, PCE_RGB(2, 5, 7)); /* spr pal0 c1: cyan player */
|
|
221
|
+
vce_set_color(272, PCE_RGB(0, 0, 0)); /* spr pal1 transparent */
|
|
222
|
+
vce_set_color(273, PCE_RGB(7, 1, 1)); /* spr pal1 c1: red enemy */
|
|
223
|
+
|
|
224
|
+
upload_font();
|
|
225
|
+
make_solid_tile(tile_buf, 1); load_tiles(GRASS_VRAM, tile_buf, 16);
|
|
226
|
+
make_solid_tile(tile_buf, 2); load_tiles(ROAD_VRAM, tile_buf, 16);
|
|
227
|
+
make_dash_tile(tile_buf); load_tiles(DASH_VRAM, tile_buf, 16);
|
|
228
|
+
make_car_sprite(PLAYER_VRAM, 1); /* colour 1 */
|
|
229
|
+
make_car_sprite(ENEMY_VRAM, 1); /* colour 1 (sub-pal 1 = red) */
|
|
230
|
+
|
|
231
|
+
draw_road();
|
|
232
|
+
|
|
233
|
+
rng = 0xBEEF;
|
|
234
|
+
road_scroll = 0;
|
|
235
|
+
prev_pad = 0;
|
|
236
|
+
sfx_timer = 0;
|
|
237
|
+
reset_run();
|
|
238
|
+
draw_score();
|
|
239
|
+
|
|
240
|
+
pce_joy_init();
|
|
241
|
+
disp_enable();
|
|
242
|
+
|
|
243
|
+
for (;;) {
|
|
244
|
+
u8 slot;
|
|
245
|
+
int16_t step;
|
|
246
|
+
waitvsync();
|
|
247
|
+
|
|
248
|
+
/* stage sprites: player + obstacles */
|
|
249
|
+
slot = 0;
|
|
250
|
+
set_sprite(slot++, player.x, player.y, PLAYER_VRAM >> 6, 0);
|
|
251
|
+
for (i = 0; i < MAX_OBST; ++i) {
|
|
252
|
+
u16 ey = obst[i].alive ? obst[i].y : 0x1F0;
|
|
253
|
+
set_sprite(slot++, obst[i].x, ey, ENEMY_VRAM >> 6, 1);
|
|
254
|
+
}
|
|
255
|
+
satb_dma();
|
|
256
|
+
|
|
257
|
+
pad = pce_joy_read();
|
|
258
|
+
|
|
259
|
+
if (crash_timer > 0) {
|
|
260
|
+
crash_timer--;
|
|
261
|
+
if (crash_timer == 0) reset_run();
|
|
262
|
+
prev_pad = pad;
|
|
263
|
+
if (sfx_timer) { --sfx_timer; if (sfx_timer == 0) psg_off(0); }
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/* lane switch (edge-triggered) */
|
|
268
|
+
if ((pad & PCE_JOY_LEFT) && !(prev_pad & PCE_JOY_LEFT) && player_lane > 0) { player_lane--; psg_tone(1, 0x2C0, 16); sfx_timer = 3; }
|
|
269
|
+
if ((pad & PCE_JOY_RIGHT) && !(prev_pad & PCE_JOY_RIGHT) && player_lane < 2) { player_lane++; psg_tone(1, 0x2C0, 16); sfx_timer = 3; }
|
|
270
|
+
player.x = lane_x[player_lane];
|
|
271
|
+
prev_pad = pad;
|
|
272
|
+
|
|
273
|
+
/* speed grows with score */
|
|
274
|
+
step = (int16_t)(2 + (score / 400));
|
|
275
|
+
if (step > 5) step = 5;
|
|
276
|
+
|
|
277
|
+
/* scroll the road to sell motion */
|
|
278
|
+
road_scroll = (u8)(road_scroll + step);
|
|
279
|
+
vdc_set_reg(VDC_BYR, (u16)road_scroll);
|
|
280
|
+
|
|
281
|
+
for (i = 0; i < MAX_OBST; ++i) {
|
|
282
|
+
if (!obst[i].alive) continue;
|
|
283
|
+
obst[i].y = (u16)(obst[i].y + step);
|
|
284
|
+
if (obst[i].y >= 216) obst[i].alive = 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
spawn_timer++;
|
|
288
|
+
if (spawn_timer >= 40) { spawn_timer = 0; spawn_obst(); }
|
|
289
|
+
|
|
290
|
+
for (i = 0; i < MAX_OBST; ++i) {
|
|
291
|
+
if (obst[i].alive && aabb(&player, &obst[i])) {
|
|
292
|
+
crash_timer = 70;
|
|
293
|
+
psg_tone(0, 0x080, 28); /* crash buzz */
|
|
294
|
+
sfx_timer = 16;
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if (score < 9999) score++;
|
|
300
|
+
if ((score & 7) == 0) draw_score();
|
|
301
|
+
|
|
302
|
+
if (sfx_timer) { --sfx_timer; if (sfx_timer == 0) { psg_off(0); psg_off(1); } }
|
|
303
|
+
}
|
|
304
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* PC Engine "shmup" — a vertical shoot-'em-up scaffold.
|
|
3
|
+
*
|
|
4
|
+
* Fly a ship around the bottom of the screen with the d-pad and fire upward
|
|
5
|
+
* with button I. Enemies spawn at the top in waves and drift down; a bullet
|
|
6
|
+
* that overlaps an enemy destroys it and scores 10. The HUD shows the score
|
|
7
|
+
* with background digit tiles. A scrolling starfield BG keeps the screen full
|
|
8
|
+
* (so it clears the verify gate and the sprites read clearly).
|
|
9
|
+
*
|
|
10
|
+
* Mirrors the NES/Genesis/SNES/GB/SMS shmup scaffolds, translated to the PCE
|
|
11
|
+
* helper API:
|
|
12
|
+
* - object pools (player + bullets + enemies) updated each frame
|
|
13
|
+
* - AABB collision
|
|
14
|
+
* - a wave spawner on a frame counter
|
|
15
|
+
* - 64-sprite shadow SATB + satb_dma()
|
|
16
|
+
*
|
|
17
|
+
* PCE notes (see pce_hw.h / MENTAL_MODEL.md):
|
|
18
|
+
* - disp_enable() turns on BG + sprites AND the VBlank IRQ so waitvsync()
|
|
19
|
+
* actually returns (without the IRQ bit the loop spins forever).
|
|
20
|
+
* - .bss must be non-empty; pce_video.c's _pce_keep[] covers that, and we
|
|
21
|
+
* touch _pce_keep[0] for clarity.
|
|
22
|
+
* - sprites get the SPBG-front bit from set_sprite(), so they draw over the
|
|
23
|
+
* opaque starfield BG.
|
|
24
|
+
*
|
|
25
|
+
* cc65 is C89 — declare locals at the top of a block.
|
|
26
|
+
*/
|
|
27
|
+
#include <pce.h>
|
|
28
|
+
#include "pce_hw.h"
|
|
29
|
+
|
|
30
|
+
/* ---- VRAM layout (word addresses) --------------------------------------- */
|
|
31
|
+
#define BAT_VRAM 0x0000 /* 32x32 background map */
|
|
32
|
+
#define FONT_VRAM 0x1000 /* digit/glyph tiles (8x8, 16 words each) */
|
|
33
|
+
#define STAR0_VRAM 0x1400 /* BG tile: empty space (solid colour 1) */
|
|
34
|
+
#define STAR1_VRAM 0x1410 /* BG tile: space band (solid colour 2) */
|
|
35
|
+
#define STAR2_VRAM 0x1420 /* BG tile: space + a star pixel */
|
|
36
|
+
#define SHIP_VRAM 0x1800 /* 16x16 player ship */
|
|
37
|
+
#define BULLET_VRAM 0x1840 /* 16x16 bullet */
|
|
38
|
+
#define ENEMY_VRAM 0x1880 /* 16x16 enemy */
|
|
39
|
+
|
|
40
|
+
#define BAT_ENTRY(pal, vram) ((u16)(((pal) << 12) | ((vram) >> 4)))
|
|
41
|
+
|
|
42
|
+
#define MAX_BULLETS 6
|
|
43
|
+
#define MAX_ENEMIES 6
|
|
44
|
+
|
|
45
|
+
/* ---- 5x7 glyph font (digits + a few letters for the HUD) ----------------- */
|
|
46
|
+
#define G_BLANK 0
|
|
47
|
+
#define G_0 1 /* digits 0..9 -> tiles 1..10 */
|
|
48
|
+
#define G_S 11
|
|
49
|
+
#define G_C 12
|
|
50
|
+
#define G_O 13
|
|
51
|
+
#define G_R 14
|
|
52
|
+
#define G_E 15
|
|
53
|
+
#define NUM_GLYPHS 16
|
|
54
|
+
|
|
55
|
+
static const u8 FONT5x7[NUM_GLYPHS][7] = {
|
|
56
|
+
/* BLANK */ {0,0,0,0,0,0,0},
|
|
57
|
+
/* 0 */ {0x0E,0x11,0x13,0x15,0x19,0x11,0x0E},
|
|
58
|
+
/* 1 */ {0x04,0x0C,0x04,0x04,0x04,0x04,0x0E},
|
|
59
|
+
/* 2 */ {0x0E,0x11,0x01,0x02,0x04,0x08,0x1F},
|
|
60
|
+
/* 3 */ {0x1F,0x02,0x04,0x02,0x01,0x11,0x0E},
|
|
61
|
+
/* 4 */ {0x02,0x06,0x0A,0x12,0x1F,0x02,0x02},
|
|
62
|
+
/* 5 */ {0x1F,0x10,0x1E,0x01,0x01,0x11,0x0E},
|
|
63
|
+
/* 6 */ {0x06,0x08,0x10,0x1E,0x11,0x11,0x0E},
|
|
64
|
+
/* 7 */ {0x1F,0x01,0x02,0x04,0x08,0x08,0x08},
|
|
65
|
+
/* 8 */ {0x0E,0x11,0x11,0x0E,0x11,0x11,0x0E},
|
|
66
|
+
/* 9 */ {0x0E,0x11,0x11,0x0F,0x01,0x02,0x0C},
|
|
67
|
+
/* S */ {0x0F,0x10,0x10,0x0E,0x01,0x01,0x1E},
|
|
68
|
+
/* C */ {0x0E,0x11,0x10,0x10,0x10,0x11,0x0E},
|
|
69
|
+
/* O */ {0x0E,0x11,0x11,0x11,0x11,0x11,0x0E},
|
|
70
|
+
/* R */ {0x1E,0x11,0x11,0x1E,0x14,0x12,0x11},
|
|
71
|
+
/* E */ {0x1F,0x10,0x10,0x1E,0x10,0x10,0x1F},
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/* ---- game state --------------------------------------------------------- */
|
|
75
|
+
typedef struct { u16 x, y; u8 alive; } Obj;
|
|
76
|
+
|
|
77
|
+
static Obj player;
|
|
78
|
+
static Obj bullets[MAX_BULLETS];
|
|
79
|
+
static Obj enemies[MAX_ENEMIES];
|
|
80
|
+
static u16 score;
|
|
81
|
+
static u8 spawn_timer;
|
|
82
|
+
static u16 rng;
|
|
83
|
+
static u8 pad, prev_pad;
|
|
84
|
+
static u8 sfx_timer;
|
|
85
|
+
|
|
86
|
+
static u16 tile_buf[16]; /* scratch for one 8x8 tile */
|
|
87
|
+
static u16 spr_buf[64]; /* scratch for one 16x16 sprite */
|
|
88
|
+
|
|
89
|
+
/* ---- tile/sprite builders ----------------------------------------------- */
|
|
90
|
+
static void make_solid_tile(u16 *t, u8 ci) {
|
|
91
|
+
u8 r;
|
|
92
|
+
u8 p0 = (ci & 1) ? 0xFF : 0x00;
|
|
93
|
+
u8 p1 = (ci & 2) ? 0xFF : 0x00;
|
|
94
|
+
u8 p2 = (ci & 4) ? 0xFF : 0x00;
|
|
95
|
+
u8 p3 = (ci & 8) ? 0xFF : 0x00;
|
|
96
|
+
for (r = 0; r < 8; ++r) {
|
|
97
|
+
t[r] = (u16)(p0 | (p1 << 8));
|
|
98
|
+
t[r + 8] = (u16)(p2 | (p3 << 8));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* space tile with one star pixel in colour index 3 at (row 2, col 5) */
|
|
103
|
+
static void make_star_tile(u16 *t) {
|
|
104
|
+
u8 r;
|
|
105
|
+
for (r = 0; r < 8; ++r) { t[r] = 0x00FF; t[r + 8] = 0x0000; } /* base = colour 1 */
|
|
106
|
+
/* star = colour 3 (planes 0+1) at row 2: set plane1 bit too for that row */
|
|
107
|
+
t[2] = (u16)(0x00FF | (0x04 << 8)); /* plane0 row + plane1 single pixel */
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* upload one 16x16 sprite from a 16-row body mask in colour `ci` */
|
|
111
|
+
static void make_sprite(u16 vram, const u16 *body, u8 ci) {
|
|
112
|
+
u8 r;
|
|
113
|
+
for (r = 0; r < 64; ++r) spr_buf[r] = 0;
|
|
114
|
+
for (r = 0; r < 16; ++r) {
|
|
115
|
+
if (ci & 1) spr_buf[r] = body[r]; /* plane0 */
|
|
116
|
+
if (ci & 2) spr_buf[r + 16] = body[r]; /* plane1 */
|
|
117
|
+
if (ci & 4) spr_buf[r + 32] = body[r]; /* plane2 */
|
|
118
|
+
if (ci & 8) spr_buf[r + 48] = body[r]; /* plane3 */
|
|
119
|
+
}
|
|
120
|
+
load_tiles(vram, spr_buf, 64);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
static void upload_font(void) {
|
|
124
|
+
u8 g, row, bits, plane0;
|
|
125
|
+
for (g = 0; g < NUM_GLYPHS; ++g) {
|
|
126
|
+
for (row = 0; row < 16; ++row) tile_buf[row] = 0;
|
|
127
|
+
for (row = 0; row < 7; ++row) {
|
|
128
|
+
bits = FONT5x7[g][row];
|
|
129
|
+
plane0 = 0;
|
|
130
|
+
if (bits & 0x10) plane0 |= 0x40;
|
|
131
|
+
if (bits & 0x08) plane0 |= 0x20;
|
|
132
|
+
if (bits & 0x04) plane0 |= 0x10;
|
|
133
|
+
if (bits & 0x02) plane0 |= 0x08;
|
|
134
|
+
if (bits & 0x01) plane0 |= 0x04;
|
|
135
|
+
tile_buf[row] = (u16)plane0;
|
|
136
|
+
}
|
|
137
|
+
load_tiles((u16)(FONT_VRAM + g * 16), tile_buf, 16);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
static void upload_art(void) {
|
|
142
|
+
/* ship: an upward-pointing arrow */
|
|
143
|
+
static const u16 ship[16] = {
|
|
144
|
+
0x0180, 0x0180, 0x03C0, 0x03C0, 0x07E0, 0x07E0, 0x0FF0, 0x0FF0,
|
|
145
|
+
0x1FF8, 0x1FF8, 0x3FFC, 0x7FFE, 0xFFFF, 0xE187, 0xC003, 0x8001
|
|
146
|
+
};
|
|
147
|
+
/* bullet: a small vertical pellet */
|
|
148
|
+
static const u16 bullet[16] = {
|
|
149
|
+
0x0000, 0x0180, 0x03C0, 0x03C0, 0x07E0, 0x07E0, 0x07E0, 0x07E0,
|
|
150
|
+
0x07E0, 0x07E0, 0x03C0, 0x03C0, 0x0180, 0x0000, 0x0000, 0x0000
|
|
151
|
+
};
|
|
152
|
+
/* enemy: a downward, blocky invader */
|
|
153
|
+
static const u16 enemy[16] = {
|
|
154
|
+
0x0000, 0x4002, 0x6006, 0x7FFE, 0x7FFE, 0xFDBF, 0xFFFF, 0xFFFF,
|
|
155
|
+
0xFFFF, 0x7FFE, 0x3FFC, 0x1FF8, 0x300C, 0x6006, 0x4002, 0x0000
|
|
156
|
+
};
|
|
157
|
+
upload_font();
|
|
158
|
+
make_solid_tile(tile_buf, 1); load_tiles(STAR0_VRAM, tile_buf, 16);
|
|
159
|
+
make_solid_tile(tile_buf, 2); load_tiles(STAR1_VRAM, tile_buf, 16);
|
|
160
|
+
make_star_tile(tile_buf); load_tiles(STAR2_VRAM, tile_buf, 16);
|
|
161
|
+
make_sprite(SHIP_VRAM, ship, 1); /* white */
|
|
162
|
+
make_sprite(BULLET_VRAM, bullet, 1); /* white (sub-pal 1 = yellow) */
|
|
163
|
+
make_sprite(ENEMY_VRAM, enemy, 1); /* white (sub-pal 2 = red) */
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ---- BAT / HUD ---------------------------------------------------------- */
|
|
167
|
+
static void draw_starfield(void) {
|
|
168
|
+
u8 r, c;
|
|
169
|
+
u16 e0 = BAT_ENTRY(0, STAR0_VRAM);
|
|
170
|
+
u16 e1 = BAT_ENTRY(0, STAR1_VRAM);
|
|
171
|
+
u16 e2 = BAT_ENTRY(0, STAR2_VRAM);
|
|
172
|
+
u16 e;
|
|
173
|
+
for (r = 0; r < 32; ++r) {
|
|
174
|
+
vram_set_write_addr((u16)(BAT_VRAM + r * 32));
|
|
175
|
+
for (c = 0; c < 32; ++c) {
|
|
176
|
+
e = (r & 2) ? e1 : e0; /* depth bands */
|
|
177
|
+
if (((r * 7 + c * 5) & 7) == 0) e = e2; /* sparse stars */
|
|
178
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
179
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
static void put_glyph(u8 col, u8 row, u8 glyph) {
|
|
185
|
+
u16 e = BAT_ENTRY(0, (u16)(FONT_VRAM + glyph * 16));
|
|
186
|
+
vram_set_write_addr((u16)(BAT_VRAM + row * 32 + col));
|
|
187
|
+
VDC_DATA_LO = (u8)(e & 0xFF);
|
|
188
|
+
VDC_DATA_HI = (u8)(e >> 8);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
static void draw_hud_label(void) {
|
|
192
|
+
static const u8 lbl[5] = { G_S, G_C, G_O, G_R, G_E };
|
|
193
|
+
u8 i;
|
|
194
|
+
for (i = 0; i < 5; ++i) put_glyph((u8)(1 + i), 1, lbl[i]);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
static void draw_score(void) {
|
|
198
|
+
u16 v = score;
|
|
199
|
+
u8 d0, d1, d2, d3;
|
|
200
|
+
d3 = (u8)(v % 10); v /= 10;
|
|
201
|
+
d2 = (u8)(v % 10); v /= 10;
|
|
202
|
+
d1 = (u8)(v % 10); v /= 10;
|
|
203
|
+
d0 = (u8)(v % 10);
|
|
204
|
+
put_glyph(7, 1, (u8)(G_0 + d0));
|
|
205
|
+
put_glyph(8, 1, (u8)(G_0 + d1));
|
|
206
|
+
put_glyph(9, 1, (u8)(G_0 + d2));
|
|
207
|
+
put_glyph(10, 1, (u8)(G_0 + d3));
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/* ---- gameplay helpers --------------------------------------------------- */
|
|
211
|
+
static u8 aabb(Obj *a, Obj *b) {
|
|
212
|
+
return (u8)(a->x < b->x + 14 && a->x + 14 > b->x &&
|
|
213
|
+
a->y < b->y + 14 && a->y + 14 > b->y);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
static u16 next_rand(void) {
|
|
217
|
+
rng = (u16)(rng * 25173u + 13849u);
|
|
218
|
+
return rng;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
static void fire(void) {
|
|
222
|
+
u8 i;
|
|
223
|
+
for (i = 0; i < MAX_BULLETS; ++i) {
|
|
224
|
+
if (!bullets[i].alive) {
|
|
225
|
+
bullets[i].x = player.x;
|
|
226
|
+
bullets[i].y = (u16)(player.y - 10);
|
|
227
|
+
bullets[i].alive = 1;
|
|
228
|
+
psg_tone(2, 0x180, 26);
|
|
229
|
+
sfx_timer = 4;
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
static void spawn(void) {
|
|
236
|
+
u8 i;
|
|
237
|
+
for (i = 0; i < MAX_ENEMIES; ++i) {
|
|
238
|
+
if (!enemies[i].alive) {
|
|
239
|
+
enemies[i].x = (u16)(8 + (next_rand() >> 8) % 224);
|
|
240
|
+
enemies[i].y = 8;
|
|
241
|
+
enemies[i].alive = 1;
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
void main(void) {
|
|
248
|
+
u8 i, j;
|
|
249
|
+
|
|
250
|
+
_pce_keep[0] = 0;
|
|
251
|
+
|
|
252
|
+
/* palette: BG sub-pal 0 + sprite sub-pals 0/1/2 */
|
|
253
|
+
vce_set_color(0, PCE_RGB(0, 0, 1)); /* backdrop dark blue */
|
|
254
|
+
vce_set_color(1, PCE_RGB(0, 0, 3)); /* BG c1: deep space blue */
|
|
255
|
+
vce_set_color(2, PCE_RGB(1, 1, 4)); /* BG c2: lighter space band */
|
|
256
|
+
vce_set_color(3, PCE_RGB(7, 7, 7)); /* BG c3: star white */
|
|
257
|
+
vce_set_color(256, PCE_RGB(0, 0, 0)); /* spr pal0 transparent */
|
|
258
|
+
vce_set_color(257, PCE_RGB(2, 6, 7)); /* spr pal0 c1: cyan ship */
|
|
259
|
+
vce_set_color(272, PCE_RGB(0, 0, 0)); /* spr pal1 transparent */
|
|
260
|
+
vce_set_color(273, PCE_RGB(7, 7, 0)); /* spr pal1 c1: yellow bullet */
|
|
261
|
+
vce_set_color(288, PCE_RGB(0, 0, 0)); /* spr pal2 transparent */
|
|
262
|
+
vce_set_color(289, PCE_RGB(7, 1, 1)); /* spr pal2 c1: red enemy */
|
|
263
|
+
|
|
264
|
+
upload_art();
|
|
265
|
+
draw_starfield();
|
|
266
|
+
draw_hud_label();
|
|
267
|
+
|
|
268
|
+
player.x = 120; player.y = 180; player.alive = 1;
|
|
269
|
+
for (i = 0; i < MAX_BULLETS; ++i) bullets[i].alive = 0;
|
|
270
|
+
for (i = 0; i < MAX_ENEMIES; ++i) enemies[i].alive = 0;
|
|
271
|
+
score = 0;
|
|
272
|
+
spawn_timer = 0;
|
|
273
|
+
rng = 0xC0DE;
|
|
274
|
+
prev_pad = 0;
|
|
275
|
+
sfx_timer = 0;
|
|
276
|
+
draw_score();
|
|
277
|
+
|
|
278
|
+
pce_joy_init();
|
|
279
|
+
disp_enable();
|
|
280
|
+
|
|
281
|
+
for (;;) {
|
|
282
|
+
waitvsync();
|
|
283
|
+
pad = pce_joy_read();
|
|
284
|
+
|
|
285
|
+
/* move ship */
|
|
286
|
+
if ((pad & PCE_JOY_LEFT) && player.x > 2) player.x -= 3;
|
|
287
|
+
if ((pad & PCE_JOY_RIGHT) && player.x < 238) player.x += 3;
|
|
288
|
+
if ((pad & PCE_JOY_UP) && player.y > 8) player.y -= 3;
|
|
289
|
+
if ((pad & PCE_JOY_DOWN) && player.y < 208) player.y += 3;
|
|
290
|
+
if ((pad & PCE_JOY_I) && !(prev_pad & PCE_JOY_I)) fire();
|
|
291
|
+
prev_pad = pad;
|
|
292
|
+
|
|
293
|
+
/* advance bullets */
|
|
294
|
+
for (i = 0; i < MAX_BULLETS; ++i) {
|
|
295
|
+
if (!bullets[i].alive) continue;
|
|
296
|
+
if (bullets[i].y < 6) { bullets[i].alive = 0; continue; }
|
|
297
|
+
bullets[i].y -= 6;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/* advance enemies */
|
|
301
|
+
for (i = 0; i < MAX_ENEMIES; ++i) {
|
|
302
|
+
if (!enemies[i].alive) continue;
|
|
303
|
+
enemies[i].y += 1;
|
|
304
|
+
if (enemies[i].y >= 224) enemies[i].alive = 0;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/* spawn waves */
|
|
308
|
+
spawn_timer++;
|
|
309
|
+
if (spawn_timer >= 36) { spawn_timer = 0; spawn(); }
|
|
310
|
+
|
|
311
|
+
/* bullet vs enemy */
|
|
312
|
+
for (i = 0; i < MAX_BULLETS; ++i) {
|
|
313
|
+
if (!bullets[i].alive) continue;
|
|
314
|
+
for (j = 0; j < MAX_ENEMIES; ++j) {
|
|
315
|
+
if (!enemies[j].alive) continue;
|
|
316
|
+
if (aabb(&bullets[i], &enemies[j])) {
|
|
317
|
+
bullets[i].alive = 0;
|
|
318
|
+
enemies[j].alive = 0;
|
|
319
|
+
if (score < 9999) score += 10;
|
|
320
|
+
draw_score();
|
|
321
|
+
psg_tone(3, 0x040, 28);
|
|
322
|
+
sfx_timer = 6;
|
|
323
|
+
break;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/* free the SFX channels so they're blips, not drones */
|
|
329
|
+
if (sfx_timer) {
|
|
330
|
+
--sfx_timer;
|
|
331
|
+
if (sfx_timer == 0) { psg_off(2); psg_off(3); }
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/* push sprites: player(0), bullets(1..6), enemies(7..12) */
|
|
335
|
+
set_sprite(0, player.x, player.y, SHIP_VRAM >> 6, 0);
|
|
336
|
+
for (i = 0; i < MAX_BULLETS; ++i) {
|
|
337
|
+
u16 by = bullets[i].alive ? bullets[i].y : 0x1F0; /* park off-screen */
|
|
338
|
+
set_sprite((u8)(1 + i), bullets[i].x, by, BULLET_VRAM >> 6, 1);
|
|
339
|
+
}
|
|
340
|
+
for (i = 0; i < MAX_ENEMIES; ++i) {
|
|
341
|
+
u16 ey = enemies[i].alive ? enemies[i].y : 0x1F0;
|
|
342
|
+
set_sprite((u8)(7 + i), enemies[i].x, ey, ENEMY_VRAM >> 6, 2);
|
|
343
|
+
}
|
|
344
|
+
satb_dma();
|
|
345
|
+
}
|
|
346
|
+
}
|