integrated explosion animation, reformatted roulette-GA file

This commit is contained in:
s452645 2021-06-20 21:55:23 +02:00
parent 2944099e93
commit dc539223db
12 changed files with 223 additions and 207 deletions

View File

@ -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)

View File

@ -7,20 +7,22 @@ from numpy.random import rand
from algorithms.learn.genetic_algorithm import helpers 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] # this is helper function for sum_distance function, it counts the taxi cab distance between 2 vectors [x,y]
def distance(x,y): def distance(x, y):
temp1 = abs(x[0]-y[0]) temp1 = abs(x[0] - y[0])
temp2 = abs(x[1]-y[1]) temp2 = abs(x[1] - y[1])
vector_distance = temp1+temp2 vector_distance = temp1 + temp2
return vector_distance return vector_distance
# this is fitting function which tells how well specimen fits the environment # 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 function counts the sum of distances between vectors for a specimen
# this was just for testing, it should be probably changed to A* # this was just for testing, it should be probably changed to A*
def sum_distance(speciment): def sum_distance(speciment):
sum = 0 sum = 0
for i in range(0,len(speciment)-2): for i in range(0, len(speciment) - 2):
pom = distance(speciment[i],speciment[i+1]) pom = distance(speciment[i], speciment[i + 1])
sum = sum + pom sum = sum + pom
return sum return sum
@ -30,7 +32,7 @@ def sum_distance(speciment):
def selection(pop, scores, k=10): def selection(pop, scores, k=10):
# first random selection # first random selection
selection_ix = randint(len(pop)) 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) # check if better (e.g. perform a tournament)
if scores[ix] < scores[selection_ix]: if scores[ix] < scores[selection_ix]:
selection_ix = ix selection_ix = ix
@ -40,8 +42,8 @@ def selection(pop, scores, k=10):
# crossover two parents to create two children # crossover two parents to create two children
# this function creates speciments for new generation # this function creates speciments for new generation
def crossover(p1, p2, r_cross): def crossover(p1, p2, r_cross):
p1=list(p1) p1 = list(p1)
p2=list(p2) p2 = list(p2)
# children are copies of parents by default # children are copies of parents by default
c1, c2 = p1.copy(), p2.copy() c1, c2 = p1.copy(), p2.copy()
# check for recombination # check for recombination
@ -71,17 +73,16 @@ def mutation(speciment, r_mut):
if rand() < r_mut: if rand() < r_mut:
# flip the bit # flip the bit
temp = speciment[i] temp = speciment[i]
pom = random.randint(0,len(speciment)-1) pom = random.randint(0, len(speciment) - 1)
speciment[i] = speciment[pom] speciment[i] = speciment[pom]
speciment[pom] = temp speciment[pom] = temp
# genetic algorithm # genetic algorithm
def genetic_algorithm(minefield, objective, n_iter, n_pop, r_cross, r_mut): 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 # 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 # 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)] pop = [random.sample(speciment, len(speciment)) for _ in range(n_pop)]
# permutation function returns tuples so I change them to lists # 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__": if __name__ == "__main__":
# define the total iterations # define the total iterations
n_iter = 100 n_iter = 100
# bits # bits
@ -131,11 +131,10 @@ if __name__ == "__main__":
# create new minefield instance # create new minefield instance
import os import os
from minefield import Minefield from minefield import Minefield
minefield = Minefield(os.path.join("..", "..", "..", "resources", "minefields", "fourthmap.json")) minefield = Minefield(os.path.join("..", "..", "..", "resources", "minefields", "fourthmap.json"))
# perform the genetic algorithm search # perform the genetic algorithm search
best, score = genetic_algorithm(minefield, helpers.get_score, n_iter, n_pop, r_cross, r_mut) best, score = genetic_algorithm(minefield, helpers.get_score, n_iter, n_pop, r_cross, r_mut)
print('Done!') print('Done!')
print('f(%s) = %f' % (best, score)) print('f(%s) = %f' % (best, score))

View File

@ -87,7 +87,7 @@ def get_score(minefield, speciment, table=None):
mine.active = False mine.active = False
minefield.next_turn(n_turns=cost) minefield.next_turn(n_turns=cost, mode="ga")
score += cost score += cost
score += 200 * minefield.explosions score += 200 * minefield.explosions

View File

@ -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)

View File

@ -74,6 +74,9 @@ def blit_graphics(minefield):
seconds = tile.mine.timer % 60 seconds = tile.mine.timer % 60
display_time_mine(tile.position, minutes, "0" + str(seconds)) 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) # changed sapper's movement from jumping to continuous movement (moved everything to Agent's class)
# # sapper # # sapper
# display_sapper( # display_sapper(

View File

@ -3,12 +3,15 @@ import pygame
import os import os
from project_constants import V_TILE_SIZE, DIR_ASSETS, SCREEN from project_constants import V_TILE_SIZE, DIR_ASSETS, SCREEN
from objects.mine_models.mine import Mine
import assets.display_assets import assets.display_assets
class Explosion(pygame.sprite.Sprite): 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) pygame.sprite.Sprite.__init__(self)
@ -28,6 +31,7 @@ class Explosion(pygame.sprite.Sprite):
assets.display_assets.calculate_screen_position(coords), assets.display_assets.calculate_screen_position(coords),
(V_TILE_SIZE, V_TILE_SIZE) (V_TILE_SIZE, V_TILE_SIZE)
) )
self.mine = mine
def update(self, *args, **kwargs): def update(self, *args, **kwargs):
@ -39,6 +43,10 @@ class Explosion(pygame.sprite.Sprite):
if self.frame == len(self.explosion_animation): if self.frame == len(self.explosion_animation):
self.kill() self.kill()
elif self.frame == len(self.explosion_animation) - 1:
if self.mine is not None:
self.mine.blacked = True
else: else:
self.image = self.explosion_animation[self.frame] self.image = self.explosion_animation[self.frame]

10
game.py
View File

@ -5,7 +5,7 @@ import project_constants as const
from assets.display_assets import blit_graphics from assets.display_assets import blit_graphics
from algorithms.search import a_star 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 from minefield import Minefield
@ -150,11 +150,11 @@ class Game:
genetics_minefield = Minefield(const.MAP_RANDOM_10x10) genetics_minefield = Minefield(const.MAP_RANDOM_10x10)
sequence = \ sequence = \
new_ga.genetic_algorithm(minefield=genetics_minefield, ga_roulette.genetic_algorithm(minefield=genetics_minefield,
population=helpers.get_mines_coords(self.minefield), population=helpers.get_mines_coords(self.minefield),
popSize=100, pop_size=100,
eliteSize=20, elite_size=20,
mutationRate=0.01, mutation_rate=0.01,
generations=generations) generations=generations)
self.genetic_sequence = sequence self.genetic_sequence = sequence

View File

@ -57,12 +57,6 @@ def main():
# checking if game should stop running # checking if game should stop running
running = not is_quit_button_pressed(events) 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 # drawing minefield and agent instances
game.draw_minefield() game.draw_minefield()
@ -187,7 +181,7 @@ def main():
if not game.agent.defuse_a_mine(game.get_mine(game.goal)): if not game.agent.defuse_a_mine(game.get_mine(game.goal)):
print("BOOOOOOM\n\n") print("BOOOOOOM\n\n")
game.explosion(game.get_mine(game.goal)) game.explosion(game.get_mine(game.goal))
# is_game_over = True const.EXPLOSIONS.add(Explosion(mine=game.get_mine(game.goal)))
else: else:
print("guess you will live a little longer...\n\n") print("guess you will live a little longer...\n\n")

View File

@ -1,4 +1,5 @@
import project_constants as const import project_constants as const
from assets.explosion import Explosion
from objects import tile as tl, agent as ag from objects import tile as tl, agent as ag
from objects.mine_models.time_mine import TimeMine from objects.mine_models.time_mine import TimeMine
import json_generator as jg import json_generator as jg
@ -39,7 +40,7 @@ class Minefield:
self.time_mines = self._get_time_mines() 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.turn += n_turns
self.points += n_turns self.points += n_turns
@ -47,11 +48,13 @@ class Minefield:
mine.timer = max(0, mine.starting_time - int(self.turn)) mine.timer = max(0, mine.starting_time - int(self.turn))
if mine.timer == 0 and mine.active: if mine.timer == 0 and mine.active:
# TODO: BOOM
self.explosions += 1 self.explosions += 1
self.points += const.EXPLOSION_PENALTY self.points += const.EXPLOSION_PENALTY
mine.active = False mine.active = False
if mode == "main":
const.EXPLOSIONS.add(Explosion(mine=mine))
def get_active_mines(self): def get_active_mines(self):
mines = list() mines = list()

View File

@ -17,11 +17,13 @@ class Mine(ABC):
self.position = position self.position = position
self.wire = None self.wire = None
self.active = active self.active = active
self.blacked = False
@abstractmethod @abstractmethod
def disarm(self, wire): def disarm(self, wire):
if wire == self.wire: if wire == self.wire:
self.active = False self.active = False
self.blacked = True
return True return True
else: else:

View File

@ -161,6 +161,12 @@ HIGHLIGHT = pygame.transform.scale(
) )
HIGHLIGHT.set_alpha(100) 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 ==== # # ==== MAPS ==== #

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B