Module pacai.core.gamestate

Expand source code
import abc
import copy

from pacai.core.agentstate import AgentState
from pacai.core.directions import Directions
from pacai.util import util

class AbstractGameState(abc.ABC):
    """
    A game state specifies the status of a game, including the food, capsules, agents, and score.

    Game states are used by the `pacai.core.game.Game` to capture the actual state of the game,
    and can be used by agents to reason about the game.

    Only use the accessor methods to get data about the game state.
    """

    def __init__(self, layout):
        self._lastAgentMoved = None
        self._gameover = False
        self._win = False

        self._layout = layout

        # Keep a copy of the hash, since it is expensive to compute.
        # Any children should be sure to clear the hash when modifications are made.
        self._hash = None

        # For food and capsules, we will only copy on write (if we eat one of them).
        # This avoid additional copies on successors that don't eat.

        self._foodCopied = False
        self._food = layout.food.copy()
        self._lastFoodEaten = None

        self._capsulesCopied = False
        self._capsules = layout.capsules.copy()
        self._lastCapsuleEaten = None

        # An ordered list of locations that this state considers special.
        # A view may choose to specially represent these locations.
        self._highlightLocations = []

        self._agentStates = []
        for (isPacman, position) in layout.agentPositions:
            self._agentStates.append(AgentState(position, Directions.STOP, isPacman))

        self._score = 0

    @abc.abstractmethod
    def generateSuccessor(self, agentIndex, action):
        """
        Returns the successor state after the specified agent takes the action.
        Treat the returned state as a SHALLOW copy that has been modified.
        """

        pass

    @abc.abstractmethod
    def getLegalActions(self, agentIndex = 0):
        """
        Gets the legal actions for the agent specified.
        """

        pass

    def addScore(self, score):
        self._hash = None
        self._score += score

    def eatCapsule(self, x, y):
        """
        Mark the capsule at the given location as eaten.
        """

        if (not self.hasCapsule(x, y)):
            return False

        if (not self._capsulesCopied):
            self._capsules = self._capsules.copy()
            self._capsulesCopied = True

        self._capsules.remove((x, y))
        self._lastCapsuleEaten = (x, y)

        self._hash = None
        return True

    def eatFood(self, x, y):
        """
        Mark the food at the given location as eaten.
        """

        if (not self.hasFood(x, y)):
            return False

        if (not self._foodCopied):
            self._food = self._food.copy()
            self._foodCopied = True

        self._food[x][y] = False
        self._lastFoodEaten = (x, y)

        self._hash = None
        return True

    def endGame(self, win):
        self._gameover = True
        self._win = win

        self._hash = None

    def getAgentPosition(self, index):
        """
        Returns a location tuple of the agent with the given index.
        It is possible for this method to return None if the agent's position is unknown
        (like if it just died and is respawning).
        """

        position = self._agentStates[index].getPosition()
        if (position is None):
            return None

        # Ensure positions are ints.
        return tuple(int(pos) for pos in position)

    def getAgentState(self, index):
        return self._agentStates[index]

    def getAgentStates(self):
        return self._agentStates

    def getCapsules(self):
        """
        Returns a list of positions (x, y) of the remaining capsules.
        """

        return self._capsules

    def getFood(self):
        """
        Returns a Grid of boolean food indicator variables.

        Grids can be accessed via list notation.
        So to check if there is food at (x, y), just do something like: food[x][y].

        Callers should favor hasFood() over this, since this will make a copy of the grid.
        """

        return self._food.copy()

    def getHighlightLocations(self):
        return self._highlightLocations

    def getInitialAgentPosition(self, agentIndex):
        return self._layout.agentPositions[agentIndex][1]

    def getInitialLayout(self):
        """
        Get the initial layout this state starte with.
        User's should typically call one of the more detailed methods directly,
        e.g. getWalls().
        """

        return self._layout

    def getLastAgentMoved(self):
        return self._lastAgentMoved

    def getLastCapsuleEaten(self):
        return self._lastCapsuleEaten

    def getLastFoodEaten(self):
        return self._lastFoodEaten

    def getNumAgents(self):
        return len(self._agentStates)

    def getNumCapsules(self):
        """
        Get the amount of capsules left on the board.
        """

        return len(self._capsules)

    def getNumFood(self):
        """
        Get the amount of food left on the board.
        """

        return self._food.count()

    def getScore(self):
        return self._score

    def getWalls(self):
        """
        Returns a Grid of boolean wall indicator variables.

        Grids can be accessed via list notation.
        So to check if there is a wall at (x, y), just do something like: walls[x][y].

        The caller should not try to modify the walls.
        """

        return self._layout.walls

    def hasCapsule(self, x, y):
        """
        Returns true if the location (x, y) has a capsule.
        """

        return (x, y) in self._capsules

    def hasFood(self, x, y):
        """
        Returns true if the location (x, y) has food.
        """

        return self._food[x][y]

    def hasWall(self, x, y):
        """
        Returns true if (x, y) has a wall, false otherwise.
        """

        return self._layout.walls[x][y]

    def isLose(self):
        return self.isOver() and not self._win

    def isOver(self):
        return self._gameover

    def isWin(self):
        return self.isOver() and self._win

    def setHighlightLocations(self, locations):
        self._highlightLocations = list(locations)

    def setScore(self, score):
        self._score = score
        self._hash = None

    def _initSuccessor(self):
        """
        Get a state that will eventually serve as a successor.
        Initialize the successor to look like this state.
        """

        # Start with a shallow copy.
        successor = copy.copy(self)
        successor._hash = None

        # Leave food and capsules as a shallow copy, but mark them to be copied on write.
        successor._foodCopied = False
        successor._capsulesCopied = False

        # Agent states need to be deep copied.
        successor._agentStates = [agentState.copy() for agentState in self._agentStates]

        return successor

    def __eq__(self, other):
        if (other is None):
            return False

        # Reference equality check.
        if (self is other):
            return True

        if (type(self) != type(other)):
            return False

        # Note that not all fields are being used because we are checking if two states are equal,
        # not is they got to this confiruation in the same way.

        # Check simple fields first.
        if (self._score != other._score
                or self._gameover != other._gameover
                or self._win != other._win):
            return False

        # Now check the complex fields in increasing order of complexity.
        return (self._capsules == other._capsules
                and self._food == other._food
                and self._agentStates == other._agentStates
                and self._layout == other._layout)

    def __hash__(self):
        if (self._hash is None):
            self._hash = util.buildHash(self._score, self._gameover, self._win, *self._capsules,
                self._food, *self._agentStates, self._layout)

        return self._hash

Classes

class AbstractGameState (layout)

A game state specifies the status of a game, including the food, capsules, agents, and score.

Game states are used by the Game to capture the actual state of the game, and can be used by agents to reason about the game.

Only use the accessor methods to get data about the game state.

Expand source code
class AbstractGameState(abc.ABC):
    """
    A game state specifies the status of a game, including the food, capsules, agents, and score.

    Game states are used by the `pacai.core.game.Game` to capture the actual state of the game,
    and can be used by agents to reason about the game.

    Only use the accessor methods to get data about the game state.
    """

    def __init__(self, layout):
        self._lastAgentMoved = None
        self._gameover = False
        self._win = False

        self._layout = layout

        # Keep a copy of the hash, since it is expensive to compute.
        # Any children should be sure to clear the hash when modifications are made.
        self._hash = None

        # For food and capsules, we will only copy on write (if we eat one of them).
        # This avoid additional copies on successors that don't eat.

        self._foodCopied = False
        self._food = layout.food.copy()
        self._lastFoodEaten = None

        self._capsulesCopied = False
        self._capsules = layout.capsules.copy()
        self._lastCapsuleEaten = None

        # An ordered list of locations that this state considers special.
        # A view may choose to specially represent these locations.
        self._highlightLocations = []

        self._agentStates = []
        for (isPacman, position) in layout.agentPositions:
            self._agentStates.append(AgentState(position, Directions.STOP, isPacman))

        self._score = 0

    @abc.abstractmethod
    def generateSuccessor(self, agentIndex, action):
        """
        Returns the successor state after the specified agent takes the action.
        Treat the returned state as a SHALLOW copy that has been modified.
        """

        pass

    @abc.abstractmethod
    def getLegalActions(self, agentIndex = 0):
        """
        Gets the legal actions for the agent specified.
        """

        pass

    def addScore(self, score):
        self._hash = None
        self._score += score

    def eatCapsule(self, x, y):
        """
        Mark the capsule at the given location as eaten.
        """

        if (not self.hasCapsule(x, y)):
            return False

        if (not self._capsulesCopied):
            self._capsules = self._capsules.copy()
            self._capsulesCopied = True

        self._capsules.remove((x, y))
        self._lastCapsuleEaten = (x, y)

        self._hash = None
        return True

    def eatFood(self, x, y):
        """
        Mark the food at the given location as eaten.
        """

        if (not self.hasFood(x, y)):
            return False

        if (not self._foodCopied):
            self._food = self._food.copy()
            self._foodCopied = True

        self._food[x][y] = False
        self._lastFoodEaten = (x, y)

        self._hash = None
        return True

    def endGame(self, win):
        self._gameover = True
        self._win = win

        self._hash = None

    def getAgentPosition(self, index):
        """
        Returns a location tuple of the agent with the given index.
        It is possible for this method to return None if the agent's position is unknown
        (like if it just died and is respawning).
        """

        position = self._agentStates[index].getPosition()
        if (position is None):
            return None

        # Ensure positions are ints.
        return tuple(int(pos) for pos in position)

    def getAgentState(self, index):
        return self._agentStates[index]

    def getAgentStates(self):
        return self._agentStates

    def getCapsules(self):
        """
        Returns a list of positions (x, y) of the remaining capsules.
        """

        return self._capsules

    def getFood(self):
        """
        Returns a Grid of boolean food indicator variables.

        Grids can be accessed via list notation.
        So to check if there is food at (x, y), just do something like: food[x][y].

        Callers should favor hasFood() over this, since this will make a copy of the grid.
        """

        return self._food.copy()

    def getHighlightLocations(self):
        return self._highlightLocations

    def getInitialAgentPosition(self, agentIndex):
        return self._layout.agentPositions[agentIndex][1]

    def getInitialLayout(self):
        """
        Get the initial layout this state starte with.
        User's should typically call one of the more detailed methods directly,
        e.g. getWalls().
        """

        return self._layout

    def getLastAgentMoved(self):
        return self._lastAgentMoved

    def getLastCapsuleEaten(self):
        return self._lastCapsuleEaten

    def getLastFoodEaten(self):
        return self._lastFoodEaten

    def getNumAgents(self):
        return len(self._agentStates)

    def getNumCapsules(self):
        """
        Get the amount of capsules left on the board.
        """

        return len(self._capsules)

    def getNumFood(self):
        """
        Get the amount of food left on the board.
        """

        return self._food.count()

    def getScore(self):
        return self._score

    def getWalls(self):
        """
        Returns a Grid of boolean wall indicator variables.

        Grids can be accessed via list notation.
        So to check if there is a wall at (x, y), just do something like: walls[x][y].

        The caller should not try to modify the walls.
        """

        return self._layout.walls

    def hasCapsule(self, x, y):
        """
        Returns true if the location (x, y) has a capsule.
        """

        return (x, y) in self._capsules

    def hasFood(self, x, y):
        """
        Returns true if the location (x, y) has food.
        """

        return self._food[x][y]

    def hasWall(self, x, y):
        """
        Returns true if (x, y) has a wall, false otherwise.
        """

        return self._layout.walls[x][y]

    def isLose(self):
        return self.isOver() and not self._win

    def isOver(self):
        return self._gameover

    def isWin(self):
        return self.isOver() and self._win

    def setHighlightLocations(self, locations):
        self._highlightLocations = list(locations)

    def setScore(self, score):
        self._score = score
        self._hash = None

    def _initSuccessor(self):
        """
        Get a state that will eventually serve as a successor.
        Initialize the successor to look like this state.
        """

        # Start with a shallow copy.
        successor = copy.copy(self)
        successor._hash = None

        # Leave food and capsules as a shallow copy, but mark them to be copied on write.
        successor._foodCopied = False
        successor._capsulesCopied = False

        # Agent states need to be deep copied.
        successor._agentStates = [agentState.copy() for agentState in self._agentStates]

        return successor

    def __eq__(self, other):
        if (other is None):
            return False

        # Reference equality check.
        if (self is other):
            return True

        if (type(self) != type(other)):
            return False

        # Note that not all fields are being used because we are checking if two states are equal,
        # not is they got to this confiruation in the same way.

        # Check simple fields first.
        if (self._score != other._score
                or self._gameover != other._gameover
                or self._win != other._win):
            return False

        # Now check the complex fields in increasing order of complexity.
        return (self._capsules == other._capsules
                and self._food == other._food
                and self._agentStates == other._agentStates
                and self._layout == other._layout)

    def __hash__(self):
        if (self._hash is None):
            self._hash = util.buildHash(self._score, self._gameover, self._win, *self._capsules,
                self._food, *self._agentStates, self._layout)

        return self._hash

Ancestors

  • abc.ABC

Subclasses

Methods

def addScore(self, score)
Expand source code
def addScore(self, score):
    self._hash = None
    self._score += score
def eatCapsule(self, x, y)

Mark the capsule at the given location as eaten.

Expand source code
def eatCapsule(self, x, y):
    """
    Mark the capsule at the given location as eaten.
    """

    if (not self.hasCapsule(x, y)):
        return False

    if (not self._capsulesCopied):
        self._capsules = self._capsules.copy()
        self._capsulesCopied = True

    self._capsules.remove((x, y))
    self._lastCapsuleEaten = (x, y)

    self._hash = None
    return True
def eatFood(self, x, y)

Mark the food at the given location as eaten.

Expand source code
def eatFood(self, x, y):
    """
    Mark the food at the given location as eaten.
    """

    if (not self.hasFood(x, y)):
        return False

    if (not self._foodCopied):
        self._food = self._food.copy()
        self._foodCopied = True

    self._food[x][y] = False
    self._lastFoodEaten = (x, y)

    self._hash = None
    return True
def endGame(self, win)
Expand source code
def endGame(self, win):
    self._gameover = True
    self._win = win

    self._hash = None
def generateSuccessor(self, agentIndex, action)

Returns the successor state after the specified agent takes the action. Treat the returned state as a SHALLOW copy that has been modified.

Expand source code
@abc.abstractmethod
def generateSuccessor(self, agentIndex, action):
    """
    Returns the successor state after the specified agent takes the action.
    Treat the returned state as a SHALLOW copy that has been modified.
    """

    pass
def getAgentPosition(self, index)

Returns a location tuple of the agent with the given index. It is possible for this method to return None if the agent's position is unknown (like if it just died and is respawning).

Expand source code
def getAgentPosition(self, index):
    """
    Returns a location tuple of the agent with the given index.
    It is possible for this method to return None if the agent's position is unknown
    (like if it just died and is respawning).
    """

    position = self._agentStates[index].getPosition()
    if (position is None):
        return None

    # Ensure positions are ints.
    return tuple(int(pos) for pos in position)
def getAgentState(self, index)
Expand source code
def getAgentState(self, index):
    return self._agentStates[index]
def getAgentStates(self)
Expand source code
def getAgentStates(self):
    return self._agentStates
def getCapsules(self)

Returns a list of positions (x, y) of the remaining capsules.

Expand source code
def getCapsules(self):
    """
    Returns a list of positions (x, y) of the remaining capsules.
    """

    return self._capsules
def getFood(self)

Returns a Grid of boolean food indicator variables.

Grids can be accessed via list notation. So to check if there is food at (x, y), just do something like: food[x][y].

Callers should favor hasFood() over this, since this will make a copy of the grid.

Expand source code
def getFood(self):
    """
    Returns a Grid of boolean food indicator variables.

    Grids can be accessed via list notation.
    So to check if there is food at (x, y), just do something like: food[x][y].

    Callers should favor hasFood() over this, since this will make a copy of the grid.
    """

    return self._food.copy()
def getHighlightLocations(self)
Expand source code
def getHighlightLocations(self):
    return self._highlightLocations
def getInitialAgentPosition(self, agentIndex)
Expand source code
def getInitialAgentPosition(self, agentIndex):
    return self._layout.agentPositions[agentIndex][1]
def getInitialLayout(self)

Get the initial layout this state starte with. User's should typically call one of the more detailed methods directly, e.g. getWalls().

Expand source code
def getInitialLayout(self):
    """
    Get the initial layout this state starte with.
    User's should typically call one of the more detailed methods directly,
    e.g. getWalls().
    """

    return self._layout
def getLastAgentMoved(self)
Expand source code
def getLastAgentMoved(self):
    return self._lastAgentMoved
def getLastCapsuleEaten(self)
Expand source code
def getLastCapsuleEaten(self):
    return self._lastCapsuleEaten
def getLastFoodEaten(self)
Expand source code
def getLastFoodEaten(self):
    return self._lastFoodEaten
def getLegalActions(self, agentIndex=0)

Gets the legal actions for the agent specified.

Expand source code
@abc.abstractmethod
def getLegalActions(self, agentIndex = 0):
    """
    Gets the legal actions for the agent specified.
    """

    pass
def getNumAgents(self)
Expand source code
def getNumAgents(self):
    return len(self._agentStates)
def getNumCapsules(self)

Get the amount of capsules left on the board.

Expand source code
def getNumCapsules(self):
    """
    Get the amount of capsules left on the board.
    """

    return len(self._capsules)
def getNumFood(self)

Get the amount of food left on the board.

Expand source code
def getNumFood(self):
    """
    Get the amount of food left on the board.
    """

    return self._food.count()
def getScore(self)
Expand source code
def getScore(self):
    return self._score
def getWalls(self)

Returns a Grid of boolean wall indicator variables.

Grids can be accessed via list notation. So to check if there is a wall at (x, y), just do something like: walls[x][y].

The caller should not try to modify the walls.

Expand source code
def getWalls(self):
    """
    Returns a Grid of boolean wall indicator variables.

    Grids can be accessed via list notation.
    So to check if there is a wall at (x, y), just do something like: walls[x][y].

    The caller should not try to modify the walls.
    """

    return self._layout.walls
def hasCapsule(self, x, y)

Returns true if the location (x, y) has a capsule.

Expand source code
def hasCapsule(self, x, y):
    """
    Returns true if the location (x, y) has a capsule.
    """

    return (x, y) in self._capsules
def hasFood(self, x, y)

Returns true if the location (x, y) has food.

Expand source code
def hasFood(self, x, y):
    """
    Returns true if the location (x, y) has food.
    """

    return self._food[x][y]
def hasWall(self, x, y)

Returns true if (x, y) has a wall, false otherwise.

Expand source code
def hasWall(self, x, y):
    """
    Returns true if (x, y) has a wall, false otherwise.
    """

    return self._layout.walls[x][y]
def isLose(self)
Expand source code
def isLose(self):
    return self.isOver() and not self._win
def isOver(self)
Expand source code
def isOver(self):
    return self._gameover
def isWin(self)
Expand source code
def isWin(self):
    return self.isOver() and self._win
def setHighlightLocations(self, locations)
Expand source code
def setHighlightLocations(self, locations):
    self._highlightLocations = list(locations)
def setScore(self, score)
Expand source code
def setScore(self, score):
    self._score = score
    self._hash = None