title: PacMan Ghost Movement
body: <p>So I've been trying to recreate the <a href="https://en.wikipedia.org/wiki/Pac-Man" rel="nofollow noreferrer">PacMan</a> Game, I have been stuck on how to approach the idea of the Ghosts moving around the maze, I've heard of A* and Dijkstra's Algorithms, but is there a simpler way to implement ghosts moving around the maze? Aside from figuring out the different modes they can go into, Frightened, Chase, and Scatter, I just want to be able to understand whats the best way to get them to move randomly in the maze with the wall detection function in place.</p>
  <pre><code>import pygame
  import time
  #import random
  import pickle
  import math
  
  pygame.init()
  pygame.mixer.init()
  pygame.display.set_caption(&quot;Pac-Man&quot;)
  
  # Sets the size of the screen via (WIDTH, HEIGHT)
  SCREEN_WIDTH = 478
  SCREEN_HEIGHT = 608
  # Speed of Characters
  SPEED = 1
  # Frames per second, how fast the game runs
  FPS = 50
  # Colors (RED,GREEN,BLUE)
  BLACK = (0, 0, 0)
  WHITE = (255, 255, 255)
  YELLOW = (255, 255, 0)
  BLUE = (0, 0, 255)
  
  # Sets the WIDTH and HEIGHT of the window
  WINDOW = (SCREEN_WIDTH, SCREEN_HEIGHT)
  # Displays the screen
  SCREEN = pygame.display.set_mode(WINDOW)
  CLOCK = pygame.time.Clock()
  
  PacManStartSurface = pygame.transform.scale(pygame.image.load 
                                             (&quot;PacManStart.png&quot;), (23, 23))
  PacManStartSurface.convert()
  PacManStartRect = PacManStartSurface.get_rect(topleft = 
                                               (((SCREEN_WIDTH - 25) // 2),
                                               (SCREEN_HEIGHT + 144) // 2))
  
  PacManSurface = pygame.transform.scale(pygame.image.load 
                                        (&quot;PacManRight.png&quot;), (23, 23))
  PacManSurface.convert()
  PacManRect = PacManStartSurface.get_rect(topleft = 
                                          (((SCREEN_WIDTH - 125) // 2),
                                          (SCREEN_HEIGHT + 144) // 2))
  
  CurrentSurface = PacManStartSurface
  CurrentRect = PacManStartRect 
  
  BackgroundSurface = pygame.image.load(&quot;Background.png&quot;).convert()
  
  PinkGhostSurface = pygame.transform.scale(pygame.image.load(&quot;PinkGhost.png&quot;)
                                             .convert(), (23, 23))
  PinkGhostRect = PinkGhostSurface.get_rect()
  YellowGhostSurface = pygame.transform.scale(pygame.image.load
                                             (&quot;YellowGhost.png&quot;)
                                             .convert(), (23, 23))
  YellowGhostRect = YellowGhostSurface.get_rect()
  RedGhostSurface = pygame.transform.scale(pygame.image.load(&quot;RedGhost.png&quot;)
                                           .convert(), (23, 23))
  RedGhostRect = RedGhostSurface.get_rect()
  BlueGhostSurface = pygame.transform.scale(pygame.image.load(&quot;BlueGhost.png&quot;)
                                            .convert(), (23, 23))
  BlueGhostRect = BlueGhostSurface.get_rect()
  
  pygame.mixer.music.load('power_pellet.wav')
  
  Font = pygame.font.Font(&quot;emulogic.ttf&quot;, 15)
  
  class PacMan():
      def __init__(self):
          self.LIVES = 3
          
  class Maze():
      def __init__(self):
          self.DOTS = []
          self.WALLS = []
          self.ENERGIZER = []
          self.GHOSTS = []
          self.DECISION_NODES = []
          self.BLOCK_WIDTH = 25
          self.BLOCK_HEIGHT = 25
          self.MAZE_OFFSET_X = 0
          self.MAZE_OFFSET_Y = 50
          self.MARGIN = 3
          # 0 - Dots
          # 1 - Walls
          # 2 - Energizers
          # 3 - Empty Spaces
          # 4 - Ghosts
          # 5 - Decision Nodes &amp; will be added for intersections in maze
          self.MATRIX = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1], \
                        [1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1], \
                        [1,2,1,1,0,1,1,1,0,1,0,1,1,1,0,1,1,2,1], \
                        [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], \
                        [1,0,1,1,0,1,0,1,1,1,1,1,0,1,0,1,1,0,1], \
                        [1,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,1], \
                        [1,1,1,1,0,1,1,1,3,1,3,1,1,1,0,1,1,1,1], \
                        [3,3,3,1,0,1,3,3,5,4,5,3,3,1,0,1,3,3,3], \
                        [1,1,1,1,0,1,3,1,1,1,1,1,3,1,0,1,1,1,1], \
                        [0,0,0,0,0,3,5,1,4,4,4,1,5,3,0,0,0,0,0], \
                        [1,1,1,1,0,1,3,1,1,1,1,1,3,1,0,1,1,1,1], \
                        [3,3,3,1,0,1,5,3,3,3,3,3,5,1,0,1,3,3,3], \
                        [1,1,1,1,0,1,3,1,1,1,1,1,3,1,0,1,1,1,1], \
                        [1,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,1], \
                        [1,2,1,1,0,1,1,1,0,1,0,1,1,1,0,1,1,2,1], \
                        [1,0,0,1,0,0,0,0,0,3,0,0,0,0,0,1,0,0,1], \
                        [1,1,0,1,0,1,0,1,1,1,1,1,0,1,0,1,0,1,1], \
                        [1,0,0,0,0,1,0,0,0,1,0,0,0,1,0,0,0,0,1], \
                        [1,0,1,1,1,1,1,1,0,1,0,1,1,1,1,1,1,0,1], \
                        [1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1], \
                        [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
                        
          # BackgroundImage(X, Y, WIDTH, HEIGHT)
          self.MAZE_X = self.BLOCK_WIDTH * (len(self.MATRIX[0]) 
                        + self.MAZE_OFFSET_X)
          self.MAZE_Y = self.BLOCK_HEIGHT * (len(self.MATRIX)
                        + self.MAZE_OFFSET_Y)
          self.MAZE_WIDTH = self.BLOCK_WIDTH * len(self.MATRIX[0])
          self.MAZE_HEIGHT = self.BLOCK_HEIGHT * len(self.MATRIX) 
  
      def DrawMaze(self, MazeSurface):
          for ROW in range(len(self.MATRIX)):
              for COLUMN in range(len(self.MATRIX[0])):
                  # Only saves the position of each dot
                  if self.MATRIX[ROW][COLUMN] == 0:
                      self.DOTS.append([(self.BLOCK_WIDTH * COLUMN),
                                      (self.BLOCK_HEIGHT * ROW), 4, 4])
                  if self.MATRIX[ROW][COLUMN] == 1:
                      self.WALLS.append(pygame.draw.rect(MazeSurface, WHITE,
                                       [((self.BLOCK_WIDTH) * COLUMN),
                                       ((self.BLOCK_HEIGHT) * ROW), 
                                       self.BLOCK_WIDTH, self.BLOCK_HEIGHT]))
                  if self.MATRIX[ROW][COLUMN] == 2:
                      self.ENERGIZER.append([(self.BLOCK_WIDTH * COLUMN),
                                           (self.BLOCK_HEIGHT * ROW), 14, 14])
                  if self.MATRIX[ROW][COLUMN] == 4:
                      self.GHOSTS.append([(self.BLOCK_WIDTH * COLUMN), 
                                        (self.BLOCK_HEIGHT * ROW), 23, 23])
                  if self.MATRIX[ROW][COLUMN] == 5:
                      self.DECISION_NODES.append([(self.BLOCK_WIDTH * COLUMN), 
                                                (self.BLOCK_HEIGHT * ROW), 4, 4])  
           
  class Main(Maze):
      def __init__(self):
          # Inherits Maze class
          Maze.__init__(self)
          self.TimeBetweenBites = 0.1
          self.LastBiteTime = time.time()
          self.MouthOpen = False
          self.PacManDirection = &quot;&quot;
          self.GhostDirection = &quot;&quot;
          self.PreviousGhostDirection = &quot;&quot;
          self.SCORE = 0
          self.HIGH_SCORE = 0
          
      def PacManMovement(self):
          key = pygame.key.get_pressed()
          if key[pygame.K_LEFT] and not key[pygame.K_UP] \
                                and not key[pygame.K_DOWN]:
              self.PacManDirection = &quot;LEFT&quot;
          elif key[pygame.K_RIGHT] and not key[pygame.K_UP] \
                                 and not key[pygame.K_DOWN]:
              self.PacManDirection = &quot;RIGHT&quot;
          elif key[pygame.K_UP] and not key[pygame.K_LEFT] \
                              and not key[pygame.K_RIGHT]:
              self.PacManDirection = &quot;UP&quot;
          elif key[pygame.K_DOWN] and not key[pygame.K_LEFT] \
                                and not key[pygame.K_RIGHT]:
              self.PacManDirection = &quot;DOWN&quot;
          
      def ContinuePacManMovement(self):
          if self.PacManDirection == &quot;LEFT&quot;:
              CurrentRect.x -= SPEED
              self.PacManWallDetection(-1, 0, CurrentRect)
          if self.PacManDirection == &quot;RIGHT&quot;:
              CurrentRect.x += SPEED
              self.PacManWallDetection(1, 0, CurrentRect)
          if self.PacManDirection == &quot;UP&quot;:
              CurrentRect.y -= SPEED
              self.PacManWallDetection(0, -1, CurrentRect)
          if self.PacManDirection == &quot;DOWN&quot;:
              CurrentRect.y += SPEED
              self.PacManWallDetection(0, 1, CurrentRect)
              
      def PacManTeleport(self):
          if CurrentRect.right &lt; 0:
              CurrentRect.right = SCREEN_WIDTH + 20
          if CurrentRect.left &gt; SCREEN_WIDTH:
              CurrentRect.right = 0
      
      def GhostTeleport(self):
          if PinkGhostRect.right &lt; 0:
              PinkGhostRect.right = SCREEN_WIDTH + 20
          if PinkGhostRect.left &gt; SCREEN_WIDTH:
              PinkGhostRect.right = 0
              
      def PacManWallDetection(self, x, y, CurrentRect):
          CurrentRect.right += x
          for WALL in self.WALLS:
              COLLIDE = CurrentRect.colliderect(WALL)
              if COLLIDE:
                  if x &lt; 0: 
                      CurrentRect.left = WALL.right
                      CurrentSurface = pygame.transform.rotate(PacManSurface, 180)
                      MazeSurface.blit(CurrentSurface, CurrentRect)
                  if x &gt; 0:
                      CurrentRect.right = WALL.left
                  break
          
          CurrentRect.top += y
          for WALL in self.WALLS:
              COLLIDE = CurrentRect.colliderect(WALL)
              if COLLIDE:
                  if y &lt; 0:
                      CurrentRect.top = WALL.bottom
                  if y &gt; 0:
                      CurrentRect.bottom = WALL.top
                  break
              
      def GhostWallDetection(self, x, y, PinkGhostRect):
          PinkGhostRect.right += x
          for WALL in self.WALLS:
              COLLIDE = PinkGhostRect.colliderect(WALL)
              if COLLIDE:
                  if x &lt; 0:
                      PinkGhostRect.left = WALL.right
                  if x &gt; 0:
                      PinkGhostRect.right = WALL.left
                  break
          
          PinkGhostRect.top += y
          for WALL in self.WALLS:
              COLLIDE = PinkGhostRect.colliderect(WALL)
              if COLLIDE:
                  if y &lt; 0:
                      PinkGhostRect.top = WALL.bottom
                  if y &gt; 0:
                      PinkGhostRect.bottom = WALL.top 
                  break
              
      def EatDots(self):
          for ROW in range(len(self.MATRIX)):
              for COLUMN in range(len(self.MATRIX[0])):
                  for DOT in self.DOTS:
                      CHOMP = CurrentRect.colliderect(DOT)
                      if CHOMP:
                          Main.PlaySound(self, 0)
                          self.DOTS.remove(DOT)
                          self.MATRIX[ROW][COLUMN] = 3
                          self.SCORE += 10
          if self.SCORE &gt; self.HIGH_SCORE:
              self.HIGH_SCORE = self.SCORE
          return str(self.SCORE), str(self.HIGH_SCORE)
      
      def EatEnergizer(self):
          for ROW in range(len(self.MATRIX)):
              for COLUMN in range(len(self.MATRIX[0])):
                  for POWERUP in self.ENERGIZER:
                      CHOMP = CurrentRect.colliderect(POWERUP)
                      if CHOMP:
                          self.ENERGIZER.remove(POWERUP)
                          self.MATRIX[ROW][COLUMN] = 3
                          self.SCORE += 50
                          Main.PlaySound(self, 1)
          if self.SCORE &gt; self.HIGH_SCORE:
              self.HIGH_SCORE = self.SCORE
          return str(self.SCORE), str(self.HIGH_SCORE)
      
      def EatGhosts(self):
          pass
                  
      def DrawDots(self):
          for POSITION in self.DOTS:
              X = POSITION[0] + 13
              Y = POSITION[1] + 13
              WIDTH = POSITION[2]
              HEIGHT = POSITION[3]
              pygame.draw.circle(MazeSurface, YELLOW, (X, Y), 
                                 WIDTH // 2, HEIGHT // 2)
              
      def DrawEnergizer(self):
          for POSITION in self.ENERGIZER:
              X = POSITION[0] + 13
              Y = POSITION[1] + 13
              WIDTH = POSITION[2] 
              HEIGHT = POSITION[3] 
              pygame.draw.circle(MazeSurface, YELLOW, (X, Y), 
                                 WIDTH // 2, HEIGHT // 2)
  
      def DrawGhosts(self):        
          MazeSurface.blit(PinkGhostSurface, PinkGhostRect) 
          MazeSurface.blit(YellowGhostSurface, YellowGhostRect) 
          MazeSurface.blit(RedGhostSurface, RedGhostRect) 
          MazeSurface.blit(BlueGhostSurface, BlueGhostRect) 
          
      def GhostPosition(self):
          X, Y, WIDTH, HEIGHT = self.GHOSTS[0]
          PinkGhostRect.x = X
          PinkGhostRect.y = Y
          PinkGhostRect.width = WIDTH
          PinkGhostRect.height = HEIGHT
          
          X, Y, WIDTH, HEIGHT = self.GHOSTS[1]
          YellowGhostRect.x = X
          YellowGhostRect.y = Y
          YellowGhostRect.width = WIDTH
          YellowGhostRect.height = HEIGHT
          
          X, Y, WIDTH, HEIGHT = self.GHOSTS[2]
          RedGhostRect.x = X
          RedGhostRect.y = Y
          RedGhostRect.width = WIDTH
          RedGhostRect.height = HEIGHT
          
          X, Y, WIDTH, HEIGHT = self.GHOSTS[3]
          BlueGhostRect.x = X
          BlueGhostRect.y = Y
          BlueGhostRect.width = WIDTH
          BlueGhostRect.height = HEIGHT
      
      def ChaseMode(self):
          self.GhostDirection = &quot;LEFT&quot;
          self.GhostWallDetection(-1, 0, PinkGhostRect)
          if PinkGhostRect.x &lt; CurrentRect.x:
              self.GhostDirection = &quot;RIGHT&quot;
              self.GhostWallDetection(1, 0, PinkGhostRect)
          if PinkGhostRect.y &gt; CurrentRect.y:
              self.GhostDirection = &quot;UP&quot;
              self.GhostWallDetection(0, -1, PinkGhostRect)
          if PinkGhostRect.y &lt; CurrentRect.y:
              self.GhostDirection = &quot;DOWN&quot;
              self.GhostWallDetection(0, 1, PinkGhostRect)
      
      def ScatterMode(self):
          pass
      
      def FrightenedMode(self):
          pass
          
      def PlaySound(self, Track):
          if Track == 0:
              Eat = pygame.mixer.Sound(&quot;pacman_chomp.wav&quot;)
              Eat.play()
              pygame.mixer.fadeout(400)
          if Track == 1:
              EatPellet = pygame.mixer.Sound(&quot;pacman_eatghost.wav&quot;)
              EatPellet.play()
              pygame.mixer.music.play(7)
              pygame.mixer.fadeout(400)
                  
      def ShowScore(self):
          global Font
          OneUpText = Font.render(&quot;1UP&quot;, True, WHITE)
          OneUpTextRect = OneUpText.get_rect(center = (70, 10))
          # Displays current score
          OneUpScoreText = Font.render(str(self.SCORE), True, WHITE)
          OneUpScoreRect = OneUpScoreText.get_rect(center =
                                                  ((SCREEN_WIDTH - 290) 
                                                  // 2, 26))
          HighScoreText = Font.render(&quot;High Score&quot;, True, WHITE)
          HighScoreTextRect = HighScoreText.get_rect(center = 
                                                    (SCREEN_WIDTH // 2, 10))
          # Displays High Score
          HighScoreNumber = Font.render(str(self.HIGH_SCORE), True, WHITE)
          HighScoreNumberRect = HighScoreNumber.get_rect(center = 
                                                        ((SCREEN_WIDTH + 90) 
                                                        // 2, 26))
          SCREEN.blit(OneUpText, OneUpTextRect)
          SCREEN.blit(OneUpScoreText, OneUpScoreRect)
          SCREEN.blit(HighScoreText, HighScoreTextRect)
          SCREEN.blit(HighScoreNumber, HighScoreNumberRect)
          
      def PacManBite(self):
          global CurrentSurface
          CurrentTime = time.time()
          if CurrentTime - self.LastBiteTime &gt;= self.TimeBetweenBites:
              self.LastBiteTime = CurrentTime
              if self.MouthOpen:
                  CurrentSurface = PacManStartSurface
              else:
                  CurrentSurface = PacManSurface
              self.MouthOpen = not self.MouthOpen
              if self.PacManDirection == &quot;LEFT&quot;:
                  CurrentSurface = pygame.transform.rotate(CurrentSurface, 180)
              if self.PacManDirection == &quot;RIGHT&quot;:
                  CurrentSurface = CurrentSurface
              if self.PacManDirection == &quot;UP&quot;:
                  CurrentSurface = pygame.transform.rotate(CurrentSurface, 90)
              if self.PacManDirection == &quot;DOWN&quot;:
                  CurrentSurface = pygame.transform.rotate(CurrentSurface, 270)
      
      def PacManLives(self):
          pass
          
  Player = Main()
  
  BackgroundSurface = pygame.transform.scale(BackgroundSurface, 
                                            (Player.MAZE_WIDTH, 
                                             Player.MAZE_HEIGHT))
  BackgroundRect = BackgroundSurface.get_rect()
  
  MazeSurface = pygame.Surface((Player.MAZE_WIDTH, Player.MAZE_HEIGHT))
  MazeRect = MazeSurface.get_rect(topleft = (Player.MAZE_OFFSET_X, 
                                             Player.MAZE_OFFSET_Y))
  Player.DrawMaze(MazeSurface)
  Player.GhostPosition()
  #Player.GhostMovement()
  
  '''
  Before the game starts ...
  pregame = True
  while pregame:
      if key button pressed:
          pregame = False
      run = True
  '''
  
  run = True
  while run:
      SCREEN.fill(BLACK)
      for event in pygame.event.get():
          if event.type == pygame.QUIT:
              run = False
          if event.type == pygame.KEYDOWN:
              Player.PacManMovement()
              Player.PacManTeleport()   
              
      Player.ContinuePacManMovement()
      MazeSurface.blit(BackgroundSurface, BackgroundRect) 
      Player.DrawDots()
      Player.DrawEnergizer()
      Player.DrawGhosts()
      Player.GhostTeleport()
      Player.EatDots()
      Player.EatEnergizer()
      Player.ChaseMode()
      MazeSurface.blit(CurrentSurface, CurrentRect)
      Player.PacManBite()
      SCREEN.blit(MazeSurface, MazeRect)
      Player.ShowScore()
      pygame.display.update()
      CLOCK.tick(FPS)
      
  pygame.quit()
  </code></pre>