You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
225 lines
6.5 KiB
225 lines
6.5 KiB
#include <gb/gb.h> |
|
#include <stdint.h> |
|
#include <string.h> |
|
#include <rand.h> |
|
#include "engine.h" |
|
|
|
// Generated by png2asset |
|
#include "tiles.h" |
|
#include "player.h" |
|
|
|
#define MAP_SIZE 7 |
|
|
|
uint8_t maze[MAP_SIZE][MAP_SIZE]; |
|
static uint8_t map_buffer[32 * 32]; |
|
|
|
uint8_t player_lx = 1; |
|
uint8_t player_ly = 1; |
|
uint8_t player_dir = 0; // 0=DR, 1=DL, 2=UL, 3=UR |
|
uint8_t walk_timer = 0; |
|
uint8_t scroll_x = 0; |
|
uint8_t scroll_y = 0; |
|
|
|
// DAS variables |
|
uint8_t das_timer = 0; |
|
uint8_t das_active = 0; |
|
#define DAS_DELAY 12 |
|
#define DAS_REPEAT 6 |
|
|
|
static void generate_maze(void) { |
|
// Clear maze (all 0/walls) |
|
memset(maze, 0, sizeof(maze)); |
|
|
|
// Stack for backtracking (3x3 grid has 9 odd cells max) |
|
uint8_t stack_x[9]; |
|
uint8_t stack_y[9]; |
|
uint8_t stack_ptr = 0; |
|
|
|
// Start at (1, 1) |
|
uint8_t cx = 1; |
|
uint8_t cy = 1; |
|
maze[cy][cx] = 1; |
|
|
|
while (1) { |
|
// Find unvisited neighbors at distance 2 |
|
// Up: (cx, cy-2), Down: (cx, cy+2), Left: (cx-2, cy), Right: (cx+2, cy) |
|
uint8_t nx[4]; |
|
uint8_t ny[4]; |
|
uint8_t count = 0; |
|
|
|
// Up |
|
if (cy >= 3 && maze[cy - 2][cx] == 0) { |
|
nx[count] = cx; ny[count] = cy - 2; count++; |
|
} |
|
// Down |
|
if (cy <= 3 && maze[cy + 2][cx] == 0) { |
|
nx[count] = cx; ny[count] = cy + 2; count++; |
|
} |
|
// Left |
|
if (cx >= 3 && maze[cy][cx - 2] == 0) { |
|
nx[count] = cx - 2; ny[count] = cy; count++; |
|
} |
|
// Right |
|
if (cx <= 3 && maze[cy][cx + 2] == 0) { |
|
nx[count] = cx + 2; ny[count] = cy; count++; |
|
} |
|
|
|
if (count > 0) { |
|
// Choose a random neighbor |
|
uint8_t dir = rand() % count; |
|
|
|
// Push current cell to stack |
|
stack_x[stack_ptr] = cx; |
|
stack_y[stack_ptr] = cy; |
|
stack_ptr++; |
|
|
|
// Remove wall between current cell and chosen neighbor |
|
maze[(cy + ny[dir]) / 2][(cx + nx[dir]) / 2] = 1; |
|
|
|
// Move to neighbor |
|
cx = nx[dir]; |
|
cy = ny[dir]; |
|
maze[cy][cx] = 1; |
|
} else if (stack_ptr > 0) { |
|
// Pop from stack |
|
stack_ptr--; |
|
cx = stack_x[stack_ptr]; |
|
cy = stack_y[stack_ptr]; |
|
} else { |
|
break; |
|
} |
|
} |
|
} |
|
|
|
static void draw_map(void) { |
|
// Fill entire map with tile index 0 (completely black empty tile) |
|
memset(map_buffer, 0, sizeof(map_buffer)); |
|
|
|
// Draw logical map path tiles |
|
for (int8_t ly = 0; ly < MAP_SIZE; ly++) { |
|
for (int8_t lx = 0; lx < MAP_SIZE; lx++) { |
|
if (maze[ly][lx] == 1) { |
|
// Centering math for 7x7 map on 32x32 background map |
|
int8_t iso_x = (lx - ly) * 2 + 12; |
|
int8_t iso_y = (lx + ly) * 1 + 2; |
|
|
|
// Alternate colors (checkerboard) |
|
uint8_t is_alt = ((lx + ly) % 2 == 0); |
|
|
|
// Calculate neighbor mask |
|
uint8_t has_tl = (lx > 0 && maze[ly][lx - 1] == 1); |
|
uint8_t has_tr = (ly > 0 && maze[ly - 1][lx] == 1); |
|
uint8_t has_bl = (ly < MAP_SIZE - 1 && maze[ly + 1][lx] == 1); |
|
uint8_t has_br = (lx < MAP_SIZE - 1 && maze[ly][lx + 1] == 1); |
|
|
|
uint8_t mask = (has_tl ? 1 : 0) | (has_tr ? 2 : 0) | (has_bl ? 4 : 0) | (has_br ? 8 : 0); |
|
uint8_t v = is_alt ? (16 + mask) : mask; |
|
|
|
for (uint8_t y = 0; y < 2; y++) { |
|
for (uint8_t x = 0; x < 4; x++) { |
|
uint8_t target_x = (iso_x + x) & 31; |
|
uint8_t target_y = (iso_y + y) & 31; |
|
|
|
uint8_t src_tile = tiles_map[4 + v * 8 + y * 4 + x]; |
|
map_buffer[target_y * 32 + target_x] = src_tile; |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
set_bkg_tiles(0, 0, 32, 32, map_buffer); |
|
} |
|
|
|
static void update_camera(void) { |
|
// Center the camera on the player's logical tile coordinates |
|
int16_t px = (player_lx - player_ly) * 16 + 96; |
|
int16_t py = (player_lx + player_ly) * 8 + 16; |
|
|
|
scroll_x = px - 64; |
|
scroll_y = py - 72; |
|
|
|
move_bkg(scroll_x, scroll_y); |
|
} |
|
|
|
static void update_player_sprite(void) { |
|
uint8_t frame_offset = (walk_timer > 0) ? ((walk_timer >> 2) & 1) : 0; |
|
move_metasprite(player_metasprites[player_dir * 2 + frame_offset], 0, 0, 88, 88); |
|
} |
|
|
|
void engine_init(void) { |
|
SPRITES_8x16; |
|
|
|
// Seed random number generator with hardware divider register |
|
initrand(DIV_REG); |
|
|
|
// Generate maze path cells |
|
generate_maze(); |
|
|
|
// Explicitly initialize DMG palette registers |
|
OBP0_REG = 0xE4; // 11 10 01 00 (Black, Dark Gray, Light Gray, White) |
|
BGP_REG = 0xE4; |
|
|
|
// Initialize GBC palettes (safe on DMG, does nothing) |
|
set_bkg_palette(0, 8, tiles_palettes); |
|
set_sprite_palette(0, 8, player_palettes); |
|
|
|
// Load tiles |
|
set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles); |
|
set_sprite_data(0, player_TILE_COUNT, player_tiles); |
|
|
|
draw_map(); |
|
update_camera(); |
|
update_player_sprite(); |
|
} |
|
|
|
void engine_update(uint8_t keys, uint8_t prev_keys) { |
|
if (walk_timer > 0) { |
|
walk_timer--; |
|
update_player_sprite(); |
|
} |
|
|
|
uint8_t keys_pressed = keys & ~prev_keys; |
|
|
|
int8_t move_lx = 0; |
|
int8_t move_ly = 0; |
|
|
|
if (keys_pressed) { |
|
das_timer = DAS_DELAY; |
|
das_active = 1; |
|
} else if (keys && das_active) { |
|
das_timer--; |
|
if (das_timer == 0) { |
|
das_timer = DAS_REPEAT; |
|
keys_pressed = keys; // trigger move |
|
} |
|
} else { |
|
das_active = 0; |
|
} |
|
|
|
if (keys_pressed & J_RIGHT) { |
|
move_lx = 1; player_dir = 0; // DR |
|
} else if (keys_pressed & J_LEFT) { |
|
move_lx = -1; player_dir = 2; // UL |
|
} else if (keys_pressed & J_DOWN) { |
|
move_ly = 1; player_dir = 1; // DL |
|
} else if (keys_pressed & J_UP) { |
|
move_ly = -1; player_dir = 3; // UR |
|
} |
|
|
|
if (move_lx != 0 || move_ly != 0) { |
|
int8_t new_lx = player_lx + move_lx; |
|
int8_t new_ly = player_ly + move_ly; |
|
|
|
// Bounds check & wall/empty area collision check |
|
if (new_lx >= 0 && new_lx < MAP_SIZE && new_ly >= 0 && new_ly < MAP_SIZE) { |
|
if (maze[new_ly][new_lx] == 1) { |
|
player_lx = new_lx; |
|
player_ly = new_ly; |
|
walk_timer = 12; // 12 frames of walk cycle |
|
} |
|
} |
|
update_player_sprite(); |
|
update_camera(); |
|
} |
|
}
|
|
|