Compare commits

..

15 Commits

Author SHA1 Message Date
Wojciech Kubicki
b761c83ee3 Merge branch 'main' of https://git.wmi.amu.edu.pl/s483780/inteligenty-traktor 2024-06-10 15:38:10 +02:00
Wojciech Kubicki
790438a9a2 fix: apply decision tree for choosing new crops 2024-06-10 15:36:47 +02:00
84699d2824 Update README.md 2024-06-10 15:36:04 +02:00
Adam Mikolajczak
f0970e414b fix: more examples to the learning set of decision tree 2024-06-10 15:07:49 +02:00
Adam Mikolajczak
d765a4f818 fix: pietruszka problem
adde even more examples to the learning set of decision tree. :)
2024-06-10 14:45:30 +02:00
Adam Mikolajczak
e053757bcf fix: ziemniaki problem
Added more examples to the learning set of decision tree.
2024-06-10 14:34:38 +02:00
Adam Mikolajczak
1856cd5fca fix: More exmaples with dynia added to the decision tree learning set 2024-06-10 14:18:22 +02:00
Wojciech Kubicki
a2c0137ddd fix: add missing prediction attribute to Tile class 2024-06-09 18:45:52 +02:00
Wojciech Kubicki
7c1814630a merge genetic algorithm with main 2024-06-09 17:10:52 +02:00
Wojciech Kubicki
77d559a63e feat: update field generation and load layouts in main program 2024-06-09 16:31:11 +02:00
Adam Mikolajczak
30878adedd feat: After arriving at selected by user tile, Tractor makes decision by using decision tree.
The decision is printed in terminal.
2024-06-09 11:26:14 +02:00
Adam Mikolajczak
a6f51420e1 feat: tractor goes on the tile clicked by user. 2024-06-09 11:11:29 +02:00
Adam Mikolajczak
b0ac67a105 fix: Slightly changed tractor.py - also checking if branch works 2024-06-09 10:39:48 +02:00
Zofia Lorenc
4595a5fe83 Merge branch 'sieci-neuronowe-uczenie' 2024-06-09 09:28:11 +02:00
Wojciech Kubicki
f7279fc846 feat: generate initial field layout using genetic algorithm 2024-06-08 22:05:54 +02:00
11 changed files with 243 additions and 82 deletions

1
.gitignore vendored
View File

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

View File

@ -1,3 +0,0 @@
{
"recommendations": ["sbsnippets.pytorch-snippets"]
}

View File

@ -8,6 +8,8 @@ Wymagane biblioteki do pobrania:
pip install pygame pip install pygame
pip install python-dotenv pip install python-dotenv
pip install pytholog pip install pytholog
pip install torch
pip install sklearn
``` ```
Stwórz plik `.env` w głównym folderze projektu o poniższej treści: Stwórz plik `.env` w głównym folderze projektu o poniższej treści:
@ -25,10 +27,6 @@ STARTING_DIRECTION = north
START_X = 0 START_X = 0
START_Y = 0 START_Y = 0
# coordinates of destination tile
FINAL_X = 15
FINAL_Y = 15
# tiles without plants modifier # tiles without plants modifier
FREE_TILES = 2 FREE_TILES = 2
``` ```
@ -37,9 +35,12 @@ Uruchom środowisko używając komend:
``` ```
cd src cd src
python generate_field.py
python main.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 ## 🧑‍🌾 Członkowie grupy
- Wojciech Kubicki (483780) - Wojciech Kubicki (483780)

1
src/.~lock.tree.csv# Normal file
View File

@ -0,0 +1 @@
,adam,adam-thinkpad,10.06.2024 15:05,file:///home/adam/.config/libreoffice/4;

View File

@ -1,13 +1,16 @@
import pygame import pygame
from tile import Tile from tile import Tile
from tractor import Tractor from tractor import Tractor
from ast import literal_eval
class Field: class Field:
def __init__(self): def __init__(self):
self.tiles = pygame.sprite.Group() self.tiles = pygame.sprite.Group()
# TODO: enable resizing field grid from 16x16 to any size with open('./field', 'r', encoding='UTF-8') as file:
for x in range(256): content = file.read()
self.tiles.add(Tile(x, self)) tiles = literal_eval(content)
for x in range(len(tiles)):
self.tiles.add(Tile(x, self, tiles[x]))
self.tractor = Tractor(self) self.tractor = Tractor(self)

116
src/generate_field.py Normal file
View File

@ -0,0 +1,116 @@
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):
score = 0
for index in range(len(field)):
neighbours = []
if index >= 16 and field[index-16] != 'water':
neighbours.append(field[index-16])
if index % 15 != 0 and field[index+1] != 'water':
neighbours.append(field[index+1])
if index < 240 and field[index+16] != 'water':
neighbours.append(field[index+16])
if index % 16 != 0 and field[index-1] != 'water':
neighbours.append(field[index-1])
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):
total_weights = sum(entity[0] for entity in population)
weights = [entity[0] / total_weights for entity in population]
selection = npchoice(len(population), size=2, replace=False, p=weights)
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 length is not equal to mom length")
if random() < 0.1:
mutation_index = randint(0, len(offspring) - 1)
while offspring[mutation_index] == 'water':
mutation_index = randint(0, len(offspring) - 1)
mutation = get_random_vegetable()
while mutation == offspring[mutation_index]:
mutation = get_random_vegetable()
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]
def get_random_vegetable():
vegetables = [x['Nazwa_warzywa'] for x in tractor_kb.query(pl.Expr("warzywo(Nazwa_warzywa)"))]
return vegetables[randint(0,len(vegetables)-1)]
def genetic_algorithm(population, iterations):
population_size = len(population)
for entity in population:
entity[0] = score_field(entity[1])
for iteration in range(iterations):
population.sort(key=lambda x: x[0], reverse=True)
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:
mom, dad = choose_parents(population)
child = breed_and_mutate(mom, dad)
new_offspring.append(child)
population.extend(new_offspring)
return population[0]
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(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, 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, szpinak, 0.85)",
"przeszkadza(burak, ziemniak, 0.85)", "przeszkadza(burak, ziemniak, 0.85)",
"przeszkadza(cebula, fasola, 0.80)", "przeszkadza(cebula, fasola, 0.80)",
"przeszkadza(cebula, groch, 85)", "przeszkadza(cebula, groch, 0.85)",
"przeszkadza(cebula, kalafior, 0.70)", "przeszkadza(cebula, kalafior, 0.70)",
"przeszkadza(cebula, kapusta, 0.75)", "przeszkadza(cebula, kapusta, 0.75)",
"przeszkadza(cebula, marchew, 0.85)", "przeszkadza(cebula, marchew, 0.85)",

View File

@ -1,7 +1,7 @@
import pygame import pygame
from field import Field from field import Field
import os import os
from config import TILE_SIZE, TICK_RATE from config import TILE_SIZE, TICK_RATE, FINAL_X, FINAL_Y
if __name__ == "__main__": if __name__ == "__main__":
pygame.init() pygame.init()
@ -13,10 +13,19 @@ if __name__ == "__main__":
field = Field() field = Field()
running = True running = True
while running: while running:
for event in pygame.event.get(): for event in pygame.event.get():
if event.type == pygame.QUIT: if event.type == pygame.QUIT:
running = False running = False
if event.type == pygame.MOUSEBUTTONDOWN:
x, y = pygame.mouse.get_pos()
print(f"Mouse clicked at: ({x}, {y})")
grid_x = x // TILE_SIZE
grid_y = y // TILE_SIZE
field.tractor.set_new_goal((grid_x, grid_y))
field.tractor.update() field.tractor.update()
screen.fill(WHITE) screen.fill(WHITE)

View File

@ -11,25 +11,26 @@ from PIL import Image
class Tile(pygame.sprite.Sprite): class Tile(pygame.sprite.Sprite):
def __init__(self, id, field): def __init__(self, id, field, tile_type):
super().__init__() super().__init__()
self.id = id self.id = id
x = id%16 x = id%16
y = id//16 y = id//16
self.field = field self.field = field
self.set_type(tile_type)
# temporary solution to have vegetables act as obstacles print('tile type set as', tile_type)
if random.randint(1, 10) % FREE_TILES == 0: if self.type == 'water':
vegetables = tractor_kb.query(pl.Expr("warzywo(Nazwa_warzywa)")) self.stage = 'no_plant'
random_vegetable = vegetables[random.randint(0, len(vegetables)-1)]['Nazwa_warzywa'] self.prediction = 'water'
self.water_level = 100
if random_vegetable in {'cebula','pietruszka','bób', 'dynia','ziemniak'}: elif self.type == 'grass':
random_vegetable = 'marchew' self.stage = 'no_plant'
self.prediction = 'grass'
self.set_type(random_vegetable)
self.water_level = random.randint(1, 5) * 10 self.water_level = random.randint(1, 5) * 10
else:
self.stage = 'planted' # wczesniej to była self.faza = 'posadzono' ale stwierdzilem ze lepiej po angielsku??? self.stage = 'planted' # wczesniej to była self.faza = 'posadzono' ale stwierdzilem ze lepiej po angielsku???
self.water_level = random.randint(1, 5) * 10
classes = [ classes = [
"bób", "brokuł", "brukselka", "burak", "cebula", "bób", "brokuł", "brukselka", "burak", "cebula",
@ -52,19 +53,6 @@ class Tile(pygame.sprite.Sprite):
self.prediction = self.predict(model, image_transforms, self.image_path, classes) self.prediction = self.predict(model, image_transforms, self.image_path, classes)
else:
if random.randint(1, 10) % 3 == 0:
self.set_type('water')
self.water_level = 100
self.stage = 'no_plant'
self.prediction = 'water'
else:
self.set_type('grass')
self.water_level = random.randint(1, 5) * 10
self.stage = 'no_plant'
self.prediction = 'grass'
self.rect = self.image.get_rect() self.rect = self.image.get_rect()
self.rect.topleft = (x * TILE_SIZE, y * TILE_SIZE) self.rect.topleft = (x * TILE_SIZE, y * TILE_SIZE)
@ -109,9 +97,6 @@ class Tile(pygame.sprite.Sprite):
result = classes[predicted.item()] result = classes[predicted.item()]
if result == "ziemniak":
result = 'marchew'
return result return result

View File

@ -18,30 +18,33 @@ class Tractor(pygame.sprite.Sprite):
def __init__(self, field): def __init__(self, field):
super().__init__ super().__init__
self.field = field self.field = field
self.water = 50
self.image = pygame.image.load('images/tractor/east.png').convert_alpha() self.image = pygame.image.load('images/tractor/east.png').convert_alpha()
self.image = pygame.transform.scale(self.image, (TILE_SIZE, TILE_SIZE)) self.image = pygame.transform.scale(self.image, (TILE_SIZE, TILE_SIZE))
self.rect = self.image.get_rect() self.rect = self.image.get_rect()
self.direction = 'east'
self.direction = STARTING_DIRECTION self.start = (0, 0)
# TODO: enable tractor to start on other tile than (0,0) self.final = (0, 0)
self.start = (START_X, START_Y)
self.final = (FINAL_X, FINAL_Y)
print('destination @', self.final[0], self.final[1]) print('destination @', self.final[0], self.final[1])
self.rect.topleft = (self.start[0] * TILE_SIZE, self.start[1] * TILE_SIZE) self.rect.topleft = (self.start[0] * TILE_SIZE, self.start[1] * TILE_SIZE)
self.water = 50 self.rect.topleft = (self.start[0] * TILE_SIZE, self.start[1] * TILE_SIZE)
self.actions = []
# A-STAR self.action_index = 0
# came_from, total_cost = self.a_star()
# path = self.reconstruct_path(came_from)
# self.actions = self.recreate_actions(path)
# self.action_index = 0
# DECISION TREE: # DECISION TREE:
self.label_encoders = {} self.label_encoders = {}
self.load_decision_tree_model() self.load_decision_tree_model()
def set_new_goal(self, goal):
self.start = self.get_coordinates()
self.final = goal
came_from, total_cost = self.a_star()
path = self.reconstruct_path(came_from)
self.actions = self.recreate_actions(path)
self.action_index = 0
print(f"New goal set to: {self.final}")
def load_decision_tree_model(self): def load_decision_tree_model(self):
data = pd.read_csv('tree.csv') data = pd.read_csv('tree.csv')
@ -94,13 +97,11 @@ class Tractor(pygame.sprite.Sprite):
def draw(self, surface): def draw(self, surface):
surface.blit(self.image, self.rect) surface.blit(self.image, self.rect)
def get_coordinates(self): def get_coordinates(self):
x = self.rect.x // TILE_SIZE x = self.rect.x // TILE_SIZE
y = self.rect.y // TILE_SIZE y = self.rect.y // TILE_SIZE
return (x,y) return (x,y)
def move(self): def move(self):
if self.direction == "north" and self.rect.y > 0: if self.direction == "north" and self.rect.y > 0:
self.rect.y -= TILE_SIZE self.rect.y -= TILE_SIZE
@ -163,28 +164,20 @@ class Tractor(pygame.sprite.Sprite):
else: else:
self.move() self.move()
def update(self): def decision_tree(self):
# A STAR: match (self.get_current_tile().type):
# if self.action_index == len(self.actions): case ('grass'):
# return action = self.make_decision()
# action = self.actions[self.action_index] case ('water'):
action = 'nothing'
# match (action): case _:
# case ('move'): action = 'water'
# self.move()
# case ('left'):
# self.rotate('left')
# case ('right'):
# self.rotate('right')
# DECISION TREE:
action = self.make_decision()
if (self.get_current_tile().type != 'grass' or self.get_current_tile().type == 'water'): action = 'move'
self.prev_action = action self.prev_action = action
print("Decyzja podjęta przez drzewo decyzyjne: ", action)
match (action): match (action):
case ('move'): case ('nothing'):
pass pass
#self.move_rotating() #self.move_rotating()
case ('harvest'): case ('harvest'):
@ -192,7 +185,7 @@ class Tractor(pygame.sprite.Sprite):
case ('water'): case ('water'):
self.get_current_tile().water_level += 10 self.get_current_tile().water_level += 10
case ('plant(bób)'): case ('plant(bób)'):
self.get_current_tile().set_type('marchew') self.get_current_tile().set_type('bób')
case ('plant(brokuł)'): case ('plant(brokuł)'):
self.get_current_tile().set_type('brokuł') self.get_current_tile().set_type('brokuł')
case ('plant(brukselka)'): case ('plant(brukselka)'):
@ -200,11 +193,11 @@ class Tractor(pygame.sprite.Sprite):
case ('plant(burak)'): case ('plant(burak)'):
self.get_current_tile().set_type('burak') self.get_current_tile().set_type('burak')
case ('plant(cebula)'): case ('plant(cebula)'):
self.get_current_tile().set_type('marchew') self.get_current_tile().set_type('cebula')
case ('plant(cukinia)'): case ('plant(cukinia)'):
self.get_current_tile().set_type('cukinia') self.get_current_tile().set_type('cukinia')
case ('plant(dynia)'): case ('plant(dynia)'):
self.get_current_tile().set_type('fasola') self.get_current_tile().set_type('dynia')
case ('plant(fasola)'): case ('plant(fasola)'):
self.get_current_tile().set_type('fasola') self.get_current_tile().set_type('fasola')
case ('plant(groch)'): case ('plant(groch)'):
@ -224,7 +217,7 @@ class Tractor(pygame.sprite.Sprite):
case ('plant(papryka)'): case ('plant(papryka)'):
self.get_current_tile().set_type('papryka') self.get_current_tile().set_type('papryka')
case ('plant(pietruszka)'): case ('plant(pietruszka)'):
self.get_current_tile().set_type('marchew') self.get_current_tile().set_type('pietruszka')
case ('plant(pomidor)'): case ('plant(pomidor)'):
self.get_current_tile().set_type('pomidor') self.get_current_tile().set_type('pomidor')
case ('plant(por)'): case ('plant(por)'):
@ -241,8 +234,22 @@ class Tractor(pygame.sprite.Sprite):
self.get_current_tile().set_type('szpinak') self.get_current_tile().set_type('szpinak')
case ('plant(ziemniak)'): case ('plant(ziemniak)'):
self.get_current_tile().set_type('ziemniak') self.get_current_tile().set_type('ziemniak')
self.move_2()
#self.action_index += 1 def update(self):
# A STAR:
if self.action_index == len(self.actions):
return
action = self.actions[self.action_index]
match (action):
case ('move'):
self.move()
case ('left'):
self.rotate('left')
case ('right'):
self.rotate('right')
self.action_index += 1
if self.get_current_tile().type == "grass": if self.get_current_tile().type == "grass":
print("Co jest faktycznie: trawa") print("Co jest faktycznie: trawa")
@ -253,8 +260,12 @@ class Tractor(pygame.sprite.Sprite):
print("Co jest faktycznie: ", self.get_current_tile().type) print("Co jest faktycznie: ", self.get_current_tile().type)
print("\n") print("\n")
if self.get_coordinates() == self.final:
self.decision_tree()
return return
def log_info(self): def log_info(self):
# print on what tile type the tractor is on # print on what tile type the tractor is on
x = self.rect.x // TILE_SIZE x = self.rect.x // TILE_SIZE
@ -367,13 +378,12 @@ class Tractor(pygame.sprite.Sprite):
if current == self.final: if current == self.final:
break break
# next_node: tuple[int, int]
for next_node in self.neighboring_nodes(coordinates=current): for next_node in self.neighboring_nodes(coordinates=current):
enter_cost = self.cost_of_entering_node(coordinates=next_node) enter_cost = self.cost_of_entering_node(coordinates=next_node)
new_cost: int = cost_so_far[current] + enter_cost new_cost = cost_so_far[current] + enter_cost
if next_node not in cost_so_far or new_cost < cost_so_far[next_node]: if next_node not in cost_so_far or new_cost < cost_so_far[next_node]:
cost_so_far[next_node] = new_cost cost_so_far[next_node] = new_cost
priority = new_cost + self.manhattan_cost(current) priority = new_cost + self.manhattan_cost(next_node)
heapq.heappush(fringe, (priority, next_node)) heapq.heappush(fringe, (priority, next_node))
came_from[next_node] = current came_from[next_node] = current

View File

@ -217,3 +217,41 @@ cebula,40,growing,szpinak,grass,kapusta,szpinak,move
cebula,50,growing,water,szpinak,kalarepa,grass,move cebula,50,growing,water,szpinak,kalarepa,grass,move
cebula,10,growing,szpinak,grass,marchew,grass,move cebula,10,growing,szpinak,grass,marchew,grass,move
cebula,20,growing,grass,cebula,burak,grass,move cebula,20,growing,grass,cebula,burak,grass,move
grass,10,no_plant,szpinak,dynia,szpinak,water,plant(seler)
grass,10,no_plant,marchew,dynia,grass,water,plant(seler)
grass,10,no_plant,water,dynia,cebula,cebula,plant(seler)
grass,10,no_plant,ogórek,dynia,cebula,grass,plant(seler)
grass,10,no_plant,szpinak,dynia,fasola,grass,plant(seler)
ziemniak,40,planted,water,grass,szpinak,grass,move
ziemniak,50,grown,szpinak,szpinak,grass,grass,move
ziemniak,10,planted,water,ziemniak,seler,water,move
ziemniak,20,planted,szpinak,szpinak,grass,seler,move
ziemniak,30,planted,grass,water,ziemniak,grass,move
ziemniak,40,planted,growing,grass,grass,burak,move
ziemniak,50,grown,growing,szpinak,water,grass,move
ziemniak,10,planted,growing,water,szpinak,marchew,move
ziemniak,20,planted,growing,szpinak,grass,kapusta,move
cukinia,50,grown,brukselka,water,grass,jarmuż,move
dynia,0,grown,burak,grass,pietruszka,kalafior,move
grass,10,grown,cebula,grass,pietruszka,kalarepa,move
grass,20,grown,cukinia,groch,pietruszka,kapusta,move
jarmuż,30,grown,grass,water,pietruszka,brokuł,move
kalafior,40,grown,fasola,pietruszka,water,brukselka,move
grass,10,grown,cebula,pietruszka,grass,kalarepa,move
grass,20,grown,cukinia,pietruszka,groch,kapusta,move
jarmuż,30,grown,grass,pietruszka,water,brokuł,move
kalafior,40,grown,fasola,kalafior,water,brukselka,move
bób,40,planted,jarmuż,grass,water,bób,move
brokuł,10,growing,kalafior,brokuł,bób,kalafior,move
brukselka,20,growing,water,brukselka,grass,bób,move
burak,30,planted,bób,burak,brukselka,bób,move
cebula,40,growing,brokuł,bób,burak,bób,move
cukinia,40,growing,grass,bób,water,grass,move
dynia,10,planted,grass,grass,cukinia,kalafior,move
grass,40,planted,jarmuż,grass,water,bób,move
grass,10,growing,kalafior,brokuł,bób,kalafior,move
grass,20,growing,water,bób,grass,bób,move
grass,30,planted,bób,burak,brukselka,bób,move
grass,40,growing,brokuł,bób,burak,bób,move
grass,40,growing,grass,bób,water,grass,move
grass,10,planted,bób,grass,cukinia,kalafior,move

1 tile_type water_level plant_stage neighbor_N neighbor_E neighbor_W neighbor_S action
217 cebula 50 growing water szpinak kalarepa grass move
218 cebula 10 growing szpinak grass marchew grass move
219 cebula 20 growing grass cebula burak grass move
220 grass 10 no_plant szpinak dynia szpinak water plant(seler)
221 grass 10 no_plant marchew dynia grass water plant(seler)
222 grass 10 no_plant water dynia cebula cebula plant(seler)
223 grass 10 no_plant ogórek dynia cebula grass plant(seler)
224 grass 10 no_plant szpinak dynia fasola grass plant(seler)
225 ziemniak 40 planted water grass szpinak grass move
226 ziemniak 50 grown szpinak szpinak grass grass move
227 ziemniak 10 planted water ziemniak seler water move
228 ziemniak 20 planted szpinak szpinak grass seler move
229 ziemniak 30 planted grass water ziemniak grass move
230 ziemniak 40 planted growing grass grass burak move
231 ziemniak 50 grown growing szpinak water grass move
232 ziemniak 10 planted growing water szpinak marchew move
233 ziemniak 20 planted growing szpinak grass kapusta move
234 cukinia 50 grown brukselka water grass jarmuż move
235 dynia 0 grown burak grass pietruszka kalafior move
236 grass 10 grown cebula grass pietruszka kalarepa move
237 grass 20 grown cukinia groch pietruszka kapusta move
238 jarmuż 30 grown grass water pietruszka brokuł move
239 kalafior 40 grown fasola pietruszka water brukselka move
240 grass 10 grown cebula pietruszka grass kalarepa move
241 grass 20 grown cukinia pietruszka groch kapusta move
242 jarmuż 30 grown grass pietruszka water brokuł move
243 kalafior 40 grown fasola kalafior water brukselka move
244 bób 40 planted jarmuż grass water bób move
245 brokuł 10 growing kalafior brokuł bób kalafior move
246 brukselka 20 growing water brukselka grass bób move
247 burak 30 planted bób burak brukselka bób move
248 cebula 40 growing brokuł bób burak bób move
249 cukinia 40 growing grass bób water grass move
250 dynia 10 planted grass grass cukinia kalafior move
251 grass 40 planted jarmuż grass water bób move
252 grass 10 growing kalafior brokuł bób kalafior move
253 grass 20 growing water bób grass bób move
254 grass 30 planted bób burak brukselka bób move
255 grass 40 growing brokuł bób burak bób move
256 grass 40 growing grass bób water grass move
257 grass 10 planted bób grass cukinia kalafior move