def __init__(self, conf): self._conf = conf self._map = Map(conf.map_rows + 2, conf.map_cols + 2) self._snake = Snake(self._map, conf.init_direc, conf.init_bodies, conf.init_types) self._pause = False self._solver = globals()[self._conf.solver_name](self._snake) self._episode = 1
def test_copy(): m = Map(5, 5) m.point(Pos(1, 1)).type = PointType.FOOD m_copy = m.copy() assert id(m) != id(m_copy) assert m.num_rows == m_copy.num_rows assert m.num_cols == m_copy.num_cols assert m.capacity == m_copy.capacity for i in range(m.num_rows): for j in range(m.num_cols): assert m.point(Pos(i, j)).type == m_copy.point(Pos(i, j)).type
def test_init(): with pytest.raises(TypeError): m = Map(5, 1.5) with pytest.raises(ValueError): m = Map(4, 5) m = Map(12, 12) for i in range(m.num_rows): for j in range(m.num_cols): if i == 0 or i == m.num_rows - 1 or j == 0 or j == m.num_cols - 1: assert m.point(Pos(i, j)).type == PointType.WALL else: assert m.point(Pos(i, j)).type == PointType.EMPTY
def test_init(): m = Map(5, 5) s = Snake(m, Direc.RIGHT, [Pos(1, 3), Pos(1, 2), Pos(1, 1)], [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR]) assert not s.dead assert s.direc is Direc.RIGHT assert s.len() == 3 assert s.head() == Pos(1, 3) assert s.bodies[1] == Pos(1, 2) assert s.tail() == Pos(1, 1) assert m.point(Pos(1, 1)).type == PointType.BODY_HOR assert m.point(Pos(1, 2)).type == PointType.BODY_HOR assert m.point(Pos(1, 3)).type == PointType.HEAD_R
def __init__(self, conf): self.__conf = conf self.__map = Map(conf.map_rows + 2, conf.map_cols + 2) self.__snake = Snake(self.__map, conf.init_direc, conf.init_bodies, conf.init_types) self.__pause = False self.__window = GameWindow( conf, self.__map, "Snake", self.__snake, self.__on_exit, (('<w>', lambda e: self.__update_direc(Direc.UP)), ('<a>', lambda e: self.__update_direc(Direc.LEFT)), ('<s>', lambda e: self.__update_direc(Direc.DOWN)), ('<d>', lambda e: self.__update_direc(Direc.RIGHT)), ('<r>', lambda e: self.__reset()), ('<space>', lambda e: self.__toggle_pause()))) self.__solver = globals()[self.__conf.solver_name](self.__snake) self.__episode = 1 self.__init_log_file()
def test_dead(): m = Map(5, 5) s = Snake(m, Direc.RIGHT, [Pos(1, 3), Pos(1, 2), Pos(1, 1)], [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR]) assert not s.dead s.move(s.direc) assert s.dead and s.len() == 3 and s.head() == Pos(1, 4) m.reset() s = Snake(m, Direc.RIGHT, [Pos(1, 3), Pos(1, 2), Pos(1, 1)], [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR]) assert not s.dead s.move(Direc.UP) assert s.dead and s.len() == 3 and s.head() == Pos(0, 3) m.reset() s = Snake(m, Direc.DOWN, [Pos(3, 1), Pos(2, 1), Pos(1, 1)], [PointType.HEAD_D, PointType.BODY_VER, PointType.BODY_VER]) assert not s.dead s.move(s.direc) assert s.dead and s.len() == 3 and s.head() == Pos(4, 1) m.reset() s = Snake(m, Direc.DOWN, [Pos(3, 1), Pos(2, 1), Pos(1, 1)], [PointType.HEAD_D, PointType.BODY_VER, PointType.BODY_VER]) assert not s.dead s.move(Direc.LEFT) assert s.dead and s.len() == 3 and s.head() == Pos(3, 0) m.reset() s = Snake( m, Direc.LEFT, [Pos(2, 2), Pos(3, 2), Pos(3, 1), Pos(2, 1), Pos(1, 1)], [ PointType.HEAD_U, PointType.BODY_LU, PointType.BODY_UR, PointType.BODY_VER, PointType.BODY_VER ]) assert not s.dead s.move(s.direc) assert s.dead and s.len() == 5 and s.head() == Pos(2, 1)
def __init__(self, conf): self._conf = conf self._map = Map(conf.map_rows + 2, conf.map_cols + 2) self._snake = Snake(self._map, conf.init_direc, conf.init_bodies, conf.init_types) self._pause = False self._solver = globals()[self._conf.solver_name](self._snake) self._episode = 1 self._init_log_file()
def test_shortest(): m = Map(7, 7) m.create_food(Pos(5, 5)) s = Snake(m, Direc.RIGHT, [Pos(2, 3), Pos(2, 2), Pos(2, 1)], [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR]) solver = PathSolver(s) act_path = solver.shortest_path_to_food() act_path = solver.shortest_path_to_food() # Check idempotence expect_path = [ Direc.RIGHT, Direc.RIGHT, Direc.DOWN, Direc.DOWN, Direc.DOWN ] assert len(act_path) == len(expect_path) for i, direc in enumerate(act_path): assert direc == expect_path[i] assert solver.table[5][1].dist == 5 assert solver.table[5][1].dist == solver.table[5][5].dist # Empty path assert not solver.shortest_path_to(s.tail())
def test_longest(): m = Map(6, 6) m.create_food(Pos(4, 4)) s = Snake(m, Direc.RIGHT, [Pos(1, 3), Pos(1, 2), Pos(1, 1)], [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR]) solver = PathSolver(s) act_path = solver.longest_path_to_tail() act_path = solver.longest_path_to_tail() # Check idempotence expect_path = [ Direc.RIGHT, Direc.DOWN, Direc.DOWN, Direc.DOWN, Direc.LEFT, Direc.LEFT, Direc.LEFT, Direc.UP, Direc.RIGHT, Direc.RIGHT, Direc.UP, Direc.LEFT, Direc.LEFT, Direc.UP ] assert m.point(s.tail()).type == PointType.BODY_HOR assert len(act_path) == len(expect_path) for i, direc in enumerate(act_path): assert direc == expect_path[i] # Empty path assert not solver.longest_path_to(s.tail())
def test_copy(): m = Map(5, 5) s = Snake(m, Direc.RIGHT, [Pos(1, 3), Pos(1, 2), Pos(1, 1)], [PointType.HEAD_R, PointType.BODY_HOR, PointType.BODY_HOR]) s.move(Direc.DOWN) s.move(Direc.LEFT) s_copy, _ = s.copy() assert id(s) != id(s_copy) assert s.steps == 2 and s.steps == s_copy.steps assert not s.dead and s.dead == s_copy.dead assert s.direc == Direc.LEFT and s.direc == s_copy.direc assert s.direc_next == Direc.LEFT and s.direc_next == s_copy.direc_next for i, b in enumerate(s.bodies): assert b == s_copy.bodies[i]
def test_cycle(): m = Map(6, 6) s = Snake(m, Direc.RIGHT, [Pos(1, 2), Pos(1, 1)], [PointType.HEAD_R, PointType.BODY_HOR]) solver = HamiltonSolver(s, False) table = solver.table cnt = 0 ori_head = s.head() while True: head = s.head() assert cnt == table[head.x][head.y].idx s.move(solver.next_direc()) cnt += 1 if s.head() == ori_head: break assert cnt == m.capacity
class Game: def __init__(self, conf): self._conf = conf self._map = Map(conf.map_rows + 2, conf.map_cols + 2) self._snake = Snake(self._map, conf.init_direc, conf.init_bodies, conf.init_types) self._pause = False self._solver = globals()[self._conf.solver_name](self._snake) self._episode = 1 self._init_log_file() @property def snake(self): return self._snake @property def episode(self): return self._episode def run(self): window = GameWindow("Snake", self._conf, self._map, self, self._on_exit, ( ('<w>', lambda e: self._update_direc(Direc.UP)), ('<a>', lambda e: self._update_direc(Direc.LEFT)), ('<s>', lambda e: self._update_direc(Direc.DOWN)), ('<d>', lambda e: self._update_direc(Direc.RIGHT)), ('<r>', lambda e: self._reset()), ('<space>', lambda e: self._toggle_pause()) )) if self._conf.mode == GameMode.NORMAL: window.show(self._game_main_normal) elif self._conf.mode == GameMode.TRAIN_DQN_GUI: window.show(self._game_main_dqn_train) self._plot_history() def _game_main_normal(self): if not self._map.has_food(): self._map.create_rand_food() if self._pause or self._is_episode_end(): return self._update_direc(self._solver.next_direc()) if self._conf.mode == GameMode.NORMAL and self._snake.direc_next != Direc.NONE: self._write_logs() self._snake.move() if self._is_episode_end(): self._write_logs() # Write the last step def _plot_history(self): self._solver.plot() def _update_direc(self, new_direc): self._snake.direc_next = new_direc if self._pause: self._snake.move() def _toggle_pause(self): self._pause = not self._pause def _is_episode_end(self): return self._snake.dead or self._map.is_full() def _reset(self): self._snake.reset() self._episode += 1 def _on_exit(self): if self._log_file: self._log_file.close() if self._solver: self._solver.close() def _init_log_file(self): try: os.makedirs("logs") except OSError as e: if e.errno != errno.EEXIST: raise try: self._log_file = None self._log_file = open('logs/snake.log', 'w') except FileNotFoundError: if self._log_file: self._log_file.close() def _write_logs(self): self._log_file.write("[ Episode %d / Step %d ]\n" % \ (self._episode, self._snake.steps)) for i in range(self._map.num_rows): for j in range(self._map.num_cols): pos = Pos(i, j) t = self._map.point(pos).type if t == PointType.EMPTY: self._log_file.write(" ") elif t == PointType.WALL: self._log_file.write("# ") elif t == PointType.FOOD: self._log_file.write("F ") elif t == PointType.HEAD_L or t == PointType.HEAD_U or \ t == PointType.HEAD_R or t == PointType.HEAD_D: self._log_file.write("H ") elif pos == self._snake.tail(): self._log_file.write("T ") else: self._log_file.write("B ") self._log_file.write("\n") self._log_file.write("[ last/next direc: %s/%s ]\n" % \ (self._snake.direc, self._snake.direc_next)) self._log_file.write("\n")
class Game: def __init__(self, conf): self._conf = conf self._map = Map(conf.map_rows + 2, conf.map_cols + 2) self._snake = Snake(self._map, conf.init_direc, conf.init_bodies, conf.init_types) self._pause = False self._solver = globals()[self._conf.solver_name](self._snake) self._episode = 1 @property def snake(self): return self._snake @property def episode(self): return self._episode def run(self): if self._conf.mode == GameMode.BENCHMARK: self._run_benchmarks() else: if self._conf.mode == GameMode.NORMAL: window = GameWindow( "Snake", self._conf, self._map, self, self._on_exit, (('<w>', lambda e: self._update_direc(Direc.UP)), ('<a>', lambda e: self._update_direc(Direc.LEFT)), ('<s>', lambda e: self._update_direc(Direc.DOWN)), ('<d>', lambda e: self._update_direc(Direc.RIGHT)), ('<r>', lambda e: self._reset()), ('<space>', lambda e: self._toggle_pause()))) window.show(self._game_main_normal) def _run_benchmarks(self): STEPS_LIMIT = 3000 NUM_EPISODES = int(input("Please input the number of episodes: ")) print("\nMap size: %dx%d" % (self._conf.map_rows, self._conf.map_cols)) print("Solver: %s\n" % self._conf.solver_name[:-6].lower()) tot_suc, tot_suc_steps = 0, 0 tot_len, tot_steps = 0, 0 for _ in range(NUM_EPISODES): print("Episode %d - " % self._episode, end="") window = GameWindow( "Snake", self._conf, self._map, self, self._on_exit, (('<w>', lambda e: self._update_direc(Direc.UP)), ('<a>', lambda e: self._update_direc(Direc.LEFT)), ('<s>', lambda e: self._update_direc(Direc.DOWN)), ('<d>', lambda e: self._update_direc(Direc.RIGHT)), ('<r>', lambda e: self._reset()), ('<space>', lambda e: self._toggle_pause()))) while True: window.show(self._game_main_normal) if self._map.is_full(): tot_suc += 1 tot_suc_steps += self._snake.steps print("FULL (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) break elif self._snake.dead: print("DEAD (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) break elif self._snake.steps >= STEPS_LIMIT: print("STEP LIMIT (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) break tot_len += self._snake.len() tot_steps += self._snake.steps self._reset() suc_ratio = tot_suc / (self._episode - 1) avg_suc_steps = 0 if tot_suc != 0: avg_suc_steps = tot_suc_steps // tot_suc avg_len = tot_len / NUM_EPISODES avg_steps = tot_steps / NUM_EPISODES print( "\n[Summary]\nAverage Length: %.2f\nAverage Steps: %.2f\nTotal Runs: %d Successful Runs: %d (%.2f%%) \nAvg Successful steps: %d\n" % (avg_len, avg_steps, self._episode - 1, tot_suc, 100 * suc_ratio, avg_suc_steps)) self._on_exit() def _game_main_normal(self): if not self._map.has_food(): self._map.create_rand_food() if self._pause or self._is_episode_end(): return self._update_direc(self._solver.next_direc()) self._snake.move() def _update_direc(self, new_direc): self._snake.direc_next = new_direc if self._pause: self._snake.move() def _toggle_pause(self): self._pause = not self._pause def _is_episode_end(self): return self._snake.dead or self._map.is_full() def _reset(self): self._snake.reset() self._episode += 1 def _on_exit(self): if self._solver: self._solver.close()
def test_game_window(): game_conf = GameConf() game_conf.map_rows = 15 game_conf.map_cols = game_conf.map_rows game_conf.show_grid_line = True game_conf.show_info_panel = False game_map = Map(game_conf.map_rows + 2, game_conf.map_cols + 2) contents = ( # Walls (Pos(1, 6), PointType.WALL), (Pos(1, 7), PointType.WALL), (Pos(1, 8), PointType.WALL), (Pos(1, 9), PointType.WALL), (Pos(1, 10), PointType.WALL), (Pos(15, 6), PointType.WALL), (Pos(15, 7), PointType.WALL), (Pos(15, 8), PointType.WALL), (Pos(15, 9), PointType.WALL), (Pos(15, 10), PointType.WALL), (Pos(6, 1), PointType.WALL), (Pos(7, 1), PointType.WALL), (Pos(8, 1), PointType.WALL), (Pos(9, 1), PointType.WALL), (Pos(10, 1), PointType.WALL), (Pos(6, 15), PointType.WALL), (Pos(7, 15), PointType.WALL), (Pos(8, 15), PointType.WALL), (Pos(9, 15), PointType.WALL), (Pos(10, 15), PointType.WALL), # Food (Pos(4, 6), PointType.FOOD), (Pos(4, 10), PointType.FOOD), (Pos(6, 4), PointType.FOOD), (Pos(10, 4), PointType.FOOD), (Pos(6, 12), PointType.FOOD), (Pos(10, 12), PointType.FOOD), (Pos(12, 6), PointType.FOOD), (Pos(12, 10), PointType.FOOD), # Top-left (Pos(2, 2), PointType.BODY_VER), (Pos(3, 2), PointType.BODY_VER), (Pos(4, 2), PointType.BODY_UR), (Pos(4, 3), PointType.BODY_LU), (Pos(3, 3), PointType.BODY_VER), (Pos(2, 3), PointType.BODY_RD), (Pos(2, 4), PointType.BODY_DL), (Pos(2, 4), PointType.BODY_DL), (Pos(3, 4), PointType.BODY_VER), (Pos(4, 4), PointType.HEAD_D), # Top-right (Pos(2, 14), PointType.BODY_VER), (Pos(3, 14), PointType.BODY_VER), (Pos(4, 14), PointType.BODY_LU), (Pos(4, 13), PointType.BODY_UR), (Pos(3, 13), PointType.BODY_VER), (Pos(2, 13), PointType.BODY_DL), (Pos(2, 12), PointType.BODY_RD), (Pos(3, 12), PointType.BODY_VER), (Pos(4, 12), PointType.HEAD_D), # Bottom-left (Pos(14, 2), PointType.BODY_VER), (Pos(13, 2), PointType.BODY_VER), (Pos(12, 2), PointType.BODY_RD), (Pos(12, 3), PointType.BODY_DL), (Pos(13, 3), PointType.BODY_VER), (Pos(14, 3), PointType.BODY_UR), (Pos(14, 4), PointType.BODY_LU), (Pos(13, 4), PointType.BODY_VER), (Pos(12, 4), PointType.HEAD_U), # Bottom-right (Pos(14, 14), PointType.BODY_VER), (Pos(13, 14), PointType.BODY_VER), (Pos(12, 14), PointType.BODY_DL), (Pos(12, 13), PointType.BODY_RD), (Pos(13, 13), PointType.BODY_VER), (Pos(14, 13), PointType.BODY_LU), (Pos(14, 12), PointType.BODY_UR), (Pos(13, 12), PointType.BODY_VER), (Pos(12, 12), PointType.HEAD_U), # Middle (Pos(10, 6), PointType.HEAD_L), (Pos(10, 7), PointType.BODY_HOR), (Pos(10, 8), PointType.BODY_HOR), (Pos(10, 9), PointType.BODY_HOR), (Pos(10, 10), PointType.BODY_LU), (Pos(9, 10), PointType.BODY_VER), (Pos(8, 10), PointType.BODY_DL), (Pos(8, 9), PointType.BODY_HOR), (Pos(8, 8), PointType.BODY_HOR), (Pos(8, 7), PointType.BODY_HOR), (Pos(8, 6), PointType.BODY_UR), (Pos(7, 6), PointType.BODY_VER), (Pos(6, 6), PointType.BODY_RD), (Pos(6, 7), PointType.BODY_HOR), (Pos(6, 8), PointType.BODY_HOR), (Pos(6, 9), PointType.BODY_HOR), (Pos(6, 10), PointType.HEAD_R) ) for content in contents: game_map.point(content[0]).type = content[1] GameWindow("Basic Elements", game_conf, game_map).show()
def test_predicate(): m = Map(5, 5) assert not m.is_full() for i in range(m.num_rows): for j in range(m.num_cols): p = Pos(i, j) if i == 0 or i == m.num_rows - 1 or j == 0 or j == m.num_cols - 1: assert not m.is_inside(p) and not m.is_empty(p) \ and not m.is_safe(p) else: assert m.is_inside(p) and m.is_empty(p) and m.is_safe(p) p1, p2, p3 = Pos(1, 1), Pos(2, 2), Pos(3, 3) m.point(p1).type = PointType.HEAD_L m.point(p2).type = PointType.BODY_VER m.point(p3).type = PointType.FOOD assert m.is_inside(p1) and not m.is_empty(p1) and not m.is_safe(p1) assert m.is_inside(p2) and not m.is_empty(p2) and not m.is_safe(p2) assert m.is_inside(p3) and not m.is_empty(p3) and m.is_safe(p3) assert not m.is_full() for i in range(1, m.num_rows - 1): for j in range(1, m.num_cols - 1): if i < m.num_rows / 2: m.point(Pos(i, j)).type = PointType.HEAD_U else: m.point(Pos(i, j)).type = PointType.BODY_UR assert m.is_full()
class Game: def __init__(self, conf): self._conf = conf self._map = Map(conf.map_rows + 2, conf.map_cols + 2) self._snake = Snake(self._map, conf.init_direc, conf.init_bodies, conf.init_types) self._pause = False self._solver = globals()[self._conf.solver_name](self._snake) self._episode = 1 self._init_log_file() @property def snake(self): return self._snake @property def episode(self): return self._episode def run(self): if self._conf.mode == GameMode.BENCHMARK: self._run_benchmarks() elif self._conf.mode == GameMode.TRAIN_DQN: self._run_dqn_train() self._plot_history() else: window = GameWindow( "Snake", self._conf, self._map, self, self._on_exit, (('<w>', lambda e: self._update_direc(Direc.UP)), ('<a>', lambda e: self._update_direc(Direc.LEFT)), ('<s>', lambda e: self._update_direc(Direc.DOWN)), ('<d>', lambda e: self._update_direc(Direc.RIGHT)), ('<r>', lambda e: self._reset()), ('<space>', lambda e: self._toggle_pause()))) if self._conf.mode == GameMode.NORMAL: window.show(self._game_main_normal) elif self._conf.mode == GameMode.TRAIN_DQN_GUI: window.show(self._game_main_dqn_train) self._plot_history() def _run_benchmarks(self): STEPS_LIMIT = 5000 NUM_EPISODES = int(input("Please input the number of episodes: ")) print("\nMap size: %dx%d" % (self._conf.map_rows, self._conf.map_cols)) print("Solver: %s\n" % self._conf.solver_name[:-6].lower()) tot_len, tot_steps = 0, 0 for _ in range(NUM_EPISODES): print("Episode %d - " % self._episode, end="") while True: self._game_main_normal() if self._map.is_full(): print("FULL (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) break elif self._snake.dead: print("DEAD (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) break elif self._snake.steps >= STEPS_LIMIT: print("STEP LIMIT (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) self._write_logs() # Write the last step break tot_len += self._snake.len() tot_steps += self._snake.steps self._reset() avg_len = tot_len / NUM_EPISODES avg_steps = tot_steps / NUM_EPISODES print("\n[Summary]\nAverage Length: %.2f\nAverage Steps: %.2f\n" % (avg_len, avg_steps)) self._on_exit() def _run_dqn_train(self): try: while not self._game_main_dqn_train(): pass except KeyboardInterrupt: pass except Exception: traceback.print_exc() finally: self._on_exit() def _game_main_dqn_train(self): if not self._map.has_food(): self._map.create_rand_food() if self._pause: return episode_end, learn_end = self._solver.train() if episode_end: self._reset() return learn_end def _game_main_normal(self): if not self._map.has_food(): self._map.create_rand_food() if self._pause or self._is_episode_end(): return self._update_direc(self._solver.next_direc()) if self._conf.mode == GameMode.NORMAL and self._snake.direc_next != Direc.NONE: self._write_logs() self._snake.move() if self._is_episode_end(): self._write_logs() # Write the last step def _plot_history(self): self._solver.plot() def _update_direc(self, new_direc): self._snake.direc_next = new_direc if self._pause: self._snake.move() def _toggle_pause(self): self._pause = not self._pause def _is_episode_end(self): return self._snake.dead or self._map.is_full() def _reset(self): self._snake.reset() self._episode += 1 def _on_exit(self): if self._log_file: self._log_file.close() if self._solver: self._solver.close() def _init_log_file(self): try: os.makedirs("logs") except OSError as e: if e.errno != errno.EEXIST: raise try: self._log_file = None self._log_file = open('logs/snake.log', 'w') except FileNotFoundError: if self._log_file: self._log_file.close() def _write_logs(self): self._log_file.write("[ Episode %d / Step %d ]\n" % \ (self._episode, self._snake.steps)) for i in range(self._map.num_rows): for j in range(self._map.num_cols): pos = Pos(i, j) t = self._map.point(pos).type if t == PointType.EMPTY: self._log_file.write(" ") elif t == PointType.WALL: self._log_file.write("# ") elif t == PointType.FOOD: self._log_file.write("F ") elif t == PointType.HEAD_L or t == PointType.HEAD_U or \ t == PointType.HEAD_R or t == PointType.HEAD_D: self._log_file.write("H ") elif pos == self._snake.tail(): self._log_file.write("T ") else: self._log_file.write("B ") self._log_file.write("\n") self._log_file.write("[ last/next direc: %s/%s ]\n" % \ (self._snake.direc, self._snake.direc_next)) self._log_file.write("\n")
def test_food(): m = Map(5, 5) assert not m.has_food() m.create_food(Pos(1, 1)) assert m.has_food() m.rm_food() assert not m.has_food() fd = m.create_rand_food() assert m.has_food() assert m.point(fd).type == PointType.FOOD m.rm_food() for i in range(1, m.num_rows - 1): for j in range(1, m.num_cols - 1): assert m.point(Pos(i, j)).type == PointType.EMPTY
class Game: def __init__(self, conf): self.__conf = conf self.__map = Map(conf.map_rows + 2, conf.map_cols + 2) self.__snake = Snake(self.__map, conf.init_direc, conf.init_bodies, conf.init_types) self.__pause = False self.__window = GameWindow( conf, self.__map, "Snake", self.__snake, self.__on_exit, (('<w>', lambda e: self.__update_direc(Direc.UP)), ('<a>', lambda e: self.__update_direc(Direc.LEFT)), ('<s>', lambda e: self.__update_direc(Direc.DOWN)), ('<d>', lambda e: self.__update_direc(Direc.RIGHT)), ('<r>', lambda e: self.__reset()), ('<space>', lambda e: self.__toggle_pause()))) self.__solver = globals()[self.__conf.solver_name](self.__snake) self.__episode = 1 self.__init_log_file() def run(self): if self.__conf.show_gui: self.__window.show(self.__game_main) else: self.__run_batch_episodes() def __run_batch_episodes(self): STEPS_LIMIT = 500 episodes = int(input("input the number of episodes bro: ")) print("\nMap size: %dx%d" % (self.__conf.map_rows, self.__conf.map_cols)) print("Solver: %s\n" % self.__conf.solver_name[:-6].lower()) tot_suc, tot_suc_steps = 0, 0 for _ in range(episodes): print("Episode %d - " % self.__episode, end="") while True: self.__game_main() if self.__map.is_full(): tot_suc += 1 tot_suc_steps += self.__snake.steps print("SUC (steps: %d)" % self.__snake.steps) break if self.__snake.dead or self.__snake.steps >= STEPS_LIMIT: print("FAIL (steps: %d)" % self.__snake.steps) if self.__snake.steps >= STEPS_LIMIT: self.__write_logs() # Write the last step break self.__reset() suc_ratio = tot_suc / (self.__episode - 1) avg_suc_steps = 0 if tot_suc != 0: avg_suc_steps = tot_suc_steps // tot_suc print("\n[Summary]\nTotal: %d SUC: %d (%.2f%%) Avg SUC steps: %d\n" % \ (self.__episode - 1, tot_suc, 100 * suc_ratio, avg_suc_steps)) self.__on_exit() def __game_main(self): """Main function in the game loop.""" if not self.__map.has_food(): self.__map.create_rand_food() if self.__pause or self.__episode_end(): return if self.__conf.enable_AI: self.__update_direc(self.__solver.next_direc()) if self.__conf.show_gui and self.__snake.direc_next != Direc.NONE: self.__write_logs() self.__snake.move() if self.__episode_end(): self.__write_logs() # Write the last step def __update_direc(self, new_direc): if Direc.opposite(new_direc) != self.__snake.direc: self.__snake.direc_next = new_direc if self.__pause: self.__snake.move() def __toggle_pause(self): self.__pause = not self.__pause def __episode_end(self): return self.__snake.dead or self.__map.is_full() def __reset(self): self.__snake.reset() self.__episode += 1 def __on_exit(self): if self.__log_file: self.__log_file.close() def __init_log_file(self): try: os.makedirs("logs") except OSError as e: if e.errno != errno.EEXIST: raise try: self.__log_file = None self.__log_file = open('logs/snake.log', 'w') except FileNotFoundError: if self.__log_file: self.__log_file.close() def __write_logs(self): self.__log_file.write("[ Episode %d / Step %d ]\n" % \ (self.__episode, self.__snake.steps)) for i in range(self.__map.num_rows): for j in range(self.__map.num_cols): pos = Pos(i, j) t = self.__map.point(pos).type if t == PointType.EMPTY: self.__log_file.write(" ") elif t == PointType.WALL: self.__log_file.write("# ") elif t == PointType.FOOD: self.__log_file.write("F ") elif t == PointType.HEAD_L or t == PointType.HEAD_U or \ t == PointType.HEAD_R or t == PointType.HEAD_D: self.__log_file.write("H ") elif pos == self.__snake.tail(): self.__log_file.write("T ") else: self.__log_file.write("B ") self.__log_file.write("\n") self.__log_file.write("[ last/next direc: %s/%s ]\n" % \ (self.__snake.direc, self.__snake.direc_next)) self.__log_file.write("\n")
def test_move_eat(): m = Map(5, 5) s = Snake(m, Direc.RIGHT, [Pos(1, 2), Pos(1, 1)], [PointType.HEAD_R, PointType.BODY_HOR]) assert s.len() == 2 m.createFood(Pos(1, 3)) assert m.hasFood() s.move(Direc.RIGHT) assert not m.hasFood() assert s.head() == Pos(1, 3) and s.tail() == Pos(1, 1) assert m.point(s.tail()).type == PointType.BODY_HOR assert m.point(Pos(1, 2)).type == PointType.BODY_HOR assert m.point(s.head()).type == PointType.HEAD_R s.move(Direc.DOWN) assert s.head() == Pos(2, 3) and s.tail() == Pos(1, 2) assert m.point(s.tail()).type == PointType.BODY_HOR assert m.point(Pos(1, 3)).type == PointType.BODY_DL assert m.point(s.head()).type == PointType.HEAD_D s.move(Direc.LEFT) assert s.head() == Pos(2, 2) and s.tail() == Pos(1, 3) assert m.point(s.tail()).type == PointType.BODY_DL assert m.point(Pos(2, 3)).type == PointType.BODY_LU assert m.point(s.head()).type == PointType.HEAD_L s.move(Direc.LEFT) assert s.head() == Pos(2, 1) and s.tail() == Pos(2, 3) assert m.point(s.tail()).type == PointType.BODY_LU assert m.point(Pos(2, 2)).type == PointType.BODY_HOR assert m.point(s.head()).type == PointType.HEAD_L s.move(Direc.DOWN) assert s.head() == Pos(3, 1) and s.tail() == Pos(2, 2) assert m.point(s.tail()).type == PointType.BODY_HOR assert m.point(Pos(2, 1)).type == PointType.BODY_RD assert m.point(s.head()).type == PointType.HEAD_D s.move(Direc.RIGHT) assert s.head() == Pos(3, 2) and s.tail() == Pos(2, 1) assert m.point(s.tail()).type == PointType.BODY_RD assert m.point(Pos(3, 1)).type == PointType.BODY_UR assert m.point(s.head()).type == PointType.HEAD_R s.move(Direc.RIGHT) assert s.head() == Pos(3, 3) and s.tail() == Pos(3, 1) assert m.point(s.tail()).type == PointType.BODY_UR assert m.point(Pos(3, 2)).type == PointType.BODY_HOR assert m.point(s.head()).type == PointType.HEAD_R s.move(Direc.UP) assert s.head() == Pos(2, 3) and s.tail() == Pos(3, 2) assert m.point(s.tail()).type == PointType.BODY_HOR assert m.point(Pos(3, 3)).type == PointType.BODY_LU assert m.point(s.head()).type == PointType.HEAD_U s.move(Direc.LEFT) assert s.head() == Pos(2, 2) and s.tail() == Pos(3, 3) assert m.point(s.tail()).type == PointType.BODY_LU assert m.point(Pos(2, 3)).type == PointType.BODY_DL assert m.point(s.head()).type == PointType.HEAD_L s.move(Direc.LEFT) assert s.head() == Pos(2, 1) and s.tail() == Pos(2, 3) assert m.point(s.tail()).type == PointType.BODY_DL assert m.point(Pos(2, 2)).type == PointType.BODY_HOR assert m.point(s.head()).type == PointType.HEAD_L s.move(Direc.UP) assert s.head() == Pos(1, 1) and s.tail() == Pos(2, 2) assert m.point(s.tail()).type == PointType.BODY_HOR assert m.point(Pos(2, 1)).type == PointType.BODY_UR assert m.point(s.head()).type == PointType.HEAD_U s.move(Direc.RIGHT) assert s.head() == Pos(1, 2) and s.tail() == Pos(2, 1) assert m.point(s.tail()).type == PointType.BODY_UR assert m.point(Pos(1, 1)).type == PointType.BODY_RD assert m.point(s.head()).type == PointType.HEAD_R s.move(Direc.RIGHT) assert s.head() == Pos(1, 3) and s.tail() == Pos(1, 1) assert m.point(s.tail()).type == PointType.BODY_RD assert m.point(Pos(1, 2)).type == PointType.BODY_HOR assert m.point(s.head()).type == PointType.HEAD_R s.move(Direc.DOWN) s.move(Direc.DOWN) assert s.head() == Pos(3, 3) and s.tail() == Pos(1, 3) assert m.point(s.tail()).type == PointType.BODY_DL assert m.point(Pos(2, 3)).type == PointType.BODY_VER assert m.point(s.head()).type == PointType.HEAD_D s.move(Direc.LEFT) s.move(Direc.LEFT) s.move(Direc.UP) s.move(Direc.UP) assert s.head() == Pos(1, 1) and s.tail() == Pos(3, 1) assert m.point(s.tail()).type == PointType.BODY_UR assert m.point(Pos(2, 1)).type == PointType.BODY_VER assert m.point(s.head()).type == PointType.HEAD_U assert s.len() == 3 # Eat full assert not m.isFull() food_pos = [ Pos(1, 2), Pos(2, 2), Pos(3, 2), Pos(3, 3), Pos(2, 3), Pos(1, 3) ] move_direc = [ Direc.RIGHT, Direc.DOWN, Direc.DOWN, Direc.RIGHT, Direc.UP, Direc.UP ] for i, pos in enumerate(food_pos): m.createFood(pos) s.move(move_direc[i]) assert m.isFull() assert s.len() == 9 and s.steps == 25 and not s.dead
class Game: def __init__(self, conf): self._conf = conf self._map = Map(conf.map_rows + 2, conf.map_cols + 2) self._snake = Snake(self._map, conf.init_direc, conf.init_bodies, conf.init_types) self._pause = False self._solver = globals()[self._conf.solver_name](self._snake) self._episode = 1 self._init_log_file() @property def snake(self): return self._snake @property def episode(self): return self._episode def run(self): if self._conf.mode == GameMode.BENCHMARK: self._run_benchmarks() elif self._conf.mode == GameMode.TRAIN_DQN: self._run_dqn_train() self._plot_history() else: window = GameWindow("Snake", self._conf, self._map, self, self._on_exit, ( ('<w>', lambda e: self._update_direc(Direc.UP)), ('<a>', lambda e: self._update_direc(Direc.LEFT)), ('<s>', lambda e: self._update_direc(Direc.DOWN)), ('<d>', lambda e: self._update_direc(Direc.RIGHT)), ('<r>', lambda e: self._reset()), ('<space>', lambda e: self._toggle_pause()) )) if self._conf.mode == GameMode.NORMAL: window.show(self._game_main_normal) elif self._conf.mode == GameMode.TRAIN_DQN_GUI: window.show(self._game_main_dqn_train) self._plot_history() def _run_benchmarks(self): STEPS_LIMIT = 5000 NUM_EPISODES = int(input("Please input the number of episodes: ")) print("\nMap size: %dx%d" % (self._conf.map_rows, self._conf.map_cols)) print("Solver: %s\n" % self._conf.solver_name[:-6].lower()) tot_len, tot_steps = 0, 0 for _ in range(NUM_EPISODES): print("Episode %d - " % self._episode, end="") while True: self._game_main_normal() if self._map.is_full(): print("FULL (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) break elif self._snake.dead: print("DEAD (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) break elif self._snake.steps >= STEPS_LIMIT: print("STEP LIMIT (len: %d | steps: %d)" % (self._snake.len(), self._snake.steps)) self._write_logs() # Write the last step break tot_len += self._snake.len() tot_steps += self._snake.steps self._reset() avg_len = tot_len / NUM_EPISODES avg_steps = tot_steps / NUM_EPISODES print("\n[Summary]\nAverage Length: %.2f\nAverage Steps: %.2f\n" % (avg_len, avg_steps)) self._on_exit() def _run_dqn_train(self): try: while not self._game_main_dqn_train(): pass except KeyboardInterrupt: pass except Exception: traceback.print_exc() finally: self._on_exit() def _game_main_dqn_train(self): if not self._map.has_food(): self._map.create_rand_food() if self._pause: return episode_end, learn_end = self._solver.train() if episode_end: self._reset() return learn_end def _game_main_normal(self): if not self._map.has_food(): self._map.create_rand_food() if self._pause or self._is_episode_end(): return self._update_direc(self._solver.next_direc()) if self._conf.mode == GameMode.NORMAL and self._snake.direc_next != Direc.NONE: self._write_logs() self._snake.move() if self._is_episode_end(): self._write_logs() # Write the last step def _plot_history(self): self._solver.plot() def _update_direc(self, new_direc): self._snake.direc_next = new_direc if self._pause: self._snake.move() def _toggle_pause(self): self._pause = not self._pause def _is_episode_end(self): return self._snake.dead or self._map.is_full() def _reset(self): self._snake.reset() self._episode += 1 def _on_exit(self): if self._log_file: self._log_file.close() if self._solver: self._solver.close() def _init_log_file(self): try: os.makedirs("logs") except OSError as e: if e.errno != errno.EEXIST: raise try: self._log_file = None self._log_file = open('logs/snake.log', 'w') except FileNotFoundError: if self._log_file: self._log_file.close() def _write_logs(self): self._log_file.write("[ Episode %d / Step %d ]\n" % \ (self._episode, self._snake.steps)) for i in range(self._map.num_rows): for j in range(self._map.num_cols): pos = Pos(i, j) t = self._map.point(pos).type if t == PointType.EMPTY: self._log_file.write(" ") elif t == PointType.WALL: self._log_file.write("# ") elif t == PointType.FOOD: self._log_file.write("F ") elif t == PointType.HEAD_L or t == PointType.HEAD_U or \ t == PointType.HEAD_R or t == PointType.HEAD_D: self._log_file.write("H ") elif pos == self._snake.tail(): self._log_file.write("T ") else: self._log_file.write("B ") self._log_file.write("\n") self._log_file.write("[ last/next direc: %s/%s ]\n" % \ (self._snake.direc, self._snake.direc_next)) self._log_file.write("\n")