Refine enemy AI pathfinding and update walkthrough documentation

This commit is contained in:
2026-06-22 19:45:04 +02:00
parent 6d94992e58
commit 9311e5d1d3
2 changed files with 158 additions and 0 deletions
+158
View File
@@ -33,6 +33,24 @@ uint8_t das_active = 0;
#define DAS_DELAY 12
#define DAS_REPEAT 6
// Enemy variables
uint8_t enemy_lx = 0;
uint8_t enemy_ly = 0;
uint8_t enemy_is_moving = 0;
uint8_t enemy_move_progress = 0;
int8_t enemy_start_lx, enemy_start_ly;
int8_t enemy_target_lx, enemy_target_ly;
int16_t enemy_start_px, enemy_start_py;
int16_t enemy_target_px, enemy_target_py;
uint8_t enemy_cooldown = 0;
uint8_t game_over = 0;
const metasprite_t enemy_metasprite[] = {
METASPR_ITEM(-8, -8, 0, S_PAL(1)),
METASPR_ITEM(0, 8, 2, S_PAL(1)),
METASPR_TERM
};
static void generate_maze(void) {
// Clear maze (all 0/walls)
memset(maze, 0, sizeof(maze));
@@ -209,6 +227,7 @@ void engine_init(void) {
// Explicitly initialize DMG palette registers
OBP0_REG = 0xE4; // 11 10 01 00 (Black, Dark Gray, Light Gray, White)
OBP1_REG = 0x1B; // 00 01 10 11 (Inverted palette for the ghost enemy)
BGP_REG = 0xE4;
// Initialize GBC palettes (safe on DMG, does nothing)
@@ -219,12 +238,38 @@ void engine_init(void) {
set_bkg_data(0, tiles_TILE_COUNT, tiles_tiles);
set_sprite_data(0, player_TILE_COUNT, player_tiles);
// Spawn enemy at a random path tile sufficiently far from the player
while (1) {
uint8_t rx = rand() % MAP_SIZE;
uint8_t ry = rand() % MAP_SIZE;
if (maze[ry][rx] == 1) {
// Distance (Manhattan) to player start (1, 1)
int8_t dx = rx - 1;
int8_t dy = ry - 1;
if (dx < 0) dx = -dx;
if (dy < 0) dy = -dy;
if ((dx + dy) >= 5) {
enemy_lx = rx;
enemy_ly = ry;
break;
}
}
}
enemy_is_moving = 0;
enemy_cooldown = 30;
game_over = 0;
draw_map(player_lx, player_ly);
update_camera();
update_player_sprite();
}
void engine_update(uint8_t keys, uint8_t prev_keys) {
if (game_over) {
BGP_REG = 0xFF; // Set screen to all black to signify Game Over
return;
}
if (is_moving) {
move_progress++;
@@ -264,6 +309,119 @@ void engine_update(uint8_t keys, uint8_t prev_keys) {
}
}
// Update enemy movement progress
if (enemy_is_moving) {
enemy_move_progress++;
if (enemy_move_progress == 16) {
enemy_is_moving = 0;
enemy_lx = enemy_target_lx;
enemy_ly = enemy_target_ly;
enemy_cooldown = 24; // Delay between steps to make the enemy move slower than the player
}
}
// Update enemy cooldown
if (enemy_cooldown > 0) {
enemy_cooldown--;
}
// Enemy AI pathfinding toward the player
if (!enemy_is_moving && enemy_cooldown == 0) {
// Calculate Chebyshev distance to player
int8_t dx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t dy = (int8_t)player_ly - (int8_t)enemy_ly;
int8_t abs_dx = (dx < 0) ? -dx : dx;
int8_t abs_dy = (dy < 0) ? -dy : dy;
int8_t dist = (abs_dx > abs_dy) ? abs_dx : abs_dy;
if (dist <= 4) {
int8_t best_nx = enemy_lx;
int8_t best_ny = enemy_ly;
// Initialize with current squared distance so we only move if we get strictly closer
int16_t min_dist_sq = (int16_t)dx * dx + (int16_t)dy * dy;
// Check all 4 directions and choose the one that minimizes the squared distance to the player
// Down-Right (lx + 1, ly)
if (enemy_lx + 1 < MAP_SIZE && maze[enemy_ly][enemy_lx + 1] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)(enemy_lx + 1);
int8_t ndy = (int8_t)player_ly - (int8_t)enemy_ly;
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx + 1; best_ny = enemy_ly; }
}
// Down-Left (lx, ly + 1)
if (enemy_ly + 1 < MAP_SIZE && maze[enemy_ly + 1][enemy_lx] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t ndy = (int8_t)player_ly - (int8_t)(enemy_ly + 1);
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx; best_ny = enemy_ly + 1; }
}
// Up-Left (lx - 1, ly)
if (enemy_lx > 0 && maze[enemy_ly][enemy_lx - 1] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)(enemy_lx - 1);
int8_t ndy = (int8_t)player_ly - (int8_t)enemy_ly;
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx - 1; best_ny = enemy_ly; }
}
// Up-Right (lx, ly - 1)
if (enemy_ly > 0 && maze[enemy_ly - 1][enemy_lx] == 1) {
int8_t ndx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t ndy = (int8_t)player_ly - (int8_t)(enemy_ly - 1);
int16_t d_sq = (int16_t)ndx * ndx + (int16_t)ndy * ndy;
if (d_sq < min_dist_sq) { min_dist_sq = d_sq; best_nx = enemy_lx; best_ny = enemy_ly - 1; }
}
if (best_nx != (int8_t)enemy_lx || best_ny != (int8_t)enemy_ly) {
enemy_is_moving = 1;
enemy_move_progress = 0;
enemy_start_lx = enemy_lx;
enemy_start_ly = enemy_ly;
enemy_target_lx = best_nx;
enemy_target_ly = best_ny;
enemy_start_px = (enemy_start_lx - enemy_start_ly) * 16 + 96;
enemy_start_py = (enemy_start_lx + enemy_start_ly) * 8 + 16;
enemy_target_px = (enemy_target_lx - enemy_target_ly) * 16 + 96;
enemy_target_py = (enemy_target_lx + enemy_target_ly) * 8 + 16;
}
}
}
// Render enemy sprite relative to the scroll viewport
int16_t enemy_px, enemy_py;
if (enemy_is_moving) {
enemy_px = enemy_start_px + (((enemy_target_px - enemy_start_px) * (int16_t)enemy_move_progress) >> 4);
enemy_py = enemy_start_py + (((enemy_target_py - enemy_start_py) * (int16_t)enemy_move_progress) >> 4);
} else {
enemy_px = (enemy_lx - enemy_ly) * 16 + 96;
enemy_py = (enemy_lx + enemy_ly) * 8 + 16;
}
int16_t enemy_screen_x = enemy_px - scroll_x;
int16_t enemy_screen_y = enemy_py - scroll_y;
// Determine player-enemy distance for visibility masking
int8_t edx = (int8_t)player_lx - (int8_t)enemy_lx;
int8_t edy = (int8_t)player_ly - (int8_t)enemy_ly;
if (edx < 0) edx = -edx;
if (edy < 0) edy = -edy;
uint8_t ep_dist = (edx > edy) ? edx : edy;
// Render only if within fog of war radius (<= 2)
if (ep_dist <= 2 && enemy_screen_x >= -8 && enemy_screen_x <= 168 && enemy_screen_y >= -8 && enemy_screen_y <= 152) {
move_metasprite(enemy_metasprite, 0, 4, enemy_screen_x, enemy_screen_y); // Sprite index offset 4
} else {
move_metasprite(enemy_metasprite, 0, 4, 0, 0); // Hide offscreen
}
// Collision detection / Game Over conditions
if ((player_lx == enemy_lx && player_ly == enemy_ly) ||
(is_moving && enemy_is_moving && target_lx == enemy_target_lx && target_ly == enemy_target_ly) ||
(is_moving && enemy_is_moving && target_lx == enemy_start_lx && target_ly == enemy_start_ly && start_lx == enemy_target_lx && start_ly == enemy_target_ly)) {
game_over = 1;
BGP_REG = 0xFF; // Screen all black
return;
}
uint8_t keys_pressed = keys & ~prev_keys;
if (keys_pressed) {
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB