cell.py
import pygame class Cell(pygame.sprite.Sprite): def __init__(self, row, col, length, width): super().__init__() self.width = length self.height = width self.id = (row, col) self.abs_x = row * self.width self.abs_y = col * self.height self.rect = pygame.Rect(self.abs_x,self.abs_y,self.width,self.height) self.occupying_piece = None def update(self, screen): pygame.draw.rect(screen, pygame.Color("blue2"), self.rect)berry.py
import pygame from settings import CHAR_SIZE, PLAYER_SPEED class Berry(pygame.sprite.Sprite): def __init__(self, row, col, size, is_power_up = False): super().__init__() self.power_up = is_power_up self.size = size self.color = pygame.Color("violetred") self.thickness = size self.abs_x = (row * CHAR_SIZE) + (CHAR_SIZE // 2) self.abs_y = (col * CHAR_SIZE) + (CHAR_SIZE // 2) # temporary rect for colliderect-checking self.rect = pygame.Rect(self.abs_x,self.abs_y, self.size * 2, self.size * 2) def update(self, screen): self.rect = pygame.draw.circle(screen, self.color, (self.abs_x, self.abs_y), self.size, self.thickness)main.py
import pygame, sys from settings import WIDTH, HEIGHT, NAV_HEIGHT from world import World pygame.init() screen = pygame.display.set_mode((WIDTH, HEIGHT + NAV_HEIGHT)) pygame.display.set_caption("PacMan") class Main: def __init__(self, screen): self.screen = screen self.FPS = pygame.time.Clock() def main(self): world = World(self.screen) while True: self.screen.fill("black") for event in pygame.event.get(): if event.type == pygame.QUIT: pygame.quit() sys.exit() world.update() pygame.display.update() self.FPS.tick(30) if __name__ == "__main__": play = Main(screen) play.main()settings.py
MAP = [ ['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'], ['1',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','1'], ['1','B','1','1',' ','1','1','1',' ','1',' ','1','1','1',' ','1','1','B','1'], ['1',' ',' ',' ',' ','1',' ',' ',' ','1',' ',' ',' ','1',' ',' ',' ',' ','1'], ['1','1',' ','1',' ','1',' ','1',' ','1',' ','1',' ','1',' ','1',' ','1','1'], ['1',' ',' ','1',' ',' ',' ','1',' ',' ',' ','1',' ',' ',' ','1',' ',' ','1'], ['1',' ','1','1','1','1',' ','1','1','1','1','1',' ','1','1','1','1',' ','1'], ['1',' ',' ',' ',' ',' ',' ',' ',' ','r',' ',' ',' ',' ',' ',' ',' ',' ','1'], ['1','1',' ','1','1','1',' ','1','1','-','1','1',' ','1','1','1',' ','1','1'], [' ',' ',' ',' ',' ','1',' ','1','s','p','o','1',' ','1',' ',' ',' ',' ',' '], ['1','1',' ','1',' ','1',' ','1','1','1','1','1',' ','1',' ','1',' ','1','1'], ['1',' ',' ','1',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','1',' ',' ','1'], ['1',' ','1','1','1','1',' ','1','1','1','1','1',' ','1','1','1','1',' ','1'], ['1',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','1'], ['1','1','1',' ','1','1','1',' ','1','1','1',' ','1','1','1',' ','1','1','1'], ['1',' ',' ',' ','1',' ',' ',' ',' ','P',' ',' ',' ',' ','1',' ',' ',' ','1'], ['1','B','1',' ','1',' ','1',' ','1','1','1',' ','1',' ','1',' ','1','B','1'], ['1',' ','1',' ',' ',' ','1',' ',' ',' ',' ',' ','1',' ',' ',' ','1',' ','1'], ['1',' ','1','1','1',' ','1','1','1',' ','1','1','1',' ','1','1','1',' ','1'], ['1',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ','1'], ['1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1','1'] ] BOARD_RATIO = (len(MAP[0]), len(MAP)) CHAR_SIZE = 32 WIDTH, HEIGHT = (BOARD_RATIO[0] * CHAR_SIZE, BOARD_RATIO[1] * CHAR_SIZE) NAV_HEIGHT = 64 PLAYER_SPEED = CHAR_SIZE // 4 GHOST_SPEED = 4animation.py
from os import walk import pygame def import_sprite(path): surface_list = [] for _, __, img_file in walk(path): for image in img_file: full_path = f"{path}/{image}" img_surface = pygame.image.load(full_path).convert_alpha() surface_list.append(img_surface) return surface_listghost.py
import pygame import random import time from settings import WIDTH, CHAR_SIZE, GHOST_SPEED class Ghost(pygame.sprite.Sprite): def __init__(self, row, col, color): super().__init__() self.abs_x = (row * CHAR_SIZE) self.abs_y = (col * CHAR_SIZE) self.rect = pygame.Rect(self.abs_x, self.abs_y, CHAR_SIZE, CHAR_SIZE) self.move_speed = GHOST_SPEED self.color = pygame.Color(color) self.move_directions = [(-1,0), (0,-1), (1,0), (0,1)] self.moving_dir = "up" self.img_path = f'assets/ghosts/{color}/' self.img_name = f'{self.moving_dir}.png' self.image = pygame.image.load(self.img_path + self.img_name) self.image = pygame.transform.scale(self.image, (CHAR_SIZE, CHAR_SIZE)) self.rect = self.image.get_rect(topleft = (self.abs_x, self.abs_y)) self.mask = pygame.mask.from_surface(self.image) self.directions = {'left': (-self.move_speed, 0), 'right': (self.move_speed, 0), 'up': (0, -self.move_speed), 'down': (0, self.move_speed)} self.keys = ['left', 'right', 'up', 'down'] self.direction = (0, 0) def move_to_start_pos(self): self.rect.x = self.abs_x self.rect.y = self.abs_y def is_collide(self, x, y, walls_collide_list): tmp_rect = self.rect.move(x, y) if tmp_rect.collidelist(walls_collide_list) == -1: return False return True def _animate(self): self.img_name = f'{self.moving_dir}.png' self.image = pygame.image.load(self.img_path + self.img_name) self.image = pygame.transform.scale(self.image, (CHAR_SIZE, CHAR_SIZE)) self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y)) def update(self, walls_collide_list): # ghost movement available_moves = [] for key in self.keys: if not self.is_collide(*self.directions[key], walls_collide_list): available_moves.append(key) randomizing = False if len(available_moves) <= 2 and self.direction != (0,0) else True # 60% chance of randomizing ghost move if randomizing and random.randrange( 0,100 ) <= 60: self.moving_dir = random.choice(available_moves) self.direction = self.directions[self.moving_dir] if not self.is_collide(*self.direction, walls_collide_list): self.rect.move_ip(self.direction) else: self.direction = (0,0) # teleporting to the other side of the map if self.rect.right <= 0: self.rect.x = WIDTH elif self.rect.left >= WIDTH: self.rect.x = 0 self._animate()display.py
import pygame from settings import WIDTH, HEIGHT, CHAR_SIZE pygame.font.init() class Display: def __init__(self, screen): self.screen = screen self.font = pygame.font.SysFont("ubuntumono", CHAR_SIZE) self.game_over_font = pygame.font.SysFont("dejavusansmono", 48) self.text_color = pygame.Color("crimson") def show_life(self, life): img_path = "assets/life/life.png" life_image = pygame.image.load(img_path) life_image = pygame.transform.scale(life_image, (CHAR_SIZE, CHAR_SIZE)) life_x = CHAR_SIZE // 2 if life != 0: for life in range(life): self.screen.blit(life_image, (life_x, HEIGHT + (CHAR_SIZE // 2))) life_x += CHAR_SIZE def show_level(self, level): level_x = WIDTH // 3 level = self.font.render(f'Level {level}', True, self.text_color) self.screen.blit(level, (level_x, (HEIGHT + (CHAR_SIZE // 2)))) def show_score(self, score): score_x = WIDTH // 3 score = self.font.render(f'{score}', True, self.text_color) self.screen.blit(score, (score_x * 2, (HEIGHT + (CHAR_SIZE // 2)))) # add game over message def game_over(self): message = self.game_over_font.render(f'GAME OVER!!', True, pygame.Color("chartreuse")) instruction = self.font.render(f'Press "R" to Restart', True, pygame.Color("aqua")) self.screen.blit(message, ((WIDTH // 4), (HEIGHT // 3))) self.screen.blit(instruction, ((WIDTH // 4), (HEIGHT // 2)))world.py
import pygame import time from settings import HEIGHT, WIDTH, NAV_HEIGHT, CHAR_SIZE, MAP, PLAYER_SPEED from pac import Pac from cell import Cell from berry import Berry from ghost import Ghost from display import Display class World: def __init__(self, screen): self.screen = screen self.player = pygame.sprite.GroupSingle() self.ghosts = pygame.sprite.Group() self.walls = pygame.sprite.Group() self.berries = pygame.sprite.Group() self.display = Display(self.screen) self.game_over = False self.reset_pos = False self.player_score = 0 self.game_level = 1 self._generate_world() # create and add player to the screen def _generate_world(self): # renders obstacle from the MAP table for y_index, col in enumerate(MAP): for x_index, char in enumerate(col): if char == "1": # for walls self.walls.add(Cell(x_index, y_index, CHAR_SIZE, CHAR_SIZE)) elif char == " ": # for paths to be filled with berries self.berries.add(Berry(x_index, y_index, CHAR_SIZE // 4)) elif char == "B": # for big berries self.berries.add(Berry(x_index, y_index, CHAR_SIZE // 2, is_power_up=True)) # for Ghosts's starting position elif char == "s": self.ghosts.add(Ghost(x_index, y_index, "skyblue")) elif char == "p": self.ghosts.add(Ghost(x_index, y_index, "pink")) elif char == "o": self.ghosts.add(Ghost(x_index, y_index, "orange")) elif char == "r": self.ghosts.add(Ghost(x_index, y_index, "red")) elif char == "P": # for PacMan's starting position self.player.add(Pac(x_index, y_index)) self.walls_collide_list = [wall.rect for wall in self.walls.sprites()] def generate_new_level(self): for y_index, col in enumerate(MAP): for x_index, char in enumerate(col): if char == " ": # for paths to be filled with berries self.berries.add(Berry(x_index, y_index, CHAR_SIZE // 4)) elif char == "B": # for big berries self.berries.add(Berry(x_index, y_index, CHAR_SIZE // 2, is_power_up=True)) time.sleep(2) def restart_level(self): self.berries.empty() [ghost.move_to_start_pos() for ghost in self.ghosts.sprites()] self.game_level = 1 self.player.sprite.pac_score = 0 self.player.sprite.life = 3 self.player.sprite.move_to_start_pos() self.player.sprite.direction = (0, 0) self.player.sprite.status = "idle" self.generate_new_level() # displays nav def _dashboard(self): nav = pygame.Rect(0, HEIGHT, WIDTH, NAV_HEIGHT) pygame.draw.rect(self.screen, pygame.Color("cornsilk4"), nav) self.display.show_life(self.player.sprite.life) self.display.show_level(self.game_level) self.display.show_score(self.player.sprite.pac_score) def _check_game_state(self): # checks if game over if self.player.sprite.life == 0: self.game_over = True # generates new level if len(self.berries) == 0 and self.player.sprite.life > 0: self.game_level += 1 for ghost in self.ghosts.sprites(): ghost.move_speed += self.game_level ghost.move_to_start_pos() self.player.sprite.move_to_start_pos() self.player.sprite.direction = (0, 0) self.player.sprite.status = "idle" self.generate_new_level() def update(self): if not self.game_over: # player movement pressed_key = pygame.key.get_pressed() self.player.sprite.animate(pressed_key, self.walls_collide_list) # teleporting to the other side of the map if self.player.sprite.rect.right <= 0: self.player.sprite.rect.x = WIDTH elif self.player.sprite.rect.left >= WIDTH: self.player.sprite.rect.x = 0 # PacMan eating-berry effect for berry in self.berries.sprites(): if self.player.sprite.rect.colliderect(berry.rect): if berry.power_up: self.player.sprite.immune_time = 150 # Timer based from FPS count self.player.sprite.pac_score += 50 else: self.player.sprite.pac_score += 10 berry.kill() # PacMan bumping into ghosts for ghost in self.ghosts.sprites(): if self.player.sprite.rect.colliderect(ghost.rect): if not self.player.sprite.immune: time.sleep(2) self.player.sprite.life -= 1 self.reset_pos = True break else: ghost.move_to_start_pos() self.player.sprite.pac_score += 100 self._check_game_state() # rendering [wall.update(self.screen) for wall in self.walls.sprites()] [berry.update(self.screen) for berry in self.berries.sprites()] [ghost.update(self.walls_collide_list) for ghost in self.ghosts.sprites()] self.ghosts.draw(self.screen) self.player.update() self.player.draw(self.screen) self.display.game_over() if self.game_over else None self._dashboard() # reset Pac and Ghosts position after PacMan get captured if self.reset_pos and not self.game_over: [ghost.move_to_start_pos() for ghost in self.ghosts.sprites()] self.player.sprite.move_to_start_pos() self.player.sprite.status = "idle" self.player.sprite.direction = (0,0) self.reset_pos = False # for restart button if self.game_over: pressed_key = pygame.key.get_pressed() if pressed_key[pygame.K_r]: self.game_over = False self.restart_level()pac.py
import pygame from settings import CHAR_SIZE, PLAYER_SPEED from animation import import_sprite class Pac(pygame.sprite.Sprite): def __init__(self, row, col): super().__init__() self.abs_x = (row * CHAR_SIZE) self.abs_y = (col * CHAR_SIZE) # pac animation self._import_character_assets() self.frame_index = 0 self.animation_speed = 0.5 self.image = self.animations["idle"][self.frame_index] self.rect = self.image.get_rect(topleft = (self.abs_x, self.abs_y)) self.mask = pygame.mask.from_surface(self.image) self.pac_speed = PLAYER_SPEED self.immune_time = 0 self.immune = False self.directions = {'left': (-PLAYER_SPEED, 0), 'right': (PLAYER_SPEED, 0), 'up': (0, -PLAYER_SPEED), 'down': (0, PLAYER_SPEED)} self.keys = {'left': pygame.K_LEFT, 'right': pygame.K_RIGHT, 'up': pygame.K_UP, 'down': pygame.K_DOWN} self.direction = (0, 0) # pac status self.status = "idle" self.life = 3 self.pac_score = 0 # gets all the image needed for animating specific player action def _import_character_assets(self): character_path = "assets/pac/" self.animations = { "up": [], "down": [], "left": [], "right": [], "idle": [], "power_up": [] } for animation in self.animations.keys(): full_path = character_path + animation self.animations[animation] = import_sprite(full_path) def _is_collide(self, x, y): tmp_rect = self.rect.move(x, y) if tmp_rect.collidelist(self.walls_collide_list) == -1: return False return True def move_to_start_pos(self): self.rect.x = self.abs_x self.rect.y = self.abs_y # update with sprite/sheets def animate(self, pressed_key, walls_collide_list): animation = self.animations[self.status] # loop over frame index self.frame_index += self.animation_speed if self.frame_index >= len(animation): self.frame_index = 0 image = animation[int(self.frame_index)] self.image = pygame.transform.scale(image, (CHAR_SIZE, CHAR_SIZE)) self.walls_collide_list = walls_collide_list for key, key_value in self.keys.items(): if pressed_key[key_value] and not self._is_collide(*self.directions[key]): self.direction = self.directions[key] self.status = key if not self.immune else "power_up" break if not self._is_collide(*self.direction): self.rect.move_ip(self.direction) self.status = self.status if not self.immune else "power_up" if self._is_collide(*self.direction): self.status = "idle" if not self.immune else "power_up" def update(self): # Timer based from FPS count self.immune = True if self.immune_time > 0 else False self.immune_time -= 1 if self.immune_time > 0 else 0 self.rect = self.image.get_rect(topleft=(self.rect.x, self.rect.y))Join 50,000+ Python Programmers & Enthusiasts like you!
Email address Subscribe This site is protected by reCAPTCHA and the Google Privacy Policy and Terms of Service apply.Ethical Hacking with Python EBook
Web Security with Python EBook
Cryptography with Python EBook
Practical Python PDF Processing EBook
Real-Time Traffic Monitoring System with YOLOv9 eBook
Mastering YOLO: Build an Automatic Number Plate Recognition System
© 2026 The Python Code. All rights reserved.