import sys from collections import deque from os import path import queue from utils import * from settings import * class Problem: """The abstract class for a formal problem. You should subclass this and implement the methods actions and result, and possibly __init__, goal_test, and path_cost. Then you will create instances of your subclass and solve them with the various search functions.""" def __init__(self, initial=[], goal=[]): """The constructor specifies the initial state, and possibly a goal state, if there is a unique goal. Your subclass's constructor can add other arguments.""" self.initial = initial self.goal = goal def actions(self, state): """Return the actions that can be executed in the given state. The result would typically be a list, but if there are many actions, consider yielding them one at a time in an iterator, rather than building them all at once.""" moves = [] if self.turn_left(self, state): moves.append('Left') if self.turn_right(self, state): moves.append('Right') if self.move_forward(self, state): moves.append('Forward') print(moves) return moves def turn_left(self, state): return True def turn_right(self, state): return True def move_forward(self, state): a_row = 0 a_column = 0 for row in range(MAP_SIZE): for column, pos in enumerate(state[row]): if pos == ">": a_row = row a_column = column if a_column == MAP_SIZE-1: return False elif state[a_row][a_column+1] == '.': return True return False if pos == "<": a_row = row a_column = column if a_column == 0: return False elif state[a_row][a_column-1] == '.': return True return False if pos == "v": a_row = row a_column = column if a_row == MAP_SIZE-1: return False elif state[a_row+1][a_column] == '.': return True return False if pos == "^": a_row = row a_column = column if row == 0: return False elif state[a_row-1][a_column] == '.': return True return False def turn_me_or_move(self, state, do_it): a_row = 0 a_column = 0 for row in range(MAP_SIZE): for column, pos in enumerate(state[row]): if pos == ">": a_row = row a_column = column if(do_it == 'Left'): state[a_row][a_column] = '^' if(do_it == 'Right'): state[a_row][a_column] = 'v' if(do_it == 'Forward'): state[a_row][a_column] = '.' state[a_row][a_column+1] = '>' if pos == "<": a_row = row a_column = column if(do_it == 'Left'): state[a_row][a_column] = 'v' if(do_it == 'Right'): state[a_row][a_column] = '^' if(do_it == 'Forward'): state[a_row][a_column] = '.' state[a_row][a_column-1] = '<' if pos == "v": a_row = row a_column = column if(do_it == 'Left'): state[a_row][a_column] = '>' if(do_it == 'Right'): state[a_row][a_column] = '<' if(do_it == 'Forward'): state[a_row][a_column] = '.' state[a_row+1][a_column] = 'v' if pos == "^": a_row = row a_column = column if(do_it == 'Left'): state[a_row][a_column] = '<' if(do_it == 'Right'): state[a_row][a_column] = '>' if(do_it == 'Forward'): state[a_row][a_column] = '.' state[a_row-1][a_column] = '^' return state def result(self, state, action): """Return the state that results from executing the given action in the given state. The action must be one of self.actions(state).""" new_state = [] if action == 'Left': new_state = self.turn_me_or_move(state, 'Left') elif action == 'Right': new_state = self.turn_me_or_move(state, 'Right') elif action == 'Forward': new_state = self.turn_me_or_move(state, 'Forward') return new_state def goal_test(self, state): """Return True if the state is a goal. The default method compares the state to self.goal or checks for state in self.goal if it is a list, as specified in the constructor. Override this method if checking against a single self.goal is not enough.""" if isinstance(self.goal, list): return is_in(state, self.goal) else: return state == self.goal class Node: """A node in a search tree. Contains a pointer to the parent (the node that this is a successor of) and to the actual state for this node. Note that if a state is arrived at by two paths, then there are two nodes with the same state. Also includes the action that got us to this state, and the total path_cost (also known as g) to reach the node. Other functions may add an f and h value; see best_first_graph_search and astar_search for an explanation of how the f and h values are handled. You will not need to subclass this class.""" def __init__(self, state, parent=None, action=None): """Create a search tree Node, derived from a parent by an action.""" self.state = state self.parent = parent self.action = action def expand(self, problem): """List the nodes reachable in one step from this node.""" return [self.child_node(problem, action) for action in problem.actions(self.state)] def child_node(self, problem, action): next_state = problem.result(self.state, action) next_node = Node(next_state, self, action) return next_node class BFS: @staticmethod def breadth_first_graph_search(problem): """[Figure 3.11] Note that this function can be implemented in a single line as below: return graph_search(problem, FIFOQueue()) """ node = Node(problem.initial) if problem.goal_test(node.state): return node frontier = deque([node]) explored = set() while frontier: node = frontier.popleft() explored.add(node.state) for child in node.expand(problem): if child.state not in explored and child not in frontier: if problem.goal_test(child.state): return child frontier.append(child) return None @staticmethod def loadMap(map_name=''): maze = [] map_folder = path.dirname(__file__) with open(path.join(map_folder, map_name), 'rt') as f: for line in f: maze.append(line) return maze @staticmethod def run(): initial_map = BFS.loadMap('map.txt') goal_map = BFS.loadMap('goal_map.txt') problem = Problem(initial_map, goal_map) result = BFS.breadth_first_graph_search(problem) print(result)