integrated explosion animation, reformatted roulette-GA file
This commit is contained in:
parent
2944099e93
commit
dc539223db
175
algorithms/learn/genetic_algorithm/ga_roulette.py
Normal file
175
algorithms/learn/genetic_algorithm/ga_roulette.py
Normal 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)
|
@ -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))
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
@ -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(
|
||||
|
@ -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]
|
||||
|
||||
|
10
game.py
10
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,11 +150,11 @@ class Game:
|
||||
genetics_minefield = Minefield(const.MAP_RANDOM_10x10)
|
||||
|
||||
sequence = \
|
||||
new_ga.genetic_algorithm(minefield=genetics_minefield,
|
||||
ga_roulette.genetic_algorithm(minefield=genetics_minefield,
|
||||
population=helpers.get_mines_coords(self.minefield),
|
||||
popSize=100,
|
||||
eliteSize=20,
|
||||
mutationRate=0.01,
|
||||
pop_size=100,
|
||||
elite_size=20,
|
||||
mutation_rate=0.01,
|
||||
generations=generations)
|
||||
|
||||
self.genetic_sequence = sequence
|
||||
|
8
main.py
8
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")
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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 ==== #
|
||||
|
BIN
resources/assets/old_tiles/tile_black.png
Normal file
BIN
resources/assets/old_tiles/tile_black.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 123 B |
Loading…
Reference in New Issue
Block a user