Module pacai.ui.frame

A Frame is the base unit of a View. Frames hold all the information necessary to draw the game in whatever medium the view chooses.

Expand source code
"""
A Frame is the base unit of a View.
Frames hold all the information necessary to draw the game in whatever medium
the view chooses.
"""

import abc

from PIL import Image
from PIL import ImageDraw

from pacai.ui import spritesheet
from pacai.ui import token
from pacai.util import util

# The range of intensity values for highlighted locations.
MAX_HIGHLIGHT_INTENSITY_RANGE = 150

SCORE_X_POSITION = 0.55
SCORE_Y_POSITION = -0.95

class Frame(abc.ABC):
    """
    A general representation of that can be seen on-screen at a given time.
    Frames are the basic units of the views.
    """

    def __init__(self, frame, state, turn):
        self._frame = frame
        self._turn = turn

        self._boardHeight = state.getInitialLayout().getHeight()
        self._boardWidth = state.getInitialLayout().getWidth()

        # All items on the board are at integral potision.
        self._board = self._buildBoard(state)

        # Agents may not be at integral positions, so they are represented independently.
        self._agentTokens = self._getAgentTokens(state)

        # Highlight locations is on the lowest layer.
        self._highlightLocations = state.getHighlightLocations()
        if (self._highlightLocations is None):
            self._highlightLocations = []

        self._score = state.getScore()

    def getAgents(self):
        return self._agentTokens

    def getDiscreteAgents(self):
        """
        Get the agentTokens, but with interger positions.
        """

        agentTokens = {}

        for (position, agent) in self._agentTokens.items():
            agentTokens[util.nearestPoint(position)] = agent

        return agentTokens

    def getBoardHeight(self):
        return self._boardHeight

    def getImageHeight(self):
        # +1 for the score.
        return (self._boardHeight + 1) * spritesheet.SQUARE_SIZE

    def getImageWidth(self):
        return self._boardWidth * spritesheet.SQUARE_SIZE

    def getToken(self, x, y):
        return self._board[x][y]

    def getCol(self, x):
        return self._board[x]

    def getBoardWidth(self):
        return self._boardWidth

    def toImage(self, sprites = {}, font = None):
        # Height is +1 for the score.
        size = (
            self._boardWidth * spritesheet.SQUARE_SIZE,
            (self._boardHeight + 1) * spritesheet.SQUARE_SIZE
        )

        image = Image.new('RGB', size, (0, 0, 0, 255))
        draw = ImageDraw.Draw(image)

        # First, draw any highlights.
        for i in range(len(self._highlightLocations)):
            (x, y) = self._highlightLocations[i]
            startPoint = self._toImageCoords(x, y)
            endPoint = self._toImageCoords(x + 1, y - 1)

            intensity = int((i / len(self._highlightLocations)) * MAX_HIGHLIGHT_INTENSITY_RANGE)

            draw.rectangle([startPoint, endPoint], fill = (255, intensity, intensity))

        # Then, draw the board.
        for x in range(self._boardWidth):
            for y in range(self._boardHeight):
                if (self._board[x][y] != token.EMPTY_TOKEN):
                    self._placeToken(x, y, self._board[x][y], sprites, image, draw)

        # Finally, overlay the agents.
        for ((x, y), agentToken) in self._agentTokens.items():
            self._placeToken(x, y, agentToken, sprites, image, draw)

        # Draw score
        position = self._toImageCoords(SCORE_X_POSITION, SCORE_Y_POSITION)
        scoreText = "Score: %d" % (self._score)
        draw.text(position, scoreText, self._getTextColor(), font)

        return image

    def _buildBoard(self, state):
        board = self._boardWidth * [None]
        for x in range(self._boardWidth):

            items = self._boardHeight * [token.EMPTY_TOKEN]
            for y in range(self._boardHeight):
                if (state.hasWall(x, y)):
                    items[y] = self._getWallToken(x, y, state)
                elif (state.hasFood(x, y)):
                    items[y] = self._getFoodToken(x, y, state)
                elif (state.hasCapsule(x, y)):
                    items[y] = self._getCapsuleToken(x, y, state)

            board[x] = items

        return board

    @abc.abstractmethod
    def _getAgentBaseToken(self, x, y, agentIndex, state):
        pass

    def _getAgentTokens(self, state):
        """
        Returns: {(x, y): token, ...}
        """

        tokens = {}

        for agentIndex in range(state.getNumAgents()):
            agentState = state.getAgentState(agentIndex)
            position = agentState.getPosition()

            if (agentState.isScaredGhost()):
                tokens[position] = token.SCARED_GHOST_TOKEN
            else:
                agentBaseToken = self._getAgentBaseToken(*position, agentIndex, state)
                direction = agentState.getDirection()

                # agentToken = token.getAnimationToken(agentBaseToken, direction, self._turn)
                agentToken = token.getAnimationToken(agentBaseToken, direction, self._frame)

                tokens[position] = agentToken

        return tokens

    @abc.abstractmethod
    def _getCapsuleBaseToken(self, x, y, state):
        pass

    def _getCapsuleToken(self, x, y, state):
        return self._getCapsuleBaseToken(x, y, state) + token.CAPSULE_OFFSET

    @abc.abstractmethod
    def _getFoodBaseToken(self, x, y, state):
        pass

    def _getFoodToken(self, x, y, state):
        return self._getFoodBaseToken(x, y, state) + token.FOOD_OFFSET

    @abc.abstractmethod
    def _getTextColor(self):
        pass

    @abc.abstractmethod
    def _getWallBaseToken(self, x, y, state):
        pass

    def _getWallToken(self, x, y, state):
        hasWallN = False
        hasWallE = False
        hasWallS = False
        hasWallW = False

        baseToken = self._getWallBaseToken(x, y, state)

        if (y != self._boardHeight - 1):
            hasWallN = state.hasWall(x, y + 1)

        if (x != self._boardWidth - 1):
            hasWallE = state.hasWall(x + 1, y)

        if (y != 0):
            hasWallS = state.hasWall(x, y - 1)

        if (x != 0):
            hasWallW = state.hasWall(x - 1, y)

        return token.getWallToken(baseToken, hasWallN, hasWallE, hasWallS, hasWallW)

    def _placeToken(self, x, y, objectToken, sprites, image, draw):
        startPoint = self._toImageCoords(x, y)
        endPoint = self._toImageCoords(x + 1, y - 1)

        if (objectToken in sprites):
            image.paste(sprites[objectToken], startPoint, sprites[objectToken])
        else:
            color = self._tokenToColor(objectToken)
            draw.rectangle([startPoint, endPoint], fill = color)

    def _toImageCoords(self, x, y):
        # PIL has (0, 0) as the upper-left, while pacai has it as the lower-left.
        return (
            int(x * spritesheet.SQUARE_SIZE),
            int((self._boardHeight - 1 - y) * spritesheet.SQUARE_SIZE)
        )

    def _tokenToColor(self, objectToken):
        if (objectToken == token.EMPTY_TOKEN):
            return (0, 0, 0)
        elif (objectToken == token.HIGHLIGHT_TOKEN):
            return (255, 0, 0)
        elif (token.isWall(objectToken)):
            return (0, 51, 255)
        elif (token.isFood(objectToken)):
            return (255, 255, 255)
        elif (token.isCapsule(objectToken)):
            return (255, 0, 255)
        elif (token.isGhost(objectToken)):
            return (229, 0, 0)
        elif (token.isPacman(objectToken)):
            return (255, 255, 61)
        elif (objectToken == token.SCARED_GHOST_TOKEN):
            return (0, 255, 0)
        else:
            return (0, 0, 0)

Classes

class Frame (frame, state, turn)

A general representation of that can be seen on-screen at a given time. Frames are the basic units of the views.

Expand source code
class Frame(abc.ABC):
    """
    A general representation of that can be seen on-screen at a given time.
    Frames are the basic units of the views.
    """

    def __init__(self, frame, state, turn):
        self._frame = frame
        self._turn = turn

        self._boardHeight = state.getInitialLayout().getHeight()
        self._boardWidth = state.getInitialLayout().getWidth()

        # All items on the board are at integral potision.
        self._board = self._buildBoard(state)

        # Agents may not be at integral positions, so they are represented independently.
        self._agentTokens = self._getAgentTokens(state)

        # Highlight locations is on the lowest layer.
        self._highlightLocations = state.getHighlightLocations()
        if (self._highlightLocations is None):
            self._highlightLocations = []

        self._score = state.getScore()

    def getAgents(self):
        return self._agentTokens

    def getDiscreteAgents(self):
        """
        Get the agentTokens, but with interger positions.
        """

        agentTokens = {}

        for (position, agent) in self._agentTokens.items():
            agentTokens[util.nearestPoint(position)] = agent

        return agentTokens

    def getBoardHeight(self):
        return self._boardHeight

    def getImageHeight(self):
        # +1 for the score.
        return (self._boardHeight + 1) * spritesheet.SQUARE_SIZE

    def getImageWidth(self):
        return self._boardWidth * spritesheet.SQUARE_SIZE

    def getToken(self, x, y):
        return self._board[x][y]

    def getCol(self, x):
        return self._board[x]

    def getBoardWidth(self):
        return self._boardWidth

    def toImage(self, sprites = {}, font = None):
        # Height is +1 for the score.
        size = (
            self._boardWidth * spritesheet.SQUARE_SIZE,
            (self._boardHeight + 1) * spritesheet.SQUARE_SIZE
        )

        image = Image.new('RGB', size, (0, 0, 0, 255))
        draw = ImageDraw.Draw(image)

        # First, draw any highlights.
        for i in range(len(self._highlightLocations)):
            (x, y) = self._highlightLocations[i]
            startPoint = self._toImageCoords(x, y)
            endPoint = self._toImageCoords(x + 1, y - 1)

            intensity = int((i / len(self._highlightLocations)) * MAX_HIGHLIGHT_INTENSITY_RANGE)

            draw.rectangle([startPoint, endPoint], fill = (255, intensity, intensity))

        # Then, draw the board.
        for x in range(self._boardWidth):
            for y in range(self._boardHeight):
                if (self._board[x][y] != token.EMPTY_TOKEN):
                    self._placeToken(x, y, self._board[x][y], sprites, image, draw)

        # Finally, overlay the agents.
        for ((x, y), agentToken) in self._agentTokens.items():
            self._placeToken(x, y, agentToken, sprites, image, draw)

        # Draw score
        position = self._toImageCoords(SCORE_X_POSITION, SCORE_Y_POSITION)
        scoreText = "Score: %d" % (self._score)
        draw.text(position, scoreText, self._getTextColor(), font)

        return image

    def _buildBoard(self, state):
        board = self._boardWidth * [None]
        for x in range(self._boardWidth):

            items = self._boardHeight * [token.EMPTY_TOKEN]
            for y in range(self._boardHeight):
                if (state.hasWall(x, y)):
                    items[y] = self._getWallToken(x, y, state)
                elif (state.hasFood(x, y)):
                    items[y] = self._getFoodToken(x, y, state)
                elif (state.hasCapsule(x, y)):
                    items[y] = self._getCapsuleToken(x, y, state)

            board[x] = items

        return board

    @abc.abstractmethod
    def _getAgentBaseToken(self, x, y, agentIndex, state):
        pass

    def _getAgentTokens(self, state):
        """
        Returns: {(x, y): token, ...}
        """

        tokens = {}

        for agentIndex in range(state.getNumAgents()):
            agentState = state.getAgentState(agentIndex)
            position = agentState.getPosition()

            if (agentState.isScaredGhost()):
                tokens[position] = token.SCARED_GHOST_TOKEN
            else:
                agentBaseToken = self._getAgentBaseToken(*position, agentIndex, state)
                direction = agentState.getDirection()

                # agentToken = token.getAnimationToken(agentBaseToken, direction, self._turn)
                agentToken = token.getAnimationToken(agentBaseToken, direction, self._frame)

                tokens[position] = agentToken

        return tokens

    @abc.abstractmethod
    def _getCapsuleBaseToken(self, x, y, state):
        pass

    def _getCapsuleToken(self, x, y, state):
        return self._getCapsuleBaseToken(x, y, state) + token.CAPSULE_OFFSET

    @abc.abstractmethod
    def _getFoodBaseToken(self, x, y, state):
        pass

    def _getFoodToken(self, x, y, state):
        return self._getFoodBaseToken(x, y, state) + token.FOOD_OFFSET

    @abc.abstractmethod
    def _getTextColor(self):
        pass

    @abc.abstractmethod
    def _getWallBaseToken(self, x, y, state):
        pass

    def _getWallToken(self, x, y, state):
        hasWallN = False
        hasWallE = False
        hasWallS = False
        hasWallW = False

        baseToken = self._getWallBaseToken(x, y, state)

        if (y != self._boardHeight - 1):
            hasWallN = state.hasWall(x, y + 1)

        if (x != self._boardWidth - 1):
            hasWallE = state.hasWall(x + 1, y)

        if (y != 0):
            hasWallS = state.hasWall(x, y - 1)

        if (x != 0):
            hasWallW = state.hasWall(x - 1, y)

        return token.getWallToken(baseToken, hasWallN, hasWallE, hasWallS, hasWallW)

    def _placeToken(self, x, y, objectToken, sprites, image, draw):
        startPoint = self._toImageCoords(x, y)
        endPoint = self._toImageCoords(x + 1, y - 1)

        if (objectToken in sprites):
            image.paste(sprites[objectToken], startPoint, sprites[objectToken])
        else:
            color = self._tokenToColor(objectToken)
            draw.rectangle([startPoint, endPoint], fill = color)

    def _toImageCoords(self, x, y):
        # PIL has (0, 0) as the upper-left, while pacai has it as the lower-left.
        return (
            int(x * spritesheet.SQUARE_SIZE),
            int((self._boardHeight - 1 - y) * spritesheet.SQUARE_SIZE)
        )

    def _tokenToColor(self, objectToken):
        if (objectToken == token.EMPTY_TOKEN):
            return (0, 0, 0)
        elif (objectToken == token.HIGHLIGHT_TOKEN):
            return (255, 0, 0)
        elif (token.isWall(objectToken)):
            return (0, 51, 255)
        elif (token.isFood(objectToken)):
            return (255, 255, 255)
        elif (token.isCapsule(objectToken)):
            return (255, 0, 255)
        elif (token.isGhost(objectToken)):
            return (229, 0, 0)
        elif (token.isPacman(objectToken)):
            return (255, 255, 61)
        elif (objectToken == token.SCARED_GHOST_TOKEN):
            return (0, 255, 0)
        else:
            return (0, 0, 0)

Ancestors

  • abc.ABC

Subclasses

Methods

def getAgents(self)
Expand source code
def getAgents(self):
    return self._agentTokens
def getBoardHeight(self)
Expand source code
def getBoardHeight(self):
    return self._boardHeight
def getBoardWidth(self)
Expand source code
def getBoardWidth(self):
    return self._boardWidth
def getCol(self, x)
Expand source code
def getCol(self, x):
    return self._board[x]
def getDiscreteAgents(self)

Get the agentTokens, but with interger positions.

Expand source code
def getDiscreteAgents(self):
    """
    Get the agentTokens, but with interger positions.
    """

    agentTokens = {}

    for (position, agent) in self._agentTokens.items():
        agentTokens[util.nearestPoint(position)] = agent

    return agentTokens
def getImageHeight(self)
Expand source code
def getImageHeight(self):
    # +1 for the score.
    return (self._boardHeight + 1) * spritesheet.SQUARE_SIZE
def getImageWidth(self)
Expand source code
def getImageWidth(self):
    return self._boardWidth * spritesheet.SQUARE_SIZE
def getToken(self, x, y)
Expand source code
def getToken(self, x, y):
    return self._board[x][y]
def toImage(self, sprites={}, font=None)
Expand source code
def toImage(self, sprites = {}, font = None):
    # Height is +1 for the score.
    size = (
        self._boardWidth * spritesheet.SQUARE_SIZE,
        (self._boardHeight + 1) * spritesheet.SQUARE_SIZE
    )

    image = Image.new('RGB', size, (0, 0, 0, 255))
    draw = ImageDraw.Draw(image)

    # First, draw any highlights.
    for i in range(len(self._highlightLocations)):
        (x, y) = self._highlightLocations[i]
        startPoint = self._toImageCoords(x, y)
        endPoint = self._toImageCoords(x + 1, y - 1)

        intensity = int((i / len(self._highlightLocations)) * MAX_HIGHLIGHT_INTENSITY_RANGE)

        draw.rectangle([startPoint, endPoint], fill = (255, intensity, intensity))

    # Then, draw the board.
    for x in range(self._boardWidth):
        for y in range(self._boardHeight):
            if (self._board[x][y] != token.EMPTY_TOKEN):
                self._placeToken(x, y, self._board[x][y], sprites, image, draw)

    # Finally, overlay the agents.
    for ((x, y), agentToken) in self._agentTokens.items():
        self._placeToken(x, y, agentToken, sprites, image, draw)

    # Draw score
    position = self._toImageCoords(SCORE_X_POSITION, SCORE_Y_POSITION)
    scoreText = "Score: %d" % (self._score)
    draw.text(position, scoreText, self._getTextColor(), font)

    return image