#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->env_map[pos.y][pos.x]>=ENV_MAP_AGENTS) { return game->agents[game->env_map[pos.y][pos.x]-ENV_MAP_AGENTS].health<=0; } return game->env_map[pos.y][pos.x]==ENV_MAP_NONE; } 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) { Coord pos = random_empty_coord_on_board(game); game->env_map[pos.y][pos.x] = ENV_MAP_AGENTS + i; game->agents[i].pos = pos; 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->env_map[pos.y][pos.x] = ENV_MAP_FOOD; } for (size_t i = 0; i < WALLS_COUNT; ++i) { Coord pos = random_empty_coord_on_board(game); game->env_map[pos.y][pos.x] = ENV_MAP_WALL; } } 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->env_map[infront.y][infront.x] == ENV_MAP_FOOD) { return &game->env_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]); int env = game->env_map[infront.y][infront.x]; //if (game->agents_map[infront.y][infront.x]) { // size_t infront_index = game->agents_map[infront.y][infront.x] - 1; // // when agent dies, remove from agents_map // // Then this can be removed // if (game->agents[infront_index].health > 0) { // return &game->agents[infront_index]; // } //} if(env >= ENV_MAP_AGENTS) { int infront_agent_index = env - ENV_MAP_AGENTS; if(infront_agent_index != agent_index && game->agents[infront_agent_index].health>0) { return &game->agents[infront_agent_index]; } } 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->env_map[infront.y][infront.x] == ENV_MAP_WALL) { return &game->env_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(Game *game, size_t agent_index) { Agent *agent = &game->agents[agent_index]; Coord new_pos = coord_infront_of_agent(agent); game->env_map[agent->pos.y][agent->pos.x] = ENV_MAP_NONE; // set prev tile to empty agent->pos.x = new_pos.x; agent->pos.y = new_pos.y; game->env_map[agent->pos.y][agent->pos.x] = ENV_MAP_AGENTS + agent_index; // register infront tile to agent } void execute_action(Game *game, size_t agent_index, Action action) { switch (action) { case ACTION_NOP: { } break; case ACTION_STEP: { Coord infront = coord_infront_of_agent(&game->agents[agent_index]); int *env = &game->env_map[infront.y][infront.x]; Agent *other_agent = agent_at(game, infront); if (*env == ENV_MAP_FOOD) { *env = ENV_MAP_NONE; // set food tile to empty game->agents[agent_index].hunger += FOOD_HUNGER_RECOVERY; if (game->agents[agent_index].hunger > HUNGER_MAX) { game->agents[agent_index].hunger = HUNGER_MAX; } } else if (other_agent != NULL) { other_agent->health -= ATTACK_DAMAGE; if (other_agent->health <= 0){ *env = ENV_MAP_NONE; // set other agent tile to empty; game->agents[agent_index].hunger += FOOD_HUNGER_RECOVERY; } if (*env == ENV_MAP_NONE) { step_agent(game, 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; default: { assert(0 && "UNREACHABLE DEFAULT ACTION"); } 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) { int env = game->env_map[pos.y][pos.x]; if(env >= ENV_MAP_AGENTS) { return &game->agents[env]; } 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->env_map[pos.x][pos.y] = ENV_MAP_FOOD; } for (size_t i = 0; i < WALLS_COUNT; ++i) { Coord pos = random_empty_coord_on_board(next_game); next_game->env_map[pos.x][pos.y] = ENV_MAP_WALL; } 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 Coord pos = random_empty_coord_on_board(next_game); next_game->env_map[pos.y][pos.x] = ENV_MAP_AGENTS + i; // register tile for agent[i] next_game->agents[i].pos = pos; 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; }