Merge pull request 'adding genetic algorithm implementation that chooses which plants to farm' (#3) from genetic_algorithm into master

Reviewed-on: #3
This commit is contained in:
Dawid Pylak 2022-06-06 18:27:42 +02:00
commit a139a5376b
4 changed files with 176 additions and 4 deletions

View File

@ -25,4 +25,8 @@ class Constants:
NONE = 'brak'
CACTUS = 'kaktus'
POTATO = 'ziemniak'
WEATH = 'pszenica'
WHEAT = 'pszenica'
# Genetic algorithm points average
POINTS_AVERAGE = 6.33

View File

@ -0,0 +1,62 @@
from src.utils.Plants import *
from random import choice, random
class GeneticAlgorithm:
def __init__(self):
self.mutation_probability = 0.1
self.stop_condition = stop_condition
def selection_strategy(self, generation: BaseField):
maximum_selected_items = int(len(generation) / 10)
sorted_elements = sorted(generation, key=lambda x: x.evaluation)
return sorted_elements[:maximum_selected_items]
def _generate_random_plants(self):
plant_names = [choice(BaseField.possibilities) for _ in range(9)]
return [plant_selector(plant_name) for plant_name in plant_names]
def _generate_first_population(self):
return [BaseField(self._generate_random_plants()) for _ in range(15)]
def run(self) -> BaseField:
first_population = self._generate_first_population()
first_population.sort(key=lambda x: x.evaluation)
population_length = len(first_population)
i = 0
while True:
selected = self.selection_strategy(first_population)
new_population = selected.copy()
while len(new_population) != population_length:
child = choice(first_population).crossover(choice(first_population))
propability = random()
if propability <= self.mutation_probability:
child.mutate()
new_population.append(child)
first_population = new_population
best_match = min(first_population, key=lambda x: x.evaluation)
i += 1
if self.stop_condition(float(best_match)):
break
print(f'Best match is {best_match} with {i} iterations')
return best_match
def get_plants(self) -> list:
result_array = []
for i in range(4):
result_array = result_array + self.run().plants
return result_array
def main():
result_array = []
genetic_algorithm = GeneticAlgorithm()
for i in range(4):
result_array = result_array + genetic_algorithm.run().plants
print(result_array)
if __name__ == '__main__':
main()

104
src/utils/Plants.py Normal file
View File

@ -0,0 +1,104 @@
from constants import Constants
from random import randint, choice
class BasePlant:
def __init__(self):
self.appearance_points = 0
self.difficulty_points = 0
self.profit_points = 0
def first_population_generator(self):
pass
def __int__(self):
return sum([self.appearance_points, self.profit_points]) - self.difficulty_points
def __float__(self):
return self.__int__()
def stop_condition(average):
return round(average, 2) == Constants.POINTS_AVERAGE
def plant_selector(plant_name: str) -> BasePlant:
if plant_name == Constants.POTATO:
return Potato()
elif plant_name == Constants.WHEAT:
return Wheat()
elif plant_name == Constants.CACTUS:
return Cactus()
class BaseField:
"""Class that represents what plants grow on a certain field divided into 9 tiles"""
possibilities = [Constants.WHEAT, Constants.POTATO, Constants.CACTUS]
def __init__(self, plants):
self.plants: list(BasePlant) = plants
self.evaluation = self.evaluate_function()
def mutate(self):
self._perform_mutation()
self.evaluation = self.evaluate_function()
def _perform_mutation(self):
random_index = randint(0, 8)
self.plants[random_index] = plant_selector(choice(self.possibilities))
def crossover(self, other_field):
length = int(randint(0, 8))
new_plants = self.plants[:length] + other_field.plants[length:]
return BaseField(new_plants)
def evaluate_function(self) -> float:
current_fields_average = self.__float__()
return abs(current_fields_average - Constants.POINTS_AVERAGE)
def __str__(self):
return ''.join([str(plant) + ' ' for plant in self.plants])
def __float__(self):
return sum([int(plant) for plant in self.plants]) / 9
class Potato(BasePlant):
def __init__(self):
super().__init__()
self.appearance_points = 3
self.difficulty_points = 4
self.profit_points = 7
def __str__(self):
return Constants.POTATO
class Cactus(BasePlant):
def __init__(self):
super(Cactus, self).__init__()
self.appearance_points = 4
self.difficulty_points = 3
self.profit_points = 2
def __str__(self):
return Constants.CACTUS
class Wheat(BasePlant):
def __init__(self):
super(Wheat, self).__init__()
self.appearance_points = 5
self.difficulty_points = 7
self.profit_points = 9
def __str__(self):
return Constants.WHEAT

View File

@ -2,6 +2,7 @@ import pygame
from constants import Constants
from src.tile import Tile
from utils.GeneticAlgorithm import GeneticAlgorithm
class World:
@ -30,6 +31,7 @@ class World:
self.farmland_wheat = pygame.image.load('assets/images/farmland_wheat.jpg')
self.farmland_potato = pygame.image.load('assets/images/farmland_potato.jpg')
self.tiles = pygame.sprite.Group() # mamy tiles jako Sprite Group, to sie przyda potem do kolizji itp.
self.plants = GeneticAlgorithm().get_plants()
self.create_tiles()
def create_tiles(self):
@ -45,7 +47,7 @@ class World:
rodzaj_gleby = self.model.df.iloc[df_idx][Constants.SOIL_TYPE]
stan_nawiezienia = self.model.df.iloc[df_idx][Constants.FERTILIZATION_STATUS]
stopien_rozwoju = self.model.df.iloc[df_idx][Constants.GROWTH_LEVEL]
rodzaj_rosliny = self.model.df.iloc[df_idx][Constants.PLANT_TYPE]
rodzaj_rosliny = self.plants[df_idx].__str__()
rodzaj_nawozu = self.model.df.iloc[df_idx][Constants.FERTILISER_TYPE]
to_water = self.model.df.iloc[df_idx][Constants.TO_WATER]
@ -53,7 +55,7 @@ class World:
img = pygame.transform.scale(self.farmland_empty, (self.settings.tile_size, self.settings.tile_size))
elif to_water == 0 and rodzaj_rosliny == Constants.CACTUS:
img = pygame.transform.scale(self.farmland_cactus, (self.settings.tile_size, self.settings.tile_size))
elif to_water == 0 and rodzaj_rosliny == Constants.WEATH:
elif to_water == 0 and rodzaj_rosliny == Constants.WHEAT:
img = pygame.transform.scale(self.farmland_wheat, (self.settings.tile_size, self.settings.tile_size))
elif to_water == 0 and rodzaj_rosliny == Constants.POTATO:
img = pygame.transform.scale(self.farmland_potato, (self.settings.tile_size, self.settings.tile_size))
@ -61,7 +63,7 @@ class World:
img = pygame.transform.scale(self.dirt_empty, (self.settings.tile_size, self.settings.tile_size))
elif to_water == 1 and rodzaj_rosliny == Constants.CACTUS:
img = pygame.transform.scale(self.dirt_cactus, (self.settings.tile_size, self.settings.tile_size))
elif to_water == 1 and rodzaj_rosliny == Constants.WEATH:
elif to_water == 1 and rodzaj_rosliny == Constants.WHEAT:
img = pygame.transform.scale(self.dirt_wheat, (self.settings.tile_size, self.settings.tile_size))
elif to_water == 1 and rodzaj_rosliny == Constants.POTATO:
img = pygame.transform.scale(self.dirt_potato, (self.settings.tile_size, self.settings.tile_size))