Implementing graphsearch
Co-authored-by: Ladislaus3III <Ladislaus3III@users.noreply.github.com> Co-authored-by: Sebastian Piotrowski <sebpio@st.amu.edu.pl>
This commit is contained in:
parent
8632841061
commit
89e0fc5b75
8
goal_map.txt
Normal file
8
goal_map.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.......
|
||||||
|
###....
|
||||||
|
.......
|
||||||
|
.......
|
||||||
|
...###.
|
||||||
|
...#^#.
|
||||||
|
.......
|
||||||
|
|
246
graphsearch.py
Normal file
246
graphsearch.py
Normal file
@ -0,0 +1,246 @@
|
|||||||
|
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 = Map(BFS.loadMap('map.txt'))
|
||||||
|
goal_map = Map(BFS.loadMap('goal_map.txt'))
|
||||||
|
problem = Problem(initial_map, goal_map)
|
||||||
|
|
||||||
|
result = BFS.breadth_first_graph_search(problem)
|
||||||
|
print(result)
|
||||||
|
|
||||||
|
|
||||||
|
class Map:
|
||||||
|
def __init__(self, maze):
|
||||||
|
self.maze = maze
|
4
main.py
4
main.py
@ -6,6 +6,7 @@ from maze import *
|
|||||||
from grid import *
|
from grid import *
|
||||||
from settings import *
|
from settings import *
|
||||||
from sprites import *
|
from sprites import *
|
||||||
|
from graphsearch import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ -96,6 +97,9 @@ class Game:
|
|||||||
self.player.parse_maze_moves()
|
self.player.parse_maze_moves()
|
||||||
self.i_like_to_move_it()
|
self.i_like_to_move_it()
|
||||||
self.wentyl_bezpieczenstwa = 1
|
self.wentyl_bezpieczenstwa = 1
|
||||||
|
if event.key == pg.K_F3:
|
||||||
|
BFS.run()
|
||||||
|
|
||||||
|
|
||||||
def i_like_to_move_it(self):
|
def i_like_to_move_it(self):
|
||||||
for i in self.player.moves:
|
for i in self.player.moves:
|
||||||
|
6
map.txt
6
map.txt
@ -1,7 +1,7 @@
|
|||||||
.A.....
|
.>.....
|
||||||
###....
|
#.#....
|
||||||
.......
|
.......
|
||||||
.......
|
.......
|
||||||
...###.
|
...###.
|
||||||
...#2#.
|
...#.#.
|
||||||
.......
|
.......
|
||||||
|
142
problem.py
Normal file
142
problem.py
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import sys
|
||||||
|
from collections import deque
|
||||||
|
|
||||||
|
from utils 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=None):
|
||||||
|
"""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."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
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)."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
def path_cost(self, c, state1, action, state2):
|
||||||
|
"""Return the cost of a solution path that arrives at state2 from
|
||||||
|
state1 via action, assuming cost c to get up to state1. If the problem
|
||||||
|
is such that the path doesn't matter, this function will only look at
|
||||||
|
state2. If the path does matter, it will consider c and maybe state1
|
||||||
|
and action. The default method costs 1 for every step in the path."""
|
||||||
|
return c + 1
|
||||||
|
|
||||||
|
def value(self, state):
|
||||||
|
"""For optimization problems, each state has a value. Hill Climbing
|
||||||
|
and related algorithms try to maximize this value."""
|
||||||
|
raise NotImplementedError
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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, path_cost=0):
|
||||||
|
"""Create a search tree Node, derived from a parent by an action."""
|
||||||
|
self.state = state
|
||||||
|
self.parent = parent
|
||||||
|
self.action = action
|
||||||
|
self.path_cost = path_cost
|
||||||
|
self.depth = 0
|
||||||
|
if parent:
|
||||||
|
self.depth = parent.depth + 1
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Node {}>".format(self.state)
|
||||||
|
|
||||||
|
def __lt__(self, node):
|
||||||
|
return self.state < node.state
|
||||||
|
|
||||||
|
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):
|
||||||
|
"""[Figure 3.10]"""
|
||||||
|
next_state = problem.result(self.state, action)
|
||||||
|
next_node = Node(next_state, self, action, problem.path_cost(self.path_cost, self.state, action, next_state))
|
||||||
|
return next_node
|
||||||
|
|
||||||
|
def solution(self):
|
||||||
|
"""Return the sequence of actions to go from the root to this node."""
|
||||||
|
return [node.action for node in self.path()[1:]]
|
||||||
|
|
||||||
|
def path(self):
|
||||||
|
"""Return a list of nodes forming the path from the root to this node."""
|
||||||
|
node, path_back = self, []
|
||||||
|
while node:
|
||||||
|
path_back.append(node)
|
||||||
|
node = node.parent
|
||||||
|
return list(reversed(path_back))
|
||||||
|
|
||||||
|
# We want for a queue of nodes in breadth_first_graph_search or
|
||||||
|
# astar_search to have no duplicated states, so we treat nodes
|
||||||
|
# with the same state as equal. [Problem: this may not be what you
|
||||||
|
# want in other contexts.]
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
return isinstance(other, Node) and self.state == other.state
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
# We use the hash value of the state
|
||||||
|
# stored in the node instead of the node
|
||||||
|
# object itself to quickly search a node
|
||||||
|
# with the same state in a Hash Table
|
||||||
|
return hash(self.state)
|
||||||
|
|
||||||
|
|
||||||
|
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
|
Loading…
Reference in New Issue
Block a user