diff --git a/algorithms/learn/genetic_algorithm/ga_roulette.py b/algorithms/learn/genetic_algorithm/ga_roulette.py new file mode 100644 index 0000000..8248276 --- /dev/null +++ b/algorithms/learn/genetic_algorithm/ga_roulette.py @@ -0,0 +1,175 @@ +import numpy as np +import random +import operator +import pandas as pd +from algorithms.learn.genetic_algorithm import helpers + +import os +from minefield import Minefield +gl_minefield = None +scores_table = None + + +class Fitness: + def __init__(self, route): + self.route = route + self.score = 0 + self.fitness = 0.0 + + def route_distance(self): + if self.score == 0: + self.score = helpers.get_score(gl_minefield, self.route, scores_table) + + return self.score + + def route_fitness(self): + if self.fitness == 0: + self.fitness = 1000 / float(self.route_distance()) + + return self.fitness + + +def create_route(mine_list): + route = random.sample(mine_list, len(mine_list)) + return route + + +def initial_population(pop_size, mine_list): + population = [] + + for i in range(0, pop_size): + population.append(create_route(mine_list)) + return population + + +def rank_routes(population): + fitness_results = {} + + for i in range(0, len(population)): + fitness_results[i] = Fitness(population[i]).route_fitness() + + return sorted(fitness_results.items(), key=operator.itemgetter(1), reverse=True) + + +def selection(pop_ranked, elite_size): + selection_results = [] + df = pd.DataFrame(np.array(pop_ranked), columns=["Index", "Fitness"]) + df['cum_sum'] = df.Fitness.cumsum() + df['cum_perc'] = 100 * df.cum_sum / df.Fitness.sum() + + for i in range(0, elite_size): + selection_results.append(pop_ranked[i][0]) + for i in range(0, len(pop_ranked) - elite_size): + pick = 100 * random.random() + for j in range(0, len(pop_ranked)): + if pick <= df.iat[j, 3]: + selection_results.append(pop_ranked[j][0]) + break + return selection_results + + +def mating_pool(population, selection_results): + matingpool = [] + for i in range(0, len(selection_results)): + index = selection_results[i] + matingpool.append(population[index]) + return matingpool + + +def breed(parent1, parent2): + child_p1 = [] + + gene_a = int(random.random() * len(parent1)) + gene_b = int(random.random() * len(parent1)) + + start_gene = min(gene_a, gene_b) + end_gene = max(gene_a, gene_b) + + for i in range(start_gene, end_gene): + child_p1.append(parent1[i]) + + child_p2 = [item for item in parent2 if item not in child_p1] + + child = child_p1 + child_p2 + return child + + +def breed_population(matingpool, elite_size): + children = [] + length = len(matingpool) - elite_size + pool = random.sample(matingpool, len(matingpool)) + + for i in range(0, elite_size): + children.append(matingpool[i]) + + for i in range(0, length): + child = breed(pool[i], pool[len(matingpool) - i - 1]) + children.append(child) + return children + + +def mutate(individual, mutation_rate): + for swapped in range(len(individual)): + if random.random() < mutation_rate: + swap_with = int(random.random() * len(individual)) + + city1 = individual[swapped] + city2 = individual[swap_with] + + individual[swapped] = city2 + individual[swap_with] = city1 + return individual + + +def mutate_population(population, mutation_rate): + mutated_pop = [] + + for ind in range(0, len(population)): + mutated_ind = mutate(population[ind], mutation_rate) + mutated_pop.append(mutated_ind) + return mutated_pop + + +def next_generation(scores, current_gen, elite_size, mutation_rate): + selection_results = selection(scores, elite_size) + matingpool = mating_pool(current_gen, selection_results) + children = breed_population(matingpool, elite_size) + next_gen = mutate_population(children, mutation_rate) + return next_gen + + +def genetic_algorithm(minefield, population, pop_size, elite_size, mutation_rate, generations): + global gl_minefield, scores_table + gl_minefield = minefield + + scores_table = helpers.create_scores_table(gl_minefield) + + pop = initial_population(pop_size, population) + scores = rank_routes(pop) + + print("Initial score: " + str(1000 / scores[0][1])) + + for i in range(0, generations): + pop = next_generation(scores, pop, elite_size, mutation_rate) + scores = rank_routes(pop) + + print(f"Generation {i} best score: {str(1000 / scores[0][1])}") + best_route_index = scores[0][0] + best_route = pop[best_route_index] + print(best_route) + + print("Final score: " + str(1000 / scores[0][1])) + best_route_index = scores[0][0] + best_route = pop[best_route_index] + return best_route + + +if __name__ == "__main__": + gl_minefield = Minefield(os.path.join("..", "..", "..", "resources", "minefields", "fourthmap.json")) + + genetic_algorithm(minefield=gl_minefield, + population=helpers.get_mines_coords(gl_minefield), + pop_size=100, + elite_size=20, + mutation_rate=0.01, + generations=100) diff --git a/algorithms/learn/genetic_algorithm/genetic_algorithm.py b/algorithms/learn/genetic_algorithm/ga_tournament.py similarity index 91% rename from algorithms/learn/genetic_algorithm/genetic_algorithm.py rename to algorithms/learn/genetic_algorithm/ga_tournament.py index be13e8a..1703e03 100644 --- a/algorithms/learn/genetic_algorithm/genetic_algorithm.py +++ b/algorithms/learn/genetic_algorithm/ga_tournament.py @@ -7,20 +7,22 @@ from numpy.random import rand from algorithms.learn.genetic_algorithm import helpers + # this is helper function for sum_distance function, it counts the taxi cab distance between 2 vectors [x,y] -def distance(x,y): - temp1 = abs(x[0]-y[0]) - temp2 = abs(x[1]-y[1]) - vector_distance = temp1+temp2 +def distance(x, y): + temp1 = abs(x[0] - y[0]) + temp2 = abs(x[1] - y[1]) + vector_distance = temp1 + temp2 return vector_distance + # this is fitting function which tells how well specimen fits the environment # this function counts the sum of distances between vectors for a specimen # this was just for testing, it should be probably changed to A* def sum_distance(speciment): sum = 0 - for i in range(0,len(speciment)-2): - pom = distance(speciment[i],speciment[i+1]) + for i in range(0, len(speciment) - 2): + pom = distance(speciment[i], speciment[i + 1]) sum = sum + pom return sum @@ -30,7 +32,7 @@ def sum_distance(speciment): def selection(pop, scores, k=10): # first random selection selection_ix = randint(len(pop)) - for ix in randint(0, len(pop), k- 1): + for ix in randint(0, len(pop), k - 1): # check if better (e.g. perform a tournament) if scores[ix] < scores[selection_ix]: selection_ix = ix @@ -40,8 +42,8 @@ def selection(pop, scores, k=10): # crossover two parents to create two children # this function creates speciments for new generation def crossover(p1, p2, r_cross): - p1=list(p1) - p2=list(p2) + p1 = list(p1) + p2 = list(p2) # children are copies of parents by default c1, c2 = p1.copy(), p2.copy() # check for recombination @@ -71,17 +73,16 @@ def mutation(speciment, r_mut): if rand() < r_mut: # flip the bit temp = speciment[i] - pom = random.randint(0,len(speciment)-1) + pom = random.randint(0, len(speciment) - 1) speciment[i] = speciment[pom] speciment[pom] = temp - # genetic algorithm def genetic_algorithm(minefield, objective, n_iter, n_pop, r_cross, r_mut): # this is hardcoded list of coordinates of all mines (for tests only) which represents one speciment in population # it is then permutated to get set number of species and create population - speciment=helpers.get_mines_coords(minefield) + speciment = helpers.get_mines_coords(minefield) pop = [random.sample(speciment, len(speciment)) for _ in range(n_pop)] # permutation function returns tuples so I change them to lists @@ -116,7 +117,6 @@ def genetic_algorithm(minefield, objective, n_iter, n_pop, r_cross, r_mut): if __name__ == "__main__": - # define the total iterations n_iter = 100 # bits @@ -131,11 +131,10 @@ if __name__ == "__main__": # create new minefield instance import os from minefield import Minefield + minefield = Minefield(os.path.join("..", "..", "..", "resources", "minefields", "fourthmap.json")) # perform the genetic algorithm search best, score = genetic_algorithm(minefield, helpers.get_score, n_iter, n_pop, r_cross, r_mut) print('Done!') print('f(%s) = %f' % (best, score)) - - diff --git a/algorithms/learn/genetic_algorithm/helpers.py b/algorithms/learn/genetic_algorithm/helpers.py index 2a833ca..d3b0e64 100644 --- a/algorithms/learn/genetic_algorithm/helpers.py +++ b/algorithms/learn/genetic_algorithm/helpers.py @@ -87,7 +87,7 @@ def get_score(minefield, speciment, table=None): mine.active = False - minefield.next_turn(n_turns=cost) + minefield.next_turn(n_turns=cost, mode="ga") score += cost score += 200 * minefield.explosions diff --git a/algorithms/learn/genetic_algorithm/new_ga.py b/algorithms/learn/genetic_algorithm/new_ga.py deleted file mode 100644 index 6d198bd..0000000 --- a/algorithms/learn/genetic_algorithm/new_ga.py +++ /dev/null @@ -1,174 +0,0 @@ -import time -import numpy as np, random, operator, pandas as pd -from algorithms.learn.genetic_algorithm import helpers - -import os -from minefield import Minefield -gl_minefield = None -scores_table = None - - -class Fitness: - def __init__(self, route): - self.route = route - self.distance = 0 - self.fitness = 0.0 - - def routeDistance(self): - if self.distance == 0: - self.distance = helpers.get_score(gl_minefield, self.route, scores_table) - - return self.distance - - def routeFitness(self): - if self.fitness == 0: - self.fitness = 1000 / float(self.routeDistance()) - - return self.fitness - - -def createRoute(cityList): - route = random.sample(cityList, len(cityList)) - return route - - -def initialPopulation(popSize, cityList): - population = [] - - for i in range(0, popSize): - population.append(createRoute(cityList)) - return population - - -def rankRoutes(population): - fitnessResults = {} - - for i in range(0, len(population)): - fitnessResults[i] = Fitness(population[i]).routeFitness() - - return sorted(fitnessResults.items(), key=operator.itemgetter(1), reverse=True) - - -def selection(popRanked, eliteSize): - selectionResults = [] - df = pd.DataFrame(np.array(popRanked), columns=["Index", "Fitness"]) - df['cum_sum'] = df.Fitness.cumsum() - df['cum_perc'] = 100 * df.cum_sum / df.Fitness.sum() - - for i in range(0, eliteSize): - selectionResults.append(popRanked[i][0]) - for i in range(0, len(popRanked) - eliteSize): - pick = 100 * random.random() - for i in range(0, len(popRanked)): - if pick <= df.iat[i, 3]: - selectionResults.append(popRanked[i][0]) - break - return selectionResults - -def matingPool(population, selectionResults): - matingpool = [] - for i in range(0, len(selectionResults)): - index = selectionResults[i] - matingpool.append(population[index]) - return matingpool - - -def breed(parent1, parent2): - child = [] - childP1 = [] - childP2 = [] - - geneA = int(random.random() * len(parent1)) - geneB = int(random.random() * len(parent1)) - - startGene = min(geneA, geneB) - endGene = max(geneA, geneB) - - for i in range(startGene, endGene): - childP1.append(parent1[i]) - - childP2 = [item for item in parent2 if item not in childP1] - - child = childP1 + childP2 - return child - - -def breedPopulation(matingpool, eliteSize): - children = [] - length = len(matingpool) - eliteSize - pool = random.sample(matingpool, len(matingpool)) - - for i in range(0, eliteSize): - children.append(matingpool[i]) - - for i in range(0, length): - child = breed(pool[i], pool[len(matingpool) - i - 1]) - children.append(child) - return children - - -def mutate(individual, mutationRate): - for swapped in range(len(individual)): - if (random.random() < mutationRate): - swapWith = int(random.random() * len(individual)) - - city1 = individual[swapped] - city2 = individual[swapWith] - - individual[swapped] = city2 - individual[swapWith] = city1 - return individual - - -def mutatePopulation(population, mutationRate): - mutatedPop = [] - - for ind in range(0, len(population)): - mutatedInd = mutate(population[ind], mutationRate) - mutatedPop.append(mutatedInd) - return mutatedPop - - -def nextGeneration(scores, currentGen, eliteSize, mutationRate): - selectionResults = selection(scores, eliteSize) - matingpool = matingPool(currentGen, selectionResults) - children = breedPopulation(matingpool, eliteSize) - nextGeneration = mutatePopulation(children, mutationRate) - return nextGeneration - - -def genetic_algorithm(minefield, population, popSize, eliteSize, mutationRate, generations): - global gl_minefield, scores_table - gl_minefield = minefield - - scores_table = helpers.create_scores_table(gl_minefield) - - pop = initialPopulation(popSize, population) - scores = rankRoutes(pop) - - print("Initial score: " + str(1000 / scores[0][1])) - - for i in range(0, generations): - pop = nextGeneration(scores, pop, eliteSize, mutationRate) - scores = rankRoutes(pop) - - print(f"Generation {i} best score: {str(1000 / scores[0][1])}") - bestRouteIndex = scores[0][0] - bestRoute = pop[bestRouteIndex] - print(bestRoute) - - print("Final score: " + str(1000 / scores[0][1])) - bestRouteIndex = scores[0][0] - bestRoute = pop[bestRouteIndex] - return bestRoute - - -if __name__ == "__main__": - gl_minefield = Minefield(os.path.join("..", "..", "..", "resources", "minefields", "fourthmap.json")) - - genetic_algorithm(minefield=gl_minefield, - population=helpers.get_mines_coords(gl_minefield), - popSize=100, - eliteSize=20, - mutationRate=0.01, - generations=100) diff --git a/assets/display_assets.py b/assets/display_assets.py index a9df13d..53f6885 100644 --- a/assets/display_assets.py +++ b/assets/display_assets.py @@ -74,6 +74,9 @@ def blit_graphics(minefield): seconds = tile.mine.timer % 60 display_time_mine(tile.position, minutes, "0" + str(seconds)) + if tile.mine.blacked: + const.SCREEN.blit(const.MINE_INACTIVE, tile_screen_coords) + # changed sapper's movement from jumping to continuous movement (moved everything to Agent's class) # # sapper # display_sapper( diff --git a/assets/explosion.py b/assets/explosion.py index ffa03d9..459dcf8 100644 --- a/assets/explosion.py +++ b/assets/explosion.py @@ -3,12 +3,15 @@ import pygame import os from project_constants import V_TILE_SIZE, DIR_ASSETS, SCREEN +from objects.mine_models.mine import Mine import assets.display_assets class Explosion(pygame.sprite.Sprite): - def __init__(self, coords: (int, int)): + def __init__(self, coords: (int, int) = None, mine: Mine = None): + if coords is None: + coords = mine.position pygame.sprite.Sprite.__init__(self) @@ -28,6 +31,7 @@ class Explosion(pygame.sprite.Sprite): assets.display_assets.calculate_screen_position(coords), (V_TILE_SIZE, V_TILE_SIZE) ) + self.mine = mine def update(self, *args, **kwargs): @@ -39,6 +43,10 @@ class Explosion(pygame.sprite.Sprite): if self.frame == len(self.explosion_animation): self.kill() + elif self.frame == len(self.explosion_animation) - 1: + if self.mine is not None: + self.mine.blacked = True + else: self.image = self.explosion_animation[self.frame] diff --git a/game.py b/game.py index f279526..55b58ce 100644 --- a/game.py +++ b/game.py @@ -5,7 +5,7 @@ import project_constants as const from assets.display_assets import blit_graphics from algorithms.search import a_star -from algorithms.learn.genetic_algorithm import new_ga, helpers +from algorithms.learn.genetic_algorithm import ga_roulette, helpers from minefield import Minefield @@ -150,12 +150,12 @@ class Game: genetics_minefield = Minefield(const.MAP_RANDOM_10x10) sequence = \ - new_ga.genetic_algorithm(minefield=genetics_minefield, - population=helpers.get_mines_coords(self.minefield), - popSize=100, - eliteSize=20, - mutationRate=0.01, - generations=generations) + ga_roulette.genetic_algorithm(minefield=genetics_minefield, + population=helpers.get_mines_coords(self.minefield), + pop_size=100, + elite_size=20, + mutation_rate=0.01, + generations=generations) self.genetic_sequence = sequence self.genetics_done = True diff --git a/main.py b/main.py index 587a822..d73687c 100644 --- a/main.py +++ b/main.py @@ -57,12 +57,6 @@ def main(): # checking if game should stop running running = not is_quit_button_pressed(events) - # TODO : added for testing, remove after moving the explosion line - keys = pygame.key.get_pressed() - if keys[pygame.K_SPACE]: - # TODO : move this line to where explosion is called - const.EXPLOSIONS.add(Explosion((2, 3))) - # drawing minefield and agent instances game.draw_minefield() @@ -187,7 +181,7 @@ def main(): 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 + const.EXPLOSIONS.add(Explosion(mine=game.get_mine(game.goal))) else: print("guess you will live a little longer...\n\n") diff --git a/minefield.py b/minefield.py index cc590da..9118458 100644 --- a/minefield.py +++ b/minefield.py @@ -1,4 +1,5 @@ import project_constants as const +from assets.explosion import Explosion from objects import tile as tl, agent as ag from objects.mine_models.time_mine import TimeMine import json_generator as jg @@ -39,7 +40,7 @@ class Minefield: self.time_mines = self._get_time_mines() - def next_turn(self, n_turns=1): + def next_turn(self, n_turns=1, mode="main"): self.turn += n_turns self.points += n_turns @@ -47,11 +48,13 @@ class Minefield: mine.timer = max(0, mine.starting_time - int(self.turn)) if mine.timer == 0 and mine.active: - # TODO: BOOM self.explosions += 1 self.points += const.EXPLOSION_PENALTY mine.active = False + if mode == "main": + const.EXPLOSIONS.add(Explosion(mine=mine)) + def get_active_mines(self): mines = list() diff --git a/objects/mine_models/mine.py b/objects/mine_models/mine.py index ca10033..a082690 100644 --- a/objects/mine_models/mine.py +++ b/objects/mine_models/mine.py @@ -17,11 +17,13 @@ class Mine(ABC): self.position = position self.wire = None self.active = active + self.blacked = False @abstractmethod def disarm(self, wire): if wire == self.wire: self.active = False + self.blacked = True return True else: diff --git a/project_constants.py b/project_constants.py index 54a8c6f..1c538bd 100644 --- a/project_constants.py +++ b/project_constants.py @@ -161,6 +161,12 @@ HIGHLIGHT = pygame.transform.scale( ) HIGHLIGHT.set_alpha(100) +MINE_INACTIVE = pygame.transform.scale( + pygame.image.load(os.path.join(DIR_ASSETS, "old_tiles/tile_black.png")), + (V_TILE_SIZE, V_TILE_SIZE) +) +MINE_INACTIVE.set_alpha(160) + # ============== # # ==== MAPS ==== # diff --git a/resources/assets/old_tiles/tile_black.png b/resources/assets/old_tiles/tile_black.png new file mode 100644 index 0000000..d4f8970 Binary files /dev/null and b/resources/assets/old_tiles/tile_black.png differ