GenericAI_Sweeper/graphsearch.py
2021-04-14 00:28:32 +02:00

244 lines
8.2 KiB
Python

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)