From 259c727658485ea00d6ef8617ecab579be871470 Mon Sep 17 00:00:00 2001 From: venomade Date: Thu, 27 Feb 2025 17:25:46 +0000 Subject: Initial Commit --- .gitignore | 9 + Makefile | 38 ++++ src/game.c | 429 ++++++++++++++++++++++++++++++++++++++++++++++ src/game.h | 122 +++++++++++++ src/inspector/main.c | 37 ++++ src/simulator/main.c | 132 ++++++++++++++ src/simulator/rendering.c | 194 +++++++++++++++++++++ src/simulator/rendering.h | 36 ++++ src/trainer/logging.c | 71 ++++++++ src/trainer/logging.h | 14 ++ src/trainer/main.c | 135 +++++++++++++++ 11 files changed, 1217 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 src/game.c create mode 100644 src/game.h create mode 100644 src/inspector/main.c create mode 100644 src/simulator/main.c create mode 100644 src/simulator/rendering.c create mode 100644 src/simulator/rendering.h create mode 100644 src/trainer/logging.c create mode 100644 src/trainer/logging.h create mode 100644 src/trainer/main.c diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4fb3190 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.idx +*.o +*.bin +.dir-locals.el +compile_commands.json +gp-simulator +gp-trainer +gp-inspector +*.dat diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..b536ba0 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +PKGS = sdl2 +CFLAGS = -O3 -Wall -Wold-style-definition -ggdb -std=c11 -pedantic `pkg-config --cflags $(PKGS)` +LIBS = `pkg-config --libs sdl2` -lm + +# Source files for the simulator +SIM_SRC = $(wildcard src/simulator/*.c) src/game.c +SIM_OBJ = $(SIM_SRC:.c=.o) + +# Source files for the trainer +TRAIN_SRC = $(wildcard src/trainer/*.c) src/game.c +TRAIN_OBJ = $(TRAIN_SRC:.c=.o) + +# Source files for the inspector +INSPECT_SRC = $(wildcard src/inspector/*.c) src/game.c +INSPECT_OBJ = $(INSPECT_SRC:.c=.o) + + +# Targets for the executables +all: gp-simulator gp-trainer gp-inspector + +gp-simulator: $(SIM_OBJ) + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +gp-trainer: $(TRAIN_OBJ) + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +gp-inspector: $(INSPECT_OBJ) + $(CC) $(CFLAGS) -o $@ $^ $(LIBS) + +# Pattern rule for object files +%.o: %.c + $(CC) $(CFLAGS) -c -o $@ $< + +clean: + rm -f $(SIM_OBJ) gp-simulator + rm -f $(TRAIN_OBJ) gp-trainer + rm -f $(INSPECT_OBJ) gp-inspector + rm -f log.dat 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; +} diff --git a/src/game.h b/src/game.h new file mode 100644 index 0000000..c276f2c --- /dev/null +++ b/src/game.h @@ -0,0 +1,122 @@ +#ifndef GAME_H +#define GAME_H + +#include +#include +#include + +#define BOARD_HEIGHT 100 +#define BOARD_WIDTH 100 + +#define AGENTS_COUNT 2000 +#define FOODS_COUNT 1000 +#define WALLS_COUNT 60 +#define STATES_COUNT 7 + +#define FOOD_HUNGER_RECOVERY 10 +#define STEP_HUNGER_DAMAGE 5 +#define HUNGER_MAX 100 +#define HEALTH_MAX 100 +#define ATTACK_DAMAGE 10 + +#define GENES_COUNT 20 +#define SELECTION_POOL 50 +#define MUTATION_CHANCE 100 + +#define DUMP_FILEPATH "./game.bin" +#define TRAINED_FILEPATH "./trained.bin" + +static_assert(SELECTION_POOL <= AGENTS_COUNT, + "Too large Selection Pool for amount of Agents"); + +static_assert(AGENTS_COUNT > 0, "Not Enough Agents"); + +static_assert(AGENTS_COUNT + FOODS_COUNT + WALLS_COUNT <= + BOARD_WIDTH * BOARD_HEIGHT, + "Board is FULL, Too many entities"); + +static_assert(GENES_COUNT % 2 == 0, + "GENES_COUNT must be even for chromosome pairing."); + +typedef struct { + int x, y; +} Coord; + +typedef enum { + DIR_RIGHT = 0, + DIR_UP, + DIR_LEFT, + DIR_DOWN, +} Dir; + +typedef int State; + +typedef enum { + ACTION_NOP = 0, + ACTION_STEP, + ACTION_TURN_LEFT, + ACTION_TURN_RIGHT, + ACTION_COUNT, +} Action; + +typedef enum { + ENV_NOTHING = 0, + ENV_AGENT, + ENV_FOOD, + ENV_WALL, + ENV_COUNT, +} Env; + +typedef struct { + State state; + Env env; + Action action; + State next_state; +} Gene; + +typedef struct { + size_t count; + Gene genes[GENES_COUNT]; +} Chromo; + + typedef struct { + Coord pos; + Dir dir; + int hunger; + int health; + State state; + int lifetime; + Chromo chromo; +} Agent; + +typedef struct { + Agent agents[AGENTS_COUNT]; + //int agents_map[BOARD_HEIGHT][BOARD_WIDTH]; + int foods_map[BOARD_HEIGHT][BOARD_WIDTH]; + int walls_map[BOARD_HEIGHT][BOARD_WIDTH]; +} Game; + +void init_game(Game *game); + +void dump_game(const char *filepath, const Game *game); + +void load_game(const char *filepath, Game *game); + +void step_game(Game *game); + +Agent *agent_at(Game *game, Coord pos); + +void print_chromo(FILE *stream, const Chromo *chromo); + +void print_agent(FILE *stream, const Agent *agent); + +void combine_agents(const Agent *agent_a, const Agent *agent_b, + Agent *agent_ab); + +void mutate_agent(Agent *agent); + +void make_next_generation(Game *prev_game, Game *next_game); + +int is_everyone_dead(const Game *game); + +#endif diff --git a/src/inspector/main.c b/src/inspector/main.c new file mode 100644 index 0000000..ed035c5 --- /dev/null +++ b/src/inspector/main.c @@ -0,0 +1,37 @@ +#include +#include +#include + +#include "../game.h" + +Game game = {0}; + +// Tsoding is now a 3-Star Developer! +const char *tsoding_shift(int *argc, char ***argv) { + assert(*argc > 0); + const char* result = **argv; + *argc -= 1; + *argv += 1; + return result; +} + +void usage(FILE *stream) { + fprintf(stream, "Usage: ./gp_inspector \n"); +} + +int main(int argc, char *argv[]) { + + tsoding_shift(&argc, &argv); //Skip Program Name + + if (argc == 0) { + usage(stderr); + fprintf(stderr, "ERROR: No input file provided\n"); + exit(1); + } + const char *input_filepath = tsoding_shift(&argc, &argv); + + load_game(input_filepath, &game); + + static_assert(AGENTS_COUNT > 0, "We need to have at least 1 agent to print"); + print_chromo(stdout, &game.agents[0].chromo); +} diff --git a/src/simulator/main.c b/src/simulator/main.c new file mode 100644 index 0000000..5c875fe --- /dev/null +++ b/src/simulator/main.c @@ -0,0 +1,132 @@ +#include +#include +#include +#include + +#include "SDL_events.h" +#include "./rendering.h" +#include "../game.h" + +Game games[2] = {0}; + +const char *tsoding_shift(int *argc, char ***argv) { + assert(*argc > 0); + const char* result = **argv; + *argc -= 1; + *argv += 1; + return result; +} + +void usage(FILE *stream) { + fprintf(stream, "./gp_simulator [input.bin]\n"); +} + +int main(int argc, char *argv[]) { + + int current = 0; + srand(time(NULL)); + + tsoding_shift(&argc, &argv); + + if (argc == 0) { + printf("WARNING: No input file provided, Generating Random State...\n"); + init_game(&games[current]); + } else { + const char *input_filepath = tsoding_shift(&argc, &argv); + printf("Loading state from %s...\n", input_filepath); + load_game(input_filepath, &games[current]); + } + + + + + + //print_chromo(stdout, game.chromos); + + scc(SDL_Init(SDL_INIT_VIDEO)); + + // Create Window + SDL_Window *window = scp(SDL_CreateWindow( + "Question Mark? (floating)", + 0, 0, + SCREEN_WIDTH, SCREEN_HEIGHT, + SDL_WINDOW_RESIZABLE)); + + // Create GPU Accelerated Renderer + SDL_Renderer *renderer = scp(SDL_CreateRenderer( + window, + -1, + SDL_RENDERER_ACCELERATED)); + + // Set Window to Scale Rendered Content on resize + scc(SDL_RenderSetLogicalSize(renderer, SCREEN_WIDTH, SCREEN_HEIGHT)); + + // Keep SDL alive and continuously poll events and render. + int quit = 0; + while (!quit) { + SDL_Event event; + while (SDL_PollEvent(&event)) { + switch (event.type) { + case SDL_QUIT: { + quit = 1; + } break; + case SDL_KEYDOWN: { + switch (event.key.keysym.sym) { + case SDLK_SPACE: { + step_game(&games[current]); + } break; + case SDLK_r: { + init_game(&games[current]); + } break; + case SDLK_n: { + //print_best_agents(stdout, &games[current], SELECTION_POOL); + int next = 1 - current; + make_next_generation(&games[current], &games[next]); + current = next; + } break; + case SDLK_q: { + SDL_DestroyRenderer(renderer); // Writes an error, but without this, + // program doesn't close properly? + SDL_DestroyWindow(window); + SDL_Quit(); + } break; + //case SDLK_d: { + // dump_game(DUMP_FILEPATH, &games[current]); + //} break; + } + } break; + case SDL_MOUSEBUTTONDOWN: { + Coord cell; + cell.x = (int) floorf(event.button.x / CELL_WIDTH); + cell.y = (int) floorf(event.button.y / CELL_HEIGHT); + Agent *agent = agent_at(&games[current], cell); + + if (agent) { + print_agent(stdout, agent); + } + } break; + } + } + + // Draw Background + SDL_SetRenderDrawColor(renderer, HEX_COLOR(BACKGROUND_COLOR)); + scc(SDL_RenderClear(renderer)); + + // Draw Board Grid + // render_board_grid(renderer); + + // Render Agents + render_game(renderer, &games[current]); + + // Render + SDL_RenderPresent(renderer); + + } + + SDL_DestroyRenderer(renderer); + SDL_DestroyWindow(window); + + SDL_Quit(); + + return 0; +} diff --git a/src/simulator/rendering.c b/src/simulator/rendering.c new file mode 100644 index 0000000..05a93f4 --- /dev/null +++ b/src/simulator/rendering.c @@ -0,0 +1,194 @@ +#include "rendering.h" +#include "SDL_stdinc.h" +#include "../game.h" + +#include + +#define GAP_SIZE (fminf(CELL_WIDTH, CELL_HEIGHT) / 1.1f) + +int scc(int code) { + if (code < 0) { + fprintf(stderr, "SDL Error: %s\n", SDL_GetError()); + exit(1); + } + + return code; +} + +void *scp(void *ptr) { + if (ptr == NULL) { + fprintf(stderr, "SDL Error: %s\n", SDL_GetError()); + exit(1); + } + + return ptr; +} + +SDL_Vertex sdl_color_hex_vertex(int x, int y, Uint32 hex) { + SDL_Vertex vertex; + + vertex.position.x = (float)x; + vertex.position.y = (float)y; + vertex.color.r = (hex >> (3 * 8)) & 0xFF; + vertex.color.g = (hex >> (2 * 8)) & 0xFF; + vertex.color.b = (hex >> (1 * 8)) & 0xFF; + vertex.color.a = (hex >> (0 * 8)) & 0xFF; + vertex.tex_coord.x = 0; + vertex.tex_coord.y = 0; + + return vertex; +} + +void render_board_grid(SDL_Renderer *renderer) { + SDL_SetRenderDrawColor(renderer, HEX_COLOR(GRID_COLOR)); + + for (int x = 1; x < BOARD_WIDTH; ++x) { + SDL_RenderDrawLine(renderer, + x * CELL_WIDTH, + 0, + x * CELL_WIDTH, + SCREEN_HEIGHT); + } + + for (int x = 1; x < BOARD_HEIGHT; ++x) { + SDL_RenderDrawLine(renderer, + 0, + x * CELL_HEIGHT, + SCREEN_WIDTH, + x * CELL_HEIGHT); + } +} + +#define AGENT_CELL_POS_X agent.pos.x * CELL_WIDTH +#define AGENT_CELL_POS_Y agent.pos.y * CELL_HEIGHT + +#define VERTEX_0 \ + sdl_color_hex_vertex(AGENT_CELL_POS_X + GAP_SIZE, \ + AGENT_CELL_POS_Y + GAP_SIZE, \ + color) + +#define VERTEX_1 \ + sdl_color_hex_vertex(AGENT_CELL_POS_X + CELL_WIDTH - GAP_SIZE, \ + AGENT_CELL_POS_Y + GAP_SIZE, \ + color) + +#define VERTEX_2 \ + sdl_color_hex_vertex(AGENT_CELL_POS_X + GAP_SIZE, \ + AGENT_CELL_POS_Y + CELL_HEIGHT - GAP_SIZE, \ + color) + +#define VERTEX_3 \ + sdl_color_hex_vertex(AGENT_CELL_POS_X + CELL_WIDTH - GAP_SIZE, \ + AGENT_CELL_POS_Y + CELL_HEIGHT - GAP_SIZE, \ + color) + +#define FOOD_CELL_POS_X x * CELL_WIDTH +#define FOOD_CELL_POS_Y y * CELL_HEIGHT + +SDL_Vertex* sdl_draw_food_diamond(int y, int x) { + SDL_Vertex* vertices = malloc(4 * sizeof(SDL_Vertex)); + if (vertices == NULL) { + fprintf(stderr, "Vertex Memory allocation failed\n"); + exit(1); + } + + vertices[0] = sdl_color_hex_vertex(FOOD_CELL_POS_X + CELL_WIDTH - GAP_SIZE, + FOOD_CELL_POS_Y + (CELL_WIDTH / 2), + FOOD_COLOR); + + vertices[1] = sdl_color_hex_vertex(FOOD_CELL_POS_X + (CELL_WIDTH / 2), + FOOD_CELL_POS_Y + GAP_SIZE, + FOOD_COLOR); + + vertices[2] = sdl_color_hex_vertex(FOOD_CELL_POS_X + GAP_SIZE, + FOOD_CELL_POS_Y + (CELL_WIDTH / 2), + FOOD_COLOR); + + vertices[3] = sdl_color_hex_vertex(FOOD_CELL_POS_X + (CELL_WIDTH / 2), + FOOD_CELL_POS_Y + CELL_HEIGHT - GAP_SIZE, + FOOD_COLOR); + + return vertices; +} + +SDL_Vertex* sdl_draw_agent_triangle(Agent agent) { + SDL_Vertex* vertices = malloc(3 * sizeof(SDL_Vertex)); + if (vertices == NULL) { + fprintf(stderr, "Vertex Memory allocation failed\n"); + exit(1); + } + + Uint32 color = agent.health <= 0 ? AGENT_COLOR_DEAD : AGENT_COLOR_ALIVE; + + switch (agent.dir) { + case DIR_LEFT: + vertices[0] = VERTEX_0; + vertices[1] = VERTEX_2; + vertices[2] = sdl_color_hex_vertex(AGENT_CELL_POS_X + CELL_WIDTH - GAP_SIZE, + AGENT_CELL_POS_Y + (CELL_WIDTH / 2), + color); + break; + case DIR_DOWN: + vertices[0] = VERTEX_2; + vertices[1] = VERTEX_3; + vertices[2] = sdl_color_hex_vertex(AGENT_CELL_POS_X + (CELL_WIDTH / 2), + AGENT_CELL_POS_Y + GAP_SIZE, + color); + break; + case DIR_RIGHT: + vertices[0] = VERTEX_1; + vertices[1] = VERTEX_3; + vertices[2] = sdl_color_hex_vertex(AGENT_CELL_POS_X + GAP_SIZE, + AGENT_CELL_POS_Y + (CELL_WIDTH / 2), + color); + break; + case DIR_UP: + vertices[0] = VERTEX_0; + vertices[1] = VERTEX_1; + vertices[2] = sdl_color_hex_vertex(AGENT_CELL_POS_X + (CELL_WIDTH / 2), + AGENT_CELL_POS_Y + CELL_HEIGHT - GAP_SIZE, + color); + break; + } + + return vertices; +} + +void render_agent(SDL_Renderer *renderer, Agent agent) { + SDL_Vertex * vertices = sdl_draw_agent_triangle(agent); + scc(SDL_RenderGeometry(renderer, NULL, vertices, 3, NULL, 0)); + free(vertices); // sdl_draw_agent_triangle's malloc +} + +void render_game(SDL_Renderer *renderer, const Game *game) { + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + render_agent(renderer, game->agents[i]); + } + + // Render Foods + const int indices[] = {0, 1, 2, 0, 3, 2}; + for(int y = 0; y < BOARD_HEIGHT; ++y) { + for (int x = 0; x < BOARD_WIDTH; ++x) { + if (game->foods_map[y][x]) { + SDL_Vertex *vertices = sdl_draw_food_diamond(y, x); + scc(SDL_RenderGeometry(renderer, NULL, vertices, 4, indices, 6)); + free(vertices); // sdl_draw_food_diamond's malloc + } + + if (game->walls_map[y][x]) { + SDL_Rect rect = {// Check why int floorf + (int)floorf(x * CELL_WIDTH), + (int)floorf(y * CELL_HEIGHT), + (int)floorf(CELL_WIDTH), (int)floorf(CELL_HEIGHT)}; + SDL_SetRenderDrawColor(renderer, HEX_COLOR(WALL_COLOR)); + SDL_RenderFillRect(renderer, &rect); + } + } + } + + // Render Walls + for (size_t i = 0; i < WALLS_COUNT; ++i) { + + } + +} diff --git a/src/simulator/rendering.h b/src/simulator/rendering.h new file mode 100644 index 0000000..a899cc2 --- /dev/null +++ b/src/simulator/rendering.h @@ -0,0 +1,36 @@ +#ifndef RENDERING_H +#define RENDERING_H + +#include +#include "../game.h" + +#define SCREEN_WIDTH 600 +#define SCREEN_HEIGHT 600 + +#define CELL_WIDTH ((float) SCREEN_WIDTH / (float) BOARD_WIDTH) +#define CELL_HEIGHT ((float) SCREEN_HEIGHT / (float) BOARD_HEIGHT) + +#define BACKGROUND_COLOR 0x190A0FFF +#define GRID_COLOR 0xEBCBF4FF +#define WALL_COLOR (GRID_COLOR) +#define AGENT_COLOR_ALIVE 0x8447FFFF +// #define AGENT_COLOR_DEAD 0x392A2FFF +#define AGENT_COLOR_DEAD (BACKGROUND_COLOR) +#define FOOD_COLOR 0xE1F0C4FF + + +#define HEX_COLOR(hex) \ + ((hex) >> (3 * 8)) & 0xFF, \ + ((hex) >> (2 * 8)) & 0xFF, \ + ((hex) >> (1 * 8)) & 0xFF, \ + ((hex) >> (0 * 8)) & 0xFF + +int scc(int code); + +void *scp(void *ptr); + +void render_board_grid(SDL_Renderer *renderer); + +void render_game(SDL_Renderer *renderer, const Game *game); + +#endif diff --git a/src/trainer/logging.c b/src/trainer/logging.c new file mode 100644 index 0000000..f35ab73 --- /dev/null +++ b/src/trainer/logging.c @@ -0,0 +1,71 @@ +// File by "Yangelis" https://github.com/yangelis + +#include "../game.h" + +FILE *gnuplot_pipe = 0; +#define nOfGnuplotCmds 14 +char *gnuplotCmds[] = { + "reset\n", + "set size 1,1\n", + "set origin 0,0\n", + "set xlabel 'Generations'\n", + "set multiplot layout 2, 2 columnsfirst title 'gp live stats'\n", + "set ylabel 'Avg Lifetime'\n", + "plot 'log.dat' using 1:2 with lines lw 2 lt 10 notitle \n", + "set ylabel 'Min lifetime'\n", + "plot 'log.dat' using 1:3 with lines lw 2 lt 10 notitle\n", + "set ylabel 'Max lifetime'\n", + "plot 'log.dat' using 1:4 with lines lw 2 lt 10 notitle\n", + "set ylabel 'Food Eaten'\n", + "plot 'log.dat' using 1:5 with lines lw 2 lt 10 notitle\n", + "unset multiplot\n" +}; + + +float avg_lifetime(Agent *agents) { + int result = 0.0; + for (size_t i = 0; i < AGENTS_COUNT; ++i) { + result += agents[i].lifetime; + } + return (float)result / AGENTS_COUNT; +} +void log_header(FILE *stream) { + fprintf(stream, "#Loggind generations\n"); + fprintf( + stream, + "#Generation, Avg Lifetime, Min Lifetime, Max Lifetime, Food eaten\n"); + gnuplot_pipe = popen("gnuplot -p", "w"); +} +void log_generation(FILE *stream, int gen, Game *game) { + float avg_lf = avg_lifetime(game->agents); + int max_lifetime = game->agents[0].lifetime; + int min_lifetime = game->agents[0].lifetime; + for (size_t i = 1; i < AGENTS_COUNT; ++i) { + if (game->agents[i].lifetime > max_lifetime) { + max_lifetime = game->agents[i].lifetime; + } + if (game->agents[i].lifetime < min_lifetime) { + min_lifetime = game->agents[i].lifetime; + } + } + + int food_eaten = FOODS_COUNT; + for (size_t y = 0; y < BOARD_HEIGHT; ++y) { + for (size_t x = 0; x < BOARD_WIDTH; ++x) { + if (game->foods_map[y][x]) { + food_eaten--; + } + } + } + + /* Writing to log file*/ + fprintf(stream, "%d, %f, %d, %d, %d\n", gen, avg_lf, min_lifetime, + max_lifetime, food_eaten); +} +void log_live_update(void) { + for (size_t i = 0; i < nOfGnuplotCmds; ++i) { + fprintf(gnuplot_pipe, "%s \n", gnuplotCmds[i]); + } + fflush(gnuplot_pipe); +} +void log_close_pipe(void) { pclose(gnuplot_pipe); } diff --git a/src/trainer/logging.h b/src/trainer/logging.h new file mode 100644 index 0000000..93cb595 --- /dev/null +++ b/src/trainer/logging.h @@ -0,0 +1,14 @@ +#ifndef LOGGING_H +#define LOGGING_H + +#include "../game.h" + +void log_header(FILE *stream); + +void log_generation(FILE *stream, int gen, Game *game); + +void log_close_pipe(void); + +void log_live_update(void); + +#endif diff --git a/src/trainer/main.c b/src/trainer/main.c new file mode 100644 index 0000000..fa4d97f --- /dev/null +++ b/src/trainer/main.c @@ -0,0 +1,135 @@ +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../game.h" +#include "logging.h" + +Game games[2] = {0}; + +// Tsoding is now a 3-Star Developer! +const char *tsoding_shift(int *argc, char ***argv) { + assert(*argc > 0); + const char* result = **argv; + *argc -= 1; + *argv += 1; + return result; +} + +void usage(FILE *stream) { + fprintf(stream, "Usage: ./gp_trainer [OPTIONS]\n"); + fprintf(stream, " --gen|g \n"); + fprintf(stream, " Amount of generations (default: 100)\n"); + fprintf(stream, " --out|o \n"); + fprintf(stream, " Output filepath (default: out.bin)\n"); + fprintf(stream, " --plot|p\n"); + fprintf(stream, " Enable Gnuplot live reporting\n"); +} + +FILE* gnulog; +int gnuplot; +int current; +const char* output_filepath; + +void ctrlc(int signum) { + if (gnuplot) { + log_close_pipe(); + fclose(gnulog); + } + + dump_game(output_filepath, &games[current]); + exit(1); +} + +int main(int argc, char *argv[]) { + + signal(SIGINT, ctrlc); + + tsoding_shift(&argc, &argv); //Skip Program Name + + srand(time(NULL)); + int gen_count = 100; + output_filepath = "out.bin"; + gnuplot = 0; + + while (argc > 0) { + const char *flag = tsoding_shift(&argc, &argv); + + if (strcmp(flag, "--gen") == 0 || strcmp(flag, "-g") == 0) { + if (argc == 0) { + usage(stderr); + fprintf(stderr, "ERROR: no value provided for flag %s\n", flag); + exit(1); + } + const char *value = tsoding_shift(&argc, &argv); + gen_count = atoi(value); + + } else if (strcmp(flag, "--out") == 0 || strcmp(flag, "-o") == 0) { + if (argc == 0) { + usage(stderr); + fprintf(stderr, "ERROR: no value provided for flag %s\n", flag); + exit(1); + } + const char *value = tsoding_shift(&argc, &argv); + output_filepath = value; + + } else if (strcmp(flag, "--plot") == 0 || strcmp(flag, "-p") == 0) { + gnuplot = 1; + + } else if (strcmp(flag, "--help") == 0 || strcmp(flag, "-h") == 0) { + usage(stdout); + exit(0); + } else { + usage(stderr); + fprintf(stderr, "ERROR: unknown flag: %s\n", flag); + exit(1); + } + } + + if (gnuplot) { + gnulog = fopen("log.dat", "w"); + log_header(gnulog); + } + + current = 0; + + init_game(&games[current]); + + for (int i = 0; i < gen_count; ++i) { + fprintf(stderr, "Generation %d... ", i); + fflush(stderr); + + clock_t begin = clock(); + while (!is_everyone_dead(&games[current])) { + step_game(&games[current]); + } + + printf("%fs\n", (float)(clock() - begin) / (float) CLOCKS_PER_SEC); + fflush(stdout); + + if (gnuplot) { + log_generation(gnulog, i, &games[current]); + fflush(gnulog); + log_live_update(); + } + + int next = 1 - current; + make_next_generation(&games[current], &games[next]); + current = next; + } + + if (gnuplot) { + log_close_pipe(); + fclose(gnulog); + } + + dump_game(output_filepath, &games[current]); + + return 0; +} -- cgit 1.4.1-2-gfad0