new map, faster genetic algorithm

This commit is contained in:
s452645 2021-06-18 17:55:38 +02:00
parent 8e7e279c4b
commit dfac987643
11 changed files with 792 additions and 37 deletions

View File

@ -27,7 +27,7 @@ def sum_distance(speciment):
# tournament selection # tournament selection
# this function randomly puts speciments to groups, from each group one best speciment is taken for reproduction # this function randomly puts speciments to groups, from each group one best speciment is taken for reproduction
def selection(pop, scores, k=3): 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):
@ -66,7 +66,7 @@ def crossover(p1, p2, r_cross):
# this function checks whether genes mutated # this function checks whether genes mutated
# if gene is mutated then it is swapped with randomly chosen gene from the same speciment # if gene is mutated then it is swapped with randomly chosen gene from the same speciment
def mutation(speciment, r_mut): def mutation(speciment, r_mut):
for i in range(len(speciment)-1): for i in range(len(speciment)):
# check for a mutation # check for a mutation
if rand() < r_mut: if rand() < r_mut:
# flip the bit # flip the bit
@ -126,12 +126,12 @@ if __name__ == "__main__":
# crossover rate # crossover rate
r_cross = 0.9 r_cross = 0.9
# mutation rate # mutation rate
r_mut = 0.05 r_mut = 0.15
# 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", "fifthmap.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)

View File

@ -4,12 +4,111 @@ import project_constants as const
from project_constants import Direction from project_constants import Direction
from algorithms.search.a_star import State, graphsearch from algorithms.search.a_star import State, graphsearch
from objects.mine_models.chained_mine import ChainedMine from objects.mine_models.chained_mine import ChainedMine
from objects.mine_models.time_mine import TimeMine
def get_score(minefield, speciment): def possible_states(row, column):
initial_state = State(row=0, column=0, direction=Direction.RIGHT) possible_states = []
if Minefield.is_valid_move(row + 1, column):
possible_states.append(State(row + 1, column, Direction.UP))
if Minefield.is_valid_move(row - 1, column):
possible_states.append(State(row + 1, column, Direction.DOWN))
if Minefield.is_valid_move(row, column + 1):
possible_states.append(State(row, column + 1, Direction.LEFT))
if Minefield.is_valid_move(row, column - 1):
possible_states.append(State(row, column - 1, Direction.RIGHT))
return possible_states
def create_scores_table(minefield):
mines = get_mines_coords(minefield)
dict_table = {
mine1: {
mine2: {
direction: 0
for direction in Direction}
for mine2 in mines}
for mine1 in mines}
dict_table[(0, 0)] = {mine: {Direction.RIGHT: 0} for mine in mines}
for mine in mines:
_, end_state, cost = \
graphsearch(State(0, 0, Direction.RIGHT),
minefield,
target_type="mine",
tox=mine[0],
toy=mine[1],
with_data=True)
dict_table[(0, 0)][mine][Direction.RIGHT] = (end_state, cost)
for mine1 in mines:
for mine2 in mines:
for initial_state in possible_states(mine1[0], mine1[1]):
_, end_state, cost = \
graphsearch(initial_state,
minefield,
target_type="mine",
tox=mine2[0],
toy=mine2[1],
with_data=True)
dict_table[mine1][mine2][initial_state.direction] = (end_state, cost)
return dict_table
def get_score(minefield, speciment, table=None):
score = 0 score = 0
initial_state = State(0, 0, Direction.RIGHT)
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]
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)
mine = minefield.matrix[speciment[el_index][0]][speciment[el_index][1]].mine
if isinstance(mine, ChainedMine) and mine.predecessor is not None and mine.predecessor.active:
cost += 200
mine.active = False
for _ in range(cost):
minefield.next_turn()
score += cost
score += 200 * minefield.explosions
return score
# IF THERE IS NO TABLE
else:
for el_index in range(len(speciment) - 1): for el_index in range(len(speciment) - 1):
action_sequence, initial_state, cost = \ action_sequence, initial_state, cost = \
graphsearch(initial_state, graphsearch(initial_state,
@ -22,7 +121,7 @@ def get_score(minefield, speciment):
mine = minefield.matrix[speciment[el_index][0]][speciment[el_index][1]].mine mine = minefield.matrix[speciment[el_index][0]][speciment[el_index][1]].mine
if isinstance(mine, ChainedMine) and mine.predecessor is not None and mine.predecessor.active: if isinstance(mine, ChainedMine) and mine.predecessor is not None and mine.predecessor.active:
cost += 10000 cost += 200
mine.active = False mine.active = False
@ -31,7 +130,7 @@ def get_score(minefield, speciment):
score += cost score += cost
score += 10000 * minefield.explosions score += 200 * minefield.explosions
return score return score
@ -43,6 +142,6 @@ def get_mines_coords(minefield: Minefield):
mine = minefield.matrix[row][column].mine mine = minefield.matrix[row][column].mine
if mine is not None: if mine is not None:
mines.append([row, column]) mines.append((row, column))
return mines return mines

View File

@ -0,0 +1,169 @@
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", "fifthmap.json"))
genetic_algorithm(minefield=gl_minefield,
population=helpers.get_mines_coords(gl_minefield),
popSize=100,
eliteSize=20,
mutationRate=0.01,
generations=100)

View File

@ -58,7 +58,7 @@ def blit_graphics(minefield):
pass # darken_tile(tile.position) pass # darken_tile(tile.position)
# draw a mine on top if there is one # draw a mine on top if there is one
if tile.mine is not None and tile.mine.active: if tile.mine is not None:
if isinstance(tile.mine, StandardMine): if isinstance(tile.mine, StandardMine):
const.SCREEN.blit(mine_asset_options["MINE"], tile_screen_coords) const.SCREEN.blit(mine_asset_options["MINE"], tile_screen_coords)

13
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 genetic_algorithm, helpers from algorithms.learn.genetic_algorithm import new_ga, helpers
from minefield import Minefield from minefield import Minefield
@ -141,11 +141,14 @@ class Game:
def run_genetics(self): def run_genetics(self):
genetics_minefield = Minefield(const.MAP_RANDOM_10x10) genetics_minefield = Minefield(const.MAP_RANDOM_10x10)
sequence, score = \ sequence = \
genetic_algorithm.genetic_algorithm(genetics_minefield, helpers.get_score, 10, 100, 0.9, 0.05) new_ga.genetic_algorithm(minefield=genetics_minefield,
population=helpers.get_mines_coords(self.minefield),
popSize=100,
eliteSize=20,
mutationRate=0.01,
generations=15)
print('Done!')
print('f(%s) = %f' % (sequence, score))
self.genetic_sequence = sequence self.genetic_sequence = sequence
def set_next_genetic_target(self): def set_next_genetic_target(self):

View File

@ -31,8 +31,11 @@ class Agent:
@staticmethod @staticmethod
def defuse_a_mine(mine): def defuse_a_mine(mine):
if mine.active:
is_success = popup.disarming_popup(mine) is_success = popup.disarming_popup(mine)
return is_success return is_success
else:
return True
def update_and_draw(self, window, delta_time, minefield): def update_and_draw(self, window, delta_time, minefield):
self.update(delta_time, minefield) self.update(delta_time, minefield)

View File

@ -11,6 +11,7 @@ class TimeMine(Mine):
def disarm(self, wire): def disarm(self, wire):
if self.active: if self.active:
self.starting_time = 0
return super().disarm(wire) return super().disarm(wire)
else: else:
# mine has already exploded, no need to return failure # mine has already exploded, no need to return failure

View File

@ -6,7 +6,8 @@ from project_constants import Terrain
# It is used in Tile.cost (giving the value to the tile) # It is used in Tile.cost (giving the value to the tile)
def assume_cost(terrain_type, mine): def assume_cost(terrain_type, mine):
if mine is not None and mine.active: # to allow walking through inactive mines -> and mine.active
if mine is not None:
return Terrain.MINE return Terrain.MINE
if terrain_type == "CONCRETE": if terrain_type == "CONCRETE":
return Terrain.CONCRETE return Terrain.CONCRETE

View File

@ -162,6 +162,6 @@ HIGHLIGHT.set_alpha(100)
# ==== MAPS ==== # # ==== MAPS ==== #
# ============== # # ============== #
MAP_RANDOM_10x10 = os.path.join("resources", "minefields", "fourthmap.json") MAP_RANDOM_10x10 = os.path.join("resources", "minefields", "fifthmap.json")

View File

@ -0,0 +1,479 @@
{
"0,0": {
"terrain": "CONCRETE",
"mine": null
},
"0,1": {
"terrain": "MUD",
"mine": null
},
"0,2": {
"terrain": "GRASS",
"mine": null
},
"0,3": {
"terrain": "GRASS",
"mine": {
"mine_type": "time",
"timer": 77
}
},
"0,4": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": null
}
},
"0,5": {
"terrain": "GRASS",
"mine": null
},
"0,6": {
"terrain": "GRASS",
"mine": null
},
"0,7": {
"terrain": "GRASS",
"mine": null
},
"0,8": {
"terrain": "MUD",
"mine": {
"mine_type": "standard"
}
},
"0,9": {
"terrain": "MUD",
"mine": {
"mine_type": "time",
"timer": 120
}
},
"1,0": {
"terrain": "CONCRETE",
"mine": null
},
"1,1": {
"terrain": "MUD",
"mine": {
"mine_type": "time",
"timer": 23
}
},
"1,2": {
"terrain": "MUD",
"mine": null
},
"1,3": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": null
}
},
"1,4": {
"terrain": "CONCRETE",
"mine": null
},
"1,5": {
"terrain": "CONCRETE",
"mine": null
},
"1,6": {
"terrain": "CONCRETE",
"mine": null
},
"1,7": {
"terrain": "GRASS",
"mine": null
},
"1,8": {
"terrain": "MUD",
"mine": null
},
"1,9": {
"terrain": "MUD",
"mine": null
},
"2,0": {
"terrain": "CONCRETE",
"mine": null
},
"2,1": {
"terrain": "MUD",
"mine": {
"mine_type": "standard"
}
},
"2,2": {
"terrain": "MUD",
"mine": null
},
"2,3": {
"terrain": "MUD",
"mine": {
"mine_type": "chained",
"predecessor": null
}
},
"2,4": {
"terrain": "CONCRETE",
"mine": null
},
"2,5": {
"terrain": "GRASS",
"mine": {
"mine_type": "time",
"timer": 68
}
},
"2,6": {
"terrain": "CONCRETE",
"mine": null
},
"2,7": {
"terrain": "CONCRETE",
"mine": null
},
"2,8": {
"terrain": "GRASS",
"mine": {
"mine_type": "time",
"timer": 120
}
},
"2,9": {
"terrain": "MUD",
"mine": null
},
"3,0": {
"terrain": "CONCRETE",
"mine": null
},
"3,1": {
"terrain": "GRASS",
"mine": {
"mine_type": "time",
"timer": 30
}
},
"3,2": {
"terrain": "GRASS",
"mine": null
},
"3,3": {
"terrain": "MUD",
"mine": {
"mine_type": "time",
"timer": 99
}
},
"3,4": {
"terrain": "CONCRETE",
"mine": null
},
"3,5": {
"terrain": "GRASS",
"mine": null
},
"3,6": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": null
}
},
"3,7": {
"terrain": "CONCRETE",
"mine": null
},
"3,8": {
"terrain": "CONCRETE",
"mine": null
},
"3,9": {
"terrain": "GRASS",
"mine": null
},
"4,0": {
"terrain": "CONCRETE",
"mine": null
},
"4,1": {
"terrain": "GRASS",
"mine": null
},
"4,2": {
"terrain": "GRASS",
"mine": {
"mine_type": "time",
"timer": 24
}
},
"4,3": {
"terrain": "CONCRETE",
"mine": null
},
"4,4": {
"terrain": "CONCRETE",
"mine": null
},
"4,5": {
"terrain": "GRASS",
"mine": null
},
"4,6": {
"terrain": "MUD",
"mine": null
},
"4,7": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": "7,0"
}
},
"4,8": {
"terrain": "CONCRETE",
"mine": null
},
"4,9": {
"terrain": "MUD",
"mine": null
},
"5,0": {
"terrain": "CONCRETE",
"mine": null
},
"5,1": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": null
}
},
"5,2": {
"terrain": "CONCRETE",
"mine": null
},
"5,3": {
"terrain": "CONCRETE",
"mine": null
},
"5,4": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": null
}
},
"5,5": {
"terrain": "MUD",
"mine": null
},
"5,6": {
"terrain": "MUD",
"mine": null
},
"5,7": {
"terrain": "GRASS",
"mine": null
},
"5,8": {
"terrain": "CONCRETE",
"mine": null
},
"5,9": {
"terrain": "MUD",
"mine": null
},
"6,0": {
"terrain": "CONCRETE",
"mine": null
},
"6,1": {
"terrain": "CONCRETE",
"mine": null
},
"6,2": {
"terrain": "CONCRETE",
"mine": null
},
"6,3": {
"terrain": "GRASS",
"mine": null
},
"6,4": {
"terrain": "MUD",
"mine": null
},
"6,5": {
"terrain": "MUD",
"mine": {
"mine_type": "standard"
}
},
"6,6": {
"terrain": "MUD",
"mine": null
},
"6,7": {
"terrain": "GRASS",
"mine": null
},
"6,8": {
"terrain": "CONCRETE",
"mine": null
},
"6,9": {
"terrain": "GRASS",
"mine": {
"mine_type": "standard"
}
},
"7,0": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": null
}
},
"7,1": {
"terrain": "GRASS",
"mine": null
},
"7,2": {
"terrain": "GRASS",
"mine": null
},
"7,3": {
"terrain": "MUD",
"mine": null
},
"7,4": {
"terrain": "MUD",
"mine": null
},
"7,5": {
"terrain": "MUD",
"mine": {
"mine_type": "time",
"timer": 101
}
},
"7,6": {
"terrain": "MUD",
"mine": {
"mine_type": "time",
"timer": 140
}
},
"7,7": {
"terrain": "GRASS",
"mine": null
},
"7,8": {
"terrain": "CONCRETE",
"mine": null
},
"7,9": {
"terrain": "CONCRETE",
"mine": null
},
"8,0": {
"terrain": "GRASS",
"mine": null
},
"8,1": {
"terrain": "MUD",
"mine": {
"mine_type": "time",
"timer": 60
}
},
"8,2": {
"terrain": "GRASS",
"mine": null
},
"8,3": {
"terrain": "GRASS",
"mine": {
"mine_type": "standard"
}
},
"8,4": {
"terrain": "MUD",
"mine": null
},
"8,5": {
"terrain": "MUD",
"mine": null
},
"8,6": {
"terrain": "MUD",
"mine": null
},
"8,7": {
"terrain": "MUD",
"mine": null
},
"8,8": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": "3,6"
}
},
"8,9": {
"terrain": "CONCRETE",
"mine": null
},
"9,0": {
"terrain": "MUD",
"mine": null
},
"9,1": {
"terrain": "MUD",
"mine": null
},
"9,2": {
"terrain": "MUD",
"mine": null
},
"9,3": {
"terrain": "GRASS",
"mine": null
},
"9,4": {
"terrain": "GRASS",
"mine": null
},
"9,5": {
"terrain": "GRASS",
"mine": {
"mine_type": "chained",
"predecessor": "0,4"
}
},
"9,6": {
"terrain": "MUD",
"mine": null
},
"9,7": {
"terrain": "MUD",
"mine": null
},
"9,8": {
"terrain": "MUD",
"mine": null
},
"9,9": {
"terrain": "CONCRETE",
"mine": null
},
"agents_initial_state": {
"direction": 1,
"position": "0,0"
}
}

View File

@ -48,7 +48,7 @@
"terrain": "MUD", "terrain": "MUD",
"mine": { "mine": {
"mine_type": "time", "mine_type": "time",
"timer": 320 "timer": 120
} }
}, },
"1,0": { "1,0": {
@ -154,7 +154,7 @@
"terrain": "GRASS", "terrain": "GRASS",
"mine": { "mine": {
"mine_type": "time", "mine_type": "time",
"timer": 40 "timer": 30
} }
}, },
"3,2": { "3,2": {
@ -363,7 +363,7 @@
"terrain": "MUD", "terrain": "MUD",
"mine": { "mine": {
"mine_type": "time", "mine_type": "time",
"timer": 240 "timer": 140
} }
}, },
"7,7": { "7,7": {
@ -386,7 +386,7 @@
"terrain": "MUD", "terrain": "MUD",
"mine": { "mine": {
"mine_type": "time", "mine_type": "time",
"timer": 150 "timer": 60
} }
}, },
"8,2": { "8,2": {