diff --git a/algorithms/learn/genetic_algorithm/helpers.py b/algorithms/learn/genetic_algorithm/helpers.py index 63a6f3a..dcba1fd 100644 --- a/algorithms/learn/genetic_algorithm/helpers.py +++ b/algorithms/learn/genetic_algorithm/helpers.py @@ -72,24 +72,11 @@ def get_score(minefield, speciment, table=None): if table is not None: for el_index in range(len(speciment) - 1): - if table[(initial_state.row, initial_state.column)] \ - [(speciment[el_index][0], speciment[el_index][1])] \ - [initial_state.direction]: + end_state, cost = table[(initial_state.row, initial_state.column)] \ + [(speciment[el_index][0], speciment[el_index][1])] \ + [initial_state.direction] - end_state, cost = table[(initial_state.row, initial_state.column)] \ - [(speciment[el_index][0], speciment[el_index][1])] \ - [initial_state.direction] - - initial_state = State(speciment[el_index][0], speciment[el_index][1], end_state.direction) - - else: - action_sequence, _, cost = \ - graphsearch(initial_state, - minefield, - target_type="mine", - tox=speciment[el_index][0], - toy=speciment[el_index][1], - with_data=True) + initial_state = State(speciment[el_index][0], speciment[el_index][1], end_state.direction) mine = minefield.matrix[speciment[el_index][0]][speciment[el_index][1]].mine diff --git a/algorithms/learn/genetic_algorithm/new_ga.py b/algorithms/learn/genetic_algorithm/new_ga.py index 1d6f9c4..bef5ee9 100644 --- a/algorithms/learn/genetic_algorithm/new_ga.py +++ b/algorithms/learn/genetic_algorithm/new_ga.py @@ -1,3 +1,4 @@ +import time import numpy as np, random, operator, pandas as pd from algorithms.learn.genetic_algorithm import helpers @@ -40,8 +41,12 @@ def initialPopulation(popSize, cityList): def rankRoutes(population): fitnessResults = {} + + timer_collect_results = Timer("Collect fitness results") for i in range(0, len(population)): fitnessResults[i] = Fitness(population[i]).routeFitness() + timer_collect_results.stop() + return sorted(fitnessResults.items(), key=operator.itemgetter(1), reverse=True) @@ -137,16 +142,24 @@ def genetic_algorithm(minefield, population, popSize, eliteSize, mutationRate, g global gl_minefield, scores_table gl_minefield = minefield + timer_scores_table = Timer("Create scores table") scores_table = helpers.create_scores_table(gl_minefield) + timer_scores_table.stop() + timer_initial_population = Timer("Init and rank population") pop = initialPopulation(popSize, population) scores = rankRoutes(pop) + timer_initial_population.stop() print("Initial score: " + str(1000 / scores[0][1])) for i in range(0, generations): pop = nextGeneration(scores, pop, eliteSize, mutationRate) + + timer_rank_generation = Timer("Rank generation") scores = rankRoutes(pop) + timer_rank_generation.stop() + print(f"Generation {i} best score: {str(1000 / scores[0][1])}") bestRouteIndex = scores[0][0] bestRoute = pop[bestRouteIndex] @@ -158,6 +171,15 @@ def genetic_algorithm(minefield, population, popSize, eliteSize, mutationRate, g return bestRoute +class Timer: + def __init__(self, name): + self.name = name + self.start_time = time.time() + + def stop(self): + print(f"{self.name} took {time.time() - self.start_time} seconds.") + + if __name__ == "__main__": gl_minefield = Minefield(os.path.join("..", "..", "..", "resources", "minefields", "fifthmap.json")) diff --git a/game.py b/game.py index 1ddc203..40627de 100644 --- a/game.py +++ b/game.py @@ -38,6 +38,7 @@ class Game: self.action_delta_time = 0 self.genetic_sequence = None + self.genetics_done = False # declaring and initializing gui components # ui_component managers @@ -45,12 +46,15 @@ class Game: self.game_over_gui_components_manager = UiComponentsManager() # in game gui + self.textbox_points = TextBox((0, 0), (0, 0)) + self.input_box_row = InputBox((0, 0), (0, 0)) self.input_box_column = InputBox((0, 0), (0, 0)) self.button_auto = Button((0, 0), (0, 0)) - self.button_genetic_algorithm = Button((0, 0), (0, 0)) self.button_random = Button((0, 0), (0, 0)) self.button_ok = Button((0, 0), (0, 0)) + self.button_genetic_algorithm = Button((0, 0), (0, 0)) + self.input_box_generations = InputBox((0, 0), (0, 0)) # game over screen self.text_box_game_over = TextBox((0, 0), (0, 0)) @@ -109,6 +113,7 @@ class Game: if self.millisecond_timer >= const.TURN_INTERVAL: self.turn += 1 self.minefield.next_turn() + self.textbox_points.text = str(self.minefield.points) # resetting timer self.millisecond_timer %= const.TURN_INTERVAL @@ -138,7 +143,9 @@ class Game: else: return False - def run_genetics(self): + def run_genetics(self, generations=10): + print("Starting genetics algorithm...") + genetics_minefield = Minefield(const.MAP_RANDOM_10x10) sequence = \ @@ -147,9 +154,10 @@ class Game: popSize=100, eliteSize=20, mutationRate=0.01, - generations=15) + generations=generations) self.genetic_sequence = sequence + self.genetics_done = True def set_next_genetic_target(self): if any(self.genetic_sequence): @@ -187,6 +195,10 @@ class Game: row, column = position return self.minefield.matrix[row][column].mine + def explosion(self, position): + # show explosion on position + self.minefield.points += const.EXPLOSION_PENALTY + # initializes attributes before game loop begins def initialize_before_game_loop(self): self.agent_action = None @@ -258,8 +270,15 @@ class Game: gui_y = const.SCREEN.get_height() / 2 - (2 * ib_height + 3 * bt_height + 50) / 2 # creating in game gui components + self.textbox_points = TextBox( + position=(gui_x, gui_y - 134), + dimensions=(gui_width, ib_height - 10), + text="0", + box_color=(172, 220, 172) + ) + self.input_box_row = InputBox( - position=(gui_x, gui_y), + position=(gui_x, gui_y - 50), dimensions=(gui_width, ib_height), text="row", box_color=(100, 200, 100), @@ -273,7 +292,7 @@ class Game: ) self.input_box_column = InputBox( - position=(gui_x, gui_y + ib_height + 10), + position=(gui_x, gui_y + ib_height - 40), dimensions=(gui_width, ib_height), text="column", box_color=(100, 200, 100), @@ -287,7 +306,7 @@ class Game: ) self.button_auto = Button( - position=(gui_x, gui_y + 2 * ib_height + 20), + position=(gui_x, gui_y + 2 * ib_height - 30), dimensions=(gui_width, bt_height), text="auto", box_color=(100, 200, 100), @@ -295,17 +314,8 @@ class Game: outline_additional_pixel=True ) - self.button_genetic_algorithm = Button( - position=(gui_x, gui_y + 2 * ib_height + bt_height + 30), - dimensions=(gui_width, bt_height), - text="genetic", - box_color=(100, 200, 100), - outline_color=(80, 180, 80), - outline_additional_pixel=True - ) - self.button_random = Button( - position=(gui_x, gui_y + 2 * ib_height + 2 * bt_height + 40), + position=(gui_x, gui_y + 2 * ib_height + 1 * bt_height - 20), dimensions=(gui_width, bt_height), text="random", box_color=(100, 200, 100), @@ -314,7 +324,7 @@ class Game: ) self.button_ok = Button( - position=(gui_x, gui_y + 2 * ib_height + 3 * bt_height + 50), + position=(gui_x, gui_y + 2 * ib_height + 2 * bt_height - 10), dimensions=(gui_width, bt_height), text="ok", box_color=(100, 200, 100), @@ -322,11 +332,37 @@ class Game: outline_additional_pixel=True ) + self.button_genetic_algorithm = Button( + position=(gui_x, gui_y + 2 * ib_height + 4 * bt_height + 10), + dimensions=(gui_width, bt_height), + text="genetic", + box_color=(100, 200, 100), + outline_color=(80, 180, 80), + outline_additional_pixel=True + ) + + self.input_box_generations = InputBox( + position=(gui_x, gui_y + 2 * ib_height + 5 * bt_height + 20), + dimensions=(gui_width, ib_height), + text="iters", + user_input="10", + box_color=(100, 200, 100), + bottom_strip_color=(120, 220, 120), + inner_box_color=(120, 220, 120), + outline_color=(80, 180, 80), + outline_additional_pixel=True, + valid_input_characters="1234567890", + input_centered=True, + clear_input_on_click=True + ) + gui_list = [ + self.textbox_points, self.input_box_row, self.input_box_column, self.button_auto, self.button_genetic_algorithm, + self.input_box_generations, self.button_random, self.button_ok ] @@ -372,3 +408,6 @@ class Game: # heading either left or right else: return self.agent.row, max(0, min(9, self.agent.column - self.agent.direction.value + 2)) + + def get_input_generations(self): + return self.input_box_generations.get_input() diff --git a/main.py b/main.py index 32c9554..9477bf1 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,7 @@ # libraries +import threading import pygame + from pyglet.gl import * # for blocky textures # other files of this project from game import Game @@ -27,6 +29,7 @@ def main(): auto = False genetics = False genetics_ready = False + genetics_in_progress = False is_game_over = False # create and initialize_gui_components game instance @@ -66,7 +69,19 @@ def main(): # if genetics button is clocked then start genetic algorithm genetics = game.button_genetic_algorithm.is_clicked(pygame.mouse.get_pos(), events) - if genetics: + if genetics and not (genetics_in_progress or genetics_ready): + generations = game.get_input_generations() + + genetics_in_progress = True + game.genetics_done = False + threading.Thread(target=game.run_genetics, args=(int(generations), )).start() + + if genetics_in_progress: + genetics = True + genetics_ready = game.genetics_done + + if genetics and genetics_ready: + genetics_in_progress = False auto = True # ========================== # @@ -76,10 +91,6 @@ def main(): # initializing action_sequence variable action_sequence = None - if genetics and not genetics_ready: - game.run_genetics() - genetics_ready = True - # getting action sequence for agent if auto and running: in_menu = False @@ -157,6 +168,7 @@ def main(): if auto: if not game.agent.defuse_a_mine(game.get_mine(game.goal)): print("BOOOOOOM\n\n") + game.explosion(game.get_mine(game.goal)) # is_game_over = True else: diff --git a/minefield.py b/minefield.py index 0b89a72..2a7bde3 100644 --- a/minefield.py +++ b/minefield.py @@ -8,6 +8,7 @@ class Minefield: def __init__(self, json_path): self.turn = 0 self.explosions = 0 + self.points = 0 self.agent = ag.Agent(json_path) self.json_path = json_path @@ -38,6 +39,7 @@ class Minefield: def next_turn(self): self.turn += 1 + self.points += 1 for row in range(const.V_GRID_VER_TILES): for column in range(const.V_GRID_VER_TILES): @@ -49,6 +51,7 @@ class Minefield: if mine.timer == 0 and mine.active: # TODO: BOOM self.explosions += 1 + self.points += const.EXPLOSION_PENALTY mine.active = False def get_active_mines(self): diff --git a/project_constants.py b/project_constants.py index 43c7cb8..91666b4 100644 --- a/project_constants.py +++ b/project_constants.py @@ -21,6 +21,8 @@ from ui.input_box import InputBox V_NAME_OF_WINDOW = "MineFusion TM" V_FPS = 60 +EXPLOSION_PENALTY = 200 + ROOT_DIR = os.path.dirname(os.path.abspath(__file__)) DIR_ASSETS = os.path.join(ROOT_DIR, "resources", "assets")