diff --git a/p1_search/searchAgents.py b/p1_search/searchAgents.py index 1d757b1..2dc220b 100644 --- a/p1_search/searchAgents.py +++ b/p1_search/searchAgents.py @@ -4,7 +4,7 @@ # 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). @@ -41,6 +41,7 @@ import util import time import search + class GoWestAgent(Agent): "An agent that goes West until it can't." @@ -56,6 +57,7 @@ class GoWestAgent(Agent): # after you fill in parts of search.py # ####################################################### + class SearchAgent(Agent): """ This very general search agent finds a path using a supplied search @@ -90,7 +92,8 @@ class SearchAgent(Agent): 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)) + 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) @@ -109,13 +112,16 @@ class SearchAgent(Agent): state: a GameState object (pacman.py) """ - if self.searchFunction == None: raise Exception, "No search function provided for SearchAgent" + 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 + 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) + 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): """ @@ -125,7 +131,8 @@ class SearchAgent(Agent): state: a GameState object (pacman.py) """ - if 'actionIndex' not in dir(self): self.actionIndex = 0 + if 'actionIndex' not in dir(self): + self.actionIndex = 0 i = self.actionIndex self.actionIndex += 1 if i < len(self.actions): @@ -133,6 +140,7 @@ class SearchAgent(Agent): else: return Directions.STOP + class PositionSearchProblem(search.SearchProblem): """ A search problem defines the state space, start state, goal test, successor @@ -144,7 +152,7 @@ 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. @@ -154,7 +162,8 @@ class PositionSearchProblem(search.SearchProblem): """ self.walls = gameState.getWalls() self.startState = gameState.getPacmanPosition() - if start != None: self.startState = start + if start != None: + self.startState = start self.goal = goal self.costFn = costFn self.visualize = visualize @@ -162,7 +171,7 @@ class PositionSearchProblem(search.SearchProblem): print 'Warning: this does not look like a regular search maze' # For display purposes - self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE def getStartState(self): return self.startState @@ -175,8 +184,10 @@ class PositionSearchProblem(search.SearchProblem): self._visitedlist.append(state) import __main__ if '_display' in dir(__main__): - if 'drawExpandedCells' in dir(__main__._display): #@UndefinedVariable - __main__._display.drawExpandedCells(self._visitedlist) #@UndefinedVariable + # @UndefinedVariable + if 'drawExpandedCells' in dir(__main__._display): + __main__._display.drawExpandedCells( + self._visitedlist) # @UndefinedVariable return isGoal @@ -194,16 +205,16 @@ class PositionSearchProblem(search.SearchProblem): successors = [] for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: - x,y = state + 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) ) + successors.append((nextState, action, cost)) # Bookkeeping for display purposes - self._expanded += 1 # DO NOT CHANGE + self._expanded += 1 # DO NOT CHANGE if state not in self._visited: self._visited[state] = True self._visitedlist.append(state) @@ -215,17 +226,20 @@ class PositionSearchProblem(search.SearchProblem): 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() + 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)) + 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 @@ -233,10 +247,13 @@ class StayEastSearchAgent(SearchAgent): 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 costFn(pos): return .5 ** pos[0] + self.searchType = lambda state: PositionSearchProblem( + state, costFn, (1, 1), None, False) + class StayWestSearchAgent(SearchAgent): """ @@ -245,27 +262,31 @@ class StayWestSearchAgent(SearchAgent): 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] + 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 + 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. @@ -280,29 +301,33 @@ class CornersProblem(search.SearchProblem): 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)) + 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._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 ***" def getStartState(self): """ Returns the start state (in your state space, not the full Pacman state space) """ - "*** YOUR CODE HERE ***" - util.raiseNotDefined() + 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 ***" - util.raiseNotDefined() + position, visited = state + if sum(visited) == 4: + return True + return False def getSuccessors(self, state): """ @@ -315,18 +340,28 @@ class CornersProblem(search.SearchProblem): is the incremental cost of expanding to that successor """ + position, visited = state + x, y = position 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] + 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)) - "*** YOUR CODE HERE ***" - - self._expanded += 1 # DO NOT CHANGE + self._expanded += 1 # DO NOT CHANGE return successors def getCostOfActions(self, actions): @@ -334,12 +369,14 @@ class CornersProblem(search.SearchProblem): 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 + 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 + if self.walls[x][y]: + return 999999 return len(actions) @@ -356,18 +393,23 @@ 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). """ - corners = problem.corners # These are the corner coordinates - walls = problem.walls # These are the walls of the maze, as a Grid (game.py) + corners = problem.corners # These are the corner coordinates + # These are the walls of the maze, as a Grid (game.py) + walls = problem.walls "*** YOUR CODE HERE ***" - return 0 # Default to trivial solution + 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.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 @@ -377,12 +419,14 @@ 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.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 + self._expanded = 0 # DO NOT CHANGE + self.heuristicInfo = {} # A dictionary for the heuristic to store information def getStartState(self): return self.start @@ -393,21 +437,21 @@ class FoodSearchProblem: def getSuccessors(self, state): "Returns successor states, the actions they require, and a cost of 1." successors = [] - self._expanded += 1 # DO NOT CHANGE + self._expanded += 1 # DO NOT CHANGE for direction in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: - x,y = state[0] + 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) ) + 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] + x, y = self.getStartState()[0] cost = 0 for action in actions: # figure out the next state and see whether it's legal @@ -418,12 +462,16 @@ class FoodSearchProblem: 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.searchFunction = lambda prob: search.aStarSearch( + prob, foodHeuristic) self.searchType = FoodSearchProblem + def foodHeuristic(state, problem): """ Your heuristic for the FoodSearchProblem goes here. @@ -456,13 +504,16 @@ def foodHeuristic(state, problem): "*** 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 + nextPathSegment = self.findPathToClosestDot( + currentState) # The missing piece self.actions += nextPathSegment for action in nextPathSegment: legal = currentState.getLegalActions() @@ -487,6 +538,7 @@ class ClosestDotSearchAgent(SearchAgent): "*** YOUR CODE HERE ***" util.raiseNotDefined() + class AnyFoodSearchProblem(PositionSearchProblem): """ A search problem for finding a path to any food. @@ -511,18 +563,19 @@ class AnyFoodSearchProblem(PositionSearchProblem): self.walls = gameState.getWalls() self.startState = gameState.getPacmanPosition() self.costFn = lambda x: 1 - self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + 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 + x, y = state "*** YOUR CODE HERE ***" util.raiseNotDefined() + def mazeDistance(point1, point2, gameState): """ Returns the maze distance between any two points, using the search functions @@ -538,5 +591,6 @@ def mazeDistance(point1, point2, gameState): 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) + prob = PositionSearchProblem( + gameState, start=point1, goal=point2, warn=False, visualize=False) return len(search.bfs(prob))