1
0
Fork 0
forked from NotAShelf/rogged

core: fix enemy movement collision with the player

Signed-off-by: NotAShelf <raf@notashelf.dev>
Change-Id: I8bfcc0f8816fbc02dbd7ad462b5c0a4e6a6a6964
This commit is contained in:
raf 2026-03-24 17:09:47 +03:00
commit 5fbe7c8c60
Signed by: NotAShelf
GPG key ID: 29D95B64378DB4BF
3 changed files with 66 additions and 57 deletions

View file

@ -56,9 +56,26 @@ typedef enum { ENEMY_GOBLIN, ENEMY_SKELETON, ENEMY_ORC } EnemyType;
typedef struct { typedef struct {
int x, y; int x, y;
int hp; int hp;
int max_hp;
int attack; int attack;
int alive; int alive;
EnemyType type; EnemyType type;
} Enemy; } Enemy;
// 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 #endif // COMMON_H

View file

@ -5,7 +5,7 @@
#include "rng.h" #include "rng.h"
// Forward declaration // 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) { void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
*count = 0; *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 // Stats based on type and floor
switch (e.type) { switch (e.type) {
case ENEMY_GOBLIN: 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; e.attack = ENEMY_BASE_ATTACK;
break; break;
case ENEMY_SKELETON: 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; e.attack = ENEMY_BASE_ATTACK + 1;
break; break;
case ENEMY_ORC: 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; e.attack = ENEMY_BASE_ATTACK + 2;
break; break;
default: default:
e.hp = ENEMY_BASE_HP; e.max_hp = ENEMY_BASE_HP;
e.hp = e.max_hp;
e.attack = ENEMY_BASE_ATTACK; e.attack = ENEMY_BASE_ATTACK;
break; break;
} }
@ -70,7 +74,7 @@ void enemy_spawn(Enemy enemies[], int *count, Map *map, Player *p, int floor) {
} }
// Check if position has an enemy // 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++) { for (int i = 0; i < count; i++) {
if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y) { if (enemies[i].alive && enemies[i].x == x && enemies[i].y == y) {
return 1; return 1;
@ -86,9 +90,13 @@ static int can_see_player(Enemy *e, Player *p) {
return (dx >= -1 && dx <= 1 && dy >= -1 && dy <= 1); 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 // Move enemy toward player
static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, static void enemy_move_toward_player(Enemy *e, Player *p, Map *map, Enemy *all_enemies, int enemy_count) {
Enemy *all_enemies, int enemy_count) {
int dx = 0, dy = 0; int dx = 0, dy = 0;
if (p->x > e->x) 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_x = e->x + dx;
int new_y = e->y; int new_y = e->y;
if (dx != 0 && is_floor(map, 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_enemy_at(all_enemies, enemy_count, new_x, new_y)) { !is_player_at(p, new_x, new_y)) {
e->x = new_x; e->x = new_x;
} else if (dy != 0) { } else if (dy != 0) {
new_x = e->x; new_x = e->x;
new_y = e->y + dy; new_y = e->y + dy;
if (is_floor(map, new_x, new_y) && if (is_floor(map, new_x, new_y) && !is_enemy_at(all_enemies, enemy_count, 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->x = new_x;
e->y = new_y; e->y = new_y;
} }

View file

@ -5,11 +5,10 @@
#include <stddef.h> #include <stddef.h>
#include <stdio.h> #include <stdio.h>
void render_map(Map *map) { void render_map(const Map *map) {
for (int y = 0; y < MAP_HEIGHT; y++) { for (int y = 0; y < MAP_HEIGHT; y++) {
for (int x = 0; x < MAP_WIDTH; x++) { for (int x = 0; x < MAP_WIDTH; x++) {
Rectangle rect = {(float)(x * TILE_SIZE), (float)(y * TILE_SIZE), Rectangle rect = {(float)(x * TILE_SIZE), (float)(y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE};
(float)TILE_SIZE, (float)TILE_SIZE};
switch (map->tiles[y][x]) { switch (map->tiles[y][x]) {
case TILE_WALL: case TILE_WALL:
@ -28,19 +27,17 @@ void render_map(Map *map) {
} }
} }
void render_player(Player *p) { void render_player(const Player *p) {
Rectangle rect = {(float)(p->x * TILE_SIZE), (float)(p->y * TILE_SIZE), Rectangle rect = {(float)(p->x * TILE_SIZE), (float)(p->y * TILE_SIZE), (float)TILE_SIZE, (float)TILE_SIZE};
(float)TILE_SIZE, (float)TILE_SIZE};
DrawRectangleRec(rect, BLUE); 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++) { for (int i = 0; i < count; i++) {
if (!enemies[i].alive) if (!enemies[i].alive)
continue; continue;
Rectangle rect = {(float)(enemies[i].x * TILE_SIZE), Rectangle rect = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE), (float)TILE_SIZE,
(float)(enemies[i].y * TILE_SIZE), (float)TILE_SIZE,
(float)TILE_SIZE}; (float)TILE_SIZE};
// Different colors based on enemy type // Different colors based on enemy type
@ -63,24 +60,21 @@ void render_enemies(Enemy *enemies, int count) {
DrawRectangleRec(rect, enemy_color); DrawRectangleRec(rect, enemy_color);
// Draw hp bar above enemy // Draw hp bar above enemy
int hp_percent = int hp_percent = (enemies[i].hp * TILE_SIZE) / enemies[i].max_hp;
(enemies[i].hp * TILE_SIZE) / 10; // FIXME: assuming max 10 hp, for now
if (hp_percent > 0) { if (hp_percent > 0) {
Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE), Rectangle hp_bar = {(float)(enemies[i].x * TILE_SIZE), (float)(enemies[i].y * TILE_SIZE - 4), (float)hp_percent,
(float)(enemies[i].y * TILE_SIZE - 4), 3};
(float)hp_percent, 3};
DrawRectangleRec(hp_bar, GREEN); 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++) { for (int i = 0; i < count; i++) {
if (items[i].picked_up) if (items[i].picked_up)
continue; continue;
Rectangle rect = {(float)(items[i].x * TILE_SIZE), Rectangle rect = {(float)(items[i].x * TILE_SIZE), (float)(items[i].y * TILE_SIZE), (float)TILE_SIZE,
(float)(items[i].y * TILE_SIZE), (float)TILE_SIZE,
(float)TILE_SIZE}; (float)TILE_SIZE};
// Different colors based on item type // Different colors based on item type
@ -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 // UI background bar at bottom of screen
Rectangle ui_bg = {0, (float)(MAP_HEIGHT * TILE_SIZE), (float)SCREEN_WIDTH, Rectangle ui_bg = {0, (float)(MAP_HEIGHT * TILE_SIZE), (float)SCREEN_WIDTH, 60};
60};
DrawRectangleRec(ui_bg, (Color){30, 30, 30, 255}); DrawRectangleRec(ui_bg, (Color){30, 30, 30, 255});
// Draw dividing line // Draw dividing line
DrawLine(0, MAP_HEIGHT * TILE_SIZE, SCREEN_WIDTH, MAP_HEIGHT * TILE_SIZE, DrawLine(0, MAP_HEIGHT * TILE_SIZE, SCREEN_WIDTH, MAP_HEIGHT * TILE_SIZE, GRAY);
GRAY);
// Player hp // Player hp
char hp_text[32]; char hp_text[32];
@ -136,8 +128,7 @@ void render_ui(Player *p) {
// Inventory count // Inventory count
char inv_text[32]; char inv_text[32];
snprintf(inv_text, sizeof(inv_text), "Inv: %d/%d", p->inventory_count, snprintf(inv_text, sizeof(inv_text), "Inv: %d/%d", p->inventory_count, MAX_INVENTORY);
MAX_INVENTORY);
DrawText(inv_text, 530, MAP_HEIGHT * TILE_SIZE + 10, 16, GREEN); DrawText(inv_text, 530, MAP_HEIGHT * TILE_SIZE + 10, 16, GREEN);
// Show first item in inventory if any // Show first item in inventory if any
@ -158,14 +149,12 @@ void render_ui(Player *p) {
break; break;
} }
char first_item[48]; char first_item[48];
snprintf(first_item, sizeof(first_item), "[%s +%d]", item_name, snprintf(first_item, sizeof(first_item), "[%s +%d]", item_name, p->inventory[0].power);
p->inventory[0].power);
DrawText(first_item, 10, MAP_HEIGHT * TILE_SIZE + 35, 14, LIGHTGRAY); DrawText(first_item, 10, MAP_HEIGHT * TILE_SIZE + 35, 14, LIGHTGRAY);
} }
// Controls hint // Controls hint
DrawText("WASD: Move | U: Use Item | Q: Quit", 280, DrawText("WASD: Move | U: Use Item | Q: Quit", 280, MAP_HEIGHT * TILE_SIZE + 35, 14, GRAY);
MAP_HEIGHT * TILE_SIZE + 35, 14, GRAY);
} }
void render_game_over(void) { void render_game_over(void) {
@ -176,13 +165,11 @@ void render_game_over(void) {
// Game over text // Game over text
const char *title = "GAME OVER"; const char *title = "GAME OVER";
int title_width = MeasureText(title, 60); int title_width = MeasureText(title, 60);
DrawText(title, (SCREEN_WIDTH - title_width) / 2, SCREEN_HEIGHT / 2 - 30, 60, DrawText(title, (SCREEN_WIDTH - title_width) / 2, SCREEN_HEIGHT / 2 - 30, 60, RED);
RED);
const char *subtitle = "Press R to restart or Q to quit"; const char *subtitle = "Press R to restart or Q to quit";
int sub_width = MeasureText(subtitle, 20); int sub_width = MeasureText(subtitle, 20);
DrawText(subtitle, (SCREEN_WIDTH - sub_width) / 2, SCREEN_HEIGHT / 2 + 40, 20, DrawText(subtitle, (SCREEN_WIDTH - sub_width) / 2, SCREEN_HEIGHT / 2 + 40, 20, WHITE);
WHITE);
} }
void render_message(const char *message) { void render_message(const char *message) {
@ -190,13 +177,10 @@ void render_message(const char *message) {
return; return;
// Draw message box // Draw message box
Rectangle msg_bg = {(float)(SCREEN_WIDTH / 2 - 150), Rectangle msg_bg = {(float)(SCREEN_WIDTH / 2 - 150), (float)(SCREEN_HEIGHT / 2 - 30), 300, 60};
(float)(SCREEN_HEIGHT / 2 - 30), 300, 60};
DrawRectangleRec(msg_bg, (Color){50, 50, 50, 230}); DrawRectangleRec(msg_bg, (Color){50, 50, 50, 230});
DrawRectangleLines((int)msg_bg.x, (int)msg_bg.y, (int)msg_bg.width, DrawRectangleLines((int)msg_bg.x, (int)msg_bg.y, (int)msg_bg.width, (int)msg_bg.height, WHITE);
(int)msg_bg.height, WHITE);
int msg_width = MeasureText(message, 20); int msg_width = MeasureText(message, 20);
DrawText(message, (SCREEN_WIDTH - msg_width) / 2, SCREEN_HEIGHT / 2 - 10, 20, DrawText(message, (SCREEN_WIDTH - msg_width) / 2, SCREEN_HEIGHT / 2 - 10, 20, WHITE);
WHITE);
} }