#include #include #include #include #include "engine.h" // Generated by png2asset #include "tiles.h" #include "player.h" #define MAP_SIZE 15 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 scroll_x = 0; uint8_t scroll_y = 0; // Movement transition variables uint8_t is_moving = 0; uint8_t move_progress = 0; int8_t start_lx, start_ly; int8_t target_lx, target_ly; int16_t start_px, start_py; int16_t target_px, target_py; // 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 (7x7 grid of odd cells has 49 cells max) uint8_t stack_x[49]; uint8_t stack_y[49]; 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 <= MAP_SIZE - 4 && 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 <= MAP_SIZE - 4 && 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; } } // Reopen some walls to create alternative paths/loops for (uint8_t y = 1; y < MAP_SIZE - 1; y++) { for (uint8_t x = 1; x < MAP_SIZE - 1; x++) { if (maze[y][x] == 0) { // If it connects two paths horizontally or vertically, open it with a 15% probability if ((maze[y][x - 1] == 1 && maze[y][x + 1] == 1) || (maze[y - 1][x] == 1 && maze[y + 1][x] == 1)) { if ((rand() % 100) < 15) { maze[y][x] = 1; } } } } } } static void draw_map(uint8_t center_x, uint8_t center_y) { // Fill entire map with tile index 0 (completely black empty tile) memset(map_buffer, 0, sizeof(map_buffer)); int8_t start_x = center_x - 2; if (start_x < 0) start_x = 0; int8_t end_x = center_x + 2; if (end_x >= MAP_SIZE) end_x = MAP_SIZE - 1; int8_t start_y = center_y - 2; if (start_y < 0) start_y = 0; int8_t end_y = center_y + 2; if (end_y >= MAP_SIZE) end_y = MAP_SIZE - 1; // Draw logical map path tiles in the visible 5x5 window for (int8_t ly = start_y; ly <= end_y; ly++) { for (int8_t lx = start_x; lx <= end_x; lx++) { if (maze[ly][lx] == 1) { // Calculate Chebyshev distance to center int8_t dx = lx - center_x; int8_t dy = ly - center_y; if (dx < 0) dx = -dx; if (dy < 0) dy = -dy; int8_t dist = (dx > dy) ? dx : dy; // Hide tiles at distance > 2 (Fog of War) if (dist > 2) continue; // Centering math for 15x15 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); // Determine variation style based on lighting distance uint8_t v; if (dist == 2) { // Darker variants (Tile 1 Dark starting at 32, Tile 2 Dark starting at 48) v = is_alt ? (48 + mask) : (32 + mask); } else { // Normal variants 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 = is_moving ? ((move_progress >> 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(player_lx, player_ly); update_camera(); update_player_sprite(); } void engine_update(uint8_t keys, uint8_t prev_keys) { if (is_moving) { move_progress++; // Interpolate camera position int16_t px = start_px + (((target_px - start_px) * (int16_t)move_progress) >> 4); int16_t py = start_py + (((target_py - start_py) * (int16_t)move_progress) >> 4); scroll_x = px - 64; scroll_y = py - 72; move_bkg(scroll_x, scroll_y); // Update walk animation frame (alternate frame every 4 ticks) update_player_sprite(); // Update visible tiles (fog of war center) mid-way through movement if (move_progress == 8) { draw_map(target_lx, target_ly); } if (move_progress == 16) { is_moving = 0; player_lx = target_lx; player_ly = target_ly; // Final snap to target position to avoid any rounding errors int16_t final_px = (player_lx - player_ly) * 16 + 96; int16_t final_py = (player_lx + player_ly) * 8 + 16; scroll_x = final_px - 64; scroll_y = final_py - 72; move_bkg(scroll_x, scroll_y); // Draw map at final coordinates draw_map(player_lx, player_ly); // Set player to idle stand frame update_player_sprite(); } } uint8_t keys_pressed = keys & ~prev_keys; if (keys_pressed) { das_timer = DAS_DELAY; das_active = 1; } else if (keys && das_active) { if (das_timer > 0) { das_timer--; } if (das_timer == 0 && !is_moving) { das_timer = DAS_REPEAT; keys_pressed = keys; // trigger move } } else { das_active = 0; } if (is_moving) { return; // Ignore other inputs while moving } int8_t move_lx = 0; int8_t move_ly = 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) { is_moving = 1; move_progress = 0; start_lx = player_lx; start_ly = player_ly; target_lx = new_lx; target_ly = new_ly; start_px = (start_lx - start_ly) * 16 + 96; start_py = (start_lx + start_ly) * 8 + 16; target_px = (target_lx - target_ly) * 16 + 96; target_py = (target_lx + target_ly) * 8 + 16; update_player_sprite(); } else { // If collision blocks, just update direction look frame update_player_sprite(); } } else { // Out of bounds: update direction look frame update_player_sprite(); } } }