Describe changes, and raport progress
This commit is contained in:
parent
2fdbf014aa
commit
9dc60c7062
131
gaTraveling.md
131
gaTraveling.md
@ -1,2 +1,131 @@
|
||||
# Wyznaczanie trasy algorytmem genetycznym
|
||||
**Autor:** Mateusz Tylka
|
||||
**Autor:** *Mateusz Tylka*
|
||||
|
||||
## Cel algorytmu
|
||||
Celem tego algorytmu jest wyznaczenie optymalnej trasy w zbieraniu ziół o konkretnych pozycjach, które
|
||||
są generowane losowo. Algorytm decyduje po które zioło udać się najpierw, starając się, aby końcowa suma odległości
|
||||
pomiędzy odwiedzonymi pozycjami była jak najmniejsza.
|
||||
|
||||
## Osobnik Traveling
|
||||
Osobnik jest to jednostka polegająca ewolucji za pomocą operacji genetycznych.
|
||||
W mojej implementacji osobnika reprezentuje obiekt [Traveling.py](). Ten obiekt przechowuje następujące metody:
|
||||
|
||||
```python
|
||||
class Traveling:
|
||||
def __init__(self, coords):
|
||||
self.coords = coords
|
||||
self.fitness = self.evaluate()
|
||||
```
|
||||
* W konstruktorze przyjmowany jako parametr jest zestaw koordynatów, który zostaje zapisany jako atrybut,
|
||||
następnie tworzymy atrybut reprezentujący sprawność danego osobnika, który jest wynikiem metody określającej
|
||||
poprawność danej trasy.
|
||||
|
||||
```python
|
||||
def evaluate(self):
|
||||
sum = 0
|
||||
for i, c in enumerate(self.coords):
|
||||
if i + 1 > len(self.coords) - 1:
|
||||
break
|
||||
nextCoord = self.coords[i + 1]
|
||||
# calculate distance
|
||||
sum += sqrt((nextCoord[0] - c[0]) ** 2 + (nextCoord[1] - c[1]) ** 2)
|
||||
return sum
|
||||
```
|
||||
* Metoda **evaluate** odpowiedzialna jest za ocenę osobnika. Liczymy w niej odległość od punktu startu do
|
||||
pierwszego punktu, następnie odległość między drugim a trzecim miejscem i tak dalej..., aż do końca listy pozycji
|
||||
ziół z rozważanej trasy. Uzyskane wyniki sumujemy, czyli uzyskujemy długość konretnej drogi.
|
||||
|
||||
```python
|
||||
def crossover(self, parentCoords):
|
||||
childCoords = self.coords[1:int(len(self.coords) / 2)]
|
||||
for coord in parentCoords.coords:
|
||||
if coord not in childCoords and coord not in END_COORD + START_COORD:
|
||||
childCoords.append(coord)
|
||||
|
||||
if len(childCoords) == len(parentCoords.coords):
|
||||
break
|
||||
return Traveling(START_COORD + childCoords + END_COORD)
|
||||
```
|
||||
* Metoda **crossover** reprezentuję operację genetyczną krzyżowania osobników. Bierzemy w niej z pierwszego osobnika
|
||||
część punktów jego trasy (w naszym przypadku połowę) i dobieramy w pętli kolejne koordynaty z drugiego osobnika
|
||||
tak, aby się one nie powtarzały. Gdy już osiągniemy odpowiednią długość nowego osobnika kończymy pętlę i zwracamy go.
|
||||
|
||||
```python
|
||||
def mutation(self):
|
||||
first = randint(1, len(self.coords) - 2)
|
||||
second = randint(1, len(self.coords) - 2)
|
||||
self.coords[first], self.coords[second] = self.coords[second], self.coords[first]
|
||||
self.fitness = self.evaluate()
|
||||
```
|
||||
* Ta metoda przedstawia proces mutacji. Polega on po prostu na zamianę miejscami dwóch losowych koordynatów
|
||||
na trasie.
|
||||
|
||||
```python
|
||||
def __repr__(self):
|
||||
return str(self.coords)
|
||||
```
|
||||
* Obiekt ten zwracany jest w formie tekstowej listy koordynatów.
|
||||
|
||||
## Obiekt GeneticAlgorithm
|
||||
W pliku [GeneticAlgorithm.py]() znajduje się model selekcji osobników, warunek stopu, oraz główna pętla
|
||||
algorytmu.
|
||||
|
||||
```python
|
||||
class GeneticAlgorithm:
|
||||
def __init__(self, firstPopulation, mutationProbability):
|
||||
self.firstPopulation = firstPopulation
|
||||
self.mutationProbability = mutationProbability
|
||||
```
|
||||
* Obiekt ten przyjmuje pierwszą populację oraz prawdopodobieństwo mutacji jako parametry i zapisuje je
|
||||
w odpowiednich atrybutach.
|
||||
|
||||
```python
|
||||
def selectionModel(self, generation):
|
||||
max_selected = int(len(generation) / 10)
|
||||
sorted_by_assess = sorted(generation, key=lambda x: x.fitness)
|
||||
return sorted_by_assess[:max_selected]
|
||||
```
|
||||
|
||||
* Model w mojej implementacji opiera się na elitaryzmie - czyli wybraniu pewnej ilości najlepszych chromosomów,
|
||||
które z pewnością przetrwają do następnej generacji. Definiujemy w niej 10% spośród przyjętej generacji jako parametr.
|
||||
Sortujemy naszą generację według odległości (metody *evaluate*) czyli wartości atrybutu **fitness**.
|
||||
|
||||
```python
|
||||
def stopCondition(self, i):
|
||||
return i == 64
|
||||
```
|
||||
|
||||
* Warunkiem końca algorytmu jest osiągnięcie 64 generacji.
|
||||
|
||||
```python
|
||||
def run(self):
|
||||
population = self.firstPopulation
|
||||
population.sort(key=lambda x: x.fitness)
|
||||
populationLen = len(population)
|
||||
i = 0
|
||||
while True:
|
||||
selected = self.selectionModel(population)
|
||||
newPopulation = selected.copy()
|
||||
while len(newPopulation) != populationLen:
|
||||
child = choice(population).crossover(choice(population))
|
||||
if random() <= self.mutationProbability:
|
||||
child.mutation()
|
||||
newPopulation.append(child)
|
||||
|
||||
population = newPopulation
|
||||
theBestMatch = min(population, key=lambda x: x.fitness)
|
||||
print("Generation: {} S: {} fitness: {}".format(i+1, theBestMatch, theBestMatch.fitness))
|
||||
|
||||
i += 1
|
||||
if self.stopCondition(i):
|
||||
return str(theBestMatch)
|
||||
```
|
||||
* W metodzie **run** zaimplementowana jest główna pętla algorytmu
|
||||
genetycznego. Na początku wskazujemy pierwszą populację i sortujemy ją według dopasowania **fitness**,
|
||||
a następnie obliczamy długość populacji i deklarujemy iterator pętli, która przebiega w następujących krokach;
|
||||
* Wybieramy najlepszych osobników według modelu selekcji (metody **selectionModel**)
|
||||
* Tworzymy nową populację z najlepszych wybranych osobników, jednak do pełnej populacji brakuje nam kilku chromosomów
|
||||
* Dopełniamy do pełnej liczności populacji, poprzez operację krzyżowania (metoda **crossover**), oraz
|
||||
ewentualną mutację (metodą **mutation**).
|
||||
* Wybieramy najlepszego osobnika z populacji po minimalnej odległości, oraz wyświetlamy wynik.
|
||||
* Przeprowadzamy w ten sposób kolejną generację dopóki nie będzie ich 64.
|
@ -32,6 +32,7 @@ class Interactable(Entity):
|
||||
def on_interaction(self, player):
|
||||
"""
|
||||
Applies outcome to the Player
|
||||
Add some ifs to handle collect herbs (traveling ga algorithm)
|
||||
|
||||
:param Player: Player object
|
||||
"""
|
||||
@ -46,6 +47,5 @@ class Interactable(Entity):
|
||||
if player.herbs == 10 and self.classifier == Classifiers.REST:
|
||||
player.readyToCrafting = True
|
||||
|
||||
|
||||
def __str__(self):
|
||||
return "Entity - ID:{}, pos:({}x, {}y), {}".format(self.id, self.x, self.y, self.classifier.name)
|
||||
|
@ -22,8 +22,8 @@ class Player(Entity):
|
||||
super().__init__("player.png", size, spawnpoint, False)
|
||||
|
||||
self.statistics = Statistics(100, 0, 0, 100)
|
||||
self.herbs = 0
|
||||
self.readyToCrafting = False
|
||||
self.herbs = 0 # Need to collect herbs (traveling ga algorithm)
|
||||
self.readyToCrafting = False # Need to reset statistics (traveling ga algorithm)
|
||||
# How many steps has the player taken through its lifetime
|
||||
self.movePoints = 0
|
||||
# Tracks how much time has passed since the player is alive
|
||||
@ -52,7 +52,7 @@ class Player(Entity):
|
||||
def applyWalkingFatigue(self):
|
||||
"""
|
||||
Lowers player's statistics. Applied every few steps.
|
||||
|
||||
Modify to not die until collect all herbs.
|
||||
"""
|
||||
# looses hunger
|
||||
# self.statistics.set_hunger(1.7)
|
||||
|
@ -116,17 +116,17 @@ class EventManager:
|
||||
target = pickEntity(self.player, self.game.map)
|
||||
self.player.gotoToTarget(target, self.game.map)
|
||||
|
||||
if keys[pygame.K_t]:
|
||||
if keys[pygame.K_t]: # Handle traveling movement to collect herbs
|
||||
if self.player.movementTarget is None and self.iterator <= 10:
|
||||
target = self.game.entityToVisitList[self.iterator]
|
||||
self.player.gotoToTarget(target, self.game.map)
|
||||
self.iterator += 1
|
||||
|
||||
if self.player.herbs > self.takenHerbs:
|
||||
if self.player.herbs > self.takenHerbs: # Console log when player collect herb
|
||||
self.game.screen.ui.console.printToConsole("Ziele zebrane! Ilość: " + str(self.player.herbs))
|
||||
self.takenHerbs = self.player.herbs
|
||||
|
||||
if self.player.readyToCrafting:
|
||||
if self.player.readyToCrafting: # Console log and reset statistics because of collect all herbs
|
||||
self.game.screen.ui.console.printToConsole("Eliksir został utworzony i spożyty!")
|
||||
self.player.statistics.set_hp(100)
|
||||
self.player.statistics.set_stamina(100)
|
||||
|
@ -101,6 +101,7 @@ class Game:
|
||||
filesPath) + os.sep + "data" + os.sep + "AI_data" + os.sep + "dt_exmpls" + os.sep + "dt_examples"
|
||||
dtExampleManager = ExamplesManager(examplesFilePath)
|
||||
dtExampleManager.generateExamples()
|
||||
# Traveling ga algorithm
|
||||
elif argv[1] == "ga_travel":
|
||||
self.travelRun(filesPath)
|
||||
# Invalid game mode
|
||||
@ -394,7 +395,7 @@ class Game:
|
||||
avg = sum(scores) / iterations
|
||||
print("Average: {}".format(str(avg)))
|
||||
|
||||
def travelRun(self, filesPath):
|
||||
def travelRun(self, filesPath): # Run game with traveling ga algorithm
|
||||
self.running = True
|
||||
print("Initializing screen, params: " + str(self.config["window"]) + "...", end=" ")
|
||||
|
||||
@ -420,10 +421,12 @@ class Game:
|
||||
self.map.addEntity(self.player, DONTADD=True)
|
||||
self.eventManager = EventManager(self, self.player)
|
||||
|
||||
# Generate random travel list
|
||||
self.travelCoords = random.sample(self.map.movableList(), 10)
|
||||
import ast
|
||||
self.travelCoords = ast.literal_eval(str(self.travelCoords))
|
||||
|
||||
# Insert herbs on random travel coordinates
|
||||
self.map.insertHerbs(self.travelCoords)
|
||||
|
||||
# Initialize genetic algorithm
|
||||
@ -433,12 +436,15 @@ class Game:
|
||||
ga = GeneticAlgorithm(firstGeneration, mutationProbability)
|
||||
self.movementList = ga.listOfTravel()
|
||||
|
||||
# Define list of entities which player should pass to collect herbs
|
||||
self.entityToVisitList = []
|
||||
for i in self.movementList:
|
||||
self.entityToVisitList.append(self.map.getEntityOnCoord(i))
|
||||
|
||||
self.screen.ui.console.printToConsole("First generation: " + str(firstGeneration[0]))
|
||||
# Remove first element, because start coordinates is None
|
||||
self.entityToVisitList.remove(self.entityToVisitList[0])
|
||||
|
||||
self.screen.ui.console.printToConsole("First generation: " + str(firstGeneration[0]))
|
||||
self.screen.ui.console.printToConsole("The best generation: " + str(self.entityToVisitList))
|
||||
|
||||
self.mainLoop()
|
||||
|
@ -243,7 +243,7 @@ class Map:
|
||||
return True
|
||||
return False
|
||||
|
||||
def insertHerbs(self, coordsList):
|
||||
def insertHerbs(self, coordsList): # Insert herbs on right coordinates
|
||||
nr = 1
|
||||
for i in range(10):
|
||||
entity = Pickupable("herb" + str(nr) + ".png", self.tileSize, coordsList[i], Statistics(0, 0, 0, 0), "herb")
|
||||
@ -252,6 +252,10 @@ class Map:
|
||||
nr += 1
|
||||
|
||||
def movableList(self):
|
||||
"""
|
||||
Return list which is movable on beginning of game,
|
||||
that is terrainTilesList without entity on self
|
||||
"""
|
||||
terrainList = self.terrainTilesList
|
||||
for i in self.entities:
|
||||
terrainList.remove(self.getTileOnCoord((i.x, i.y)))
|
||||
|
@ -2,6 +2,7 @@ from pathlib import Path
|
||||
|
||||
import pygame
|
||||
|
||||
|
||||
# TODO: Relative coords
|
||||
class TerrainTile(pygame.sprite.Sprite):
|
||||
def __init__(self, x, y, texture, tileSize, cost):
|
||||
@ -27,4 +28,4 @@ class TerrainTile(pygame.sprite.Sprite):
|
||||
|
||||
def __repr__(self):
|
||||
coords = (self.x, self.y)
|
||||
return str(coords)
|
||||
return str(coords)
|
||||
|
Loading…
Reference in New Issue
Block a user