From 997564080e71fcd1fd88e038176e74668a4bf216 Mon Sep 17 00:00:00 2001 From: MlodyJacky Date: Sun, 26 May 2024 10:12:10 +0200 Subject: [PATCH] feat and fix --- app.py | 88 ++++++++++++++++++++++++---------------------- classes/agent.py | 68 ++--------------------------------- decision_tree | 33 +++++++++++++++++ decisiontree.py | 4 +-- prefs.py | 6 ++-- sprites/dywan.png | Bin 0 -> 4259 bytes sprites/plama.png | Bin 0 -> 2211 bytes 7 files changed, 86 insertions(+), 113 deletions(-) create mode 100644 decision_tree create mode 100644 sprites/dywan.png create mode 100644 sprites/plama.png diff --git a/app.py b/app.py index 523358f..4afa9eb 100644 --- a/app.py +++ b/app.py @@ -9,67 +9,76 @@ from classes.agent import Agent from collections import deque import threading import time +import random pygame.init() window = pygame.display.set_mode((prefs.WIDTH, prefs.HEIGHT)) pygame.display.set_caption("Game Window") +table_coords = [(4, 4), (4, prefs.GRID_SIZE-5), (prefs.GRID_SIZE-5, 4), (prefs.GRID_SIZE-5, prefs.GRID_SIZE-5)] def initBoard(): + wall_probability = 0.001 global cells cells = [] for i in range(prefs.GRID_SIZE): row = [] for j in range(prefs.GRID_SIZE): - cell = Cell(i, j, 1) + waga = random.choices([1, 4, 5], weights=[0.7, 0.1, 0.1], k=1)[0] + cell = Cell(i, j, waga) + + if (i, j) not in table_coords: + if waga == 5: + cell.prepareTexture("sprites/plama.png") + + if waga == 4: + cell.prepareTexture("sprites/dywan.png") + + if random.random() < wall_probability: + cell.prepareTexture("sprites/wall.png") + cell.blocking_movement = True + # Wybierz kolor dla płytki na podstawie jej położenia - if i == 0 or i == prefs.GRID_SIZE - 1 or j == 0 or j == prefs.GRID_SIZE - 1: - color = (100, 20, 20) - elif i == 1 or i == prefs.GRID_SIZE - 2 or j == 1 or j == prefs.GRID_SIZE - 2: - color = (20, 100, 20) - elif i == 2 or i == prefs.GRID_SIZE - 3 or j == 2 or j == prefs.GRID_SIZE - 3: - color = (20, 20, 100) - else: - color = (150, 200, 200) - cell.color = color row.append(cell) cells.append(row) # Test # Na potrzeby prezentacji tworzę sobie prostokątne ściany na które nie da się wejść - x1 = 3 - y1 = 6 - for i in range(x1, x1+4): - for j in range(y1, y1+2): - cells[i][j].prepareTexture("sprites/wall.png") - cells[i][j].blocking_movement = True + # x1 = 3 + # y1 = 6 + # for i in range(x1, x1+4): + # for j in range(y1, y1+2): + # cells[i][j].prepareTexture("sprites/wall.png") + # cells[i][j].blocking_movement = True + for i in range(prefs.GRID_SIZE): + for j in range(prefs.GRID_SIZE): + if i == 0 or j==0 or j==prefs.GRID_SIZE-1 or (i == prefs.GRID_SIZE-1 and j != 17): + cells[i][j].prepareTexture("sprites/wall.png") + cells[i][j].blocking_movement = True - cells[6][4].interactableItem = BeerKeg(cells[6][4], "Beer Keg") + cells[6][6].interactableItem = BeerKeg(cells[6][6], "Beer Keg") cells[4][10].interactableItem = CoffeMachine(cells[4][10], "Coffe Machine") - cells[9][10].interactableItem = Table(cells[9][10], "Table") - cells[8][2].interactableItem = Table(cells[8][2], "Table") - cells[6][2].interactableItem = Table(cells[6][2], "Table") - cells[4][2].interactableItem = Table(cells[4][2], "Table") - for cell in cells: - for cel in cell: - cel.waga = Agent.get_cost((cel.X, cel.Y), cells) + cells[4][4].interactableItem = Table(cells[4][4], "Table") + cells[4][prefs.GRID_SIZE-5].interactableItem = Table(cells[4][prefs.GRID_SIZE-5], "Table") + cells[prefs.GRID_SIZE-5][4].interactableItem = Table(cells[prefs.GRID_SIZE-5][4], "Table") + cells[prefs.GRID_SIZE-5][prefs.GRID_SIZE-5].interactableItem = Table(cells[prefs.GRID_SIZE-5][prefs.GRID_SIZE-5], "Table") - cells[9][9].waga = 2 - cells[9][8].waga = 10 - cells[8][8].waga = 10 - cells[prefs.SPAWN_POINT[0]+1][prefs.SPAWN_POINT[1]].waga = 100 - cells[prefs.SPAWN_POINT[0]][prefs.SPAWN_POINT[1]-1].waga = 100 - cells[9][7].waga = 2 - cells[10][6].waga = 2 - cells[7][7].waga = 2 + # cells[9][9].waga = 2 + # cells[9][8].waga = 10 + # cells[8][8].waga = 10 + # cells[prefs.SPAWN_POINT[0]+1][prefs.SPAWN_POINT[1]].waga = 100 + # cells[prefs.SPAWN_POINT[0]][prefs.SPAWN_POINT[1]-1].waga = 100 + + # cells[9][7].waga = 2 + # cells[10][6].waga = 2 + # cells[7][7].waga = 2 def draw_grid(window, cells, agent): for i in range(prefs.GRID_SIZE): for j in range(prefs.GRID_SIZE): - cell = cells[i][j] - color = cell.color - pygame.draw.rect(window, cell.color, (i*prefs.CELL_SIZE, j*prefs.CELL_SIZE, prefs.CELL_SIZE, prefs.CELL_SIZE)) + cells[i][j].update(window) + if(cells[i][j].interactableItem): cells[i][j].interactableItem.update(window) if(not cells[i][j].blocking_movement): @@ -84,7 +93,7 @@ def draw_grid(window, cells, agent): initBoard() agent = Agent(prefs.SPAWN_POINT[0], prefs.SPAWN_POINT[1], cells) -target_x, target_y = 9, 11 +target_x, target_y = 18, 18 def watekDlaSciezkiAgenta(): time.sleep(3) @@ -137,13 +146,6 @@ while running: watek.daemon = True watek.start() - if keys[K_g]: - path, cost = agent.astar((target_x, target_y), start_cost=0) - print("Shortest path:", path) - print("Total cost:", cost) - watek = threading.Thread(target=watekDlaSciezkiAgenta) - watek.daemon = True - watek.start() if pygame.key.get_pressed()[pygame.K_e]: diff --git a/classes/agent.py b/classes/agent.py index 5b1e1ff..440a314 100644 --- a/classes/agent.py +++ b/classes/agent.py @@ -18,8 +18,8 @@ class Agent: self.multiplier = 1 self.direction = 0 self.directionPOM = 0 - self.xPOM = 5 - self.yPOM = 5 + self.xPOM = x + self.yPOM = y self.g_scores = {} self.textures = [ @@ -270,74 +270,12 @@ class Agent: return [] - #Algorytm astar - def moveto(self,x,y): - if pygame.time.get_ticks()-self.last_move_time > 125 and self.current_cell.X < prefs.GRID_SIZE-1 and not self.cells[x][y].blocking_movement: - self.current_cell = self.cells[x][y] - self.moved=True - self.last_move_time=pygame.time.get_ticks() - print("Agent moved to x,y: ",x,y) - else: - print("Agent cannot move to this direction") - - def get_cost(cell, cells): - x, y = cell[0], cell[1] - if x == 0 or x == len(cells) - 1 or y == 0 or y == len(cells[0]) - 1: - return 15 # Koszt dla pól na krawędziach - elif x == 1 or x == len(cells) - 2 or y == 1 or y == len(cells[0]) - 2: - return 10 # Koszt dla pól drugiego rzędu i przedostatniego oraz drugiej kolumny i przedostatniej - elif x == 2 or x == len(cells) - 3 or y == 2 or y == len(cells[0]) - 3: - return 5 # Koszt dla pól trzeciego rzędu i trzeciego od końca oraz trzeciej kolumny i trzeciej od końca - else: - return 1 - def heuristic(self, current, target): # Manhattan distance heuristic dx = abs(current[0] - target[0]) dy = abs(current[1] - target[1]) return dx + dy - - def priority(self, state, target): - # Oblicza priorytet dla danego stanu - g_score = self.g_score[state] - h_score = self.heuristic(state, target) - return g_score + h_score - - def astar(self, target, start_cost=0): - if not isinstance(target, tuple) or len(target) != 2: - raise ValueError("Target must be a tuple of two elements (x, y).") - - open_list = [(start_cost, (self.current_cell.X, self.current_cell.Y, self.directionPOM))] - came_from = {} - g_score = {(self.current_cell.X, self.current_cell.Y, self.directionPOM): start_cost} - - while open_list: - _, current = heapq.heappop(open_list) - if isinstance(current, int): - raise ValueError("Current must be a tuple of three elements (x, y, direction).") - - x, y, _ = current # Unpack the current tuple - if (x, y) == target: # Check if the current cell's coordinates match the target - path = [] - while current in came_from: - path.append((current[0], current[1])) # Append only coordinates (x, y) to the path - current = came_from[current] - path = path[::-1] # Reverse the path - cost = g_score[(x, y, self.directionPOM)] # Retrieve the cost from the g_score dictionary - return path, cost - - for neighbor in self.get_neighbors(self.cells[x][y], self.cells): - neighbor_coords = (neighbor.X, neighbor.Y, self.directionPOM) # Convert neighbor cell to tuple - tentative_g_score = g_score[current] + self.get_cost(neighbor_coords) - if tentative_g_score < g_score.get(neighbor_coords, float('inf')): - came_from[neighbor_coords] = current - g_score[neighbor_coords] = tentative_g_score - f_score = tentative_g_score + self.heuristic(neighbor_coords, target) - heapq.heappush(open_list, (f_score, neighbor_coords)) - - - return [], float('inf') - + diff --git a/decision_tree b/decision_tree new file mode 100644 index 0000000..b903d56 --- /dev/null +++ b/decision_tree @@ -0,0 +1,33 @@ +digraph Tree { +node [shape=box, style="filled, rounded", color="black", fontname="helvetica"] ; +edge [fontname="helvetica"] ; +0 [label=entropy = 1.045
samples = 135
value = [39.0, 1.0, 1.0, 93.0, 1.0]
class = No >, fillcolor="#9190f0"] ; +1 [label=entropy = 1.185
samples = 66
value = [28, 0, 1, 36, 1]
class = No >, fillcolor="#d6d5fa"] ; +0 -> 1 [labeldistance=2.5, labelangle=45, headlabel="True"] ; +2 [label=entropy = 0.678
samples = 23
value = [2, 0, 0, 20, 1]
class = No >, fillcolor="#5855e9"] ; +1 -> 2 ; +3 [label=samples = 13
value = [0, 0, 0, 13, 0]
class = No >, fillcolor="#3c39e5"] ; +2 -> 3 ; +4 [label=samples = 10
value = [2, 0, 0, 7, 1]
class = No >, fillcolor="#8583ef"] ; +2 -> 4 ; +5 [label=entropy = 1.096
samples = 43
value = [26, 0, 1, 16, 0]
class = Yes>, fillcolor="#f5d0b6"] ; +1 -> 5 ; +6 [label=samples = 22
value = [9, 0, 0, 13, 0]
class = No >, fillcolor="#c3c2f7"] ; +5 -> 6 ; +7 [label=samples = 21
value = [17, 0, 1, 3, 0]
class = Yes>, fillcolor="#eb9d65"] ; +5 -> 7 ; +8 [label=entropy = 0.739
samples = 69
value = [11.0, 1.0, 0.0, 57.0, 0.0]
class = No >, fillcolor="#6462ea"] ; +0 -> 8 [labeldistance=2.5, labelangle=-45, headlabel="False"] ; +9 [label=entropy = 0.323
samples = 34
value = [2, 0, 0, 32, 0]
class = No >, fillcolor="#4845e7"] ; +8 -> 9 ; +10 [label=samples = 15
value = [2, 0, 0, 13, 0]
class = No >, fillcolor="#5a57e9"] ; +9 -> 10 ; +11 [label=samples = 19
value = [0, 0, 0, 19, 0]
class = No >, fillcolor="#3c39e5"] ; +9 -> 11 ; +12 [label=entropy = 0.997
samples = 35
value = [9, 1, 0, 25, 0]
class = No >, fillcolor="#8785ef"] ; +8 -> 12 ; +13 [label=samples = 16
value = [7, 1, 0, 8, 0]
class = No >, fillcolor="#e9e9fc"] ; +12 -> 13 ; +14 [label=samples = 19
value = [2, 0, 0, 17, 0]
class = No >, fillcolor="#5350e8"] ; +12 -> 14 ; +} diff --git a/decisiontree.py b/decisiontree.py index ea63ace..4581a27 100644 --- a/decisiontree.py +++ b/decisiontree.py @@ -72,5 +72,5 @@ print("\nNew client:") print(new_client_df) print("Prediction:", prediction[0]) -graph = graphviz.Source(dot_data) -graph.render("decision_tree", format='png') +#graph = graphviz.Source(dot_data) +#graph.render("decision_tree", format='png') diff --git a/prefs.py b/prefs.py index bdf5bd2..897f803 100644 --- a/prefs.py +++ b/prefs.py @@ -1,9 +1,9 @@ -WIDTH = 600 +WIDTH = 1000 HEIGHT = WIDTH -GRID_SIZE = 12 +GRID_SIZE = 20 CELL_SIZE = WIDTH // GRID_SIZE SPAWN_POINT = (5, 5) -COLORS = [(100, 20, 20), (20, 100, 20), (20, 20, 100),(150, 200, 200)] +COLORS = [(190, 190, 190),(180,180,180)] diff --git a/sprites/dywan.png b/sprites/dywan.png new file mode 100644 index 0000000000000000000000000000000000000000..383d4896149b980b8616ac32804d921c84f83d6e GIT binary patch literal 4259 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 literal 0 HcmV?d00001