diff --git a/code/pacman/search/search.py b/code/pacman/search/search.py index 23a8fe5..95575cd 100644 --- a/code/pacman/search/search.py +++ b/code/pacman/search/search.py @@ -1,4 +1,21 @@ -# search.py + +
+ + + + + + + ++# search.py # --------- # Licensing Information: You are free to use or extend these projects for # educational purposes provided that (1) you do not distribute or publish @@ -12,37 +29,37 @@ # Pieter Abbeel (pabbeel@cs.berkeley.edu). -""" +""" In search.py, you will implement generic search algorithms which are called by Pacman agents (in searchAgents.py). """ -import util +import util -class SearchProblem: - """ +class SearchProblem: + """ This class outlines the structure of a search 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): - """ + def getStartState(self): + """ Returns the start state for the search problem. """ - util.raiseNotDefined() + util.raiseNotDefined() - def isGoalState(self, state): - """ + def isGoalState(self, state): + """ state: Search state Returns True if and only if the state is a valid goal state. """ - util.raiseNotDefined() + util.raiseNotDefined() - def getSuccessors(self, state): - """ + def getSuccessors(self, state): + """ state: Search state For a given state, this should return a list of triples, (successor, @@ -50,52 +67,30 @@ def getSuccessors(self, state): state, 'action' is the action required to get there, and 'stepCost' is the incremental cost of expanding to that successor. """ - util.raiseNotDefined() + util.raiseNotDefined() - def getCostOfActions(self, actions): - """ + def getCostOfActions(self, actions): + """ actions: A list of actions to take This method returns the total cost of a particular sequence of actions. The sequence must be composed of legal moves. """ - util.raiseNotDefined() + util.raiseNotDefined() -def tinyMazeSearch(problem): - """ +def tinyMazeSearch(problem): + """ Returns a sequence of moves that solves tinyMaze. For any other maze, the sequence of moves will be incorrect, so only use this for tinyMaze. """ - from game import Directions - s = Directions.SOUTH - w = Directions.WEST - return [s, s, w, s, w, w, s, w] - + from game import Directions + s = Directions.SOUTH + w = Directions.WEST + return [s, s, w, s, w, w, s, w] -def nullHeuristic(state, problem=None): - """ - A heuristic function estimates the cost from the current state to the nearest - goal in the provided SearchProblem. This heuristic is trivial. - """ - return 0 - -def generalPurposeSearch(problem, stack, heuristic=nullHeuristic): - marked = set() - stack.push(([], problem.getStartState(), 0, 0)) - while not stack.isEmpty(): - (directions, nextState, costSoFar, stateValue) = stack.pop() - if problem.isGoalState(nextState): - return directions - if nextState not in marked: - successors = problem.getSuccessors(nextState) - for (successor, direction, cost) in successors: - hCost = heuristic(successor, problem) - stack.push((directions + [direction], successor, costSoFar + cost, costSoFar + cost + hCost)) - marked.add(nextState) - -def depthFirstSearch(problem): - """ +def depthFirstSearch(problem): + """ Search the deepest nodes in the search tree first. Your search algorithm needs to return a list of actions that reaches the @@ -108,32 +103,59 @@ def depthFirstSearch(problem): print "Is the start a goal?", problem.isGoalState(problem.getStartState()) print "Start's successors:", problem.getSuccessors(problem.getStartState()) """ - # Problem with this code: does not use linear space - return generalPurposeSearch(problem, util.Stack()) - - -def breadthFirstSearch(problem): - """Search the shallowest nodes in the search tree first.""" - return generalPurposeSearch(problem, util.Queue()) + "*** YOUR CODE HERE ***" + util.raiseNotDefined() +def breadthFirstSearch(problem): + """Search the shallowest nodes in the search tree first.""" + "*** YOUR CODE HERE ***" + util.raiseNotDefined() -def uniformCostSearch(problem): - """Search the node of least total cost first.""" - return generalPurposeSearch( - problem, - util.PriorityQueueWithFunction(lambda x: x[2])) - +def uniformCostSearch(problem): + """Search the node of least total cost first.""" + "*** YOUR CODE HERE ***" + util.raiseNotDefined() -def aStarSearch(problem, heuristic=nullHeuristic): - """Search the node that has the lowest combined cost and heuristic first.""" - return generalPurposeSearch( - problem, - util.PriorityQueueWithFunction(lambda x: x[3]), - heuristic) - - -# Abbreviations -bfs = breadthFirstSearch -dfs = depthFirstSearch -astar = aStarSearch -ucs = uniformCostSearch +def nullHeuristic(state, problem=None): + """ + A heuristic function estimates the cost from the current state to the nearest + goal in the provided SearchProblem. This heuristic is trivial. + """ + return 0 + +def aStarSearch(problem, heuristic=nullHeuristic): + """Search the node that has the lowest combined cost and heuristic first.""" + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + + +# Abbreviations +bfs = breadthFirstSearch +dfs = depthFirstSearch +astar = aStarSearch +ucs = uniformCostSearch + ++ + + + diff --git a/code/pacman/search/searchAgents.py b/code/pacman/search/searchAgents.py index 8d78f7c..eddacc3 100644 --- a/code/pacman/search/searchAgents.py +++ b/code/pacman/search/searchAgents.py @@ -1,4 +1,21 @@ -# searchAgents.py + + + + + + + + + +
+# searchAgents.py # --------------- # Licensing Information: You are free to use or extend these projects for # educational purposes provided that (1) you do not distribute or publish @@ -12,13 +29,13 @@ # Pieter Abbeel (pabbeel@cs.berkeley.edu). -""" +""" This file contains all of the agents that can be selected to control Pacman. To select an agent, use the '-p' option when running pacman.py. Arguments can be passed to your agent using '-a'. For example, to load a SearchAgent that uses depth first search (dfs), run the following command: -> python pacman.py -p SearchAgent -a fn=depthFirstSearch +> python pacman.py -p SearchAgent -a fn=depthFirstSearch Commands to invoke other search strategies can be found in the project description. @@ -34,30 +51,30 @@ Good luck and happy searching! """ -from game import Directions -from game import Agent -from game import Actions -import util -import time -import search +from game import Directions +from game import Agent +from game import Actions +import util +import time +import search -class GoWestAgent(Agent): - "An agent that goes West until it can't." +class GoWestAgent(Agent): + "An agent that goes West until it can't." - def getAction(self, state): - "The agent receives a GameState (defined in pacman.py)." - if Directions.WEST in state.getLegalPacmanActions(): - return Directions.WEST - else: - return Directions.STOP + def getAction(self, state): + "The agent receives a GameState (defined in pacman.py)." + if Directions.WEST in state.getLegalPacmanActions(): + return Directions.WEST + else: + return Directions.STOP -####################################################### +####################################################### # This portion is written for you, but will only work # # after you fill in parts of search.py # ####################################################### -class SearchAgent(Agent): - """ +class SearchAgent(Agent): + """ This very general search agent finds a path using a supplied search algorithm for a supplied search problem, then returns actions to follow that path. @@ -73,35 +90,35 @@ class SearchAgent(Agent): Note: You should NOT change any code in SearchAgent """ - def __init__(self, fn='depthFirstSearch', prob='PositionSearchProblem', heuristic='nullHeuristic'): - # Warning: some advanced Python magic is employed below to find the right functions and problems + def __init__(self, fn='depthFirstSearch', prob='PositionSearchProblem', heuristic='nullHeuristic'): + # Warning: some advanced Python magic is employed below to find the right functions and problems # Get the search function from the name and heuristic - if fn not in dir(search): - raise AttributeError, fn + ' is not a search function in search.py.' - func = getattr(search, fn) - if 'heuristic' not in func.func_code.co_varnames: - print('[SearchAgent] using function ' + fn) - self.searchFunction = func - else: - if heuristic in globals().keys(): - heur = globals()[heuristic] - elif heuristic in dir(search): - heur = getattr(search, heuristic) - else: - raise AttributeError, heuristic + ' is not a function in searchAgents.py or search.py.' - print('[SearchAgent] using function %s and heuristic %s' % (fn, heuristic)) - # Note: this bit of Python trickery combines the search algorithm and the heuristic - self.searchFunction = lambda x: func(x, heuristic=heur) - - # Get the search problem type from the name - if prob not in globals().keys() or not prob.endswith('Problem'): - raise AttributeError, prob + ' is not a search problem type in SearchAgents.py.' - self.searchType = globals()[prob] - print('[SearchAgent] using problem type ' + prob) - - def registerInitialState(self, state): - """ + if fn not in dir(search): + raise AttributeError, fn + ' is not a search function in search.py.' + func = getattr(search, fn) + if 'heuristic' not in func.func_code.co_varnames: + print('[SearchAgent] using function ' + fn) + self.searchFunction = func + else: + if heuristic in globals().keys(): + heur = globals()[heuristic] + elif heuristic in dir(search): + heur = getattr(search, heuristic) + else: + raise AttributeError, heuristic + ' is not a function in searchAgents.py or search.py.' + print('[SearchAgent] using function %s and heuristic %s' % (fn, heuristic)) + # Note: this bit of Python trickery combines the search algorithm and the heuristic + self.searchFunction = lambda x: func(x, heuristic=heur) + + # Get the search problem type from the name + if prob not in globals().keys() or not prob.endswith('Problem'): + raise AttributeError, prob + ' is not a search problem type in SearchAgents.py.' + self.searchType = globals()[prob] + print('[SearchAgent] using problem type ' + prob) + + def registerInitialState(self, state): + """ This is the first time that the agent sees the layout of the game board. Here, we choose a path to the goal. In this phase, the agent should compute the path to the goal and store it in a local variable. @@ -109,32 +126,32 @@ def registerInitialState(self, state): state: a GameState object (pacman.py) """ - if self.searchFunction == None: raise Exception, "No search function provided for SearchAgent" - starttime = time.time() - problem = self.searchType(state) # Makes a new search problem - self.actions = self.searchFunction(problem) # Find a path - totalCost = problem.getCostOfActions(self.actions) - print('Path found with total cost of %d in %.1f seconds' % (totalCost, time.time() - starttime)) - if '_expanded' in dir(problem): print('Search nodes expanded: %d' % problem._expanded) - - def getAction(self, state): - """ + if self.searchFunction == None: raise Exception, "No search function provided for SearchAgent" + starttime = time.time() + problem = self.searchType(state) # Makes a new search problem + self.actions = self.searchFunction(problem) # Find a path + totalCost = problem.getCostOfActions(self.actions) + print('Path found with total cost of %d in %.1f seconds' % (totalCost, time.time() - starttime)) + if '_expanded' in dir(problem): print('Search nodes expanded: %d' % problem._expanded) + + def getAction(self, state): + """ Returns the next action in the path chosen earlier (in registerInitialState). Return Directions.STOP if there is no further action to take. state: a GameState object (pacman.py) """ - if 'actionIndex' not in dir(self): self.actionIndex = 0 - i = self.actionIndex - self.actionIndex += 1 - if i < len(self.actions): - return self.actions[i] - else: - return Directions.STOP - -class PositionSearchProblem(search.SearchProblem): - """ + if 'actionIndex' not in dir(self): self.actionIndex = 0 + i = self.actionIndex + self.actionIndex += 1 + if i < len(self.actions): + return self.actions[i] + else: + return Directions.STOP + +class PositionSearchProblem(search.SearchProblem): + """ A search problem defines the state space, start state, goal test, successor function and cost function. This search problem can be used to find paths to a particular point on the pacman board. @@ -144,44 +161,44 @@ class PositionSearchProblem(search.SearchProblem): Note: this search problem is fully specified; you should NOT change it. """ - def __init__(self, gameState, costFn = lambda x: 1, goal=(1,1), start=None, warn=True, visualize=True): - """ + def __init__(self, gameState, costFn = lambda x: 1, goal=(1,1), start=None, warn=True, visualize=True): + """ Stores the start and goal. gameState: A GameState object (pacman.py) costFn: A function from a search state (tuple) to a non-negative number goal: A position in the gameState """ - self.walls = gameState.getWalls() - self.startState = gameState.getPacmanPosition() - if start != None: self.startState = start - self.goal = goal - self.costFn = costFn - self.visualize = visualize - if warn and (gameState.getNumFood() != 1 or not gameState.hasFood(*goal)): - print 'Warning: this does not look like a regular search maze' - - # For display purposes - self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE - - def getStartState(self): - return self.startState - - def isGoalState(self, state): - isGoal = state == self.goal - - # For display purposes only - if isGoal and self.visualize: - self._visitedlist.append(state) - import __main__ - if '_display' in dir(__main__): - if 'drawExpandedCells' in dir(__main__._display): #@UndefinedVariable - __main__._display.drawExpandedCells(self._visitedlist) #@UndefinedVariable - - return isGoal - - def getSuccessors(self, state): - """ + self.walls = gameState.getWalls() + self.startState = gameState.getPacmanPosition() + if start != None: self.startState = start + self.goal = goal + self.costFn = costFn + self.visualize = visualize + if warn and (gameState.getNumFood() != 1 or not gameState.hasFood(*goal)): + print 'Warning: this does not look like a regular search maze' + + # For display purposes + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + + def getStartState(self): + return self.startState + + def isGoalState(self, state): + isGoal = state == self.goal + + # For display purposes only + if isGoal and self.visualize: + self._visitedlist.append(state) + import __main__ + if '_display' in dir(__main__): + if 'drawExpandedCells' in dir(__main__._display): #@UndefinedVariable + __main__._display.drawExpandedCells(self._visitedlist) #@UndefinedVariable + + return isGoal + + def getSuccessors(self, state): + """ Returns successor states, the actions they require, and a cost of 1. As noted in search.py: @@ -192,119 +209,120 @@ def getSuccessors(self, state): cost of expanding to that successor """ - successors = [] - for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: - x,y = state - dx, dy = Actions.directionToVector(action) - nextx, nexty = int(x + dx), int(y + dy) - if not self.walls[nextx][nexty]: - nextState = (nextx, nexty) - cost = self.costFn(nextState) - successors.append( ( nextState, action, cost) ) - - # Bookkeeping for display purposes - self._expanded += 1 # DO NOT CHANGE - if state not in self._visited: - self._visited[state] = True - self._visitedlist.append(state) - - return successors - - def getCostOfActions(self, actions): - """ + successors = [] + for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + x,y = state + dx, dy = Actions.directionToVector(action) + nextx, nexty = int(x + dx), int(y + dy) + if not self.walls[nextx][nexty]: + nextState = (nextx, nexty) + cost = self.costFn(nextState) + successors.append( ( nextState, action, cost) ) + + # Bookkeeping for display purposes + self._expanded += 1 # DO NOT CHANGE + if state not in self._visited: + self._visited[state] = True + self._visitedlist.append(state) + + return successors + + def getCostOfActions(self, actions): + """ Returns the cost of a particular sequence of actions. If those actions include an illegal move, return 999999. """ - if actions == None: return 999999 - x,y= self.getStartState() - cost = 0 - for action in actions: - # Check figure out the next state and see whether its' legal - dx, dy = Actions.directionToVector(action) - x, y = int(x + dx), int(y + dy) - if self.walls[x][y]: return 999999 - cost += self.costFn((x,y)) - return cost - -class StayEastSearchAgent(SearchAgent): - """ + if actions == None: return 999999 + x,y= self.getStartState() + cost = 0 + for action in actions: + # Check figure out the next state and see whether its' legal + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: return 999999 + cost += self.costFn((x,y)) + return cost + +class StayEastSearchAgent(SearchAgent): + """ An agent for position search with a cost function that penalizes being in positions on the West side of the board. The cost function for stepping into a position (x,y) is 1/2^x. """ - def __init__(self): - self.searchFunction = search.uniformCostSearch - costFn = lambda pos: .5 ** pos[0] - self.searchType = lambda state: PositionSearchProblem(state, costFn, (1, 1), None, False) + def __init__(self): + self.searchFunction = search.uniformCostSearch + costFn = lambda pos: .5 ** pos[0] + self.searchType = lambda state: PositionSearchProblem(state, costFn, (1, 1), None, False) -class StayWestSearchAgent(SearchAgent): - """ +class StayWestSearchAgent(SearchAgent): + """ An agent for position search with a cost function that penalizes being in positions on the East side of the board. The cost function for stepping into a position (x,y) is 2^x. """ - def __init__(self): - self.searchFunction = search.uniformCostSearch - costFn = lambda pos: 2 ** pos[0] - self.searchType = lambda state: PositionSearchProblem(state, costFn) - -def manhattanHeuristic(position, problem, info={}): - "The Manhattan distance heuristic for a PositionSearchProblem" - xy1 = position - xy2 = problem.goal - return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1]) - -def euclideanHeuristic(position, problem, info={}): - "The Euclidean distance heuristic for a PositionSearchProblem" - xy1 = position - xy2 = problem.goal - return ( (xy1[0] - xy2[0]) ** 2 + (xy1[1] - xy2[1]) ** 2 ) ** 0.5 - -##################################################### + def __init__(self): + self.searchFunction = search.uniformCostSearch + costFn = lambda pos: 2 ** pos[0] + self.searchType = lambda state: PositionSearchProblem(state, costFn) + +def manhattanHeuristic(position, problem, info={}): + "The Manhattan distance heuristic for a PositionSearchProblem" + xy1 = position + xy2 = problem.goal + return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1]) + +def euclideanHeuristic(position, problem, info={}): + "The Euclidean distance heuristic for a PositionSearchProblem" + xy1 = position + xy2 = problem.goal + return ( (xy1[0] - xy2[0]) ** 2 + (xy1[1] - xy2[1]) ** 2 ) ** 0.5 + +##################################################### # This portion is incomplete. Time to write code! # ##################################################### -class CornersProblem(search.SearchProblem): - """ +class CornersProblem(search.SearchProblem): + """ This search problem finds paths through all four corners of a layout. You must select a suitable state space and successor function """ - def __init__(self, startingGameState): - """ + def __init__(self, startingGameState): + """ Stores the walls, pacman's starting position and corners. """ - self.walls = startingGameState.getWalls() - self.startingPosition = startingGameState.getPacmanPosition() - top, right = self.walls.height-2, self.walls.width-2 - self.corners = ((1,1), (1,top), (right, 1), (right, top)) - for corner in self.corners: - if not startingGameState.hasFood(*corner): - print 'Warning: no food in corner ' + str(corner) - self._expanded = 0 # DO NOT CHANGE; Number of search nodes expanded + self.walls = startingGameState.getWalls() + self.startingPosition = startingGameState.getPacmanPosition() + top, right = self.walls.height-2, self.walls.width-2 + self.corners = ((1,1), (1,top), (right, 1), (right, top)) + for corner in self.corners: + if not startingGameState.hasFood(*corner): + print 'Warning: no food in corner ' + str(corner) + self._expanded = 0 # DO NOT CHANGE; Number of search nodes expanded # Please add any code here which you would like to use # in initializing the problem - "*** YOUR CODE HERE ***" + "*** YOUR CODE HERE ***" - def getStartState(self): - """ + def getStartState(self): + """ Returns the start state (in your state space, not the full Pacman state space) """ - visitedCorners = tuple([x == self.startingPosition for x in self.corners]) - return (self.startingPosition, visitedCorners) + "*** YOUR CODE HERE ***" + util.raiseNotDefined() - def isGoalState(self, state): - """ + def isGoalState(self, state): + """ Returns whether this search state is a goal state of the problem. """ - return state[1] == (True, True, True, True) + "*** YOUR CODE HERE ***" + util.raiseNotDefined() - def getSuccessors(self, state): - """ + def getSuccessors(self, state): + """ Returns successor states, the actions they require, and a cost of 1. As noted in search.py: @@ -314,39 +332,36 @@ def getSuccessors(self, state): is the incremental cost of expanding to that successor """ - successors = [] - (currentPosition, goals) = state - for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: - # Add a successor state to the successor list if the action is legal + successors = [] + for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + # Add a successor state to the successor list if the action is legal # Here's a code snippet for figuring out whether a new position hits a wall: - x,y = currentPosition - dx, dy = Actions.directionToVector(action) - nextx, nexty = int(x + dx), int(y + dy) - hitsWall = self.walls[nextx][nexty] - if not hitsWall: - revisedGoals = tuple([goals[i] or (nextx, nexty) == corner - for (i, corner) in enumerate(self.corners)]) - successors.append((((nextx, nexty), revisedGoals), action, 1)) - - self._expanded += 1 # DO NOT CHANGE - return successors - - def getCostOfActions(self, actions): - """ + # x,y = currentPosition + # dx, dy = Actions.directionToVector(action) + # nextx, nexty = int(x + dx), int(y + dy) + # hitsWall = self.walls[nextx][nexty] + + "*** YOUR CODE HERE ***" + + self._expanded += 1 # DO NOT CHANGE + return successors + + def getCostOfActions(self, actions): + """ Returns the cost of a particular sequence of actions. If those actions include an illegal move, return 999999. This is implemented for you. """ - if actions == None: return 999999 - x,y= self.startingPosition - for action in actions: - dx, dy = Actions.directionToVector(action) - x, y = int(x + dx), int(y + dy) - if self.walls[x][y]: return 999999 - return len(actions) + if actions == None: return 999999 + x,y= self.startingPosition + for action in actions: + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: return 999999 + return len(actions) -def cornersHeuristic(state, problem): - """ +def cornersHeuristic(state, problem): + """ A heuristic for the CornersProblem that you defined. state: The current search state @@ -358,38 +373,20 @@ def cornersHeuristic(state, problem): shortest path from the state to a goal of the problem; i.e. it should be admissible (as well as consistent). """ - def manhattanDist(xy1, xy2): - return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1]) - - def manhattanHeuristicRecursive(position, extraPositions): - "The Manhattan distance heuristic for a CornerSearchProblem" - if len(extraPositions) == 0: - return 0 - distances = [(manhattanDist(position, extraPosition), extraPosition) for extraPosition in extraPositions] - sortedDistances = sorted(distances) - if len(distances) > 1: - extra = manhattanHeuristicRecursive(sortedDistances[0][1], [pos for (i, pos) in sortedDistances[1:]]) - else: - extra = 0 - return sortedDistances[0][0] + extra - - corners = problem.corners # These are the corner coordinates - walls = problem.walls # These are the walls of the maze, as a Grid (game.py) - if problem.isGoalState(state): - return 0 - (currentPosition, visitedCorners) = state - activeCorners = [corner for (i, corner) in enumerate(corners) if not visitedCorners[i]] - heuristic = manhattanHeuristicRecursive(currentPosition, activeCorners) - return heuristic - -class AStarCornersAgent(SearchAgent): - "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" - def __init__(self): - self.searchFunction = lambda prob: search.aStarSearch(prob, cornersHeuristic) - self.searchType = CornersProblem - -class FoodSearchProblem: - """ + corners = problem.corners # These are the corner coordinates + walls = problem.walls # These are the walls of the maze, as a Grid (game.py) + + "*** YOUR CODE HERE ***" + return 0 # Default to trivial solution + +class AStarCornersAgent(SearchAgent): + "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" + def __init__(self): + self.searchFunction = lambda prob: search.aStarSearch(prob, cornersHeuristic) + self.searchType = CornersProblem + +class FoodSearchProblem: + """ A search problem associated with finding the a path that collects all of the food (dots) in a Pacman game. @@ -397,55 +394,55 @@ class FoodSearchProblem: pacmanPosition: a tuple (x,y) of integers specifying Pacman's position foodGrid: a Grid (see game.py) of either True or False, specifying remaining food """ - def __init__(self, startingGameState): - self.start = (startingGameState.getPacmanPosition(), startingGameState.getFood()) - self.walls = startingGameState.getWalls() - self.startingGameState = startingGameState - self._expanded = 0 # DO NOT CHANGE - self.heuristicInfo = {} # A dictionary for the heuristic to store information - - def getStartState(self): - return self.start - - def isGoalState(self, state): - return state[1].count() == 0 - - def getSuccessors(self, state): - "Returns successor states, the actions they require, and a cost of 1." - successors = [] - self._expanded += 1 # DO NOT CHANGE - for direction in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: - x,y = state[0] - dx, dy = Actions.directionToVector(direction) - nextx, nexty = int(x + dx), int(y + dy) - if not self.walls[nextx][nexty]: - nextFood = state[1].copy() - nextFood[nextx][nexty] = False - successors.append( ( ((nextx, nexty), nextFood), direction, 1) ) - return successors - - def getCostOfActions(self, actions): - """Returns the cost of a particular sequence of actions. If those actions + def __init__(self, startingGameState): + self.start = (startingGameState.getPacmanPosition(), startingGameState.getFood()) + self.walls = startingGameState.getWalls() + self.startingGameState = startingGameState + self._expanded = 0 # DO NOT CHANGE + self.heuristicInfo = {} # A dictionary for the heuristic to store information + + def getStartState(self): + return self.start + + def isGoalState(self, state): + return state[1].count() == 0 + + def getSuccessors(self, state): + "Returns successor states, the actions they require, and a cost of 1." + successors = [] + self._expanded += 1 # DO NOT CHANGE + for direction in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + x,y = state[0] + dx, dy = Actions.directionToVector(direction) + nextx, nexty = int(x + dx), int(y + dy) + if not self.walls[nextx][nexty]: + nextFood = state[1].copy() + nextFood[nextx][nexty] = False + successors.append( ( ((nextx, nexty), nextFood), direction, 1) ) + return successors + + def getCostOfActions(self, actions): + """Returns the cost of a particular sequence of actions. If those actions include an illegal move, return 999999""" - x,y= self.getStartState()[0] - cost = 0 - for action in actions: - # figure out the next state and see whether it's legal - dx, dy = Actions.directionToVector(action) - x, y = int(x + dx), int(y + dy) - if self.walls[x][y]: - return 999999 - cost += 1 - return cost - -class AStarFoodSearchAgent(SearchAgent): - "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" - def __init__(self): - self.searchFunction = lambda prob: search.aStarSearch(prob, foodHeuristic) - self.searchType = FoodSearchProblem - -def foodHeuristic(state, problem): - """ + x,y= self.getStartState()[0] + cost = 0 + for action in actions: + # figure out the next state and see whether it's legal + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: + return 999999 + cost += 1 + return cost + +class AStarFoodSearchAgent(SearchAgent): + "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" + def __init__(self): + self.searchFunction = lambda prob: search.aStarSearch(prob, foodHeuristic) + self.searchType = FoodSearchProblem + +def foodHeuristic(state, problem): + """ Your heuristic for the FoodSearchProblem goes here. This heuristic must be consistent to ensure correctness. First, try to come @@ -472,88 +469,43 @@ def foodHeuristic(state, problem): Subsequent calls to this heuristic can access problem.heuristicInfo['wallCount'] """ - position, foodGrid = state - heuristics = FoodHeuristics() - heuristic = heuristics.mstHeuristic([position] + foodGrid.asList()) - return heuristic - - -""" -The following are three food heuristics that obtain scores of 2/4, -3/4, and 4/4. -""" -class FoodHeuristics: - - def euclideanDist(self, xy1, xy2): - return ( (xy1[0] - xy2[0]) ** 2 + (xy1[1] - xy2[1]) ** 2 ) ** 0.5 - - def simpleFoodHeuristic(self, state, problem): - """ - Just checks how much food is left. - - Gets 2/4. - """ - position, foodGrid = state - return len(foodGrid.asList()) - - def minimalDistHeuristic(self, position, extraPositions): - """ - Gets 3/4 - """ - def findMinimalDist(position, extraPositions): - dists = [self.euclideanDist(position, pos2) for pos2 in extraPositions] - return min(dists) - minimalDists = [findMinimalDist(pos, list(set(extraPositions) - set([pos])) + [position]) for pos in extraPositions] - return sum(sorted(minimalDists)) - - def mstHeuristic(self, dots): - """ - Gets 4/4 - """ - from scipy.sparse import csr_matrix - from scipy.sparse.csgraph import minimum_spanning_tree - pairs = [(i, j) for i in range(len(dots)) for j in range(len(dots)) if i < j] - row = [dot1 for (dot1, dot2) in pairs] - col = [dot2 for (dot1, dot2) in pairs] - data = [self.euclideanDist(dots[dot1], dots[dot2]) for (dot1, dot2) in pairs] - matrix = csr_matrix((data, (row, col)), shape=(len(dots), len(dots))) - Tcsr = minimum_spanning_tree(matrix) - return Tcsr.sum() - - - - -class ClosestDotSearchAgent(SearchAgent): - "Search for all food using a sequence of searches" - def registerInitialState(self, state): - self.actions = [] - currentState = state - while(currentState.getFood().count() > 0): - nextPathSegment = self.findPathToClosestDot(currentState) # The missing piece - self.actions += nextPathSegment - for action in nextPathSegment: - legal = currentState.getLegalActions() - if action not in legal: - t = (str(action), str(currentState)) - raise Exception, 'findPathToClosestDot returned an illegal move: %s!\n%s' % t - currentState = currentState.generateSuccessor(0, action) - self.actionIndex = 0 - print 'Path found with cost %d.' % len(self.actions) - - def findPathToClosestDot(self, gameState): - """ + position, foodGrid = state + "*** YOUR CODE HERE ***" + return 0 + +class ClosestDotSearchAgent(SearchAgent): + "Search for all food using a sequence of searches" + def registerInitialState(self, state): + self.actions = [] + currentState = state + while(currentState.getFood().count() > 0): + nextPathSegment = self.findPathToClosestDot(currentState) # The missing piece + self.actions += nextPathSegment + for action in nextPathSegment: + legal = currentState.getLegalActions() + if action not in legal: + t = (str(action), str(currentState)) + raise Exception, 'findPathToClosestDot returned an illegal move: %s!\n%s' % t + currentState = currentState.generateSuccessor(0, action) + self.actionIndex = 0 + print 'Path found with cost %d.' % len(self.actions) + + def findPathToClosestDot(self, gameState): + """ Returns a path (a list of actions) to the closest dot, starting from gameState. """ - # Here are some useful elements of the startState - startPosition = gameState.getPacmanPosition() - food = gameState.getFood() - walls = gameState.getWalls() - problem = AnyFoodSearchProblem(gameState) - return search.bfs(problem) - -class AnyFoodSearchProblem(PositionSearchProblem): - """ + # Here are some useful elements of the startState + startPosition = gameState.getPacmanPosition() + food = gameState.getFood() + walls = gameState.getWalls() + problem = AnyFoodSearchProblem(gameState) + + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +class AnyFoodSearchProblem(PositionSearchProblem): + """ A search problem for finding a path to any food. This search problem is just like the PositionSearchProblem, but has a @@ -567,27 +519,29 @@ class AnyFoodSearchProblem(PositionSearchProblem): method. """ - def __init__(self, gameState): - "Stores information from the gameState. You don't need to change this." - # Store the food for later reference - self.food = gameState.getFood() + def __init__(self, gameState): + "Stores information from the gameState. You don't need to change this." + # Store the food for later reference + self.food = gameState.getFood() - # Store info for the PositionSearchProblem (no need to change this) - self.walls = gameState.getWalls() - self.startState = gameState.getPacmanPosition() - self.costFn = lambda x: 1 - self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + # Store info for the PositionSearchProblem (no need to change this) + self.walls = gameState.getWalls() + self.startState = gameState.getPacmanPosition() + self.costFn = lambda x: 1 + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE - def isGoalState(self, state): - """ + def isGoalState(self, state): + """ The state is Pacman's position. Fill this in with a goal test that will complete the problem definition. """ - x,y = state - return state in self.food.asList() + x,y = state -def mazeDistance(point1, point2, gameState): - """ + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +def mazeDistance(point1, point2, gameState): + """ Returns the maze distance between any two points, using the search functions you have already built. The gameState can be any game state -- Pacman's position in that state is ignored. @@ -596,10 +550,35 @@ def mazeDistance(point1, point2, gameState): This might be a useful helper function for your ApproximateSearchAgent. """ - x1, y1 = point1 - x2, y2 = point2 - walls = gameState.getWalls() - assert not walls[x1][y1], 'point1 is a wall: ' + str(point1) - assert not walls[x2][y2], 'point2 is a wall: ' + str(point2) - prob = PositionSearchProblem(gameState, start=point1, goal=point2, warn=False, visualize=False) - return len(search.bfs(prob)) + x1, y1 = point1 + x2, y2 = point2 + walls = gameState.getWalls() + assert not walls[x1][y1], 'point1 is a wall: ' + str(point1) + assert not walls[x2][y2], 'point2 is a wall: ' + str(point2) + prob = PositionSearchProblem(gameState, start=point1, goal=point2, warn=False, visualize=False) + return len(search.bfs(prob)) + ++ + + +