From fd6fabfc0047cc960f1f6b70865c155bf3407325 Mon Sep 17 00:00:00 2001 From: s452645 Date: Sat, 17 Apr 2021 19:56:34 +0200 Subject: [PATCH 1/4] merged new agent, display_assets and bfs --- agent.py | 43 ++++++++++++++----- display_assets.py | 4 +- main.py | 51 ++++++++++------------- minefield.py | 2 +- project_constants.py | 10 +++++ searching_algorithms/bfs.py | 83 +++++++++++++++++++++++++++---------- 6 files changed, 131 insertions(+), 62 deletions(-) diff --git a/agent.py b/agent.py index d598e8e..2fbb46a 100644 --- a/agent.py +++ b/agent.py @@ -3,29 +3,52 @@ import project_constants as const import json_generator as js import json - # Class of our agent, initialization of it - # movment functions (those defiend by the 'go_' prefix are not meant to actually move our agent, they just return some values - # that are later used by another function called 'is_valid_move' (which is defined in Minefield)); - + +# Class of our agent, initialization of it +# movement functions (those defiend by the 'go_' prefix are not meant to actually move our agent, they just return some values +# that are later used by another function called 'is_valid_move' (which is defined in Minefield)); + class Agent: def __init__(self, json_path): with open(json_path) as json_data: data = json.load(json_data) self.row, self.column = data['agent_starting_position'].split(",") self.position = [int(self.row), int(self.column)] - + # self.direction = const.Direction() + self.direction = const.Direction.UP + + def rotate_left(self): + self.direction = self.direction.previous() + + def rotate_right(self): + self.direction = self.direction.next() + + def go(self): + if self.direction == const.Direction.RIGHT: + temp = self.go_right() + self.position[1] = temp[1] + elif self.direction == const.Direction.LEFT: + temp = self.go_left() + self.position[1] = temp[1] + elif self.direction == const.Direction.UP: + temp = self.go_up() + self.position[0] = temp[0] + elif self.direction == const.Direction.DOWN: + temp = self.go_down() + self.position[0] = temp[0] + def go_right(self): - + return self.position[0], self.position[1] + 1 def go_left(self): - + return self.position[0], self.position[1] - 1 def go_up(self): - + return self.position[0] - 1, self.position[1] def go_down(self): - - return self.position[0] + 1, self.position[1] \ No newline at end of file + + return self.position[0] + 1, self.position[1] diff --git a/display_assets.py b/display_assets.py index 4582089..54b40e4 100644 --- a/display_assets.py +++ b/display_assets.py @@ -29,7 +29,7 @@ def display_sapper(sapper_x, sapper_y, sapper_dir): if sapper_dir == const.Direction.RIGHT: const.SCREEN.blit( - pygame.transform.rotate(const.ASSET_SAPPER, 90), + pygame.transform.rotate(const.ASSET_SAPPER, 270), sapper_screen_coords ) @@ -41,7 +41,7 @@ def display_sapper(sapper_x, sapper_y, sapper_dir): if sapper_dir == const.Direction.LEFT: const.SCREEN.blit( - pygame.transform.rotate(const.ASSET_SAPPER, 270), + pygame.transform.rotate(const.ASSET_SAPPER, 90), sapper_screen_coords ) diff --git a/main.py b/main.py index a1febf5..b1386ab 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,12 @@ # libraries import pygame +import time from pyglet.gl import * # for blocky textures # other files of this project import project_constants as const import minefield as mf +import searching_algorithms.bfs as bfs from display_assets import display_sapper @@ -22,6 +24,12 @@ def main(): # create an instance of Minefield, pass necessary data minefield = mf.Minefield(const.MAP_RANDOM_10x10) + # get sequence of actions found by BFS algorythm + action_sequence = bfs.graphsearch(initial_state=bfs.State(row=minefield.agent.position[0], + column=minefield.agent.position[1], + direction=const.Direction.UP), + minefield=minefield) + running = True while running: # FPS control @@ -48,42 +56,29 @@ def main(): display_sapper( minefield.agent.position[0], minefield.agent.position[1], - const.Direction.UP + minefield.agent.direction ) # update graphics after blitting pygame.display.update() - # ============== # - # === EVENTS === # - # ============== # + # make the next move from sequence of actions + if len(action_sequence): + action = action_sequence.pop(0) + if action == const.Action.ROTATE_LEFT: + minefield.agent.rotate_left() + elif action == const.Action.ROTATE_RIGHT: + minefield.agent.rotate_right() + elif action == const.Action.GO: + minefield.agent.go() - for event in pygame.event.get(): + time.sleep(const.ACTION_INTERVAL) - if event.type == pygame.QUIT: - running = False + else: + for event in pygame.event.get(): - # Assigning all input from keyboard as variables into an array - keys = pygame.key.get_pressed() - - # Depending on what key we press, the agent will move in that direction - # DISCRETION : The only keys that are available are arrow keys - # DISCRETION : is_valid_move is a new brand function that now plays a critical role in movement of our Agent (It is NOT just the "check up" function anymore) - if keys[pygame.K_RIGHT]: - target_row, target_column = minefield.agent.go_right() - minefield.is_valid_move(target_row, target_column) - - elif keys[pygame.K_LEFT]: - target_row, target_column = minefield.agent.go_left() - minefield.is_valid_move(target_row, target_column) - - elif keys[pygame.K_UP]: - target_row, target_column = minefield.agent.go_up() - minefield.is_valid_move(target_row, target_column) - - elif keys[pygame.K_DOWN]: - target_row, target_column = minefield.agent.go_down() - minefield.is_valid_move(target_row, target_column) + if event.type == pygame.QUIT: + running = False if __name__ == "__main__": diff --git a/minefield.py b/minefield.py index 0883a68..d82c039 100644 --- a/minefield.py +++ b/minefield.py @@ -93,7 +93,7 @@ class Minefield: def is_valid_move(self, target_row: int, target_column: int): if 0 <= target_row < const.V_GRID_VER_TILES \ and 0 <= target_column < const.V_GRID_HOR_TILES \ - and self.matrix[target_row][target_column] is None: + and self.matrix[target_row][target_column].mine is None: return True diff --git a/project_constants.py b/project_constants.py index b9d80d1..9adb9bf 100644 --- a/project_constants.py +++ b/project_constants.py @@ -17,6 +17,8 @@ V_NAME_OF_WINDOW = "MineFusion TM" ASSETS_DIR = os.path.join("resources", "assets") V_FPS = 60 +ACTION_INTERVAL = 1 # interval between two actions in seconds + V_TILE_SIZE = 60 V_GRID_VER_TILES = V_GRID_HOR_TILES = 10 # vertical (number of rows), horizontal (number of columns) V_SCREEN_PADDING = 10 @@ -42,6 +44,14 @@ class Direction(Enum): DOWN = 2 LEFT = 3 + def next(self): + v = (self.value + 1) % 4 + return Direction(v) + + def previous (self): + v = (self.value - 1) % 4 + return Direction(v) + class Action(Enum): ROTATE_LEFT = 0 diff --git a/searching_algorithms/bfs.py b/searching_algorithms/bfs.py index 6186611..bb59dd7 100644 --- a/searching_algorithms/bfs.py +++ b/searching_algorithms/bfs.py @@ -2,9 +2,10 @@ from __future__ import annotations from typing import List, Set from project_constants import Direction, Action +from minefield import Minefield # temporary goal for testing -GOAL = (9, 9) +GOAL = (2, 2) class State: @@ -13,6 +14,12 @@ class State: self.column = column self.direction = direction + # def __eq__(self, other): + # if not isinstance(other, State): + # # don't attempt to compare against unrelated types + # return NotImplemented + # return self.row == other.row and self.column == other.column and self.direction == other.direction + class Node: def __init__(self, state: State, parent: Node = None, action: Action = None): @@ -20,6 +27,12 @@ class Node: self.parent = parent self.action = action + # def __eq__(self, other): + # if not isinstance(other, Node): + # # don't attempt to compare against unrelated types + # return NotImplemented + # return self.state == other.state and self.parent == other.parent and self.action == other.action + def goal_test(state: State): if (state.row, state.column) == GOAL: @@ -27,26 +40,32 @@ def goal_test(state: State): return False -# TODO: depends od Agent Class (rotate/go implementation) -def get_successors(state: State): +def get_successors(state: State, minefield: Minefield): successors = list() - # state_left = get Agent State after rotate_left() - # successors.add("rotate_left", state_left) + state_left = State(state.row, state.column, state.direction.previous()) + successors.append((Action.ROTATE_LEFT, state_left)) - # state_right = get Agent State after rotate_right() - # successors.add("rotate_right", state_right) + state_right = State(state.row, state.column, state.direction.next()) + successors.append((Action.ROTATE_RIGHT, state_right)) - # target = get Agent position after go() - # if is_valid_move(target): - # state_go = Agent State after go() - # successors.add("go", state_go) + target = go(state.row, state.column, state.direction) + + if minefield.is_valid_move(target[0], target[1]): + state_go = State(target[0], target[1], state.direction) + successors.append((Action.GO, state_go)) return successors -def graphsearch(fringe: List[Node], explored: Set[Node], initial_state: State): +def graphsearch(initial_state: State, minefield: Minefield, fringe: List[Node] = None, explored: List[Node] = None): + # fringe and explored initialization + if fringe is None: + fringe = list() + if explored is None: + explored = list() + # root Node fringe.append(Node(initial_state)) while True: @@ -54,11 +73,12 @@ def graphsearch(fringe: List[Node], explored: Set[Node], initial_state: State): if not len(fringe): return False + # get first element from fringe element = fringe.pop(0) - # solution found, prepare and return actions sequence + # if solution was found, prepare and return actions sequence if goal_test(element.state): - actions_sequence = list() + actions_sequence = [element.action] parent = element.parent while parent is not None: @@ -67,20 +87,41 @@ def graphsearch(fringe: List[Node], explored: Set[Node], initial_state: State): actions_sequence.append(parent.action) parent = parent.parent - return actions_sequence.reverse() + actions_sequence.reverse() + return actions_sequence - explored.add(element) + # add current node to explored (prevents infinite cycles) + explored.append(element) - for successor in get_successors(element.state): + # loop through every possible next action + for successor in get_successors(element.state, minefield): # either me or the pseudocode is dumb # somebody has to verify it fringe_states = [el.state for el in fringe] explored_states = [el.state for el in explored] - if successor.state not in fringe_states and \ - successor.state not in explored_states: + # make sure not to fall into a cycle + if successor[1] not in fringe_states and \ + successor[1] not in explored_states: - new_node = Node(state=successor.state, + # create new Node and add it at the end of fringe + new_node = Node(state=successor[1], parent=element, - action=successor.action) + action=successor[0]) fringe.append(new_node) + + +# TEMPORARY METHOD +def go(row, column, direction): + target = tuple() + + if direction == Direction.RIGHT: + target = row, column + 1 + elif direction == Direction.LEFT: + target = row, column - 1 + elif direction == Direction.UP: + target = row - 1, column + elif direction == Direction.DOWN: + target = row + 1, column + + return target From 28cb1c134339431e393088484ddca57ad03130a6 Mon Sep 17 00:00:00 2001 From: s452645 Date: Sat, 17 Apr 2021 21:14:57 +0200 Subject: [PATCH 2/4] performance boost by Jakub Radowicz TM --- searching_algorithms/bfs.py | 33 ++++++++++++--------------------- 1 file changed, 12 insertions(+), 21 deletions(-) diff --git a/searching_algorithms/bfs.py b/searching_algorithms/bfs.py index bb59dd7..a41b258 100644 --- a/searching_algorithms/bfs.py +++ b/searching_algorithms/bfs.py @@ -1,11 +1,11 @@ from __future__ import annotations -from typing import List, Set +from typing import List from project_constants import Direction, Action from minefield import Minefield # temporary goal for testing -GOAL = (2, 2) +GOAL = (9, 9) class State: @@ -14,12 +14,6 @@ class State: self.column = column self.direction = direction - # def __eq__(self, other): - # if not isinstance(other, State): - # # don't attempt to compare against unrelated types - # return NotImplemented - # return self.row == other.row and self.column == other.column and self.direction == other.direction - class Node: def __init__(self, state: State, parent: Node = None, action: Action = None): @@ -27,12 +21,6 @@ class Node: self.parent = parent self.action = action - # def __eq__(self, other): - # if not isinstance(other, Node): - # # don't attempt to compare against unrelated types - # return NotImplemented - # return self.state == other.state and self.parent == other.parent and self.action == other.action - def goal_test(state: State): if (state.row, state.column) == GOAL: @@ -65,8 +53,12 @@ def graphsearch(initial_state: State, minefield: Minefield, fringe: List[Node] = if explored is None: explored = list() + explored_states = set() + fringe_states = set() + # root Node fringe.append(Node(initial_state)) + fringe_states.add((initial_state.row, initial_state.column, initial_state.direction)) while True: # fringe empty -> solution not found @@ -75,6 +67,7 @@ def graphsearch(initial_state: State, minefield: Minefield, fringe: List[Node] = # get first element from fringe element = fringe.pop(0) + fringe_states.remove((element.state.row, element.state.column, element.state.direction)) # if solution was found, prepare and return actions sequence if goal_test(element.state): @@ -92,23 +85,21 @@ def graphsearch(initial_state: State, minefield: Minefield, fringe: List[Node] = # add current node to explored (prevents infinite cycles) explored.append(element) + explored_states.add((element.state.row, element.state.column, element.state.direction)) # loop through every possible next action for successor in get_successors(element.state, minefield): - # either me or the pseudocode is dumb - # somebody has to verify it - fringe_states = [el.state for el in fringe] - explored_states = [el.state for el in explored] # make sure not to fall into a cycle - if successor[1] not in fringe_states and \ - successor[1] not in explored_states: - + successor_state = (successor[1].row, successor[1].column, successor[1].direction) + if successor_state not in fringe_states and \ + successor_state not in explored_states: # create new Node and add it at the end of fringe new_node = Node(state=successor[1], parent=element, action=successor[0]) fringe.append(new_node) + fringe_states.add((new_node.state.row, new_node.state.column, new_node.state.direction)) # TEMPORARY METHOD From b9bda6f883ea791aa0d5cb54a9127bd1c48eda09 Mon Sep 17 00:00:00 2001 From: s452645 Date: Sat, 17 Apr 2021 21:33:50 +0200 Subject: [PATCH 3/4] fixed mine in GOAL bug --- main.py | 4 +++- searching_algorithms/bfs.py | 8 +++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index b1386ab..4a3ae59 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,6 @@ # libraries +import ctypes + import pygame import time from pyglet.gl import * # for blocky textures @@ -63,7 +65,7 @@ def main(): pygame.display.update() # make the next move from sequence of actions - if len(action_sequence): + if any(action_sequence): action = action_sequence.pop(0) if action == const.Action.ROTATE_LEFT: minefield.agent.rotate_left() diff --git a/searching_algorithms/bfs.py b/searching_algorithms/bfs.py index a41b258..612c5d6 100644 --- a/searching_algorithms/bfs.py +++ b/searching_algorithms/bfs.py @@ -1,11 +1,12 @@ from __future__ import annotations from typing import List +import ctypes from project_constants import Direction, Action from minefield import Minefield # temporary goal for testing -GOAL = (9, 9) +GOAL = (1, 1) class State: @@ -62,8 +63,9 @@ def graphsearch(initial_state: State, minefield: Minefield, fringe: List[Node] = while True: # fringe empty -> solution not found - if not len(fringe): - return False + if not any(fringe): + ctypes.windll.user32.MessageBoxW(0, "Znowu się nie udało", "GAME OVER", 1) + return [] # get first element from fringe element = fringe.pop(0) From 01cf653c7d301c1333095cae0e91ba21d6ce1796 Mon Sep 17 00:00:00 2001 From: s452645 Date: Sun, 18 Apr 2021 18:09:39 +0200 Subject: [PATCH 4/4] changed error message --- searching_algorithms/bfs.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/searching_algorithms/bfs.py b/searching_algorithms/bfs.py index 612c5d6..80dd170 100644 --- a/searching_algorithms/bfs.py +++ b/searching_algorithms/bfs.py @@ -64,7 +64,7 @@ def graphsearch(initial_state: State, minefield: Minefield, fringe: List[Node] = while True: # fringe empty -> solution not found if not any(fringe): - ctypes.windll.user32.MessageBoxW(0, "Znowu się nie udało", "GAME OVER", 1) + ctypes.windll.user32.MessageBoxW(0, "Brak rozwiązania", "GAME OVER", 1) return [] # get first element from fringe