From a4031888dee676b4619a29911157036fdf27a799 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Sat, 13 Nov 2021 17:10:02 -0500 Subject: [PATCH] Finish problem 2. --- .gitignore | 1 + p2_multiagent/multiAgents.py | 69 +++- p2_multiagent/search.py | 153 +++++++++ p2_multiagent/searchAgents.py | 623 ++++++++++++++++++++++++++++++++++ 4 files changed, 842 insertions(+), 4 deletions(-) create mode 100644 p2_multiagent/search.py create mode 100644 p2_multiagent/searchAgents.py diff --git a/.gitignore b/.gitignore index 8d35cb3..de59a0e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ __pycache__ *.pyc +.vscode diff --git a/p2_multiagent/multiAgents.py b/p2_multiagent/multiAgents.py index 9409c35..8b33b20 100644 --- a/p2_multiagent/multiAgents.py +++ b/p2_multiagent/multiAgents.py @@ -223,8 +223,27 @@ class ExpectimaxAgent(MultiAgentSearchAgent): All ghosts should be modeled as choosing uniformly at random from their legal moves. """ - "*** YOUR CODE HERE ***" - util.raiseNotDefined() + numAgents = gameState.getNumAgents() + totalDepth = self.depth * numAgents + + def value(depth, state): + agentIndex = depth % numAgents + actions = state.getLegalActions(agentIndex) + if not actions or depth == totalDepth: + return (self.evaluationFunction(state), "terminal") + successorStates = [state.generateSuccessor(agentIndex, action) for action in actions] + successorValueActionPairs = [(value(depth + 1, state)[0], action) + for action, state in zip(actions, successorStates)] + # Pacman (agentIndex=0) maximizes, ghosts minimize. + if agentIndex == 0: + return max(successorValueActionPairs) + else: + values = [va[0] for va in successorValueActionPairs] + average = sum(values) / float(len(values)) + return (average, "expected") + + # [0] is the best value, [1] is the best action + return value(0, gameState)[1] def betterEvaluationFunction(currentGameState): @@ -234,9 +253,51 @@ def betterEvaluationFunction(currentGameState): DESCRIPTION: """ - "*** YOUR CODE HERE ***" - util.raiseNotDefined() + from searchAgents import mazeDistance + + state = currentGameState + pos = state.getPacmanPosition() + + # foodDists = [mazeDistance(pos, foodPos, state) + # for foodPos in state.getFood().asList()] + scaredTimeScore = 0 + scaredTimes = [ghostSt.scaredTimer for ghostSt in state.getGhostStates()] + if scaredTimes: + scaredTimeScore = min(scaredTimes) + + ghostDists = [] + for ghostState in state.getGhostStates(): + x, y = ghostState.getPosition() + ghostPos = (int(x), int(y)) + distance = mazeDistance(pos, ghostPos, state) + ghostDists.append(distance) + if ghostDists: + try: + ghostScore = 1. / min(ghostDists) + except ZeroDivisionError: + ghostScore = 100 + + foodDists = [manhattanDistance(pos, foodPos) + for foodPos in state.getFood().asList()] + foodScore = 0 + if foodDists: + foodScore = 1. / min(foodDists) + + gameScore = state.getScore() + + weightGhost = -0.01 + weightFood = 0.5 + weightScore = 0.2 + weightScaredTime = 0.01 + + score = ghostScore * weightGhost + \ + foodScore * weightFood + \ + gameScore * weightScore + \ + scaredTimeScore * weightScaredTime + # print(state) + # print(score, ghostScore, foodScore, gameScore, scaredTimeScore) + return score # Abbreviation better = betterEvaluationFunction diff --git a/p2_multiagent/search.py b/p2_multiagent/search.py new file mode 100644 index 0000000..c63d81e --- /dev/null +++ b/p2_multiagent/search.py @@ -0,0 +1,153 @@ +# 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 +# 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 search.py, you will implement generic search algorithms which are called by +Pacman agents (in searchAgents.py). +""" + +import util + + +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): + """ + Returns the start state for the search problem. + """ + util.raiseNotDefined() + + def isGoalState(self, state): + """ + state: Search state + + Returns True if and only if the state is a valid goal state. + """ + util.raiseNotDefined() + + def getSuccessors(self, state): + """ + state: Search state + + For a given state, this should return a list of triples, (successor, + action, stepCost), where 'successor' is a successor to the current + state, 'action' is the action required to get there, and 'stepCost' is + the incremental cost of expanding to that successor. + """ + util.raiseNotDefined() + + 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() + + +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] + + +def genericSearch(problem, getNewCostAndPriority): + fringe = util.PriorityQueue() + startState = problem.getStartState() + fringe.push((startState, [], 0), 0) + visited = {} + + while True: + if fringe.isEmpty(): + raise Exception("No path found.") + + state, actions, cost = fringe.pop() + + if problem.isGoalState(state): + return actions + + if state in visited and cost >= visited[state]: + continue + visited[state] = cost + + for successor, action, stepCost in problem.getSuccessors(state): + newCost, priority = getNewCostAndPriority(cost, stepCost, successor) + newActions = list(actions) + [action] + fringe.push((successor, newActions, newCost), priority) + + +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 + goal. Make sure to implement a graph search algorithm. + """ + def getNewCostAndPriority(cost, stepCost, successor): + newCost = cost + 1 + return newCost, -newCost + return genericSearch(problem, getNewCostAndPriority) + + +def breadthFirstSearch(problem): + """Search the shallowest nodes in the search tree first.""" + def getNewCostAndPriority(cost, stepCost, successor): + newCost = cost + 1 + return newCost, newCost + return genericSearch(problem, getNewCostAndPriority) + + +def uniformCostSearch(problem): + """Search the node of least total cost first.""" + def getNewCostAndPriority(cost, stepCost, successor): + newCost = cost + stepCost + return newCost, newCost + return genericSearch(problem, getNewCostAndPriority) + + +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 ***" + def getNewCostAndPriority(cost, stepCost, successor): + newCost = cost + stepCost + newPriority = newCost + heuristic(successor, problem) + return newCost, newPriority + return genericSearch(problem, getNewCostAndPriority) + + +# Abbreviations +bfs = breadthFirstSearch +dfs = depthFirstSearch +astar = aStarSearch +ucs = uniformCostSearch diff --git a/p2_multiagent/searchAgents.py b/p2_multiagent/searchAgents.py new file mode 100644 index 0000000..ddac644 --- /dev/null +++ b/p2_multiagent/searchAgents.py @@ -0,0 +1,623 @@ +# 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 +# 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). + + +""" +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 + +Commands to invoke other search strategies can be found in the project +description. + +Please only change the parts of the file you are asked to. Look for the lines +that say + +"*** YOUR CODE HERE ***" + +The parts you fill in start about 3/4 of the way down. Follow the project +description for details. + +Good luck and happy searching! +""" + +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." + + 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): + """ + 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. + + As a default, this agent runs DFS on a PositionSearchProblem to find + location (1,1) + + Options for fn include: + depthFirstSearch or dfs + breadthFirstSearch or bfs + + + 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 + + # 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): + """ + 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. + All of the work is done in this method! + + 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): + """ + 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): + """ + 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. + + The state space consists of (x,y) positions in a pacman game. + + 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): + """ + 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__): + # @UndefinedVariable + if 'drawExpandedCells' in dir(__main__._display): + __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: + For a given state, this should return a list of triples, + (successor, action, stepCost), where 'successor' is a + successor to the current state, 'action' is the action + required to get there, and 'stepCost' is the incremental + 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): + """ + 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): + """ + 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 + def costFn(pos): return .5 ** pos[0] + self.searchType = lambda state: PositionSearchProblem( + state, costFn, (1, 1), None, False) + + +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 + def costFn(pos): return 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): + """ + 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): + """ + 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 + # Please add any code here which you would like to use + # in initializing the problem + + def getStartState(self): + """ + Returns the start state (in your state space, not the full Pacman state + space) + """ + current = self.startingPosition + visited = tuple([1 if corner == current else 0 + for corner in self.corners]) + return (self.startingPosition, visited) + + def isGoalState(self, state): + """ + Returns whether this search state is a goal state of the problem. + """ + "*** YOUR CODE HERE ***" + position, visited = state + if sum(visited) == 4: + return True + return False + + def getSuccessors(self, state): + """ + Returns successor states, the actions they require, and a cost of 1. + + As noted in search.py: + For a given state, this should return a list of triples, (successor, + action, stepCost), where 'successor' is a successor to the current + state, 'action' is the action required to get there, and 'stepCost' + is the incremental cost of expanding to that successor + """ + + position, visited = state + x, y = position + successors = [] + options = [((x, y + 1), Directions.NORTH), + ((x, y - 1), Directions.SOUTH), + ((x + 1, y), Directions.EAST), + ((x - 1, y), Directions.WEST)] + for newPosition, action in options: + x, y = newPosition + if self.walls[x][y]: + continue + if newPosition in self.corners: + index = self.corners.index(newPosition) + newVisited = list(visited) + newVisited[index] = 1 + newVisited = tuple(newVisited) + else: + newVisited = visited + newState = (newPosition, newVisited) + successors.append((newState, action, 1)) + + 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) + + +def cornersHeuristic(state, problem): + """ + A heuristic for the CornersProblem that you defined. + + state: The current search state + (a data structure you chose in your search problem) + + problem: The CornersProblem instance for this layout. + + This function should always return a number that is a lower bound on the + shortest path from the state to a goal of the problem; i.e. it should be + admissible (as well as consistent). + """ + corners = problem.corners # These are the corner coordinates + position, visitedCorners = state + + # self.corners = ((1, 1), (1, top), (right, 1), (right, top)) + minDist = min(corners[2][0] - 1, corners[1][1] - 1) + + # Okay, I am having a way harder time with this than I should. + # First, get only the corners Pacman hasn't visited yet. + distToCorners = [util.manhattanDistance(position, corner) + for corner, visited in zip(corners, visitedCorners) + if visited == 0] + + # If there are no corners left, we are done. + if not distToCorners: + return 0 + + distanceClosestCorner = min(distToCorners) + cost = distanceClosestCorner + (len(distToCorners) - 1) * minDist + return cost + + +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. + + A search state in this problem is a tuple ( pacmanPosition, foodGrid ) where + 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 + 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): + """ + Your heuristic for the FoodSearchProblem goes here. + + This heuristic must be consistent to ensure correctness. First, try to come + up with an admissible heuristic; almost all admissible heuristics will be + consistent as well. + + If using A* ever finds a solution that is worse uniform cost search finds, + your heuristic is *not* consistent, and probably not admissible! On the + other hand, inadmissible or inconsistent heuristics may find optimal + solutions, so be careful. + + The state is a tuple ( pacmanPosition, foodGrid ) where foodGrid is a Grid + (see game.py) of either True or False. You can call foodGrid.asList() to get + a list of food coordinates instead. + + If you want access to info like walls, capsules, etc., you can query the + problem. For example, problem.walls gives you a Grid of where the walls + are. + + If you want to *store* information to be reused in other calls to the + heuristic, there is a dictionary called problem.heuristicInfo that you can + use. For example, if you only want to count the walls once and store that + value, try: problem.heuristicInfo['wallCount'] = problem.walls.count() + Subsequent calls to this heuristic can access + problem.heuristicInfo['wallCount'] + """ + position, foodGrid = state + foodPositions = foodGrid.asList() + + if not foodPositions: + return 0 + + # We have to travel at least from x_min to x_max and y_min to y_max. + foodX = [x for (x, y) in foodPositions] + foodY = [y for (x, y) in foodPositions] + cost = (max(foodX) - min(foodX)) + (max(foodY) - min(foodY)) + + # The previous gave over 9000 for trickySearch. We can improve by adding + # the distance to the closest food position which gives over 7000 points. + cost += min([util.manhattanDistance(position, foodPosition) + for foodPosition in foodPositions]) + + # If I wanted to get full score, I would use the cost to the closest food, + # plus a TSP from there. That would give us less than 7000 for sure. + return cost + + +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.ucs(problem) + + +class AnyFoodSearchProblem(PositionSearchProblem): + """ + A search problem for finding a path to any food. + + This search problem is just like the PositionSearchProblem, but has a + different goal test, which you need to fill in below. The state space and + successor function do not need to be changed. + + The class definition above, AnyFoodSearchProblem(PositionSearchProblem), + inherits the methods of the PositionSearchProblem. + + You can use this search problem to help you fill in the findPathToClosestDot + 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() + + # 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): + """ + The state is Pacman's position. Fill this in with a goal test that will + complete the problem definition. + """ + x, y = state + if (x, y) in self.food.asList(): + return True + return False + + +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. + + Example usage: mazeDistance( (2,4), (5,6), 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))