about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--.gitignore9
-rw-r--r--Makefile38
-rw-r--r--src/game.c429
-rw-r--r--src/game.h122
-rw-r--r--src/inspector/main.c37
-rw-r--r--src/simulator/main.c132
-rw-r--r--src/simulator/rendering.c194
-rw-r--r--src/simulator/rendering.h36
-rw-r--r--src/trainer/logging.c71
-rw-r--r--src/trainer/logging.h14
-rw-r--r--src/trainer/main.c135
11 files changed, 1217 insertions, 0 deletions
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 <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+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 <stddef.h>
+#include <stdio.h>
+#include <assert.h>
+
+#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 <stdio.h>
+#include <assert.h>
+#include <SDL2/SDL.h>
+
+#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 <input.bin>\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 <stdio.h>
+#include <time.h>
+#include <assert.h>
+#include <SDL2/SDL.h>
+
+#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 <SDL2/SDL.h>
+
+#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 <SDL2/SDL.h>
+#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 <assert.h>
+#include <signal.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+
+#include <SDL2/SDL.h>
+
+#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 <NUMBER>\n");
+  fprintf(stream, "         Amount of generations (default: 100)\n");
+  fprintf(stream, "       --out|o <FILEPATH>\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;
+}