655 lines
27 KiB
Python
655 lines
27 KiB
Python
# logicPlan.py
|
|
# ------------
|
|
# Licensing Information: You are free to use or extend these projects for
|
|
# educational purposes provided that (1) you do not distribute or publish
|
|
# solutions, (2) you retain this notice, and (3) you provide clear
|
|
# attribution to UC Berkeley, including a link to http://ai.berkeley.edu.
|
|
#
|
|
# Attribution Information: The Pacman AI projects were developed at UC Berkeley.
|
|
# The core projects and autograders were primarily created by John DeNero
|
|
# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
|
|
# Student side autograding was added by Brad Miller, Nick Hay, and
|
|
# Pieter Abbeel (pabbeel@cs.berkeley.edu).
|
|
|
|
|
|
"""
|
|
In logicPlan.py, you will implement logic planning methods which are called by
|
|
Pacman agents (in logicAgents.py).
|
|
"""
|
|
|
|
from typing import Dict, List, Tuple, Callable, Generator, Any
|
|
import util
|
|
import sys
|
|
import logic
|
|
import game
|
|
|
|
from logic import conjoin, disjoin
|
|
from logic import PropSymbolExpr, Expr, to_cnf, pycoSAT, parseExpr, pl_true
|
|
|
|
import itertools
|
|
import copy
|
|
|
|
pacman_str = 'P'
|
|
food_str = 'FOOD'
|
|
wall_str = 'WALL'
|
|
pacman_wall_str = pacman_str + wall_str
|
|
DIRECTIONS = ['North', 'South', 'East', 'West']
|
|
blocked_str_map = dict([(direction, (direction + "_blocked").upper()) for direction in DIRECTIONS])
|
|
geq_num_adj_wall_str_map = dict([(num, "GEQ_{}_adj_walls".format(num)) for num in range(1, 4)])
|
|
DIR_TO_DXDY_MAP = {'North':(0, 1), 'South':(0, -1), 'East':(1, 0), 'West':(-1, 0)}
|
|
|
|
|
|
#______________________________________________________________________________
|
|
# QUESTION 1
|
|
|
|
def sentence1() -> Expr:
|
|
"""Returns a Expr instance that encodes that the following expressions are all true.
|
|
|
|
A or B
|
|
(not A) if and only if ((not B) or C)
|
|
(not A) or (not B) or C
|
|
"""
|
|
A=Expr('A')
|
|
B=Expr('B')
|
|
C=Expr('C')
|
|
return conjoin([A | B, (~A % ( ~B | C)), disjoin([~A , ~B , C])])
|
|
|
|
|
|
def sentence2() -> Expr:
|
|
"""Returns a Expr instance that encodes that the following expressions are all true.
|
|
|
|
C if and only if (B or D)
|
|
A implies ((not B) and (not D))
|
|
(not (B and (not C))) implies A
|
|
(not D) implies C
|
|
"""
|
|
A,B,C,D=Expr('A'),Expr('B'),Expr('C'),Expr('D')
|
|
return conjoin([(C % (B | D)), (A >> (~B & ~D)), (~(B & ~C) >> A), (~D >> C)])
|
|
|
|
|
|
def sentence3() -> Expr:
|
|
"""Using the symbols PacmanAlive_1 PacmanAlive_0, PacmanBorn_0, and PacmanKilled_0,
|
|
created using the PropSymbolExpr constructor, return a PropSymbolExpr
|
|
instance that encodes the following English sentences (in this order):
|
|
|
|
Pacman is alive at time 1 if and only if Pacman was alive at time 0 and it was
|
|
not killed at time 0 or it was not alive at time 0 and it was born at time 0.
|
|
|
|
Pacman cannot both be alive at time 0 and be born at time 0.
|
|
|
|
Pacman is born at time 0.
|
|
"""
|
|
PacmanAlive_0=PropSymbolExpr('PacmanAlive_0')
|
|
PacmanAlive_1=PropSymbolExpr('PacmanAlive_1')
|
|
PacmanBorn_0=PropSymbolExpr('PacmanBorn_0')
|
|
PacmanKilled_0=PropSymbolExpr('PacmanKilled_0')
|
|
# the answer should be "((PacmanAlive_1 <=> ((PacmanAlive_0 & ~PacmanKilled_0) | (~PacmanAlive_0 & PacmanBorn_0))) & ~(PacmanAlive_0 & PacmanBorn_0) & PacmanBorn_0)"
|
|
return conjoin([(PacmanAlive_1 % ((PacmanAlive_0 & ~PacmanKilled_0) | (~PacmanAlive_0 & PacmanBorn_0))), (~(PacmanAlive_0 & PacmanBorn_0)), PacmanBorn_0])
|
|
|
|
def findModel(sentence: Expr) -> Dict[Expr, bool]:
|
|
"""Given a propositional logic sentence (i.e. a Expr instance), returns a satisfying
|
|
model if one exists. Otherwise, returns False.
|
|
"""
|
|
cnf_sentence = to_cnf(sentence)
|
|
return pycoSAT(cnf_sentence)
|
|
|
|
def findModelUnderstandingCheck() -> Dict[Expr, bool]:
|
|
"""Returns the result of findModel(Expr('a')) if lower cased expressions were allowed.
|
|
You should not use findModel or Expr in this method.
|
|
"""
|
|
a = Expr('A')
|
|
a.__dict__['op']='a'
|
|
return {a:True}
|
|
|
|
def entails(premise: Expr, conclusion: Expr) -> bool:
|
|
"""Returns True if the premise entails the conclusion and False otherwise.
|
|
"""
|
|
expr=conjoin([premise,~conclusion])
|
|
return not pycoSAT(to_cnf(expr))
|
|
|
|
def plTrueInverse(assignments: Dict[Expr, bool], inverse_statement: Expr) -> bool:
|
|
"""Returns True if the (not inverse_statement) is True given assignments and False otherwise.
|
|
pl_true may be useful here; see logic.py for its description.
|
|
"""
|
|
return pl_true(~inverse_statement,assignments)
|
|
|
|
#______________________________________________________________________________
|
|
# QUESTION 2
|
|
|
|
def atLeastOne(literals: List[Expr]) -> Expr:
|
|
"""
|
|
Given a list of Expr literals (i.e. in the form A or ~A), return a single
|
|
Expr instance in CNF (conjunctive normal form) that represents the logic
|
|
that at least one of the literals ist is true.
|
|
>>> A = PropSymbolExpr('A');
|
|
>>> B = PropSymbolExpr('B');
|
|
>>> symbols = [A, B]
|
|
>>> atleast1 = atLeastOne(symbols)
|
|
>>> model1 = {A:False, B:False}
|
|
>>> print(pl_true(atleast1,model1))
|
|
False
|
|
>>> model2 = {A:False, B:True}
|
|
>>> print(pl_true(atleast1,model2))
|
|
True
|
|
>>> model3 = {A:True, B:True}
|
|
>>> print(pl_true(atleast1,model2))
|
|
True
|
|
"""
|
|
return disjoin(literals)
|
|
|
|
|
|
def atMostOne(literals: List[Expr]) -> Expr:
|
|
"""
|
|
Given a list of CNF Expr literals, return a single Expr instance in
|
|
CNF (conjunctive normal form) that represents the logic that at most one of
|
|
the expressions in the list is true.
|
|
itertools.combinations is used here.
|
|
"""
|
|
clauses = []
|
|
|
|
# Generate all pairs of literals
|
|
for pair in itertools.combinations(literals, 2):
|
|
# Create a clause that says "not both of these can be true"
|
|
clause = disjoin(~pair[0], ~pair[1])
|
|
clauses.append(clause)
|
|
|
|
# If there are no clauses (i.e., 0 or 1 literal), return True
|
|
if not clauses:
|
|
return Expr('True')
|
|
|
|
# Combine all clauses with AND
|
|
return conjoin(clauses)
|
|
|
|
def exactlyOne(literals: List[Expr]) -> Expr:
|
|
"""
|
|
Given a list of Expr literals, return a single Expr instance in
|
|
CNF (conjunctive normal form)that represents the logic that exactly one of
|
|
the expressions in the list is true.
|
|
"""
|
|
at_least_one = atLeastOne(literals)
|
|
at_most_one = atMostOne(literals)
|
|
return conjoin([at_least_one, at_most_one])
|
|
|
|
#______________________________________________________________________________
|
|
# QUESTION 3
|
|
|
|
def pacmanSuccessorAxiomSingle(x: int, y: int, time: int, walls_grid: List[List[bool]]=None) -> Expr:
|
|
"""
|
|
Successor state axiom for state (x,y,t) (from t-1), given the board (as a
|
|
grid representing the wall locations).
|
|
Current <==> (previous position at time t-1) & (took action to move to x, y)
|
|
Available actions are ['North', 'East', 'South', 'West']
|
|
Note that STOP is not an available action.
|
|
"""
|
|
now, last = time, time - 1
|
|
possible_causes: List[Expr] = [] # enumerate all possible causes for P[x,y]_t
|
|
# the if statements give a small performance boost and are required for q4 and q5 correctness
|
|
if walls_grid[x][y+1] != 1:
|
|
possible_causes.append( PropSymbolExpr(pacman_str, x, y+1, time=last)
|
|
& PropSymbolExpr('South', time=last))
|
|
if walls_grid[x][y-1] != 1:
|
|
possible_causes.append( PropSymbolExpr(pacman_str, x, y-1, time=last)
|
|
& PropSymbolExpr('North', time=last))
|
|
if walls_grid[x+1][y] != 1:
|
|
possible_causes.append( PropSymbolExpr(pacman_str, x+1, y, time=last)
|
|
& PropSymbolExpr('West', time=last))
|
|
if walls_grid[x-1][y] != 1:
|
|
possible_causes.append( PropSymbolExpr(pacman_str, x-1, y, time=last)
|
|
& PropSymbolExpr('East', time=last))
|
|
if not possible_causes:
|
|
return None
|
|
|
|
"*** BEGIN YOUR CODE HERE ***"
|
|
util.raiseNotDefined()
|
|
"*** END YOUR CODE HERE ***"
|
|
|
|
|
|
def SLAMSuccessorAxiomSingle(x: int, y: int, time: int, walls_grid: List[List[bool]]) -> Expr:
|
|
"""
|
|
Similar to `pacmanSuccessorStateAxioms` but accounts for illegal actions
|
|
where the pacman might not move timestep to timestep.
|
|
Available actions are ['North', 'East', 'South', 'West']
|
|
"""
|
|
now, last = time, time - 1
|
|
moved_causes: List[Expr] = [] # enumerate all possible causes for P[x,y]_t, assuming moved to having moved
|
|
if walls_grid[x][y+1] != 1:
|
|
moved_causes.append( PropSymbolExpr(pacman_str, x, y+1, time=last)
|
|
& PropSymbolExpr('South', time=last))
|
|
if walls_grid[x][y-1] != 1:
|
|
moved_causes.append( PropSymbolExpr(pacman_str, x, y-1, time=last)
|
|
& PropSymbolExpr('North', time=last))
|
|
if walls_grid[x+1][y] != 1:
|
|
moved_causes.append( PropSymbolExpr(pacman_str, x+1, y, time=last)
|
|
& PropSymbolExpr('West', time=last))
|
|
if walls_grid[x-1][y] != 1:
|
|
moved_causes.append( PropSymbolExpr(pacman_str, x-1, y, time=last)
|
|
& PropSymbolExpr('East', time=last))
|
|
if not moved_causes:
|
|
return None
|
|
|
|
moved_causes_sent: Expr = conjoin([~PropSymbolExpr(pacman_str, x, y, time=last) , ~PropSymbolExpr(wall_str, x, y), disjoin(moved_causes)])
|
|
|
|
failed_move_causes: List[Expr] = [] # using merged variables, improves speed significantly
|
|
auxilary_expression_definitions: List[Expr] = []
|
|
for direction in DIRECTIONS:
|
|
dx, dy = DIR_TO_DXDY_MAP[direction]
|
|
wall_dir_clause = PropSymbolExpr(wall_str, x + dx, y + dy) & PropSymbolExpr(direction, time=last)
|
|
wall_dir_combined_literal = PropSymbolExpr(wall_str + direction, x + dx, y + dy, time=last)
|
|
failed_move_causes.append(wall_dir_combined_literal)
|
|
auxilary_expression_definitions.append(wall_dir_combined_literal % wall_dir_clause)
|
|
|
|
failed_move_causes_sent: Expr = conjoin([
|
|
PropSymbolExpr(pacman_str, x, y, time=last),
|
|
disjoin(failed_move_causes)])
|
|
|
|
return conjoin([PropSymbolExpr(pacman_str, x, y, time=now) % disjoin([moved_causes_sent, failed_move_causes_sent])] + auxilary_expression_definitions)
|
|
|
|
|
|
def pacphysicsAxioms(t: int, all_coords: List[Tuple], non_outer_wall_coords: List[Tuple], walls_grid: List[List] = None, sensorModel: Callable = None, successorAxioms: Callable = None) -> Expr:
|
|
"""
|
|
Given:
|
|
t: timestep
|
|
all_coords: list of (x, y) coordinates of the entire problem
|
|
non_outer_wall_coords: list of (x, y) coordinates of the entire problem,
|
|
excluding the outer border (these are the actual squares pacman can
|
|
possibly be in)
|
|
walls_grid: 2D array of either -1/0/1 or T/F. Used only for successorAxioms.
|
|
Do NOT use this when making possible locations for pacman to be in.
|
|
sensorModel(t, non_outer_wall_coords) -> Expr: function that generates
|
|
the sensor model axioms. If None, it's not provided, so shouldn't be run.
|
|
successorAxioms(t, walls_grid, non_outer_wall_coords) -> Expr: function that generates
|
|
the sensor model axioms. If None, it's not provided, so shouldn't be run.
|
|
Return a logic sentence containing all of the following:
|
|
- for all (x, y) in all_coords:
|
|
If a wall is at (x, y) --> Pacman is not at (x, y)
|
|
- Pacman is at exactly one of the squares at timestep t.
|
|
- Pacman takes exactly one action at timestep t.
|
|
- Results of calling sensorModel(...), unless None.
|
|
- Results of calling successorAxioms(...), describing how Pacman can end in various
|
|
locations on this time step. Consider edge cases. Don't call if None.
|
|
"""
|
|
pacphysics_sentences = []
|
|
|
|
"*** BEGIN YOUR CODE HERE ***"
|
|
util.raiseNotDefined()
|
|
"*** END YOUR CODE HERE ***"
|
|
|
|
return conjoin(pacphysics_sentences)
|
|
|
|
|
|
def checkLocationSatisfiability(x1_y1: Tuple[int, int], x0_y0: Tuple[int, int], action0, action1, problem):
|
|
"""
|
|
Given:
|
|
- x1_y1 = (x1, y1), a potential location at time t = 1
|
|
- x0_y0 = (x0, y0), Pacman's location at time t = 0
|
|
- action0 = one of the four items in DIRECTIONS, Pacman's action at time t = 0
|
|
- action1 = to ensure match with autograder solution
|
|
- problem = an instance of logicAgents.LocMapProblem
|
|
Note:
|
|
- there's no sensorModel because we know everything about the world
|
|
- the successorAxioms should be allLegalSuccessorAxioms where needed
|
|
Return:
|
|
- a model where Pacman is at (x1, y1) at time t = 1
|
|
- a model where Pacman is not at (x1, y1) at time t = 1
|
|
"""
|
|
walls_grid = problem.walls
|
|
walls_list = walls_grid.asList()
|
|
all_coords = list(itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)))
|
|
non_outer_wall_coords = list(itertools.product(range(1, problem.getWidth()+1), range(1, problem.getHeight()+1)))
|
|
KB = []
|
|
x0, y0 = x0_y0
|
|
x1, y1 = x1_y1
|
|
|
|
# We know which coords are walls:
|
|
map_sent = [PropSymbolExpr(wall_str, x, y) for x, y in walls_list]
|
|
KB.append(conjoin(map_sent))
|
|
|
|
"*** BEGIN YOUR CODE HERE ***"
|
|
util.raiseNotDefined()
|
|
"*** END YOUR CODE HERE ***"
|
|
|
|
#______________________________________________________________________________
|
|
# QUESTION 4
|
|
|
|
def positionLogicPlan(problem) -> List:
|
|
"""
|
|
Given an instance of a PositionPlanningProblem, return a list of actions that lead to the goal.
|
|
Available actions are ['North', 'East', 'South', 'West']
|
|
Note that STOP is not an available action.
|
|
Overview: add knowledge incrementally, and query for a model each timestep. Do NOT use pacphysicsAxioms.
|
|
"""
|
|
walls_grid = problem.walls
|
|
width, height = problem.getWidth(), problem.getHeight()
|
|
walls_list = walls_grid.asList()
|
|
x0, y0 = problem.startState
|
|
xg, yg = problem.goal
|
|
|
|
# Get lists of possible locations (i.e. without walls) and possible actions
|
|
all_coords = list(itertools.product(range(width + 2),
|
|
range(height + 2)))
|
|
non_wall_coords = [loc for loc in all_coords if loc not in walls_list]
|
|
actions = [ 'North', 'South', 'East', 'West' ]
|
|
KB = []
|
|
|
|
"*** BEGIN YOUR CODE HERE ***"
|
|
util.raiseNotDefined()
|
|
"*** END YOUR CODE HERE ***"
|
|
|
|
#______________________________________________________________________________
|
|
# QUESTION 5
|
|
|
|
def foodLogicPlan(problem) -> List:
|
|
"""
|
|
Given an instance of a FoodPlanningProblem, return a list of actions that help Pacman
|
|
eat all of the food.
|
|
Available actions are ['North', 'East', 'South', 'West']
|
|
Note that STOP is not an available action.
|
|
Overview: add knowledge incrementally, and query for a model each timestep. Do NOT use pacphysicsAxioms.
|
|
"""
|
|
walls = problem.walls
|
|
width, height = problem.getWidth(), problem.getHeight()
|
|
walls_list = walls.asList()
|
|
(x0, y0), food = problem.start
|
|
food = food.asList()
|
|
|
|
# Get lists of possible locations (i.e. without walls) and possible actions
|
|
all_coords = list(itertools.product(range(width + 2), range(height + 2)))
|
|
|
|
non_wall_coords = [loc for loc in all_coords if loc not in walls_list]
|
|
actions = [ 'North', 'South', 'East', 'West' ]
|
|
|
|
KB = []
|
|
|
|
"*** BEGIN YOUR CODE HERE ***"
|
|
util.raiseNotDefined()
|
|
"*** END YOUR CODE HERE ***"
|
|
|
|
#______________________________________________________________________________
|
|
# QUESTION 6
|
|
|
|
def localization(problem, agent) -> Generator:
|
|
'''
|
|
problem: a LocalizationProblem instance
|
|
agent: a LocalizationLogicAgent instance
|
|
'''
|
|
walls_grid = problem.walls
|
|
walls_list = walls_grid.asList()
|
|
all_coords = list(itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)))
|
|
non_outer_wall_coords = list(itertools.product(range(1, problem.getWidth()+1), range(1, problem.getHeight()+1)))
|
|
|
|
KB = []
|
|
|
|
"*** BEGIN YOUR CODE HERE ***"
|
|
util.raiseNotDefined()
|
|
|
|
for t in range(agent.num_timesteps):
|
|
"*** END YOUR CODE HERE ***"
|
|
yield possible_locations
|
|
|
|
#______________________________________________________________________________
|
|
# QUESTION 7
|
|
|
|
def mapping(problem, agent) -> Generator:
|
|
'''
|
|
problem: a MappingProblem instance
|
|
agent: a MappingLogicAgent instance
|
|
'''
|
|
pac_x_0, pac_y_0 = problem.startState
|
|
KB = []
|
|
all_coords = list(itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)))
|
|
non_outer_wall_coords = list(itertools.product(range(1, problem.getWidth()+1), range(1, problem.getHeight()+1)))
|
|
|
|
# map describes what we know, for GUI rendering purposes. -1 is unknown, 0 is open, 1 is wall
|
|
known_map = [[-1 for y in range(problem.getHeight()+2)] for x in range(problem.getWidth()+2)]
|
|
|
|
# Pacman knows that the outer border of squares are all walls
|
|
outer_wall_sent = []
|
|
for x, y in all_coords:
|
|
if ((x == 0 or x == problem.getWidth() + 1)
|
|
or (y == 0 or y == problem.getHeight() + 1)):
|
|
known_map[x][y] = 1
|
|
outer_wall_sent.append(PropSymbolExpr(wall_str, x, y))
|
|
KB.append(conjoin(outer_wall_sent))
|
|
|
|
"*** BEGIN YOUR CODE HERE ***"
|
|
util.raiseNotDefined()
|
|
|
|
for t in range(agent.num_timesteps):
|
|
"*** END YOUR CODE HERE ***"
|
|
yield known_map
|
|
|
|
#______________________________________________________________________________
|
|
# QUESTION 8
|
|
|
|
def slam(problem, agent) -> Generator:
|
|
'''
|
|
problem: a SLAMProblem instance
|
|
agent: a SLAMLogicAgent instance
|
|
'''
|
|
pac_x_0, pac_y_0 = problem.startState
|
|
KB = []
|
|
all_coords = list(itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)))
|
|
non_outer_wall_coords = list(itertools.product(range(1, problem.getWidth()+1), range(1, problem.getHeight()+1)))
|
|
|
|
# map describes what we know, for GUI rendering purposes. -1 is unknown, 0 is open, 1 is wall
|
|
known_map = [[-1 for y in range(problem.getHeight()+2)] for x in range(problem.getWidth()+2)]
|
|
|
|
# We know that the outer_coords are all walls.
|
|
outer_wall_sent = []
|
|
for x, y in all_coords:
|
|
if ((x == 0 or x == problem.getWidth() + 1)
|
|
or (y == 0 or y == problem.getHeight() + 1)):
|
|
known_map[x][y] = 1
|
|
outer_wall_sent.append(PropSymbolExpr(wall_str, x, y))
|
|
KB.append(conjoin(outer_wall_sent))
|
|
|
|
"*** BEGIN YOUR CODE HERE ***"
|
|
util.raiseNotDefined()
|
|
|
|
for t in range(agent.num_timesteps):
|
|
"*** END YOUR CODE HERE ***"
|
|
yield (known_map, possible_locations)
|
|
|
|
|
|
# Abbreviations
|
|
plp = positionLogicPlan
|
|
loc = localization
|
|
mp = mapping
|
|
flp = foodLogicPlan
|
|
# Sometimes the logic module uses pretty deep recursion on long expressions
|
|
sys.setrecursionlimit(100000)
|
|
|
|
#______________________________________________________________________________
|
|
# Important expression generating functions, useful to read for understanding of this project.
|
|
|
|
|
|
def sensorAxioms(t: int, non_outer_wall_coords: List[Tuple[int, int]]) -> Expr:
|
|
all_percept_exprs = []
|
|
combo_var_def_exprs = []
|
|
for direction in DIRECTIONS:
|
|
percept_exprs = []
|
|
dx, dy = DIR_TO_DXDY_MAP[direction]
|
|
for x, y in non_outer_wall_coords:
|
|
combo_var = PropSymbolExpr(pacman_wall_str, x, y, x + dx, y + dy, time=t)
|
|
percept_exprs.append(combo_var)
|
|
combo_var_def_exprs.append(combo_var % (
|
|
PropSymbolExpr(pacman_str, x, y, time=t) & PropSymbolExpr(wall_str, x + dx, y + dy)))
|
|
|
|
percept_unit_clause = PropSymbolExpr(blocked_str_map[direction], time = t)
|
|
all_percept_exprs.append(percept_unit_clause % disjoin(percept_exprs))
|
|
|
|
return conjoin(all_percept_exprs + combo_var_def_exprs)
|
|
|
|
|
|
def fourBitPerceptRules(t: int, percepts: List) -> Expr:
|
|
"""
|
|
Localization and Mapping both use the 4 bit sensor, which tells us True/False whether
|
|
a wall is to pacman's north, south, east, and west.
|
|
"""
|
|
assert isinstance(percepts, list), "Percepts must be a list."
|
|
assert len(percepts) == 4, "Percepts must be a length 4 list."
|
|
|
|
percept_unit_clauses = []
|
|
for wall_present, direction in zip(percepts, DIRECTIONS):
|
|
percept_unit_clause = PropSymbolExpr(blocked_str_map[direction], time=t)
|
|
if not wall_present:
|
|
percept_unit_clause = ~PropSymbolExpr(blocked_str_map[direction], time=t)
|
|
percept_unit_clauses.append(percept_unit_clause) # The actual sensor readings
|
|
return conjoin(percept_unit_clauses)
|
|
|
|
|
|
def numAdjWallsPerceptRules(t: int, percepts: List) -> Expr:
|
|
"""
|
|
SLAM uses a weaker numAdjWallsPerceptRules sensor, which tells us how many walls pacman is adjacent to
|
|
in its four directions.
|
|
000 = 0 adj walls.
|
|
100 = 1 adj wall.
|
|
110 = 2 adj walls.
|
|
111 = 3 adj walls.
|
|
"""
|
|
assert isinstance(percepts, list), "Percepts must be a list."
|
|
assert len(percepts) == 3, "Percepts must be a length 3 list."
|
|
|
|
percept_unit_clauses = []
|
|
for i, percept in enumerate(percepts):
|
|
n = i + 1
|
|
percept_literal_n = PropSymbolExpr(geq_num_adj_wall_str_map[n], time=t)
|
|
if not percept:
|
|
percept_literal_n = ~percept_literal_n
|
|
percept_unit_clauses.append(percept_literal_n)
|
|
return conjoin(percept_unit_clauses)
|
|
|
|
|
|
def SLAMSensorAxioms(t: int, non_outer_wall_coords: List[Tuple[int, int]]) -> Expr:
|
|
all_percept_exprs = []
|
|
combo_var_def_exprs = []
|
|
for direction in DIRECTIONS:
|
|
percept_exprs = []
|
|
dx, dy = DIR_TO_DXDY_MAP[direction]
|
|
for x, y in non_outer_wall_coords:
|
|
combo_var = PropSymbolExpr(pacman_wall_str, x, y, x + dx, y + dy, time=t)
|
|
percept_exprs.append(combo_var)
|
|
combo_var_def_exprs.append(combo_var % (PropSymbolExpr(pacman_str, x, y, time=t) & PropSymbolExpr(wall_str, x + dx, y + dy)))
|
|
|
|
blocked_dir_clause = PropSymbolExpr(blocked_str_map[direction], time=t)
|
|
all_percept_exprs.append(blocked_dir_clause % disjoin(percept_exprs))
|
|
|
|
percept_to_blocked_sent = []
|
|
for n in range(1, 4):
|
|
wall_combos_size_n = itertools.combinations(blocked_str_map.values(), n)
|
|
n_walls_blocked_sent = disjoin([
|
|
conjoin([PropSymbolExpr(blocked_str, time=t) for blocked_str in wall_combo])
|
|
for wall_combo in wall_combos_size_n])
|
|
# n_walls_blocked_sent is of form: (N & S) | (N & E) | ...
|
|
percept_to_blocked_sent.append(
|
|
PropSymbolExpr(geq_num_adj_wall_str_map[n], time=t) % n_walls_blocked_sent)
|
|
|
|
return conjoin(all_percept_exprs + combo_var_def_exprs + percept_to_blocked_sent)
|
|
|
|
|
|
def allLegalSuccessorAxioms(t: int, walls_grid: List[List], non_outer_wall_coords: List[Tuple[int, int]]) -> Expr:
|
|
"""walls_grid can be a 2D array of ints or bools."""
|
|
all_xy_succ_axioms = []
|
|
for x, y in non_outer_wall_coords:
|
|
xy_succ_axiom = pacmanSuccessorAxiomSingle(
|
|
x, y, t, walls_grid)
|
|
if xy_succ_axiom:
|
|
all_xy_succ_axioms.append(xy_succ_axiom)
|
|
return conjoin(all_xy_succ_axioms)
|
|
|
|
|
|
def SLAMSuccessorAxioms(t: int, walls_grid: List[List], non_outer_wall_coords: List[Tuple[int, int]]) -> Expr:
|
|
"""walls_grid can be a 2D array of ints or bools."""
|
|
all_xy_succ_axioms = []
|
|
for x, y in non_outer_wall_coords:
|
|
xy_succ_axiom = SLAMSuccessorAxiomSingle(
|
|
x, y, t, walls_grid)
|
|
if xy_succ_axiom:
|
|
all_xy_succ_axioms.append(xy_succ_axiom)
|
|
return conjoin(all_xy_succ_axioms)
|
|
|
|
#______________________________________________________________________________
|
|
# Various useful functions, are not needed for completing the project but may be useful for debugging
|
|
|
|
|
|
def modelToString(model: Dict[Expr, bool]) -> str:
|
|
"""Converts the model to a string for printing purposes. The keys of a model are
|
|
sorted before converting the model to a string.
|
|
|
|
model: Either a boolean False or a dictionary of Expr symbols (keys)
|
|
and a corresponding assignment of True or False (values). This model is the output of
|
|
a call to pycoSAT.
|
|
"""
|
|
if model == False:
|
|
return "False"
|
|
else:
|
|
# Dictionary
|
|
modelList = sorted(model.items(), key=lambda item: str(item[0]))
|
|
return str(modelList)
|
|
|
|
|
|
def extractActionSequence(model: Dict[Expr, bool], actions: List) -> List:
|
|
"""
|
|
Convert a model in to an ordered list of actions.
|
|
model: Propositional logic model stored as a dictionary with keys being
|
|
the symbol strings and values being Boolean: True or False
|
|
Example:
|
|
>>> model = {"North[2]":True, "P[3,4,0]":True, "P[3,3,0]":False, "West[0]":True, "GhostScary":True, "West[2]":False, "South[1]":True, "East[0]":False}
|
|
>>> actions = ['North', 'South', 'East', 'West']
|
|
>>> plan = extractActionSequence(model, actions)
|
|
>>> print(plan)
|
|
['West', 'South', 'North']
|
|
"""
|
|
plan = [None for _ in range(len(model))]
|
|
for sym, val in model.items():
|
|
parsed = parseExpr(sym)
|
|
if type(parsed) == tuple and parsed[0] in actions and val:
|
|
action, _, time = parsed
|
|
plan[time] = action
|
|
#return list(filter(lambda x: x is not None, plan))
|
|
return [x for x in plan if x is not None]
|
|
|
|
|
|
# Helpful Debug Method
|
|
def visualizeCoords(coords_list, problem) -> None:
|
|
wallGrid = game.Grid(problem.walls.width, problem.walls.height, initialValue=False)
|
|
for (x, y) in itertools.product(range(problem.getWidth()+2), range(problem.getHeight()+2)):
|
|
if (x, y) in coords_list:
|
|
wallGrid.data[x][y] = True
|
|
print(wallGrid)
|
|
|
|
|
|
# Helpful Debug Method
|
|
def visualizeBoolArray(bool_arr, problem) -> None:
|
|
wallGrid = game.Grid(problem.walls.width, problem.walls.height, initialValue=False)
|
|
wallGrid.data = copy.deepcopy(bool_arr)
|
|
print(wallGrid)
|
|
|
|
class PlanningProblem:
|
|
"""
|
|
This class outlines the structure of a planning problem, but doesn't implement
|
|
any of the methods (in object-oriented terminology: an abstract class).
|
|
|
|
You do not need to change anything in this class, ever.
|
|
"""
|
|
|
|
def getStartState(self):
|
|
"""
|
|
Returns the start state for the planning problem.
|
|
"""
|
|
util.raiseNotDefined()
|
|
|
|
def getGhostStartStates(self):
|
|
"""
|
|
Returns a list containing the start state for each ghost.
|
|
Only used in problems that use ghosts (FoodGhostPlanningProblem)
|
|
"""
|
|
util.raiseNotDefined()
|
|
|
|
def getGoalState(self):
|
|
"""
|
|
Returns goal state for problem. Note only defined for problems that have
|
|
a unique goal state such as PositionPlanningProblem
|
|
"""
|
|
util.raiseNotDefined()
|