From 77d559a63e377a51f6f2d27150f4a686782ea9c7 Mon Sep 17 00:00:00 2001 From: Wojciech Kubicki Date: Sun, 9 Jun 2024 16:31:11 +0200 Subject: [PATCH] feat: update field generation and load layouts in main program --- .gitignore | 1 + README.md | 3 ++ src/field.py | 9 ++++-- src/generate_field.py | 69 +++++++++++++++++++------------------------ src/kb.py | 2 +- src/tile.py | 29 +++++------------- 6 files changed, 50 insertions(+), 63 deletions(-) diff --git a/.gitignore b/.gitignore index 4f37e584..46f5d421 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ __pycache__/ .DS_Store .env +src/field diff --git a/README.md b/README.md index 5c271f0d..29b984e7 100644 --- a/README.md +++ b/README.md @@ -37,9 +37,12 @@ Uruchom środowisko używając komend: ``` cd src +python generate_field.py python main.py ``` +Skrypt `generate_field.py` musi zostać przy pierwszym uruchomieniu, aby wygenerować pole. Kolejne wykonania skryptu nie są wymagane. + ## 🧑‍🌾 Członkowie grupy - Wojciech Kubicki (483780) diff --git a/src/field.py b/src/field.py index f085b9ae..7124041f 100644 --- a/src/field.py +++ b/src/field.py @@ -1,13 +1,16 @@ import pygame from tile import Tile from tractor import Tractor +from ast import literal_eval class Field: def __init__(self): self.tiles = pygame.sprite.Group() - # TODO: enable resizing field grid from 16x16 to any size - for x in range(256): - self.tiles.add(Tile(x, self)) + with open('./field', 'r', encoding='UTF-8') as file: + content = file.read() + tiles = literal_eval(content) + for x in range(len(tiles)): + self.tiles.add(Tile(x, self, tiles[x])) self.tractor = Tractor(self) diff --git a/src/generate_field.py b/src/generate_field.py index 9d7fb069..bd007662 100644 --- a/src/generate_field.py +++ b/src/generate_field.py @@ -1,6 +1,7 @@ from random import randint, choices, random from kb import tractor_kb, multi_sasiedzi import pytholog as pl +from numpy.random import choice as npchoice def score_field(field): @@ -17,33 +18,32 @@ def score_field(field): if index % 16 != 0 and field[index-1] != 'water': neighbours.append(field[index-1]) - score += multi_sasiedzi(field[index], neighbours)[0]["Mul"] + mod = multi_sasiedzi(field[index], neighbours)[0]["Mul"] + if mod > 10: + print(mod, '= multi(', field[index], ', ', neighbours, ')') + score += mod + score = score / 256 return score def choose_parents(population): - weights = [x[0] for x in population] - - weights_sum = 0 - for weight in weights: - weights_sum += weight + total_weights = sum(entity[0] for entity in population) - weights = [weight/weights_sum for weight in weights] + weights = [entity[0] / total_weights for entity in population] - mom = choices(population, cum_weights=weights, k=1)[0] - remaining_elements = [el for el in population if el != mom] - remaining_weights = [weights[population.index(el)] for el in remaining_elements] - dad = choices(remaining_elements, weights=remaining_weights, k=1)[0] + selection = npchoice(len(population), size=2, replace=False, p=weights) - return mom, dad + parents = [population[i] for i in selection] + + return parents[0], parents[1] def breed_and_mutate(mom, dad): crossover_point = randint(1, len(mom[1]) - 2) offspring = mom[1][:crossover_point] + dad[1][crossover_point:] if len(offspring) != len(mom): - ValueError("offspring lenght is not equal to mom length") + ValueError("offspring length is not equal to mom length") if random() < 0.1: mutation_index = randint(0, len(offspring) - 1) @@ -57,6 +57,7 @@ def breed_and_mutate(mom, dad): offspring[mutation_index] = mutation offspring_score = score_field(offspring) + # print('offspring score', offspring_score, 'for parents', mom[0], 'and', dad[0]) return [offspring_score, offspring] @@ -73,9 +74,10 @@ def genetic_algorithm(population, iterations): for entity in population: entity[0] = score_field(entity[1]) - for _ in range(iterations): + for iteration in range(iterations): population.sort(key=lambda x: x[0], reverse=True) - population = population[:5] + print('\n=====\n\n💪 Best individual in iteration', iteration, 'has a score of', population[0][0]) + population = population[:population_size//2] new_offspring = [] while len(population) + len(new_offspring) < population_size: @@ -88,36 +90,27 @@ def genetic_algorithm(population, iterations): return population[0] -vegetables = [x['Nazwa_warzywa'] for x in tractor_kb.query(pl.Expr("warzywo(Nazwa_warzywa)"))] -# water tiles locations -# _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -# _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -# _ _ X X _ _ _ _ _ _ X X X _ _ _ -# _ X X X _ _ _ _ _ X X X _ _ _ _ -# _ _ X _ _ _ _ _ _ _ _ _ _ _ _ _ -# _ _ X _ _ _ _ X X _ X X _ _ _ _ -# _ _ _ _ _ _ X X X X X X _ _ _ _ -# _ _ _ _ _ _ X X _ _ _ _ _ _ _ _ -# _ _ _ _ _ _ _ X X _ _ _ _ _ _ _ -# _ _ _ _ X _ _ _ _ _ _ _ _ _ _ _ -# _ _ _ _ X X _ _ _ _ _ _ _ _ _ _ -# _ _ _ _ X _ _ _ _ _ X _ _ _ _ _ -# _ _ _ _ _ _ _ _ X X X X _ _ _ _ -# _ _ _ X _ _ _ _ _ X X X _ _ _ _ -# _ X _ _ _ _ _ _ _ _ _ _ _ _ _ _ -# _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ -water_tile_indexes = [37, 38, 45, 46, 47, 53, 54, 55, 61, 62, 63, 71, 88, 93, 94, 96, 97, 109, 110, 111, 112, 113, 114, 126, 127, 144, 145, 158, 175, 176, 192, 198, 213, 214, 215, 216, 225, 231, 232, 233, 240] population = [] +# each field has unmutable locations of water and grass tiles +water_tile_indexes = [1, 2, 3, 34, 37, 44, 45, 53, 60, 61, 69, 81, 82, 83, 84, 119, 120, 121, 136, 152, 187, 194, 202, 203, 204, 210, 219, 226, 227, 228] +grass_tile_indexes = [0, 39, 40, 56, 71, 72, 73, 86, 88, 114, 115, 130, 146, 147, 163, 164, 166, 167, 180, 181, 182, 231, 232, 233] +vegetables = [x['Nazwa_warzywa'] for x in tractor_kb.query(pl.Expr("warzywo(Nazwa_warzywa)"))] -for _ in range(10): +for _ in range(100): field = [vegetables[randint(0, 24)] for _ in range(256)] for index in water_tile_indexes: field[index] = "water" + for index in grass_tile_indexes: + field[index] = "grass" + # entities of the population are stored with two properties # the first being the average score of the field # and the second being the layout of the field population.append([0, field]) -best = genetic_algorithm(population, 10) -print('final field layout', best[1]) -print('final field multiplier score', best[0]) +best = genetic_algorithm(population, 20) +print('\n=====\n\nfinal field multiplier score is', best[0]) +with open('field', 'w', encoding='utf-8') as file: + file.write(str(best[1])) +file.close +print('final field layout saved to file "field" in the current working directory\n') diff --git a/src/kb.py b/src/kb.py index 0320e307..5d9165c2 100644 --- a/src/kb.py +++ b/src/kb.py @@ -370,7 +370,7 @@ tractor_kb([ "przeszkadza(burak, szpinak, 0.85)", "przeszkadza(burak, ziemniak, 0.85)", "przeszkadza(cebula, fasola, 0.80)", - "przeszkadza(cebula, groch, 85)", + "przeszkadza(cebula, groch, 0.85)", "przeszkadza(cebula, kalafior, 0.70)", "przeszkadza(cebula, kapusta, 0.75)", "przeszkadza(cebula, marchew, 0.85)", diff --git a/src/tile.py b/src/tile.py index da49e101..e4defae5 100644 --- a/src/tile.py +++ b/src/tile.py @@ -7,35 +7,22 @@ from config import TILE_SIZE, FREE_TILES class Tile(pygame.sprite.Sprite): - def __init__(self, id, field): + def __init__(self, id, field, tile_type): super().__init__() self.id = id x = id%16 y = id//16 self.field = field - - # temporary solution to have vegetables act as obstacles - if random.randint(1, 10) % FREE_TILES == 0: - vegetables = tractor_kb.query(pl.Expr("warzywo(Nazwa_warzywa)")) - random_vegetable = vegetables[random.randint(0, len(vegetables)-1)]['Nazwa_warzywa'] - - if random_vegetable in {'cebula','pietruszka','bób', 'dynia','ziemniak'}: - random_vegetable = 'marchew' - - self.set_type(random_vegetable) + self.set_type(tile_type) + if self.type == 'water': + self.stage = 'no_plant' + self.water_level = 100 + elif self.type == 'grass': + self.stage = 'no_plant' self.water_level = random.randint(1, 5) * 10 - self.stage = 'planted' # wczesniej to była self.faza = 'posadzono' ale stwierdzilem ze lepiej po angielsku??? else: - if random.randint(1, 10) % 3 == 0: - self.set_type('water') - self.water_level = 100 - self.stage = 'no_plant' - else: - self.set_type('grass') - self.water_level = random.randint(1, 5) * 10 - self.stage = 'no_plant' - + self.stage = 'planted' self.rect = self.image.get_rect() self.rect.topleft = (x * TILE_SIZE, y * TILE_SIZE)