def run(self): if self._conf.mode == GameMode.BENCHMARK: self._run_benchmarks() 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)
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 __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.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 __init__(self, conf): self.__conf = conf self.__map = Map( conf.map_rows + 2, conf.map_cols + 2) # The extra two rows and columns are for the walls. 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) # By importing both the HamiltonSolver and the GreedySolver into the module, # we can freely access them from this module using the globals function. self.__episode = 1 # This is for non-gui logging. self.__init_log_file()
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 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()
class Game: """ Game. Where all the functions are processed. You can run it without or with a gui. """ def __init__(self, conf): self.__conf = conf self.__map = Map( conf.map_rows + 2, conf.map_cols + 2) # The extra two rows and columns are for the walls. 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) # By importing both the HamiltonSolver and the GreedySolver into the module, # we can freely access them from this module using the globals function. self.__episode = 1 # This is for non-gui logging. self.__init_log_file() # Open log files. def run(self): """ Main method. This is the only method called from outside the object. If gui is enabled, then the gui is called upon to start the loop with self.game_main. Otherwise, no gui is shown, and the program runs tests on the snakes. :return: None. """ if self.__conf.show_gui: self.__window.show(self.__game_main) # Self.__game_main is passed as an argument to this argument to loop it using tkinter after and recursion. else: self.__run_batch_episodes() def __run_batch_episodes(self): """ Main method for running batches of episodes at once. First the method gets the number of episodes to run. Then for each episodes, it simulates a full game, and logs the result. At the end, it prints out the total number of episodes, the total number of successful episodes, the average success rate, and the average number of steps taken to achieve victory. :return: None. """ steps_limit = self.__map.capacity * 100 episodes = self.__get_positive_integer( 'Please enter the amount of episodes: ') print('\nMap size: {}x{}'.format(self.__conf.map_rows, self.__conf.map_cols)) print('Solver: {}\n'.format(self.__conf.solver_name[:-6].lower())) tot_suc, tot_suc_steps = 0, 0 # Initialize the total number of successes, # and the sum of the number of steps taken for each success. for _ in range( episodes ): # Underscore is used to show that the episode number is not important. print('Episode {} - '.format(self.__episode), end='') while True: # Constantly run the game until the snake is either dead, # the map is full, or the snake has entered an infinite loop, # at which point the episode will report a fail. self.__game_main() if self.__map.is_full(): tot_suc += 1 tot_suc_steps += self.__snake.steps print('SUCCESS! (steps: {})'.format(self.__snake.steps)) break if self.__snake.dead or self.__snake.steps > steps_limit: print('FAIL! (steps:{})'.format(self.__snake.steps)) if self.__snake.steps > steps_limit: # We call the __write_logs() method because __game_main only writes logs for batch episodes # if the game has ended. In our case, it hasn't technically ended, # but has instead timed out. Therefore, we must manually call the __write_logs() method. self.__write_logs() break self.__reset() # Resets the map and the snake. # Remember, the reset method for the snake draws upon the init_direc,init_bodies, and init_types again, # so we can simulate another run. suc_ratio = tot_suc / (self.__episode - 1 ) # Calculate the number of successes. # We subtract one from the episodes because each reset increments self.__episodes. # However, the last reset didn't actually start a new episode, so we decrement it. avg_suc_steps = 0 if tot_suc: # We make sure that there were successes before calculating the average number of steps needed to succeed. avg_suc_steps = tot_suc_steps / tot_suc print('\n[Summary]\n' 'Total: {}\n' 'Successful: {}\n' 'Success Ratio: {:.2f}%\n' 'Average Success Steps:{:.2f}'.format(self.__episode - 1, tot_suc, 100 * suc_ratio, avg_suc_steps)) self.__on_exit( ) # Closes the log file. Now the program is done and will exit. @staticmethod def __get_positive_integer(question): """ This method gets a positive integer from the user. This is done by attempting to convert the input to an integer using a try-except block. Upon successfully converting the input into an integer, it is then double-checked to see if it is positive. :param question: The question to ask when getting the input. :return: A positive integer. """ while True: try: ui = int(input(question)) # Step 1. Get an input from the user and attempt to convert that into an integer. # If conversion is successful, move to Step 2. Otherwise, move to Step 3. if ui <= 0: # Step 2: Check if the integer is positive. # If it is not positive, then ask for the input again. # Else, return the integer. print('Sorry. Please input a positive integer.') continue return ui except ValueError: # Step 3: Catch the ValueError, and inform the user to enter a valid number. print('Please input a valid number.') def __game_main(self): """ The main game loop. This is called by self.__batch_episodes when gui is disabled, and by the game window when gui is enabled. 1. If the snake just ate food, then a new piece of food is created. 2. If the game has ended (which means that the snake has either run into itself or has won), then don't do anything. 3. If AI is enabled, then update the next snake direction, according to what the solver says. 4. If the GUI has been enabled and the snake is going to move somewhere, or if picture logging has been enabled, then write logs. 5. Move the snake according to next_direc. Of course, if next_direc is NONE or Direc.None, the method returns, and so the snake does not move. 6. Finally, if the snake just won, which means that step 2 has not been triggered yet, then log the results. :return: None. """ 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 ) or self.__conf.picture_logging: self.__write_logs() self.__snake.move() if self.__episode_end(): self.__write_logs() def __update_direc(self, new_direc): """ This method is triggered by the game main, but more importantly, by key-bindings. It checks if the snake is not moving in the opposite direction. If it isn't, then it sets the new direction to whatever the parameter is. Because next_direc is never deleted by the Snake object, it continuously moves in the specified direction until it dies or the direction is changed. :param new_direc: A direction for the snake to go to, of type Direc. :return: None. """ if Direc.opposite(new_direc) != self.__snake.direc: self.__snake.direc_next = new_direc if self.__pause: # If it's paused, then we still forcibly move the snake once. # This can be used to make very precise moves. self.__snake.move() def __toggle_pause(self): """ Toggles the pause property. This is activated by the key binding <Space>. :return: None """ self.__pause = not self.__pause def __episode_end(self): """ Checks if the episode has ended. This method checks if either the snake is dead (It has lost) or if the snake has won. :return: Boolean Value, which depends on whether or not the game has won. """ return self.__snake.dead or self.__map.is_full() def __reset(self): """ Resets the snake and increments the episode count. :return: None. """ self.__snake.reset() self.__episode += 1 def __on_exit(self): """ Closes the log file if possible. This is called by the gui's on_destroy method and the batch_episode method. :return: None. """ if self.__log_file: self.__log_file.close() def __init_log_file(self): """ Tries to create a logs folder. If it already exists, that's fine, but if some weird error occurs, then raise that error and terminate the program. Then, tries to open the log file to write to it. If no file exists, that's fine, just don't write to it. :return: None. """ 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): """ Writes game states to log. If picture logging is enabled, then an ascii rendering of the game state will be logged at each step. :return: None. """ if self.__conf.picture_logging: self.__log_file.write('[Episode {} Step {}]\n'.format( 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 direction: {}/{} ]\n".format( 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.__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")