diff --git a/AStar.py b/AStar.py new file mode 100644 index 0000000..0827e86 --- /dev/null +++ b/AStar.py @@ -0,0 +1,284 @@ +""" +f(n) = g(n) + h(n) +g(n) = dotychczasowy koszt -> dodać currentCost w Node lub brać koszt na nowo przy oddtawrzaniu ścieżki +h(n) = abs(state['x'] - goalTreassure[0]) + abs(state['y'] - goalTreassure[1]) -> odległość Manhatan -> można zrobić jeszcze drugą wersje gdzie mnoży się razy 5.5 ze wzgledu na średni koszt przejścia +Należy zaimplementować kolejkę priorytetową oraz zaimplementować algorytm przeszukiwania grafu stanów z uwzględnieniem kosztu za pomocą przerobienia algorytmu przeszukiwania grafu stanów +""" +import random +import pygame +import Node +import BFS +from displayControler import NUM_X, NUM_Y +from Pole import stoneList +from queue import PriorityQueue + + +def getRandomGoalTreasure(): + while True: + goalTreasure = (random.randint(0, NUM_X - 1), random.randint(0, NUM_Y - 1)) # Współrzędne celu + if goalTreasure not in stoneList: + break + return goalTreasure + + +def heuristic(state, goal): + # Oblicz odległość Manhattanowską między aktualnym stanem a celem + manhattan_distance = abs(state['x'] - goal[0]) + abs(state['y'] - goal[1]) + return manhattan_distance + + +'''def get_cost_for_plant(plant_name): + plant_costs = { + "pszenica": 7, + "kukurydza": 9, + "ziemniak": 2, + "slonecznik": 5, + "borowka": 3, + "winogrono": 4, + "mud": 15, + "dirt": 0, + } + if plant_name in plant_costs: + return plant_costs[plant_name] + else: + # Jeśli nazwa rośliny nie istnieje w słowniku, zwróć domyślną wartość + return 0 +''' + +def A_star(istate, pole, goalTreasure): + # goalTreasure = (random.randint(0,NUM_X-1), random.randint(0,NUM_Y-1)) + # #jeśli chcemy używać random musimy wykreslić sloty z kamieniami, ponieważ tez mogą się wylosować i wtedy traktor w ogóle nie rusza + #lub zrobić to jakoś inaczej, np. funkcja szukająca najmniej nawodnionej rośliny + + # przeniesione wyżej do funkcji getRandomGoalTreasure, wykorzystywana jest w App.py + # while True: + # goalTreasure = (random.randint(0, NUM_X - 1), random.randint(0, NUM_Y - 1)) # Współrzędne celu + # if goalTreasure not in stoneList: + # break + + fringe = PriorityQueue() # Kolejka priorytetowa dla wierzchołków do rozpatrzenia + explored = [] # Lista odwiedzonych stanów + obrot = 1 + + # Tworzenie węzła początkowego + x = Node.Node(istate) + x.g = 0 + x.h = heuristic(x.state, goalTreasure) + fringe.put((x.g + x.h, x)) # Dodanie węzła do kolejki + total_cost = 0 + + while not fringe.empty(): + _, elem = fringe.get() # Pobranie węzła z najniższym priorytetem + + if BFS.goalTest3(elem.state, goalTreasure): # Sprawdzenie, czy osiągnięto cel + path = [] + cost_list=[] + while elem.parent is not None: # Odtworzenie ścieżki + path.append([elem.parent, elem.action]) + elem = elem.parent + for node, action in path: + # Obliczanie kosztu ścieżki dla każdego pola i wyświetlanie + plant_cost = get_plant_name_and_cost_from_coordinates(node.state['x'],node.state['y'], pole) + if action == "left" or action == "right": # Liczenie kosztu tylko dla pól nie będących obrotami + total_cost += obrot + cost_list.append(obrot) + else: + total_cost += plant_cost + cost_list.append(plant_cost) + return path,cost_list,total_cost + + explored.append(elem.state) + + for resp in succ3A(elem.state): + child_state = resp[1] + if child_state not in explored: + child = Node.Node(child_state) + child.parent = elem + child.action = resp[0] + + # Pobranie nazwy rośliny z danego slotu na podstawie współrzędnych + plant_cost = get_plant_name_and_cost_from_coordinates(child_state['x'], child_state['y'], pole) + # Pobranie kosztu dla danej rośliny + #plant_cost = get_cost_for_plant(plant_name) + + if child.action == "left" or child.action == "right": + child.g = elem.g + obrot + else: + child.g = elem.g + plant_cost + + # Obliczenie heurystyki dla dziecka + child.h = heuristic(child.state, goalTreasure) + + in_fringe = False + for priority, item in fringe.queue: + if item.state == child.state: + in_fringe = True + if priority > child.g + child.h: + # Jeśli znaleziono węzeł w kolejce o gorszym priorytecie, zastąp go nowym + fringe.queue.remove((priority, item)) + fringe.put((child.g + child.h, child)) + break + + if not in_fringe: + # Jeśli stan dziecka nie jest w kolejce, dodaj go do kolejki + fringe.put((child.g + child.h, child)) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + quit() + + return False + + +def get_plant_name_and_cost_from_coordinates(x, y, pole): + if (x, y) in pole.slot_dict: # Sprawdzenie, czy podane współrzędne znajdują się na polu + slot = pole.slot_dict[(x, y)] # Pobranie slotu na podstawie współrzędnych + if slot.plant: # Sprawdzenie, czy na slocie znajduje się roślina + return slot.plant.stan.koszt # Zwrócenie nazwy rośliny na slocie + else: + return 0 # jeśli na slocie nie ma rośliny + else: + return 0 # jeśli podane współrzędne są poza polem + + +#to ogólnie identyczna funkcja jak w BFS ale nie chciałam tam ruszać, żeby przypadkiem nie zapsuć do BFS, +#tylko musiałam dodac sprawdzenie kolizji, bo traktor brał sloty z Y których nie ma na planszy +def succ3A(state): + resp = [] + if state["direction"] == "N": + if state["y"] > 0 and (state['x'], state["y"] - 1) not in stoneList: + resp.append(["forward", {'x': state["x"], 'y': state["y"]-1, 'direction': state["direction"]}]) + resp.append(["right", {'x': state["x"], 'y': state["y"], 'direction': "E"}]) + resp.append(["left", {'x': state["x"], 'y': state["y"], 'direction': "W"}]) + elif state["direction"] == "S": + if state["y"] < NUM_Y - 1 and (state['x'], state["y"] + 1) not in stoneList: + resp.append(["forward", {'x': state["x"], 'y': state["y"]+1, 'direction': state["direction"]}]) + resp.append(["right", {'x': state["x"], 'y': state["y"], 'direction': "W"}]) + resp.append(["left", {'x': state["x"], 'y': state["y"], 'direction': "E"}]) + elif state["direction"] == "E": + if state["x"] < NUM_X - 1 and (state['x'] + 1, state["y"]) not in stoneList: + resp.append(["forward", {'x': state["x"]+1, 'y': state["y"], 'direction': state["direction"]}]) + resp.append(["right", {'x': state["x"], 'y': state["y"], 'direction': "S"}]) + resp.append(["left", {'x': state["x"], 'y': state["y"], 'direction': "N"}]) + else: #state["direction"] == "W" + if state["x"] > 0 and (state['x'] - 1, state["y"]) not in stoneList: + resp.append(["forward", {'x': state["x"]-1, 'y': state["y"], 'direction': state["direction"]}]) + resp.append(["right", {'x': state["x"], 'y': state["y"], 'direction': "N"}]) + resp.append(["left", {'x': state["x"], 'y': state["y"], 'direction': "S"}]) + + return resp + + + + +def heuristic2(state, goal): + # Oblicz odległość Manhattanowską między aktualnym stanem a celem + manhattan_distance = (abs(state['x'] - goal[0]) + abs(state['y'] - goal[1])) * 2.5 + return manhattan_distance + + +def A_star2(istate, pole, goalTreasure): + # goalTreasure = (random.randint(0,NUM_X-1), random.randint(0,NUM_Y-1)) + # #jeśli chcemy używać random musimy wykreslić sloty z kamieniami, ponieważ tez mogą się wylosować i wtedy traktor w ogóle nie rusza + #lub zrobić to jakoś inaczej, np. funkcja szukająca najmniej nawodnionej rośliny + + # przeniesione wyżej do funkcji getRandomGoalTreasure, wykorzystywana jest w App.py + # while True: + # goalTreasure = (random.randint(0, NUM_X - 1), random.randint(0, NUM_Y - 1)) # Współrzędne celu + # if goalTreasure not in stoneList: + # break + fringe = PriorityQueue() # Kolejka priorytetowa dla wierzchołków do rozpatrzenia + explored = [] # Lista odwiedzonych stanów + obrot = 1 + + # Tworzenie węzła początkowego + x = Node.Node(istate) + x.g = 0 + x.h = heuristic2(x.state, goalTreasure) + fringe.put((x.g + x.h, x)) # Dodanie węzła do kolejki + total_cost=0 + + while not fringe.empty(): + _, elem = fringe.get() # Pobranie węzła z najniższym priorytetem + + if BFS.goalTest3(elem.state, goalTreasure): # Sprawdzenie, czy osiągnięto cel + path = [] + cost_list=[] + while elem.parent is not None: # Odtworzenie ścieżki + path.append([elem.parent, elem.action]) + elem = elem.parent + for node, action in path: + # Obliczanie kosztu ścieżki dla każdego pola i wyświetlanie + plant_cost = get_plant_name_and_cost_from_coordinates(node.state['x'],node.state['y'], pole) + if action == "left" or action == "right": # Liczenie kosztu tylko dla pól nie będących obrotami + total_cost += obrot + cost_list.append(obrot) + else: + total_cost += plant_cost + cost_list.append(plant_cost) + return path,cost_list,total_cost + + explored.append(elem.state) + + for resp in succ3A(elem.state): + child_state = resp[1] + if child_state not in explored: + child = Node.Node(child_state) + child.parent = elem + child.action = resp[0] + + # Pobranie nazwy rośliny z danego slotu na podstawie współrzędnych + plant_cost = get_plant_name_and_cost_from_coordinates(child_state['x'], child_state['y'], pole) + + if child.action == "left" or child.action == "right": + child.g = elem.g + obrot + else: + child.g = elem.g + plant_cost + # Obliczenie heurystyki dla dziecka + child.h = heuristic2(child.state, goalTreasure) + + in_fringe = False + for priority, item in fringe.queue: + if item.state == child.state: + in_fringe = True + if priority > child.g + child.h: + # Jeśli znaleziono węzeł w kolejce o gorszym priorytecie, zastąp go nowym + fringe.queue.remove((priority, item)) + fringe.put((child.g + child.h, child)) + break + + if not in_fringe: + # Jeśli stan dziecka nie jest w kolejce, dodaj go do kolejki + fringe.put((child.g + child.h, child)) + + for event in pygame.event.get(): + if event.type == pygame.QUIT: + quit() + + return False + +""" +TO TEST SPEED OF ASTAR + +test_speed = False + + if test_speed: + time1 = 0 + time2 = 0 + cost1 = 0 + cost2 = 0 + for i in range(500): + print(i) + start = time.time() + aStarRoot, cost_list, total_cost = AStar.A_star({'x': 0, 'y': 0, 'direction': "E"}, pole, goalTreasure) + end = time.time() + time1 += end - start + cost1 += total_cost + start = time.time() + aStarRoot2, cost_list, total_cost = AStar.A_star2({'x': 0, 'y': 0, 'direction': "E"}, pole, goalTreasure) + end = time.time() + time2 += end - start + cost2 += total_cost + print(time1, time2) + print(float(cost1 / 1000), float(cost2 / 1000)) +""" \ No newline at end of file diff --git a/App.py b/App.py index 93c18ba..090d36d 100644 --- a/App.py +++ b/App.py @@ -8,6 +8,17 @@ import Image import Osprzet import Ui import BFS +import AStar +import random + + +bfs1_flag=False +bfs2_flag=False #Change this lines to show different bfs implementation +bfs3_flag=False +Astar = False +Astar2 = True +if bfs3_flag or Astar or Astar2: + Pole.stoneFlag = True pygame.init() @@ -17,13 +28,11 @@ FPS=5 clock=pygame.time.Clock() image_loader=Image.Image() image_loader.load_images() -pole=Pole.Pole(screen,image_loader) +goalTreasure = AStar.getRandomGoalTreasure() # nie wiem czy to najlepsze miejsce, obecnie pole zawiera pole gasStation, które służy do renderowania odpowiedniego zdjęcia +pole=Pole.Pole(screen,image_loader, goalTreasure) pole.draw_grid() #musi byc tutaj wywołane ponieważ inicjalizuje sloty do slownika ui=Ui.Ui(screen) #Tractor creation -bfs1_flag=True -bfs2_flag=False #Change this lines to show different bfs implementation -bfs3_flag=False traktor_slot = pole.get_slot_from_cord((0, 0)) traktor = Tractor.Tractor(traktor_slot, screen, Osprzet.opryskiwacz,clock,bfs2_flag) @@ -39,7 +48,10 @@ def init_demo(): #Demo purpose clock.tick(FPS) if(start_flag): ui.render_text_to_console(string_to_print="Przejazd inicjalizujacy- traktor sprawdza poziom nawodnienia") - traktor.initial_move(pole) + if not bfs1_flag: + time.sleep(2) + else: + traktor.initial_move(pole) traktor.reset_pos(pole) clock.tick(20) ui.clear_console() @@ -61,12 +73,46 @@ def init_demo(): #Demo purpose print_to_console("Traktor porusza sie obliczona sciezka BFS") traktor.move_by_root(bfsRoot2, pole, [traktor.irrigateSlot]) if(bfs3_flag): - bfsRoot3 = BFS.BFS2({'x': 0, 'y': 0, 'direction': "E"}) + bfsRoot3 = BFS.BFS3({'x': 0, 'y': 0, 'direction': "E"}) #displayControler: NUM_X: 20, NUM_Y: 12 (skarb) CHANGE THIS IN DCON BY HAND!!!!!!!! bfsRoot3.reverse() print_to_console("Traktor porusza sie obliczona sciezka BFS") traktor.move_by_root(bfsRoot3, pole, [traktor.irrigateSlot]) + if (Astar): + aStarRoot,cost_list,total_cost= AStar.A_star({'x': 0, 'y': 0, 'direction': "E"}, pole, goalTreasure) + if aStarRoot: + print("Pełna ścieżka agenta:") + aStarRoot.reverse() + cost_list.reverse() + i=0 + for node in aStarRoot: + state = node[0].state # Pobranie stanu z obiektu Node + action = node[1] # Pobranie akcji + print("Współrzędne pola:", state['x'], state['y'], "- Akcja:",action,"- Koszt: ",cost_list[i]) + i=i+1 + print_to_console("Traktor porusza się obliczoną ścieżką A*") + traktor.move_by_root(aStarRoot, pole, [traktor.irrigateSlot]) + print("Koszt:", total_cost) + else: + print_to_console("Nie można znaleźć ścieżki A*") # Wyświetl komunikat, jeśli nie znaleziono ścieżki + if (Astar2): + aStarRoot2,cost_list, total_cost= AStar.A_star2({'x': 0, 'y': 0, 'direction': "E"}, pole, goalTreasure) + if aStarRoot2: + print("Pełna ścieżka agenta:") + aStarRoot2.reverse() + cost_list.reverse() + i=0 + for node in aStarRoot2: + state = node[0].state # Pobranie stanu z obiektu Node + action = node[1] # Pobranie akcji + print("Współrzędne pola:", state['x'], state['y'], "- Akcja:",action,"- Koszt: ",cost_list[i]) + i=i+1 + print_to_console("Traktor porusza się obliczoną ścieżką A*") + traktor.move_by_root(aStarRoot2, pole, [traktor.irrigateSlot]) + print("Koszt:", total_cost) + else: + print_to_console("Nie można znaleźć ścieżki A*") # Wyświetl komunikat, jeśli nie znaleziono ścieżki start_flag=False @@ -106,3 +152,5 @@ def get_info(old_info): + + diff --git a/BFS.py b/BFS.py index 366bcac..4324471 100644 --- a/BFS.py +++ b/BFS.py @@ -3,6 +3,7 @@ import random import pygame import Node from displayControler import NUM_X, NUM_Y +from Pole import stoneList def goalTest1(hIndex): @@ -93,31 +94,31 @@ def BFS1(istate): -def goalTest2(state, goalTreassure): +def goalTest3(state, goalTreassure): if state["x"] == goalTreassure[0] and state["y"] == goalTreassure[1]: return True return False -def succ2(state): +def succ3(state): resp = [] if state["direction"] == "N": - if state["y"] > 0: + if state["y"] > 0 and (state['x'], state["y"] - 1) not in stoneList: resp.append(["forward", {'x': state["x"], 'y': state["y"]-1, 'direction': state["direction"]}]) resp.append(["right", {'x': state["x"], 'y': state["y"], 'direction': "E"}]) resp.append(["left", {'x': state["x"], 'y': state["y"], 'direction': "W"}]) elif state["direction"] == "S": - if state["y"] < NUM_Y: + if state["y"] < NUM_Y - 1 and (state['x'], state["y"] + 1) not in stoneList: resp.append(["forward", {'x': state["x"], 'y': state["y"]+1, 'direction': state["direction"]}]) resp.append(["right", {'x': state["x"], 'y': state["y"], 'direction': "W"}]) resp.append(["left", {'x': state["x"], 'y': state["y"], 'direction': "E"}]) elif state["direction"] == "E": - if state["x"] < NUM_X: + if state["x"] < NUM_X - 1 and (state['x'] + 1, state["y"]) not in stoneList: resp.append(["forward", {'x': state["x"]+1, 'y': state["y"], 'direction': state["direction"]}]) resp.append(["right", {'x': state["x"], 'y': state["y"], 'direction': "S"}]) resp.append(["left", {'x': state["x"], 'y': state["y"], 'direction': "N"}]) else: #state["zwrot"] == "W" - if state["x"] > 0: + if state["x"] > 0 and (state['x'] - 1, state["y"]) not in stoneList: resp.append(["forward", {'x': state["x"]-1, 'y': state["y"], 'direction': state["direction"]}]) resp.append(["right", {'x': state["x"], 'y': state["y"], 'direction': "N"}]) resp.append(["left", {'x': state["x"], 'y': state["y"], 'direction': "S"}]) @@ -125,14 +126,14 @@ def succ2(state): return resp -def check2(tab, state): +def check3(tab, state): for i in tab: if i.state == state: return False return True -def BFS2(istate): +def BFS3(istate): goalTreassuere = (random.randint(0,NUM_X-1), random.randint(0,NUM_Y-1)) print(goalTreassuere) fringe = [] @@ -148,7 +149,7 @@ def BFS2(istate): elem = fringe.pop(0) - if goalTest2(elem.state, goalTreassuere): + if goalTest3(elem.state, goalTreassuere): x = elem tab = [] while x.parent != None: @@ -158,8 +159,8 @@ def BFS2(istate): explored.append(elem) - for resp in succ2(elem.state): - if check2(fringe, resp[1]) and check2(explored, resp[1]): + for resp in succ3(elem.state): + if check3(fringe, resp[1]) and check3(explored, resp[1]): x = Node.Node(resp[1]) x.parent = elem x.action = resp[0] diff --git a/Image.py b/Image.py index a924bd0..3a8557d 100644 --- a/Image.py +++ b/Image.py @@ -7,23 +7,37 @@ class Image: self.plants_image_dict={} self.tractor_image=None self.garage_image=None + self.stone_image=None + self.gasStation_image=None def load_images(self): - files_plants={0:"borowka", + files_plants={ + 0:"borowka", 1:"kukurydza", 2:"pszenica", 3:"slonecznik", 4:"winogrono", - 5:"ziemniak"} + 5:"ziemniak", + 6:"dirt", + 7:"mud", + 8:"road"} for index in files_plants: - plant_image=pygame.image.load("images/plants/"+files_plants[index]+".jpg") + if index >= 6: + plant_image = pygame.image.load("images/" + files_plants[index] + ".jpg") + else: + plant_image=pygame.image.load("images/plants/"+files_plants[index]+".jpg") plant_image=pygame.transform.scale(plant_image,(dCon.CUBE_SIZE,dCon.CUBE_SIZE)) self.plants_image_dict[files_plants[index]]=plant_image tractor_image=pygame.image.load("images/traktor.png") tractor_image=pygame.transform.scale(tractor_image,(dCon.CUBE_SIZE,dCon.CUBE_SIZE)) garage=pygame.image.load("images/garage.png") self.garage_image=pygame.transform.scale(garage,(dCon.CUBE_SIZE,dCon.CUBE_SIZE)) + stone=pygame.image.load("images/stone.png") + self.stone_image=pygame.transform.scale(stone,(dCon.CUBE_SIZE,dCon.CUBE_SIZE)) + gasStation=pygame.image.load("images/gasStation.png") + self.gasStation_image=pygame.transform.scale(gasStation,(dCon.CUBE_SIZE,dCon.CUBE_SIZE)) + def return_random_plant(self): - x=random.randint(0,5) + x=random.randint(0,7) keys=list(self.plants_image_dict.keys()) plant=keys[x] return (plant,self.plants_image_dict[plant]) @@ -32,4 +46,10 @@ class Image: return (plant_name,self.plants_image_dict[plant_name]) def return_garage(self): - return self.garage_image \ No newline at end of file + return self.garage_image + + def return_stone(self): + return self.stone_image + + def return_gasStation(self): + return self.gasStation_image diff --git a/Node.py b/Node.py index cc88608..8982784 100644 --- a/Node.py +++ b/Node.py @@ -6,3 +6,8 @@ class Node: def __init__(self, state): self.state = state + def __lt__(self, other): + """ + Definicja metody __lt__ (less than), która jest wymagana do porównywania obiektów typu Node. + """ + return self.g + self.h < other.g + other.h \ No newline at end of file diff --git a/Pole.py b/Pole.py index dc7d82e..f0e5b8b 100644 --- a/Pole.py +++ b/Pole.py @@ -5,13 +5,18 @@ import pygame import time import Ui import math +import random + +stoneList = [(3,3), (3,4), (3,5), (3,6), (4,6), (5,6), (6,6), (7,6), (8,6), (9,6), (10,6), (11,6), (12,6), (13,6), (14,6), (15,6), (16,6), (16,7), (16,8), (16,9)] +stoneFlag = False class Pole: - def __init__(self,screen,image_loader): + def __init__(self,screen,image_loader, gasStation = (-1, -1)): self.screen=screen self.slot_dict={} #Slot are stored in dictionary with key being a Tuple of x and y coordinates so top left slot key is (0,0) and value is slot object self.ui=Ui.Ui(screen) self.image_loader=image_loader + self.gasStation=gasStation def get_slot_from_cord(self,coordinates): (x_axis,y_axis)=coordinates @@ -23,7 +28,7 @@ class Pole: def get_slot_dict(self): #returns whole slot_dict return self.slot_dict - + #Draw grid and tractor (new one) def draw_grid(self): for x in range(0,dCon.NUM_X): #Draw all cubes in X axis @@ -35,13 +40,20 @@ class Pole: slot_dict[coordinates].draw() garage=self.slot_dict[(0,0)] garage.set_garage_image() + if stoneFlag: + for i in stoneList: + st=self.slot_dict[i] + st.set_stone_image() + if self.gasStation[0] != -1: + st=self.slot_dict[self.gasStation] + st.set_gasStation_image() def randomize_colors(self): pygame.display.update() time.sleep(3) - self.ui.render_text("Randomizing Crops") + #self.ui.render_text("Randomizing Crops") for coordinates in self.slot_dict: - if(coordinates==(0,0)): + if(coordinates==(0,0) or coordinates in stoneList or coordinates == self.gasStation): continue else: self.slot_dict[coordinates].set_random_plant() @@ -64,4 +76,6 @@ class Pole: if(mouse_y