From 259c727658485ea00d6ef8617ecab579be871470 Mon Sep 17 00:00:00 2001 From: venomade Date: Thu, 27 Feb 2025 17:25:46 +0000 Subject: Initial Commit --- src/game.c | 429 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 429 insertions(+) create mode 100644 src/game.c (limited to 'src/game.c') diff --git a/src/game.c b/src/game.c new file mode 100644 index 0000000..9714cde --- /dev/null +++ b/src/game.c @@ -0,0 +1,429 @@ +#include "game.h" + +#include +#include +#include +#include +#include + +int coord_equals(Coord a, Coord b) { + return a.x == b.x && a.y == b.y; +} + +Coord coord_dirs[4] = { + //DIR_RIGHT + { 1, 0}, + //DIR_UP + {0, -1}, + //DIR_LEFT + {-1, 0}, + //DIR_DOWN + { 0, 1} +}; + +const char *action_as_cstr(Action action) { + switch (action) { + case ACTION_NOP: return "ACTION_NOP"; + case ACTION_STEP: return "ACTION_STEP"; + case ACTION_TURN_LEFT: return "ACTION_TURN_LEFT"; + case ACTION_TURN_RIGHT: return "ACTION_TURN_RIGHT"; + case ACTION_COUNT: assert(0 && "Unreachable"); + } + assert(0 && "Unreachable"); +} + +const char *env_as_cstr(Env env) { + switch (env) { + case ENV_NOTHING: return "ENV_NOTHING"; + case ENV_AGENT: return "ENV_AGENT"; + case ENV_FOOD: return "ENV_FOOD"; + case ENV_WALL: return "ENV_WALL"; + case ENV_COUNT: assert(0 && "Unreachable"); + } + assert(0 && "Unreachable"); +} + +const char *dir_as_cstr(Dir dir) { + switch (dir) { + case DIR_RIGHT: return "DIR_RIGHT"; + case DIR_UP: return "DIR_UP"; + case DIR_LEFT: return "DIR_LEFT"; + case DIR_DOWN: return "DIR_DOWN"; + } + assert(0 && "Unreachable"); +} + +void print_chromo(FILE *stream, const Chromo *chromo) { + for (size_t i = 0; i < GENES_COUNT; ++i) { + fprintf(stream, "%d %s %s %d\n", + chromo->genes[i].state, + env_as_cstr(chromo->genes[i].env), + action_as_cstr(chromo->genes[i].action), + chromo->genes[i].next_state); + } +} + +Agent agents[AGENTS_COUNT]; + + +int random_int_range(int low, int high) { + return rand() % (high - low) + low; +} + +Dir random_dir(void) { + return random_int_range(0, 4); +} + +int is_cell_empty(const Game *game, Coord pos) { + + //if (game->agents_map[pos.y][pos.x]) { + // return 0; + //} + + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + if (coord_equals(game->agents[i].pos, pos)) { + return 0; + } + } + + if (game->foods_map[pos.y][pos.x]) { + return 0; + } + + if (game->walls_map[pos.y][pos.x]) { + return 0; + } + + return 1; +} + +Coord random_coord_on_board(void) { + Coord result; + result.x = random_int_range(0, BOARD_WIDTH); + result.y = random_int_range(0, BOARD_HEIGHT); + return result; +} + +Coord random_empty_coord_on_board(const Game *game) { + Coord result = random_coord_on_board(); + while (!is_cell_empty(game, result)) { + result = random_coord_on_board(); + } + return result; +} + +Env random_env(void) { return random_int_range(0, ENV_COUNT); } + +Action random_action(void) { return random_int_range(0, ACTION_COUNT); } + +void init_game(Game *game) { + + memset(game, 0, sizeof(*game)); + + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + game->agents[i].pos = random_empty_coord_on_board(game); + game->agents[i].dir = random_dir(); + game->agents[i].hunger = HUNGER_MAX; + game->agents[i].health = HEALTH_MAX; + game->agents[i].lifetime = 0; + + for (size_t j = 0; j < GENES_COUNT; ++j) { + game->agents[i].chromo.genes[j].state = random_int_range(0, STATES_COUNT); + game->agents[i].chromo.genes[j].env = random_env(); + game->agents[i].chromo.genes[j].action = random_action(); + game->agents[i].chromo.genes[j].next_state = random_int_range(0, STATES_COUNT); + } + + //game->agents_map[pos.y][pos.x] = i + 1; + } + + for (size_t i = 0; i < FOODS_COUNT; ++i) { + Coord pos = random_empty_coord_on_board(game); + game->foods_map[pos.y][pos.x] = 1; + } + + for (size_t i = 0; i < WALLS_COUNT; ++i) { + Coord pos = random_empty_coord_on_board(game); + game->walls_map[pos.y][pos.x] = 1; + } + +} + +int mod_int(int a, int b) { + return (a % b + b) % b; +} + +Coord coord_infront_of_agent(const Agent *agent) { + Coord d = coord_dirs[agent->dir]; + Coord result = agent->pos; + result.x = mod_int(result.x + d.x, BOARD_WIDTH); + result.y = mod_int(result.y + d.y, BOARD_HEIGHT); + + return result; +} + +int *food_infront_of_agent(Game *game, size_t agent_index) { + + Coord infront = coord_infront_of_agent(&game->agents[agent_index]); + + if (game->foods_map[infront.y][infront.x]) { + return &game->foods_map[infront.y][infront.x]; + } + + return NULL; +} + +Agent *agent_infront_of_agent(Game *game, size_t agent_index) { + + Coord infront = coord_infront_of_agent(&game->agents[agent_index]); + + //if (game->agents_map[infront.y][infront.x]) { + // size_t infront_index = game->agents_map[infront.y][infront.x] - 1; + // // TODO: when agent dies, remove from agents_map + // // Then this can be removed + // if (game->agents[infront_index].health > 0) { + // return &game->agents[infront_index]; + // } + //} + + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + if (i != agent_index && + game->agents[i].health > 0 && + coord_equals(infront, game->agents[i].pos)) + { + return &game->agents[i]; + } + } + + return NULL; +} + +int *wall_infront_of_agent(Game *game, size_t agent_index) { + + Coord infront = coord_infront_of_agent(&game->agents[agent_index]); + + if (game->walls_map[infront.y][infront.x]) { + return &game->walls_map[infront.y][infront.x]; + } + + return NULL; +} + +Env env_infront_of_agent(Game *game, size_t agent_index) { + + if (food_infront_of_agent(game, agent_index) != NULL) { + return ENV_FOOD; + } + + if (wall_infront_of_agent(game, agent_index) != NULL) { + return ENV_WALL; + } + + if (agent_infront_of_agent(game, agent_index) != NULL) { + return ENV_AGENT; + } + + return ENV_NOTHING; +} + +void step_agent(Agent *agent) { + Coord d = coord_dirs[agent->dir]; + agent->pos.x = mod_int(agent->pos.x + d.x, BOARD_WIDTH); + agent->pos.y = mod_int(agent->pos.y + d.y, BOARD_HEIGHT); +} + +// TODO: void step_agent(Game *game, size_t agent_index) {} +// Update Agent Map + +void execute_action(Game *game, size_t agent_index, Action action) { + switch (action) { + case ACTION_NOP: { + } break; + case ACTION_STEP: { + int *food = food_infront_of_agent(game, agent_index); + Agent *other_agent = agent_infront_of_agent(game, agent_index); + + if (food != NULL) { + *food = 0; + game->agents[agent_index].hunger += FOOD_HUNGER_RECOVERY; + if (game->agents[agent_index].hunger > HUNGER_MAX) { + game->agents[agent_index].hunger = HUNGER_MAX; + } + step_agent(&game->agents[agent_index]); + } else if (other_agent != NULL) { + other_agent->health -= ATTACK_DAMAGE; + if (other_agent->health <= 0){ + game->agents[agent_index].hunger += FOOD_HUNGER_RECOVERY; + step_agent(&game->agents[agent_index]); + } + } else if (wall_infront_of_agent(game, agent_index) != NULL) { + step_agent(&game->agents[agent_index]); + } + } break; + case ACTION_TURN_LEFT: { + game->agents[agent_index].dir = + mod_int(game->agents[agent_index].dir + 1, 4); + } break; + case ACTION_TURN_RIGHT: { + game->agents[agent_index].dir = + mod_int(game->agents[agent_index].dir - 1, 4); + } break; + case ACTION_COUNT: { + assert(0 && "UNREACHABLE ACTION_COUNT"); + } break; + } +} + +void step_game(Game *game) { + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + if (game->agents[i].health > 0) { + // Interpret Genes + for (size_t j = 0; j < GENES_COUNT; ++j) { + Gene gene = game->agents[i].chromo.genes[j]; + if (gene.state == game->agents[i].state && + gene.env == env_infront_of_agent(game, i)) { + execute_action(game, i, gene.action); + game->agents[i].state = gene.next_state; + break; + } + } + + // Apply Hunger + game->agents[i].hunger -= STEP_HUNGER_DAMAGE; + if (game->agents[i].hunger <= 0) { + game->agents[i].health = 0; + } + + game->agents[i].lifetime += 1; + } + } +} + +Agent *agent_at(Game *game, Coord pos) { + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + if (coord_equals(game->agents[i].pos, pos)) { + return &game->agents[i]; + } + } + + return NULL; +} + +void print_agent(FILE *stream, const Agent *agent) { + printf("Agent{\n"); + printf(" .pos = (%d, %d)\n", agent->pos.x, agent->pos.y); + printf(" .dir = %s\n", dir_as_cstr(agent->dir)); + printf(" .hunger = %d\n", agent->hunger); + printf(" .health = %d\n", agent->health); + printf(" .state = %d\n", agent->state); + printf(" .lifetime = %d\n", agent->lifetime); + printf(" .chromo =\n"); + print_chromo(stream, &agent->chromo); + printf("}\n"); +} + +int compare_agents_lifetimes(const void *a, const void *b) { + const Agent *agent_a = a; + const Agent *agent_b = b; + return agent_b->lifetime - agent_a->lifetime; +} + +void combine_agents(const Agent *agent_a, const Agent *agent_b, Agent *agent_ab) { + const size_t length = GENES_COUNT / 2; + memcpy(agent_ab->chromo.genes, agent_a->chromo.genes, length * sizeof(Gene)); + memcpy(agent_ab->chromo.genes + length, agent_b->chromo.genes + length, length * sizeof(Gene)); +} + +void mutate_agent(Agent *agent) { + for (size_t i = 0; i < GENES_COUNT; ++i) { + if (random_int_range(0, MUTATION_CHANCE) == 0) { + agent->chromo.genes[i].state = random_int_range(0, STATES_COUNT); + agent->chromo.genes[i].env = random_env(); + agent->chromo.genes[i].action = random_action(); + agent->chromo.genes[i].next_state = random_int_range(0, STATES_COUNT); + } + } +} + +void make_next_generation(Game *prev_game, Game *next_game){ + + memset(next_game, 0, sizeof(*next_game)); + + qsort(prev_game->agents, AGENTS_COUNT, sizeof(Agent), + &compare_agents_lifetimes); + + for (size_t i = 0; i < FOODS_COUNT; ++i) { + Coord pos = random_empty_coord_on_board(next_game); + next_game->foods_map[pos.x][pos.y] = 1; + } + + for (size_t i = 0; i < WALLS_COUNT; ++i) { + Coord pos = random_empty_coord_on_board(next_game); + next_game->walls_map[pos.x][pos.y] = 1; + } + + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + size_t p1 = random_int_range(0, SELECTION_POOL); + size_t p2 = random_int_range(0, SELECTION_POOL); + + combine_agents(&prev_game->agents[p1], + &prev_game->agents[p2], + &next_game->agents[i]); + mutate_agent(&next_game->agents[i]); + + // Work out agent positioning on new game + next_game->agents[i].pos = random_empty_coord_on_board(next_game); + next_game->agents[i].dir = random_dir(); + next_game->agents[i].hunger = HUNGER_MAX; + next_game->agents[i].health = HEALTH_MAX; + next_game->agents[i].lifetime = 0; + } +} + +void dump_game(const char *filepath, const Game *game) { + FILE *stream = fopen(filepath, "wb"); + if (stream == NULL) { + fprintf(stderr, "Could not open file: %s\n", filepath); + exit(1); + } + + fwrite(game, sizeof(*game), 1, stream); + + if (ferror(stream)) { + fprintf(stderr, "Could not dump to file: %s\n", filepath); + exit(1); + } + + fclose(stream); + + printf("Dumped current state to '%s'\n", filepath); +} + +void load_game(const char *filepath, Game *game) { + + FILE *stream = fopen(filepath, "rb"); + + if (stream == NULL) { + fprintf(stderr, "Could not open file: %s\n", filepath); + exit(1); + } + + size_t n = fread(game, sizeof(*game), 1, stream); + assert(n == 1); + + if (ferror(stream)) { + fprintf(stderr, "Could not load from file: %s\n", filepath); + exit(1); + } + + fclose(stream); +} + +int is_everyone_dead(const Game *game) { + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + if (game->agents[i].health > 0) { + return 0; + } + } + return 1; +} -- cgit 1.4.1-2-gfad0