From 5fbe7c8c6005a1c03575725706a8ca4bb8c4d861 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Tue, 24 Mar 2026 17:09:47 +0300 Subject: [PATCH] core: fix enemy movement collision with the player Signed-off-by: NotAShelf Change-Id: I8bfcc0f8816fbc02dbd7ad462b5c0a4e6a6a6964 --- src/common.h | 19 +++++++++++++- src/enemy.c | 32 ++++++++++++++--------- src/render.c | 72 ++++++++++++++++++++-------------------------------- 3 files changed, 66 insertions(+), 57 deletions(-) diff --git a/src/common.h b/src/common.h index 5474109..e0df30c 100644 --- a/src/common.h +++ b/src/common.h @@ -56,9 +56,26 @@ typedef enum { ENEMY_GOBLIN, ENEMY_SKELETON, ENEMY_ORC } EnemyType; typedef struct { int x, y; int hp; + int max_hp; int attack; int alive; EnemyType type; } Enemy; -#endif // COMMON_H +// GameState - encapsulates all game state for testability and save/load +typedef struct { + Player player; + Map map; + Dungeon dungeon; + Enemy enemies[MAX_ENEMIES]; + int enemy_count; + Item items[MAX_ITEMS]; + int item_count; + int game_over; + int game_won; + const char *last_message; + int message_timer; + int turn_count; +} GameState; + +#endif // COMMON_H diff --git a/src/enemy.c b/src/enemy.c index fa35de5..d864d8d 100644 --- a/src/enemy.c +++ b/src/enemy.c @@ -5,7 +5,7 @@ #include "rng.h" // Forward declaration -int is_enemy_at(Enemy *enemies, int count, int x, int y); +int is_enemy_at(const Enemy *enemies, int count, int x, int y); void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) { *count = 0; @@ -47,19 +47,23 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) { // Stats based on type and floor switch (e.type) { case ENEMY_GOBLIN: - e.hp = ENEMY_BASE_HP + floor; + e.max_hp = ENEMY_BASE_HP + floor; + e.hp = e.max_hp; e.attack = ENEMY_BASE_ATTACK; break; case ENEMY_SKELETON: - e.hp = ENEMY_BASE_HP + floor + 2; + e.max_hp = ENEMY_BASE_HP + floor + 2; + e.hp = e.max_hp; e.attack = ENEMY_BASE_ATTACK + 1; break; case ENEMY_ORC: - e.hp = ENEMY_BASE_HP + floor + 4; + e.max_hp = ENEMY_BASE_HP + floor + 4; + e.hp = e.max_hp; e.attack = ENEMY_BASE_ATTACK + 2; break; default: - e.hp = ENEMY_BASE_HP; + e.max_hp = ENEMY_BASE_HP; + e.hp = e.max_hp; e.attack = ENEMY_BASE_ATTACK; break; } @@ -70,7 +74,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) { } // Check if position has an enemy -int is_enemy_at(Enemy *enemies, int count, int x, int y) { +int is_enemy_at(const Enemy *enemies, int count, int x, int y) { for (int i = 0; i < count; i++) { if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y) { return 1; @@ -86,9 +90,13 @@ static int can_see_player(Enemy *e, Player *p) { return (dx >= -1 && dx <= 1 && dy >= -1 && dy <= 1); } +// Check if position is occupied by player +static int is_player_at(Player *p, int x, int y) { + return (p->x == x && p->y == y); +} + // Move enemy toward player -static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, - Enemy *all_enemies, int enemy_count) { +static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) { int dx = 0, dy = 0; if (p->x > e->x) @@ -105,14 +113,14 @@ static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, int new_x = e->x + dx; int new_y = e->y; - if (dx != 0 && is_floor(map, new_x, new_y) && - !is_enemy_at(all_enemies, enemy_count, new_x, new_y)) { + if (dx != 0 && is_floor(map, new_x, new_y) && !is_enemy_at(all_enemies, enemy_count, new_x, new_y) && + !is_player_at(p, new_x, new_y)) { e->x = new_x; } else if (dy != 0) { new_x = e->x; new_y = e->y + dy; - if (is_floor(map, new_x, new_y) && - !is_enemy_at(all_enemies, enemy_count, new_x, new_y)) { + if (is_floor(map, new_x, new_y) && !is_enemy_at(all_enemies, enemy_count, new_x, new_y) && + !is_player_at(p, new_x, new_y)) { e->x = new_x; e->y = new_y; } diff --git a/src/render.c b/src/render.c index 80224aa..ef71f96 100644 --- a/src/render.c +++ b/src/render.c @@ -5,11 +5,10 @@ #include #include -void render_map(Map *map) { +void render_map(const Map *map) { for (int y = 0; y < MAP_HEIGHT; y++) { for (int x = 0; x < MAP_WIDTH; x++) { - Rectangle rect = {(float)(x * TILE_SIZE), (float)(y * TILE_SIZE), - (float)TILE_SIZE, (float)TILE_SIZE}; + Rectangle rect = {(float)(x * TILE_SIZE), (float)(y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE}; switch (map->tiles[y][x]) { case TILE_WALL: @@ -28,32 +27,30 @@ void render_map(Map *map) { } } -void render_player(Player *p) { - Rectangle rect = {(float)(p->x * TILE_SIZE), (float)(p->y * TILE_SIZE), - (float)TILE_SIZE, (float)TILE_SIZE}; +void render_player(const Player *p) { + Rectangle rect = {(float)(p->x * TILE_SIZE), (float)(p->y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE}; DrawRectangleRec(rect, BLUE); } -void render_enemies(Enemy *enemies, int count) { +void render_enemies(const Enemy *enemies, int count) { for (int i = 0; i < count; i++) { if (!enemies[i].alive) continue; - Rectangle rect = {(float)(enemies[i].x * TILE_SIZE), - (float)(enemies[i].y * TILE_SIZE), (float)TILE_SIZE, + Rectangle rect = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE}; // Different colors based on enemy type Color enemy_color; switch (enemies[i].type) { case ENEMY_GOBLIN: - enemy_color = (Color){150, 50, 50, 255}; // dark red + enemy_color = (Color){150, 50, 50, 255}; // dark red break; case ENEMY_SKELETON: - enemy_color = (Color){200, 200, 200, 255}; // light gray + enemy_color = (Color){200, 200, 200, 255}; // light gray break; case ENEMY_ORC: - enemy_color = (Color){50, 150, 50, 255}; // dark green + enemy_color = (Color){50, 150, 50, 255}; // dark green break; default: enemy_color = RED; @@ -63,37 +60,34 @@ void render_enemies(Enemy *enemies, int count) { DrawRectangleRec(rect, enemy_color); // Draw hp bar above enemy - int hp_percent = - (enemies[i].hp * TILE_SIZE) / 10; // FIXME: assuming max 10 hp, for now + int hp_percent = (enemies[i].hp * TILE_SIZE) / enemies[i].max_hp; if (hp_percent > 0) { - Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE), - (float)(enemies[i].y * TILE_SIZE - 4), - (float)hp_percent, 3}; + Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE - 4), (float)hp_percent, + 3}; DrawRectangleRec(hp_bar, GREEN); } } } -void render_items(Item *items, int count) { +void render_items(const Item *items, int count) { for (int i = 0; i < count; i++) { if (items[i].picked_up) continue; - Rectangle rect = {(float)(items[i].x * TILE_SIZE), - (float)(items[i].y * TILE_SIZE), (float)TILE_SIZE, + Rectangle rect = {(float)(items[i].x * TILE_SIZE), (float)(items[i].y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE}; // Different colors based on item type Color item_color; switch (items[i].type) { case ITEM_POTION: - item_color = (Color){255, 100, 100, 255}; // red/pink + item_color = (Color){255, 100, 100, 255}; // red/pink break; case ITEM_WEAPON: - item_color = (Color){255, 255, 100, 255}; // yellow + item_color = (Color){255, 255, 100, 255}; // yellow break; case ITEM_ARMOR: - item_color = (Color){100, 100, 255, 255}; // blue + item_color = (Color){100, 100, 255, 255}; // blue break; default: item_color = GREEN; @@ -104,15 +98,13 @@ void render_items(Item *items, int count) { } } -void render_ui(Player *p) { +void render_ui(const Player *p) { // UI background bar at bottom of screen - Rectangle ui_bg = {0, (float)(MAP_HEIGHT * TILE_SIZE), (float)SCREEN_WIDTH, - 60}; + Rectangle ui_bg = {0, (float)(MAP_HEIGHT * TILE_SIZE), (float)SCREEN_WIDTH, 60}; DrawRectangleRec(ui_bg, (Color){30, 30, 30, 255}); // Draw dividing line - DrawLine(0, MAP_HEIGHT * TILE_SIZE, SCREEN_WIDTH, MAP_HEIGHT * TILE_SIZE, - GRAY); + DrawLine(0, MAP_HEIGHT * TILE_SIZE, SCREEN_WIDTH, MAP_HEIGHT * TILE_SIZE, GRAY); // Player hp char hp_text[32]; @@ -136,8 +128,7 @@ void render_ui(Player *p) { // Inventory count char inv_text[32]; - snprintf(inv_text, sizeof(inv_text), "Inv: %d/%d", p->inventory_count, - MAX_INVENTORY); + snprintf(inv_text, sizeof(inv_text), "Inv: %d/%d", p->inventory_count, MAX_INVENTORY); DrawText(inv_text, 530, MAP_HEIGHT * TILE_SIZE + 10, 16, GREEN); // Show first item in inventory if any @@ -158,14 +149,12 @@ void render_ui(Player *p) { break; } char first_item[48]; - snprintf(first_item, sizeof(first_item), "[%s +%d]", item_name, - p->inventory[0].power); + snprintf(first_item, sizeof(first_item), "[%s +%d]", item_name, p->inventory[0].power); DrawText(first_item, 10, MAP_HEIGHT * TILE_SIZE + 35, 14, LIGHTGRAY); } // Controls hint - DrawText("WASD: Move | U: Use Item | Q: Quit", 280, - MAP_HEIGHT * TILE_SIZE + 35, 14, GRAY); + DrawText("WASD: Move | U: Use Item | Q: Quit", 280, MAP_HEIGHT * TILE_SIZE + 35, 14, GRAY); } void render_game_over(void) { @@ -176,13 +165,11 @@ void render_game_over(void) { // Game over text const char *title = "GAME OVER"; int title_width = MeasureText(title, 60); - DrawText(title, (SCREEN_WIDTH - title_width) / 2, SCREEN_HEIGHT / 2 - 30, 60, - RED); + DrawText(title, (SCREEN_WIDTH - title_width) / 2, SCREEN_HEIGHT / 2 - 30, 60, RED); const char *subtitle = "Press R to restart or Q to quit"; int sub_width = MeasureText(subtitle, 20); - DrawText(subtitle, (SCREEN_WIDTH - sub_width) / 2, SCREEN_HEIGHT / 2 + 40, 20, - WHITE); + DrawText(subtitle, (SCREEN_WIDTH - sub_width) / 2, SCREEN_HEIGHT / 2 + 40, 20, WHITE); } void render_message(const char *message) { @@ -190,13 +177,10 @@ void render_message(const char *message) { return; // Draw message box - Rectangle msg_bg = {(float)(SCREEN_WIDTH / 2 - 150), - (float)(SCREEN_HEIGHT / 2 - 30), 300, 60}; + Rectangle msg_bg = {(float)(SCREEN_WIDTH / 2 - 150), (float)(SCREEN_HEIGHT / 2 - 30), 300, 60}; DrawRectangleRec(msg_bg, (Color){50, 50, 50, 230}); - DrawRectangleLines((int)msg_bg.x, (int)msg_bg.y, (int)msg_bg.width, - (int)msg_bg.height, WHITE); + DrawRectangleLines((int)msg_bg.x, (int)msg_bg.y, (int)msg_bg.width, (int)msg_bg.height, WHITE); int msg_width = MeasureText(message, 20); - DrawText(message, (SCREEN_WIDTH - msg_width) / 2, SCREEN_HEIGHT / 2 - 10, 20, - WHITE); + DrawText(message, (SCREEN_WIDTH - msg_width) / 2, SCREEN_HEIGHT / 2 - 10, 20, WHITE); }