You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
822 lines
31 KiB
822 lines
31 KiB
2 years ago
|
# searchTestClasses.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).
|
||
|
|
||
|
|
||
|
import re
|
||
|
import testClasses
|
||
|
import textwrap
|
||
|
|
||
|
# import project specific code
|
||
|
import layout
|
||
|
import pacman
|
||
|
from search import SearchProblem
|
||
|
|
||
|
# helper function for printing solutions in solution files
|
||
|
def wrap_solution(solution):
|
||
|
if type(solution) == type([]):
|
||
|
return '\n'.join(textwrap.wrap(' '.join(solution)))
|
||
|
else:
|
||
|
return str(solution)
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
def followAction(state, action, problem):
|
||
|
for successor1, action1, cost1 in problem.getSuccessors(state):
|
||
|
if action == action1: return successor1
|
||
|
return None
|
||
|
|
||
|
def followPath(path, problem):
|
||
|
state = problem.getStartState()
|
||
|
states = [state]
|
||
|
for action in path:
|
||
|
state = followAction(state, action, problem)
|
||
|
states.append(state)
|
||
|
return states
|
||
|
|
||
|
def checkSolution(problem, path):
|
||
|
state = problem.getStartState()
|
||
|
for action in path:
|
||
|
state = followAction(state, action, problem)
|
||
|
return problem.isGoalState(state)
|
||
|
|
||
|
# Search problem on a plain graph
|
||
|
class GraphSearch(SearchProblem):
|
||
|
|
||
|
# Read in the state graph; define start/end states, edges and costs
|
||
|
def __init__(self, graph_text):
|
||
|
self.expanded_states = []
|
||
|
lines = graph_text.split('\n')
|
||
|
r = re.match('start_state:(.*)', lines[0])
|
||
|
if r == None:
|
||
|
print "Broken graph:"
|
||
|
print '"""%s"""' % graph_text
|
||
|
raise Exception("GraphSearch graph specification start_state not found or incorrect on line:" + l)
|
||
|
self.start_state = r.group(1).strip()
|
||
|
r = re.match('goal_states:(.*)', lines[1])
|
||
|
if r == None:
|
||
|
print "Broken graph:"
|
||
|
print '"""%s"""' % graph_text
|
||
|
raise Exception("GraphSearch graph specification goal_states not found or incorrect on line:" + l)
|
||
|
goals = r.group(1).split()
|
||
|
self.goals = map(str.strip, goals)
|
||
|
self.successors = {}
|
||
|
all_states = set()
|
||
|
self.orderedSuccessorTuples = []
|
||
|
for l in lines[2:]:
|
||
|
if len(l.split()) == 3:
|
||
|
start, action, next_state = l.split()
|
||
|
cost = 1
|
||
|
elif len(l.split()) == 4:
|
||
|
start, action, next_state, cost = l.split()
|
||
|
else:
|
||
|
print "Broken graph:"
|
||
|
print '"""%s"""' % graph_text
|
||
|
raise Exception("Invalid line in GraphSearch graph specification on line:" + l)
|
||
|
cost = float(cost)
|
||
|
self.orderedSuccessorTuples.append((start, action, next_state, cost))
|
||
|
all_states.add(start)
|
||
|
all_states.add(next_state)
|
||
|
if start not in self.successors:
|
||
|
self.successors[start] = []
|
||
|
self.successors[start].append((next_state, action, cost))
|
||
|
for s in all_states:
|
||
|
if s not in self.successors:
|
||
|
self.successors[s] = []
|
||
|
|
||
|
# Get start state
|
||
|
def getStartState(self):
|
||
|
return self.start_state
|
||
|
|
||
|
# Check if a state is a goal state
|
||
|
def isGoalState(self, state):
|
||
|
return state in self.goals
|
||
|
|
||
|
# Get all successors of a state
|
||
|
def getSuccessors(self, state):
|
||
|
self.expanded_states.append(state)
|
||
|
return list(self.successors[state])
|
||
|
|
||
|
# Calculate total cost of a sequence of actions
|
||
|
def getCostOfActions(self, actions):
|
||
|
total_cost = 0
|
||
|
state = self.start_state
|
||
|
for a in actions:
|
||
|
successors = self.successors[state]
|
||
|
match = False
|
||
|
for (next_state, action, cost) in successors:
|
||
|
if a == action:
|
||
|
state = next_state
|
||
|
total_cost += cost
|
||
|
match = True
|
||
|
if not match:
|
||
|
print 'invalid action sequence'
|
||
|
sys.exit(1)
|
||
|
return total_cost
|
||
|
|
||
|
# Return a list of all states on which 'getSuccessors' was called
|
||
|
def getExpandedStates(self):
|
||
|
return self.expanded_states
|
||
|
|
||
|
def __str__(self):
|
||
|
print self.successors
|
||
|
edges = ["%s %s %s %s" % t for t in self.orderedSuccessorTuples]
|
||
|
return \
|
||
|
"""start_state: %s
|
||
|
goal_states: %s
|
||
|
%s""" % (self.start_state, " ".join(self.goals), "\n".join(edges))
|
||
|
|
||
|
|
||
|
|
||
|
def parseHeuristic(heuristicText):
|
||
|
heuristic = {}
|
||
|
for line in heuristicText.split('\n'):
|
||
|
tokens = line.split()
|
||
|
if len(tokens) != 2:
|
||
|
print "Broken heuristic:"
|
||
|
print '"""%s"""' % graph_text
|
||
|
raise Exception("GraphSearch heuristic specification broken:" + l)
|
||
|
state, h = tokens
|
||
|
heuristic[state] = float(h)
|
||
|
|
||
|
def graphHeuristic(state, problem=None):
|
||
|
if state in heuristic:
|
||
|
return heuristic[state]
|
||
|
else:
|
||
|
pp = pprint.PrettyPrinter(indent=4)
|
||
|
print "Heuristic:"
|
||
|
pp.pprint(heuristic)
|
||
|
raise Exception("Graph heuristic called with invalid state: " + str(state))
|
||
|
|
||
|
return graphHeuristic
|
||
|
|
||
|
|
||
|
class GraphSearchTest(testClasses.TestCase):
|
||
|
|
||
|
def __init__(self, question, testDict):
|
||
|
super(GraphSearchTest, self).__init__(question, testDict)
|
||
|
self.graph_text = testDict['graph']
|
||
|
self.alg = testDict['algorithm']
|
||
|
self.diagram = testDict['diagram']
|
||
|
self.exactExpansionOrder = testDict.get('exactExpansionOrder', 'True').lower() == "true"
|
||
|
if 'heuristic' in testDict:
|
||
|
self.heuristic = parseHeuristic(testDict['heuristic'])
|
||
|
else:
|
||
|
self.heuristic = None
|
||
|
|
||
|
# Note that the return type of this function is a tripple:
|
||
|
# (solution, expanded states, error message)
|
||
|
def getSolInfo(self, search):
|
||
|
alg = getattr(search, self.alg)
|
||
|
problem = GraphSearch(self.graph_text)
|
||
|
if self.heuristic != None:
|
||
|
solution = alg(problem, self.heuristic)
|
||
|
else:
|
||
|
solution = alg(problem)
|
||
|
|
||
|
if type(solution) != type([]):
|
||
|
return None, None, 'The result of %s must be a list. (Instead, it is %s)' % (self.alg, type(solution))
|
||
|
|
||
|
return solution, problem.getExpandedStates(), None
|
||
|
|
||
|
# Run student code. If an error message is returned, print error and return false.
|
||
|
# If a good solution is returned, printn the solution and return true; otherwise,
|
||
|
# print both the correct and student's solution and return false.
|
||
|
def execute(self, grades, moduleDict, solutionDict):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
gold_solution = [str.split(solutionDict['solution']), str.split(solutionDict['rev_solution'])]
|
||
|
gold_expanded_states = [str.split(solutionDict['expanded_states']), str.split(solutionDict['rev_expanded_states'])]
|
||
|
|
||
|
solution, expanded_states, error = self.getSolInfo(search)
|
||
|
if error != None:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('\t%s' % error)
|
||
|
return False
|
||
|
|
||
|
if solution in gold_solution and (not self.exactExpansionOrder or expanded_states in gold_expanded_states):
|
||
|
grades.addMessage('PASS: %s' % self.path)
|
||
|
grades.addMessage('\tsolution:\t\t%s' % solution)
|
||
|
grades.addMessage('\texpanded_states:\t%s' % expanded_states)
|
||
|
return True
|
||
|
else:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('\tgraph:')
|
||
|
for line in self.diagram.split('\n'):
|
||
|
grades.addMessage('\t %s' % (line,))
|
||
|
grades.addMessage('\tstudent solution:\t\t%s' % solution)
|
||
|
grades.addMessage('\tstudent expanded_states:\t%s' % expanded_states)
|
||
|
grades.addMessage('')
|
||
|
grades.addMessage('\tcorrect solution:\t\t%s' % gold_solution[0])
|
||
|
grades.addMessage('\tcorrect expanded_states:\t%s' % gold_expanded_states[0])
|
||
|
grades.addMessage('\tcorrect rev_solution:\t\t%s' % gold_solution[1])
|
||
|
grades.addMessage('\tcorrect rev_expanded_states:\t%s' % gold_expanded_states[1])
|
||
|
return False
|
||
|
|
||
|
def writeSolution(self, moduleDict, filePath):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
# open file and write comments
|
||
|
handle = open(filePath, 'w')
|
||
|
handle.write('# This is the solution file for %s.\n' % self.path)
|
||
|
handle.write('# This solution is designed to support both right-to-left\n')
|
||
|
handle.write('# and left-to-right implementations.\n')
|
||
|
|
||
|
# write forward solution
|
||
|
solution, expanded_states, error = self.getSolInfo(search)
|
||
|
if error != None: raise Exception("Error in solution code: %s" % error)
|
||
|
handle.write('solution: "%s"\n' % ' '.join(solution))
|
||
|
handle.write('expanded_states: "%s"\n' % ' '.join(expanded_states))
|
||
|
|
||
|
# reverse and write backwards solution
|
||
|
search.REVERSE_PUSH = not search.REVERSE_PUSH
|
||
|
solution, expanded_states, error = self.getSolInfo(search)
|
||
|
if error != None: raise Exception("Error in solution code: %s" % error)
|
||
|
handle.write('rev_solution: "%s"\n' % ' '.join(solution))
|
||
|
handle.write('rev_expanded_states: "%s"\n' % ' '.join(expanded_states))
|
||
|
|
||
|
# clean up
|
||
|
search.REVERSE_PUSH = not search.REVERSE_PUSH
|
||
|
handle.close()
|
||
|
return True
|
||
|
|
||
|
|
||
|
|
||
|
class PacmanSearchTest(testClasses.TestCase):
|
||
|
|
||
|
def __init__(self, question, testDict):
|
||
|
super(PacmanSearchTest, self).__init__(question, testDict)
|
||
|
self.layout_text = testDict['layout']
|
||
|
self.alg = testDict['algorithm']
|
||
|
self.layoutName = testDict['layoutName']
|
||
|
|
||
|
# TODO: sensible to have defaults like this?
|
||
|
self.leewayFactor = float(testDict.get('leewayFactor', '1'))
|
||
|
self.costFn = eval(testDict.get('costFn', 'None'))
|
||
|
self.searchProblemClassName = testDict.get('searchProblemClass', 'PositionSearchProblem')
|
||
|
self.heuristicName = testDict.get('heuristic', None)
|
||
|
|
||
|
|
||
|
def getSolInfo(self, search, searchAgents):
|
||
|
alg = getattr(search, self.alg)
|
||
|
lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')])
|
||
|
start_state = pacman.GameState()
|
||
|
start_state.initialize(lay, 0)
|
||
|
|
||
|
problemClass = getattr(searchAgents, self.searchProblemClassName)
|
||
|
problemOptions = {}
|
||
|
if self.costFn != None:
|
||
|
problemOptions['costFn'] = self.costFn
|
||
|
problem = problemClass(start_state, **problemOptions)
|
||
|
heuristic = getattr(searchAgents, self.heuristicName) if self.heuristicName != None else None
|
||
|
|
||
|
if heuristic != None:
|
||
|
solution = alg(problem, heuristic)
|
||
|
else:
|
||
|
solution = alg(problem)
|
||
|
|
||
|
if type(solution) != type([]):
|
||
|
return None, None, 'The result of %s must be a list. (Instead, it is %s)' % (self.alg, type(solution))
|
||
|
|
||
|
from game import Directions
|
||
|
dirs = Directions.LEFT.keys()
|
||
|
if [el in dirs for el in solution].count(False) != 0:
|
||
|
return None, None, 'Output of %s must be a list of actions from game.Directions' % self.alg
|
||
|
|
||
|
expanded = problem._expanded
|
||
|
return solution, expanded, None
|
||
|
|
||
|
def execute(self, grades, moduleDict, solutionDict):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
gold_solution = [str.split(solutionDict['solution']), str.split(solutionDict['rev_solution'])]
|
||
|
gold_expanded = max(int(solutionDict['expanded_nodes']), int(solutionDict['rev_expanded_nodes']))
|
||
|
|
||
|
solution, expanded, error = self.getSolInfo(search, searchAgents)
|
||
|
if error != None:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('%s' % error)
|
||
|
return False
|
||
|
|
||
|
# FIXME: do we want to standardize test output format?
|
||
|
|
||
|
if solution not in gold_solution:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('Solution not correct.')
|
||
|
grades.addMessage('\tstudent solution length: %s' % len(solution))
|
||
|
grades.addMessage('\tstudent solution:\n%s' % wrap_solution(solution))
|
||
|
grades.addMessage('')
|
||
|
grades.addMessage('\tcorrect solution length: %s' % len(gold_solution[0]))
|
||
|
grades.addMessage('\tcorrect (reversed) solution length: %s' % len(gold_solution[1]))
|
||
|
grades.addMessage('\tcorrect solution:\n%s' % wrap_solution(gold_solution[0]))
|
||
|
grades.addMessage('\tcorrect (reversed) solution:\n%s' % wrap_solution(gold_solution[1]))
|
||
|
return False
|
||
|
|
||
|
if expanded > self.leewayFactor * gold_expanded and expanded > gold_expanded + 1:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('Too many node expanded; are you expanding nodes twice?')
|
||
|
grades.addMessage('\tstudent nodes expanded: %s' % expanded)
|
||
|
grades.addMessage('')
|
||
|
grades.addMessage('\tcorrect nodes expanded: %s (leewayFactor %s)' % (gold_expanded, self.leewayFactor))
|
||
|
return False
|
||
|
|
||
|
grades.addMessage('PASS: %s' % self.path)
|
||
|
grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName)
|
||
|
grades.addMessage('\tsolution length: %s' % len(solution))
|
||
|
grades.addMessage('\tnodes expanded:\t\t%s' % expanded)
|
||
|
return True
|
||
|
|
||
|
|
||
|
def writeSolution(self, moduleDict, filePath):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
# open file and write comments
|
||
|
handle = open(filePath, 'w')
|
||
|
handle.write('# This is the solution file for %s.\n' % self.path)
|
||
|
handle.write('# This solution is designed to support both right-to-left\n')
|
||
|
handle.write('# and left-to-right implementations.\n')
|
||
|
handle.write('# Number of nodes expanded must be with a factor of %s of the numbers below.\n' % self.leewayFactor)
|
||
|
|
||
|
# write forward solution
|
||
|
solution, expanded, error = self.getSolInfo(search, searchAgents)
|
||
|
if error != None: raise Exception("Error in solution code: %s" % error)
|
||
|
handle.write('solution: """\n%s\n"""\n' % wrap_solution(solution))
|
||
|
handle.write('expanded_nodes: "%s"\n' % expanded)
|
||
|
|
||
|
# write backward solution
|
||
|
search.REVERSE_PUSH = not search.REVERSE_PUSH
|
||
|
solution, expanded, error = self.getSolInfo(search, searchAgents)
|
||
|
if error != None: raise Exception("Error in solution code: %s" % error)
|
||
|
handle.write('rev_solution: """\n%s\n"""\n' % wrap_solution(solution))
|
||
|
handle.write('rev_expanded_nodes: "%s"\n' % expanded)
|
||
|
|
||
|
# clean up
|
||
|
search.REVERSE_PUSH = not search.REVERSE_PUSH
|
||
|
handle.close()
|
||
|
return True
|
||
|
|
||
|
|
||
|
from game import Actions
|
||
|
def getStatesFromPath(start, path):
|
||
|
"Returns the list of states visited along the path"
|
||
|
vis = [start]
|
||
|
curr = start
|
||
|
for a in path:
|
||
|
x,y = curr
|
||
|
dx, dy = Actions.directionToVector(a)
|
||
|
curr = (int(x + dx), int(y + dy))
|
||
|
vis.append(curr)
|
||
|
return vis
|
||
|
|
||
|
class CornerProblemTest(testClasses.TestCase):
|
||
|
|
||
|
def __init__(self, question, testDict):
|
||
|
super(CornerProblemTest, self).__init__(question, testDict)
|
||
|
self.layoutText = testDict['layout']
|
||
|
self.layoutName = testDict['layoutName']
|
||
|
|
||
|
def solution(self, search, searchAgents):
|
||
|
lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')])
|
||
|
gameState = pacman.GameState()
|
||
|
gameState.initialize(lay, 0)
|
||
|
problem = searchAgents.CornersProblem(gameState)
|
||
|
path = search.bfs(problem)
|
||
|
|
||
|
gameState = pacman.GameState()
|
||
|
gameState.initialize(lay, 0)
|
||
|
visited = getStatesFromPath(gameState.getPacmanPosition(), path)
|
||
|
top, right = gameState.getWalls().height-2, gameState.getWalls().width-2
|
||
|
missedCorners = [p for p in ((1,1), (1,top), (right, 1), (right, top)) if p not in visited]
|
||
|
|
||
|
return path, missedCorners
|
||
|
|
||
|
def execute(self, grades, moduleDict, solutionDict):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
gold_length = int(solutionDict['solution_length'])
|
||
|
solution, missedCorners = self.solution(search, searchAgents)
|
||
|
|
||
|
if type(solution) != type([]):
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('The result must be a list. (Instead, it is %s)' % type(solution))
|
||
|
return False
|
||
|
|
||
|
if len(missedCorners) != 0:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('Corners missed: %s' % missedCorners)
|
||
|
return False
|
||
|
|
||
|
if len(solution) != gold_length:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('Optimal solution not found.')
|
||
|
grades.addMessage('\tstudent solution length:\n%s' % len(solution))
|
||
|
grades.addMessage('')
|
||
|
grades.addMessage('\tcorrect solution length:\n%s' % gold_length)
|
||
|
return False
|
||
|
|
||
|
grades.addMessage('PASS: %s' % self.path)
|
||
|
grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName)
|
||
|
grades.addMessage('\tsolution length:\t\t%s' % len(solution))
|
||
|
return True
|
||
|
|
||
|
def writeSolution(self, moduleDict, filePath):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
# open file and write comments
|
||
|
handle = open(filePath, 'w')
|
||
|
handle.write('# This is the solution file for %s.\n' % self.path)
|
||
|
|
||
|
print "Solving problem", self.layoutName
|
||
|
print self.layoutText
|
||
|
|
||
|
path, _ = self.solution(search, searchAgents)
|
||
|
length = len(path)
|
||
|
print "Problem solved"
|
||
|
|
||
|
handle.write('solution_length: "%s"\n' % length)
|
||
|
handle.close()
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# template = """class: "HeuristicTest"
|
||
|
#
|
||
|
# heuristic: "foodHeuristic"
|
||
|
# searchProblemClass: "FoodSearchProblem"
|
||
|
# layoutName: "Test %s"
|
||
|
# layout: \"\"\"
|
||
|
# %s
|
||
|
# \"\"\"
|
||
|
# """
|
||
|
#
|
||
|
# for i, (_, _, l) in enumerate(doneTests + foodTests):
|
||
|
# f = open("food_heuristic_%s.test" % (i+1), "w")
|
||
|
# f.write(template % (i+1, "\n".join(l)))
|
||
|
# f.close()
|
||
|
|
||
|
class HeuristicTest(testClasses.TestCase):
|
||
|
|
||
|
def __init__(self, question, testDict):
|
||
|
super(HeuristicTest, self).__init__(question, testDict)
|
||
|
self.layoutText = testDict['layout']
|
||
|
self.layoutName = testDict['layoutName']
|
||
|
self.searchProblemClassName = testDict['searchProblemClass']
|
||
|
self.heuristicName = testDict['heuristic']
|
||
|
|
||
|
def setupProblem(self, searchAgents):
|
||
|
lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')])
|
||
|
gameState = pacman.GameState()
|
||
|
gameState.initialize(lay, 0)
|
||
|
problemClass = getattr(searchAgents, self.searchProblemClassName)
|
||
|
problem = problemClass(gameState)
|
||
|
state = problem.getStartState()
|
||
|
heuristic = getattr(searchAgents, self.heuristicName)
|
||
|
|
||
|
return problem, state, heuristic
|
||
|
|
||
|
def checkHeuristic(self, heuristic, problem, state, solutionCost):
|
||
|
h0 = heuristic(state, problem)
|
||
|
|
||
|
if solutionCost == 0:
|
||
|
if h0 == 0:
|
||
|
return True, ''
|
||
|
else:
|
||
|
return False, 'Heuristic failed H(goal) == 0 test'
|
||
|
|
||
|
if h0 < 0:
|
||
|
return False, 'Heuristic failed H >= 0 test'
|
||
|
if not h0 > 0:
|
||
|
return False, 'Heuristic failed non-triviality test'
|
||
|
if not h0 <= solutionCost:
|
||
|
return False, 'Heuristic failed admissibility test'
|
||
|
|
||
|
for succ, action, stepCost in problem.getSuccessors(state):
|
||
|
h1 = heuristic(succ, problem)
|
||
|
if h1 < 0: return False, 'Heuristic failed H >= 0 test'
|
||
|
if h0 - h1 > stepCost: return False, 'Heuristic failed consistency test'
|
||
|
|
||
|
return True, ''
|
||
|
|
||
|
def execute(self, grades, moduleDict, solutionDict):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
solutionCost = int(solutionDict['solution_cost'])
|
||
|
problem, state, heuristic = self.setupProblem(searchAgents)
|
||
|
|
||
|
passed, message = self.checkHeuristic(heuristic, problem, state, solutionCost)
|
||
|
|
||
|
if not passed:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('%s' % message)
|
||
|
return False
|
||
|
else:
|
||
|
grades.addMessage('PASS: %s' % self.path)
|
||
|
return True
|
||
|
|
||
|
def writeSolution(self, moduleDict, filePath):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
# open file and write comments
|
||
|
handle = open(filePath, 'w')
|
||
|
handle.write('# This is the solution file for %s.\n' % self.path)
|
||
|
|
||
|
print "Solving problem", self.layoutName, self.heuristicName
|
||
|
print self.layoutText
|
||
|
problem, _, heuristic = self.setupProblem(searchAgents)
|
||
|
path = search.astar(problem, heuristic)
|
||
|
cost = problem.getCostOfActions(path)
|
||
|
print "Problem solved"
|
||
|
|
||
|
handle.write('solution_cost: "%s"\n' % cost)
|
||
|
handle.close()
|
||
|
return True
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
class HeuristicGrade(testClasses.TestCase):
|
||
|
|
||
|
def __init__(self, question, testDict):
|
||
|
super(HeuristicGrade, self).__init__(question, testDict)
|
||
|
self.layoutText = testDict['layout']
|
||
|
self.layoutName = testDict['layoutName']
|
||
|
self.searchProblemClassName = testDict['searchProblemClass']
|
||
|
self.heuristicName = testDict['heuristic']
|
||
|
self.basePoints = int(testDict['basePoints'])
|
||
|
self.thresholds = [int(t) for t in testDict['gradingThresholds'].split()]
|
||
|
|
||
|
def setupProblem(self, searchAgents):
|
||
|
lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')])
|
||
|
gameState = pacman.GameState()
|
||
|
gameState.initialize(lay, 0)
|
||
|
problemClass = getattr(searchAgents, self.searchProblemClassName)
|
||
|
problem = problemClass(gameState)
|
||
|
state = problem.getStartState()
|
||
|
heuristic = getattr(searchAgents, self.heuristicName)
|
||
|
|
||
|
return problem, state, heuristic
|
||
|
|
||
|
|
||
|
def execute(self, grades, moduleDict, solutionDict):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
problem, _, heuristic = self.setupProblem(searchAgents)
|
||
|
|
||
|
path = search.astar(problem, heuristic)
|
||
|
|
||
|
expanded = problem._expanded
|
||
|
|
||
|
if not checkSolution(problem, path):
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('\tReturned path is not a solution.')
|
||
|
grades.addMessage('\tpath returned by astar: %s' % expanded)
|
||
|
return False
|
||
|
|
||
|
grades.addPoints(self.basePoints)
|
||
|
points = 0
|
||
|
for threshold in self.thresholds:
|
||
|
if expanded <= threshold:
|
||
|
points += 1
|
||
|
grades.addPoints(points)
|
||
|
if points >= len(self.thresholds):
|
||
|
grades.addMessage('PASS: %s' % self.path)
|
||
|
else:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('\texpanded nodes: %s' % expanded)
|
||
|
grades.addMessage('\tthresholds: %s' % self.thresholds)
|
||
|
|
||
|
return True
|
||
|
|
||
|
|
||
|
def writeSolution(self, moduleDict, filePath):
|
||
|
handle = open(filePath, 'w')
|
||
|
handle.write('# This is the solution file for %s.\n' % self.path)
|
||
|
handle.write('# File intentionally blank.\n')
|
||
|
handle.close()
|
||
|
return True
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
# template = """class: "ClosestDotTest"
|
||
|
#
|
||
|
# layoutName: "Test %s"
|
||
|
# layout: \"\"\"
|
||
|
# %s
|
||
|
# \"\"\"
|
||
|
# """
|
||
|
#
|
||
|
# for i, (_, _, l) in enumerate(foodTests):
|
||
|
# f = open("closest_dot_%s.test" % (i+1), "w")
|
||
|
# f.write(template % (i+1, "\n".join(l)))
|
||
|
# f.close()
|
||
|
|
||
|
class ClosestDotTest(testClasses.TestCase):
|
||
|
|
||
|
def __init__(self, question, testDict):
|
||
|
super(ClosestDotTest, self).__init__(question, testDict)
|
||
|
self.layoutText = testDict['layout']
|
||
|
self.layoutName = testDict['layoutName']
|
||
|
|
||
|
def solution(self, searchAgents):
|
||
|
lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')])
|
||
|
gameState = pacman.GameState()
|
||
|
gameState.initialize(lay, 0)
|
||
|
path = searchAgents.ClosestDotSearchAgent().findPathToClosestDot(gameState)
|
||
|
return path
|
||
|
|
||
|
def execute(self, grades, moduleDict, solutionDict):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
gold_length = int(solutionDict['solution_length'])
|
||
|
solution = self.solution(searchAgents)
|
||
|
|
||
|
if type(solution) != type([]):
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('\tThe result must be a list. (Instead, it is %s)' % type(solution))
|
||
|
return False
|
||
|
|
||
|
if len(solution) != gold_length:
|
||
|
grades.addMessage('FAIL: %s' % self.path)
|
||
|
grades.addMessage('Closest dot not found.')
|
||
|
grades.addMessage('\tstudent solution length:\n%s' % len(solution))
|
||
|
grades.addMessage('')
|
||
|
grades.addMessage('\tcorrect solution length:\n%s' % gold_length)
|
||
|
return False
|
||
|
|
||
|
grades.addMessage('PASS: %s' % self.path)
|
||
|
grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName)
|
||
|
grades.addMessage('\tsolution length:\t\t%s' % len(solution))
|
||
|
return True
|
||
|
|
||
|
def writeSolution(self, moduleDict, filePath):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
# open file and write comments
|
||
|
handle = open(filePath, 'w')
|
||
|
handle.write('# This is the solution file for %s.\n' % self.path)
|
||
|
|
||
|
print "Solving problem", self.layoutName
|
||
|
print self.layoutText
|
||
|
|
||
|
length = len(self.solution(searchAgents))
|
||
|
print "Problem solved"
|
||
|
|
||
|
handle.write('solution_length: "%s"\n' % length)
|
||
|
handle.close()
|
||
|
return True
|
||
|
|
||
|
|
||
|
|
||
|
|
||
|
class CornerHeuristicSanity(testClasses.TestCase):
|
||
|
|
||
|
def __init__(self, question, testDict):
|
||
|
super(CornerHeuristicSanity, self).__init__(question, testDict)
|
||
|
self.layout_text = testDict['layout']
|
||
|
|
||
|
def execute(self, grades, moduleDict, solutionDict):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
game_state = pacman.GameState()
|
||
|
lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')])
|
||
|
game_state.initialize(lay, 0)
|
||
|
problem = searchAgents.CornersProblem(game_state)
|
||
|
start_state = problem.getStartState()
|
||
|
h0 = searchAgents.cornersHeuristic(start_state, problem)
|
||
|
succs = problem.getSuccessors(start_state)
|
||
|
# cornerConsistencyA
|
||
|
for succ in succs:
|
||
|
h1 = searchAgents.cornersHeuristic(succ[0], problem)
|
||
|
if h0 - h1 > 1:
|
||
|
grades.addMessage('FAIL: inconsistent heuristic')
|
||
|
return False
|
||
|
heuristic_cost = searchAgents.cornersHeuristic(start_state, problem)
|
||
|
true_cost = float(solutionDict['cost'])
|
||
|
# cornerNontrivial
|
||
|
if heuristic_cost == 0:
|
||
|
grades.addMessage('FAIL: must use non-trivial heuristic')
|
||
|
return False
|
||
|
# cornerAdmissible
|
||
|
if heuristic_cost > true_cost:
|
||
|
grades.addMessage('FAIL: Inadmissible heuristic')
|
||
|
return False
|
||
|
path = solutionDict['path'].split()
|
||
|
states = followPath(path, problem)
|
||
|
heuristics = []
|
||
|
for state in states:
|
||
|
heuristics.append(searchAgents.cornersHeuristic(state, problem))
|
||
|
for i in range(0, len(heuristics) - 1):
|
||
|
h0 = heuristics[i]
|
||
|
h1 = heuristics[i+1]
|
||
|
# cornerConsistencyB
|
||
|
if h0 - h1 > 1:
|
||
|
grades.addMessage('FAIL: inconsistent heuristic')
|
||
|
return False
|
||
|
# cornerPosH
|
||
|
if h0 < 0 or h1 <0:
|
||
|
grades.addMessage('FAIL: non-positive heuristic')
|
||
|
return False
|
||
|
# cornerGoalH
|
||
|
if heuristics[len(heuristics) - 1] != 0:
|
||
|
grades.addMessage('FAIL: heuristic non-zero at goal')
|
||
|
return False
|
||
|
grades.addMessage('PASS: heuristic value less than true cost at start state')
|
||
|
return True
|
||
|
|
||
|
def writeSolution(self, moduleDict, filePath):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
# write comment
|
||
|
handle = open(filePath, 'w')
|
||
|
handle.write('# In order for a heuristic to be admissible, the value\n')
|
||
|
handle.write('# of the heuristic must be less at each state than the\n')
|
||
|
handle.write('# true cost of the optimal path from that state to a goal.\n')
|
||
|
|
||
|
# solve problem and write solution
|
||
|
lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')])
|
||
|
start_state = pacman.GameState()
|
||
|
start_state.initialize(lay, 0)
|
||
|
problem = searchAgents.CornersProblem(start_state)
|
||
|
solution = search.astar(problem, searchAgents.cornersHeuristic)
|
||
|
handle.write('cost: "%d"\n' % len(solution))
|
||
|
handle.write('path: """\n%s\n"""\n' % wrap_solution(solution))
|
||
|
handle.close()
|
||
|
return True
|
||
|
|
||
|
|
||
|
|
||
|
class CornerHeuristicPacman(testClasses.TestCase):
|
||
|
|
||
|
def __init__(self, question, testDict):
|
||
|
super(CornerHeuristicPacman, self).__init__(question, testDict)
|
||
|
self.layout_text = testDict['layout']
|
||
|
|
||
|
def execute(self, grades, moduleDict, solutionDict):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
total = 0
|
||
|
true_cost = float(solutionDict['cost'])
|
||
|
thresholds = map(int, solutionDict['thresholds'].split())
|
||
|
game_state = pacman.GameState()
|
||
|
lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')])
|
||
|
game_state.initialize(lay, 0)
|
||
|
problem = searchAgents.CornersProblem(game_state)
|
||
|
start_state = problem.getStartState()
|
||
|
if searchAgents.cornersHeuristic(start_state, problem) > true_cost:
|
||
|
grades.addMessage('FAIL: Inadmissible heuristic')
|
||
|
return False
|
||
|
path = search.astar(problem, searchAgents.cornersHeuristic)
|
||
|
print "path:", path
|
||
|
print "path length:", len(path)
|
||
|
cost = problem.getCostOfActions(path)
|
||
|
if cost > true_cost:
|
||
|
grades.addMessage('FAIL: Inconsistent heuristic')
|
||
|
return False
|
||
|
expanded = problem._expanded
|
||
|
points = 0
|
||
|
for threshold in thresholds:
|
||
|
if expanded <= threshold:
|
||
|
points += 1
|
||
|
grades.addPoints(points)
|
||
|
if points >= len(thresholds):
|
||
|
grades.addMessage('PASS: Heuristic resulted in expansion of %d nodes' % expanded)
|
||
|
else:
|
||
|
grades.addMessage('FAIL: Heuristic resulted in expansion of %d nodes' % expanded)
|
||
|
return True
|
||
|
|
||
|
def writeSolution(self, moduleDict, filePath):
|
||
|
search = moduleDict['search']
|
||
|
searchAgents = moduleDict['searchAgents']
|
||
|
# write comment
|
||
|
handle = open(filePath, 'w')
|
||
|
handle.write('# This solution file specifies the length of the optimal path\n')
|
||
|
handle.write('# as well as the thresholds on number of nodes expanded to be\n')
|
||
|
handle.write('# used in scoring.\n')
|
||
|
|
||
|
# solve problem and write solution
|
||
|
lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')])
|
||
|
start_state = pacman.GameState()
|
||
|
start_state.initialize(lay, 0)
|
||
|
problem = searchAgents.CornersProblem(start_state)
|
||
|
solution = search.astar(problem, searchAgents.cornersHeuristic)
|
||
|
handle.write('cost: "%d"\n' % len(solution))
|
||
|
handle.write('path: """\n%s\n"""\n' % wrap_solution(solution))
|
||
|
handle.write('thresholds: "2000 1600 1200"\n')
|
||
|
handle.close()
|
||
|
return True
|
||
|
|