feat: update field generation and load layouts in main program

This commit is contained in:
Wojciech Kubicki 2024-06-09 16:31:11 +02:00
parent f7279fc846
commit 77d559a63e
6 changed files with 50 additions and 63 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
__pycache__/
.DS_Store
.env
src/field

View File

@ -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)

View File

@ -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)

View File

@ -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]
total_weights = sum(entity[0] for entity in population)
weights_sum = 0
for weight in weights:
weights_sum += weight
weights = [entity[0] / total_weights for entity in population]
weights = [weight/weights_sum for weight in weights]
selection = npchoice(len(population), size=2, replace=False, p=weights)
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]
parents = [population[i] for i in selection]
return mom, dad
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')

View File

@ -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)",

View File

@ -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.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.set_type(tile_type)
if self.type == 'water':
self.stage = 'no_plant'
self.water_level = 100
elif self.type == 'grass':
self.stage = 'no_plant'
else:
self.set_type('grass')
self.water_level = random.randint(1, 5) * 10
self.stage = 'no_plant'
else:
self.stage = 'planted'
self.rect = self.image.get_rect()
self.rect.topleft = (x * TILE_SIZE, y * TILE_SIZE)