JustToThePoint English Website Version
JustToThePoint en español
JustToThePoint in Thai

Programming a Pac-Man in Python III

The Text class

Pacman eats pellets because he has to, fruit because he should, and ghosts because they remind him that death is the only true escape from his hellish maze, Connor Choadsworth

Programming a Pac-Man in Python

Programming a Pac-Man in Python

Our game needs to be able to render some text, so we can display the player’s score, current level, and inform the player when the game is over, paused, etc.

Every instance of our class myText has a text, color, size, lifespan, and position (x, y).

import pygame
from vector import myVector
from settings import *

class myText(object):
    def __init__(self, text, color, x, y, size, time=None, visible=True):
        self.color = color
        self.visible = visible  # The text can be set as visible or invisible.
        self.position = myVector(x, y)
        self.timer = 0
        self.lifespan = time # We can define a lifespan, so the text will only be visible for a small amount of time.
        self.destroy = False
        self.font = pygame.font.Font("myfont.ttf", size) # The class Font allows for rendering TrueType fonts into a new Surface object. 
        self.myText = self.font.render(text, 1, self.color) # The render() method is used to create a Surface object from a string.
        
    def setText(self, newtext):
        # We can update the text if needed.
        self.myText = str(newtext)
        self.myText = self.font.render(self.myText, 1, self.color)

    def update(self, dt):
        # It checks if the object has a lifespan and if so, it updates its timer. If the timer has already reached its lifespan, it sets the flag "destroy" to True. The class myTextGame will finish the job: if text.destroy: [...]  self.myTemporaryText.remove(text)
        if self.lifespan is not None:
            self.timer += dt
            if self.timer >= self.lifespan:
                self.timer = 0
                self.lifespan = None
                self.destroy = True

    def render(self, screen):
        # If the text's attribute or property "visible" is set to True, it will render or blit the text on the screen.
        if self.visible:
            x, y = self.position.getVector()
            screen.blit(self.myText, (x, y))

class myTextGame(object):
    """ A custom text class to handle all the game's texts."""
    def __init__(self):
        self.nextid = 10
        self.myText = {} # We use this dictionary to keep track of all "permanent" text objects that will remain _active but not necessarily visible during the game_.
        self.setupText()
        self.showText(READYTXT) # Initially, we show the "READY!" text.
        self.myTemporaryText = [] # We use a list to keep track of all "temporary" text objects that will be erased after a brief appearance on the game.
        
    def setupText(self):
        size = TILEHEIGHT
        self.myText[SCORETXT] = myText("0".zfill(8), WHITE, 0, TILEHEIGHT, size) # This is the player's score. It needs to be constantly updated.
        self.myText[LEVELTXT] = myText(str(1).zfill(3), WHITE, 23*TILEWIDTH, TILEHEIGHT, size) # This is the level text.
        self.myText[READYTXT] = myText("READY!", YELLOW, 11.25*TILEWIDTH, 20*TILEHEIGHT, size, visible=False) # READY, PAUSED and GAMEOVER will be displayed in the middle of the screen when the game begins, pauses or finishes because the player has lost all its lives.
        self.myText[PAUSETXT] = myText("PAUSED!", YELLOW, 10.625*TILEWIDTH, 20*TILEHEIGHT, size, visible=False)
        self.myText[GAMEOVERTXT] = myText("GAMEOVER!", YELLOW, 10*TILEWIDTH, 20*TILEHEIGHT, size, visible=False)
        self.myText[SCORELABEL] = myText("SCORE", WHITE, 0, 0, size) # A permanent label placed in the top left corner of the screen.
        self.myText[LEVELLABEL] = myText("LEVEL", WHITE, 23*TILEWIDTH, 0, size) # This is another permanent label placed in the top right corner of the screen. 

    def addText(self, text, color, x, y, size, time=None):
        # It adds a temporary text.
        self.myTemporaryText.append(myText(text, color, x, y, size, time=time))
        
    def update(self, dt):
        # It updates all the text objects in the class. 
        for tkey in list(self.myText.keys()):
            self.myText[tkey].update(dt)
        
        if self.myTemporaryText!=[]:
            for text in self.myTemporaryText:
                text.update(dt)
                if text.destroy: # If the text object is marked for destruction, it removes it from its list (self.myTemporaryText) and actually destroys the object.
                    self.myTemporaryText.remove(text)

    def showText(self, id):
        # It is used to show one of the three following texts: READY!, PAUSE! or GAME OVER! in the middle of the screen.
        self.hideText()
        self.myText[id].visible = True

    def hideText(self):
        self.myText[READYTXT].visible = False
        self.myText[PAUSETXT].visible = False
        self.myText[GAMEOVERTXT].visible = False

    def updateScore(self, score):
        # It updates the user's score.
        self.myText[SCORETXT].setText(str(score).zfill(8))

    def updateLevel(self, level):
        # It updates the game's level.
        self.myText[LEVELTXT].setText(str(level + 1).zfill(3))

    def render(self, screen):
        # It renders all the game's texts.
        for tkey in list(self.myText.keys()):
            self.myText[tkey].render(screen)
        
        if self.myTemporaryText!=[]:
            for text in self.myTemporaryText:
                text.render(screen)
Programming a Pac-Man in Python

Programming a Pac-Man in Python

The Game class

“Where shall I begin, please your Majesty?” he asked. “Begin at the beginning,” the King said gravely, “and go on till you come to the end: then stop," Alice in Wonderland.

Finally, we will create a class for our game.

import pygame, sys
from settings import *
from pygame.locals import *
from pacman import Pacman
from maze import Maze
from pellets import Pellets
from ghosts import Ghosts
from fruit import Fruit
from text import myTextGame
from sprites import LifeSprites
from sprites import MazeSprites
from modes import GameControl

class Game: # It represents the game itself

This code is inspired on Making Games with Python & Pygame by Al Sweigart, [email protected], http://inventwithpython.com/pygame, Creative Commons BY-NC-SA 3.0 US.

    def __init__(self):
        pygame.init()
        self.screen = pygame.display.set_mode((SCREENWIDTH, SCREENHEIGHT)) # It sets the display mode and creates an instance of the pygame.Surface class.
        self.background = None
        self.fpsClock = pygame.time.Clock() # A pygame.time.Clock object helps us to make sure our program runs at a certain FPS.
        self.fruit = None
        self.level = 0
        self.lives = NUM_LIVES
        self.score = 0
        self.myTextGame = myTextGame()
        self.lifesprites = LifeSprites(self.lives)
        self.ghost = None
        
     def setBackground(self):
        """ It creates a surface and fills it with black."""
        self.background = pygame.surface.Surface((SCREENWIDTH, SCREENHEIGHT)).convert()
        self.background.fill(BLACK)

    def update(self):
        """ It updates the interface. It governs everything that is dynamic in our game."""
        dt = self.fpsClock.tick(FPS) / 1000.0
        self.myTextGame.update(dt) # It calls myTextGame to update itself.
        self.checkEvents() # It checks for user input events.
        
        if not self.control.paused: # If the game is not paused,...
            self.pacman.update(dt) # it tells Pac-Man and the ghosts to update themselves.
            self.ghosts.update(dt)
            # Next, it checks all events related to the ghosts, pellets, and the fruit.
            self.checkGhostEvents() 
            self.checkPelletEvents()
            self.checkFruitEvents()
            self.pellets.update(dt) # It updates the power pellets, so they can flash by turning on and off their visibility.
            if self.fruit is not None: # If there is a fruit, it calls its update method.
                self.fruit.update(dt)
        elif not self.control.playerPaused: 
        # If the user has not paused the game and Pac-Man or a ghost is death, it updates Pac-Man or the ghost so their death's animations can be rendered and seen by the user.
            if not self.pacman.alive:
                self.pacman.update(dt)
            if self.ghost is not None:
                self.ghost.update(dt)
            
        afterPauseMethod = self.control.update(dt)
        # It updates the Game controller. If the Game controller's update() method returns a function (a function to be run after the pause), it executes it.
        if afterPauseMethod is not None:
            afterPauseMethod()
            
        self.render() # Finally, we draw the current state of the game.
        self.reset_keys() # We reset the keys as we have already processed the key's associated event.
        self.end_game() # It checks if there are no more pellets left.

    def end_game(self):
        # If there are no more pellets left and the game is not paused, we make a small pause, and move on to the next level by using the Game Controller's setPause() method.
        if self.pellets.isEmpty() and not self.control.paused:
            self.hideEntities()
            self.control.setPause(pauseTime=SMALL_PAUSE, func=self.nextLevel)

    def updateScore(self, points):
        # It updates the game's score. 
        self.score += points
        self.myTextGame.updateScore(self.score)

    def checkGhostEvents(self):
        # It _checks for ghostly events_, i.e., it iterates over all the ghosts and checks if Pac-Man has collided with one of them.
        for ghost in self.ghosts:
            if self.pacman.collideGhost(ghost):
                if ghost.mode == FREIGHT:

If Pac-Man has collided with a ghost in FREIGHT mode, the ghost shall definitely die (*1). Then, it updates the player’s score (*2), shows a temporary white text with the ghost’s points in its position (*3), asks the ghosts to update their points (*4), and starts the ghost’s death animation.

                    self.pacman.visible = False
                    self.ghost = ghost
                    ghost.die() # (*1)
                    self.updateScore(ghost.points) (*2)
                    self.myTextGame.addText(str(ghost.points), WHITE, ghost.position.x, ghost.position.y, 8, time=1) (*3)
                    self.ghosts.updatePoints() (*4)
                    self.control.setAnimationInProgress(True)
                    self.control.setPause(playerPaused = False, pauseTime=SMALL_PAUSE, func=self.showEntities)
                elif ghost.mode is not DEATH and ghost.mode is not ZOMBIE:
                # If Pac-Man has collided with a ghost that was not in FREIGTH, DEATH, or ZOMBIE mode, then Pac-Man shall definitely die! 
                     if self.pacman.alive:
                         self.lives -= 1 # It decreases the number of lives by one.
                         self.lifesprites.removeImage() # It removes one of the Pac-Man sprites from the lifesprites (self.lifesprites = LifeSprites(self.lives))
                         self.pacman.die() # Pac-Man is death!
                         self.control.setAnimationInProgress(True)
                         self.ghosts.hide()
                         if self.lives <= 0: # After a somehow brief pause to show Pac-Man death's animation, it will call the restartGame() or resetLevel() methods depending on the remaining number of lives.
                             self.myTextGame.showText(GAMEOVERTXT)
                             self.control.setPause(playerPaused = False, pauseTime=BIG_PAUSE, func=self.restartGame)
                         else:
                             self.control.setPause(playerPaused = False, pauseTime=BIG_PAUSE, func=self.resetLevel)

    
    def showEntities(self):
        """ It makes Pac-Man and the ghosts visible. If a ghost was killed by Pac-MAN (if sel.ghost is not None), it revives it."""
        self.pacman.visible = True
        self.ghosts.show()
        if self.ghost is not None:
            self.ghost.revive() # The ghost will change to a "ZOMBIE" mode, only his eyes will be drawn, and it will be seen moving towards the ghosts' home at fast speed.
            self.ghost = None

    def hideEntities(self):
        """ It makes Pac-Man and the ghosts invisible. It is called after the player pauses the game or the game or a new level is started.""" 
        self.pacman.visible = False
        self.ghosts.hide()

    def render(self):
        """ It renders or draws the current state of the game."""
        self.screen.blit(self.background, (0, 0)) # First, it draws the background.
        self.pacman.render(self.screen) # Secondly, it renders Pac-Man, the ghosts, and the pellets.
        self.ghosts.render(self.screen)
        self.pellets.render(self.screen)
        if self.fruit is not None: # Thirdly, if there is a piece of fruit, it renders it.
            self.fruit.render(self.screen)
        self.myTextGame.render(self.screen) # It draws the texts.
        for i in range(len(self.lifesprites.images)): # It draws Pac-Man's lives at the bottom left corner of the screen.
            x = self.lifesprites.images[i].get_width() * i
            y = SCREENHEIGHT - self.lifesprites.images[i].get_height()
            self.screen.blit(self.lifesprites.images[i], (x, y))
        pygame.display.update() # Finally, it will update the full display surface to the screen.

    def checkFruitEvents(self):
        """ It checks events that are related to the fruits.""" 
        if self.pellets.numEaten == FIRST_FRUIT or self.pellets.numEaten == SECOND_FRUIT:
        # If Pac-Man has already eaten FIRST_FRUIT or SECOND_FRUIT pellets, it creates a new instance of Fruit.
            if self.fruit is None:
                self.fruit = Fruit(self.myMaze.getNodeFromTiles(9, 20), self.level)
        # If Pac-Man eats a piece of fruit, it updates the player's score, shows temporarily the (piece of) fruit's points in the piece of fruit's position, and destroys the object (self.fruit = None).
        if self.fruit is not None:
            if self.pacman.collideCheck(self.fruit):
                self.updateScore(self.fruit.points)
                self.myTextGame.addText(str(self.fruit.points), WHITE, self.fruit.position.x, self.fruit.position.y, 8, time=1)
                self.fruit = None
            # If the fruit's lifespan has already expired and it has marked itself to be destroyed (self.fruit.destroy = True), it destroys the object.
            elif self.fruit.destroy:
                self.fruit = None

    def startGame(self):
    """ It starts a new game by creating a new maze, and new objects for the Maze, Pac-Man, the pellets, the four ghosts, and the Game Controller."""   
        self.setBackground()
        self.mazesprites = MazeSprites([MAZEFILE](https://pacmancode.com/graphical-mazes), [MAZEROTATION](https://pacmancode.com/graphical-mazes-part-2)) # The 18th row is different: G - - - - - + - - n 5 X X X X X X 5 n - - + - - - - - G
        self.background = self.mazesprites.constructBackground(self.background, self.level%5)
        self.KEYUP = self.KEYLEFT = self.KEYRIGHT = self.KEYDOWN = False
        self.myMaze = Maze()
        self.homekey = self.myMaze.createHomeGhost(*HOMEGHOSTS)
        self.pacman = Pacman(self)
        self.pellets = Pellets(MAZEFILE)
        self.ghosts = Ghosts(self.myMaze.nodes[self.homekey], self.myMaze, self.pacman)
        self.control = GameControl(self.ghosts)
        self.hideEntities() # It hides the ghosts and Pac-Man waiting for the user to press the spacebar.
                
    def checkEvents(self):
        # Pygame handles all its event messaging through an event queue. This method checks all these events. 
        for event in pygame.event.get(): # It gets events from the queue.
            if event.type == QUIT:
                exit()
            elif event.type == KEYDOWN:
                if event.key == K_q:
                    exit()
                elif event.key == K_SPACE:
                    # If there is an animation taking place (Pac-Man or one of the ghost's death animations), we don't allow the user to pause the game.
                    if self.control.animationInProgress:
                        break

                    paused = self.control.setPause(playerPaused=True) # It calls the game controller's setPause() method.
                    if not paused: # It is flipped or changed from pause to play.
                            self.myTextGame.hideText() # It makes the READYTXT, PAUSETXT, and GAMEOVERTXT invisible.
                            self.showEntities() # It shows Pac-Man and the four ghosts.
                    else:   # It is flipped or changed from play to pause.
                        self.myTextGame.showText(PAUSETXT) # It shows the PAUSED! text.
                        self.hideEntities() # It hides PAC-MAN and the four ghots.

        key_pressed = pygame.key.get_pressed()
        if key_pressed[K_UP]:
            self.KEYUP = True
        if key_pressed[K_DOWN]:
            self.KEYDOWN = True
        if key_pressed[K_LEFT]:
            self.KEYLEFT = True
        if key_pressed[K_RIGHT]:
            self.KEYRIGHT = True
            
    def checkPelletEvents(self):
    """ It checks events related to the pellets. More specifically, it checks if our hero Pac-Man has eaten any pellets."""
        pellet = self.pacman.eatPellets()
        if pellet:
        # If Pac-Man has eaten a pellet, then it increases the player's score and removes it from the game's pellets object.
            self.updateScore(pellet.points)
            self.pellets.removePellet(pellet)
            # If the pellet is a power pellet, it calls the game controller's startFreight() method. The game controller's object will, in turn, update all the game's ghosts with the new status.
            if pellet.isPowerPellet():
               self.control.startFreight()
            
    def reset_keys(self): # Reset all the keys
        self.KEYUP = self.KEYLEFT = self.KEYRIGHT = self.KEYDOWN = False
 
    def restartGame(self):
        """ It restarts the game."""
        self.lives = NUM_LIVES
        self.control.setPause(playerPaused=False) # The game is paused waiting for the player to press the space bar.
        self.fruit = None
        self.startGame()
        self.level = 0 # It needs to reset the level and the score.
        self.score = 0
        self.myTextGame.updateScore(self.score) # It updates the score and the level texts with the new values.
        self.myTextGame.updateLevel(self.level)
        self.myTextGame.showText(READYTXT) # It shows the text READY! in the middle of the screen.
        self.lifesprites.resetLives(self.lives) # It resets the number of images to be shown in the bottom left corner of the screen corresponding to Pac-Man's lives.

    def resetLevel(self):
        self.control.setPause(playerPaused=False) # The game is paused waiting for the player to press the space bar.
        self.showEntities()  
        self.pacman = Pacman(self) # It creates new instances of Pac-Man, the ghosts, GameControl, and if there was a fruit, it destroys it.
        self.ghosts = Ghosts(self.myMaze.nodes[self.homekey], self.myMaze, self.pacman)
        self.control = GameControl(self.ghosts)
        self.fruit = None
        self.myTextGame.showText(READYTXT) # It shows the text READY! in the middle of the screen.

    def nextLevel(self):
        self.showEntities()
        self.level += 1 # It updates the level.
        self.control.setPause(playerPaused=False) # The game is paused waiting for the player to press the space bar.
        self.startGame() # It starts a new game.
        self.myTextGame.updateLevel(self.level) # It updates the level text with the new value.
        self.myTextGame.showText(READYTXT) # It shows the text READY! in the middle of the screen.
    
if __name__ == '__main__':
    game = Game() # The main function is quite simple. We create an instance of our Game class.
    game.startGame() # It initializes our game instance.
    while True:
        game.update() # It will run an infinite game loop.

Settings

# General settings
TILEWIDTH = 30
TILEHEIGHT = 30
NROWS = 36
NCOLS = 28
FPS = 30
SCREENWIDTH = NCOLS*TILEWIDTH
SCREENHEIGHT = NROWS*TILEHEIGHT
BASETILEWIDTH = 32
BASETILEHEIGHT = 32
THRESHOLD = 0.000001
NUM_LIVES = 5

# Colors
BLACK = (0, 0, 0)
YELLOW = (255, 255, 0)
WHITE = (255, 255, 255)
RED = (255, 0, 0)
PINK = (255,100,150)
TEAL = (100,255,255)
ORANGE = (230,190,40)
GREEN = (0, 255, 0)

STOP = 0
UP = 1
DOWN = -1
LEFT = 2
RIGHT = -2
PORTAL = 3

# Ghost and PacMan settings
SCATTER = 0
CHASE = 1
FREIGHT = 2
DEATH = 3
ZOMBIE = 4

PACMAN = 0
PELLET = 1
POWERPELLET = 2
GHOST = 3
BLINKY = 4
PINKY = 5
INKY = 6
CLYDE = 7
FRUIT = 8

SCATTER_TIME = 7
CHASE_TIME = 20
FREIGHT_TIME = 10

# Text settings
SCORETXT = 0
LEVELTXT = 1
READYTXT = 2
PAUSETXT = 3
GAMEOVERTXT = 4
SCORELABEL = 5
LEVELLABEL = 6

# Animation settings
SPEEDANIMATION = 0.2
SUPERSPEEDANIMATION = 0.1

# Game's mode. It select the ghosts' AI.
CLASSSIC_MODE = 0
ADVANCED_MODE = 1
STUPID_MODE = 2
GAME_MODE = ADVANCED_MODE

# Maze settings
MAZEFILE="myMaze.txt"
MAZEROTATION="myMazeRotation.txt"
HOMEMAZE = [['X','X','+','X','X'], 
['X','X','.','X','X'],
['+','X','.','X','+'],
['+','.','+','.','+'], 
['+','X','X','X','+']]
HOMEGHOSTS = (11.5, 14)
SPAWNGHOST = (2+11.5, 3+14)
CONNECTLEFT = (12, 14)
CONNECTRIGHT = (15, 14)

# Entity settings
NORMAL_SPEED = 0
LOW_SPEED = 1
SUPER_SPEED = 2

# Pause settings
BIG_PAUSE = 3
SMALL_PAUSE = 1

# Fruit settings
FIRST_FRUIT = 40
SECOND_FRUIT = 150
LIFESPAN_FRUIT = 15
Bitcoin donation

JustToThePoint Copyright © 2011 - 2022 PhD. Máximo Núñez Alarcón, Anawim. ALL RIGHTS RESERVED. Bilingual e-books, articles, and videos to help your child and your entire family succeed, develop a healthy lifestyle, and have a lot of fun.

This website uses cookies to improve your navigation experience.
By continuing, you are consenting to our use of cookies, in accordance with our Cookies Policy and Website Terms and Conditions of use.