From 7f501d21772d96756a851421690db08387df3c26 Mon Sep 17 00:00:00 2001 From: omagdy7 Date: Sun, 7 May 2023 11:32:10 +0300 Subject: Finished clyde algorithm and added a debug mode to run the program --- src/blinky.py | 4 +- src/clyde.py | 47 ++++++++++++++++++++- src/game.py | 28 ++++++------- src/ghost.py | 37 +++++++++-------- src/inky.py | 55 +++++++------------------ src/map.py | 124 ++++++++++++++++++++++++++++++++++---------------------- src/pinky.py | 25 ++++++------ src/settings.py | 2 + 8 files changed, 185 insertions(+), 137 deletions(-) (limited to 'src') diff --git a/src/blinky.py b/src/blinky.py index eee44a8..4c45644 100644 --- a/src/blinky.py +++ b/src/blinky.py @@ -1,8 +1,6 @@ from ghost import Ghost + class Blinky(Ghost): def __init__(self, sprite_sheet, x, y): super().__init__(sprite_sheet, "red", x, y) - - - diff --git a/src/clyde.py b/src/clyde.py index 117f71a..95de2cb 100644 --- a/src/clyde.py +++ b/src/clyde.py @@ -1,9 +1,54 @@ from ghost import Ghost +import pygame +from settings import settings +from typing_extensions import override +import math + class Clyde(Ghost): def __init__(self, sprite_sheet, x, y): - super().__init__(sprite_sheet, "orange", x, y) + super().__init__(sprite_sheet, "cyan", x, y) + + def is_eight_tiles_away(self, pacman): + tile_width = 30 + dx = self.x - pacman.x + dy = self.y - pacman.y + return math.sqrt(dx * dx + dy * dy) <= tile_width * 8 + + @override + def get_next_move(self, pacman, maze, screen, blinky): + dx = [1, 0, -1, 0] # right, down, left, up + dy = [0, 1, 0, -1] + + inv_dir = [2, 3, 0, 1] + + ret = len(dx) * [math.inf] + bottom_left_corner = (2.5 * 30, (len(maze) - 1 - 1 - 0.5) * 30) + forbidden = inv_dir[self.last_move] + for i in range(len(dx)): + nx = self.x + dx[i] * self.speed + ny = self.y + dy[i] * self.speed + if self.check_collision(nx, ny, 30, 30, maze): + if i != forbidden: + if self.is_eight_tiles_away(pacman): + ret[i] = self.heuristic( + (nx, ny), bottom_left_corner[0], bottom_left_corner[1]) + if settings.debug: + pygame.draw.line(screen, self.color, (bottom_left_corner), + (self.x, self.y), 1) + else: + ret[i] = self.heuristic( + (nx, ny), pacman.x, pacman.y) + if settings.debug: + pygame.draw.line(screen, self.color, (pacman.x, pacman.y), + (self.x, self.y), 1) + min_h = min(ret) + # Favour going up when there is a conflict + if min_h == ret[3] and min_h != math.inf: + return 3 + min_idx = ret.index(min_h) + return min_idx diff --git a/src/game.py b/src/game.py index 5b85c39..5a41f88 100644 --- a/src/game.py +++ b/src/game.py @@ -24,13 +24,15 @@ class Game(): screen = pygame.display.set_mode((WIDTH, HEIGHT)) # Sprite sheet for pacman - sprite_sheet = pygame.image.load( '../assets/pacman_left_sprite.png').convert_alpha() + sprite_sheet = pygame.image.load( + '../assets/pacman_left_sprite.png').convert_alpha() # Sprite sheets for the ghosts - blinky_sprite = pygame.image.load('../assets/blinky.png').convert_alpha() - pinky_sprite = pygame.image.load( '../assets/pinky.png').convert_alpha() - clyde_sprite = pygame.image.load( '../assets/clyde.png').convert_alpha() - inky_sprite = pygame.image.load( '../assets/inky.png').convert_alpha() + blinky_sprite = pygame.image.load( + '../assets/blinky.png').convert_alpha() + pinky_sprite = pygame.image.load('../assets/pinky.png').convert_alpha() + clyde_sprite = pygame.image.load('../assets/clyde.png').convert_alpha() + inky_sprite = pygame.image.load('../assets/inky.png').convert_alpha() # our beautiful maze maze = Map.Map() @@ -41,7 +43,7 @@ class Game(): # Initialize the player and the ghosts player = Player(sprite_sheet) - blinky = Blinky(blinky_sprite,75, 75) + blinky = Blinky(blinky_sprite, 75, 75) pinky = Pinky(pinky_sprite, 27 * 30, 30 * 30 + 15) inky = Inky(inky_sprite, 75, 30 * 30 + 15) clyde = Clyde(clyde_sprite, 27 * 30 + 15, 75) @@ -63,7 +65,6 @@ class Game(): siren_sound.play(-1) is_game_over = [False] - # Main game loop while not is_game_over[0]: # setting game fps @@ -124,7 +125,6 @@ class Game(): tx = player.speed ty = 0 - # if tx and ty doesn't lead to colliding change the current dx and dy to them and other wise # let pacman move in his previous direction if player.check_collision(maze, tx, ty, TILE_WIDTH, TILE_HEIGHT): @@ -145,12 +145,11 @@ class Game(): player.y += dy player.x %= 900 - # Move ghosts - blinky.move(maze.maze, player, screen, is_game_over) - pinky.move(maze.maze, player, screen, is_game_over) - inky.move(maze.maze, player, screen, is_game_over) - clyde.move(maze.maze, player, screen, is_game_over) + blinky.move(maze.maze, player, screen, is_game_over, blinky) + pinky.move(maze.maze, player, screen, is_game_over, blinky) + # inky.move(maze.maze, player, screen, is_game_over, blinky) + clyde.move(maze.maze, player, screen, is_game_over, blinky) # Draw the map on each frame maze.draw_map(screen) @@ -159,10 +158,9 @@ class Game(): player.draw(screen, counter) blinky.draw(screen) pinky.draw(screen) - inky.draw(screen) + # inky.draw(screen) clyde.draw(screen) - # Update the screen pygame.display.flip() diff --git a/src/ghost.py b/src/ghost.py index e810bfe..301645b 100644 --- a/src/ghost.py +++ b/src/ghost.py @@ -4,19 +4,20 @@ from util import get_sprites from settings import settings import map as Map -dx = [1, 0, -1, 0] # right, down, left, up +dx = [1, 0, -1, 0] # right, down, left, up dy = [0, 1, 0, -1] inv_dir = [2, 3, 0, 1] sprite_sheet = [2, 0, 3, 1] + class Ghost(): - def __init__(self,sprite_sheet, color, x, y): + def __init__(self, sprite_sheet, color, x, y): self.x = x self.y = y self.sprite = get_sprites(sprite_sheet) self.color = color - self.last_move = 3 # this represents the direction based on the dx, dy arrays + self.last_move = 3 # this represents the direction based on the dx, dy arrays self.speed = 3 def in_bounds(self, pos): @@ -26,67 +27,69 @@ class Ghost(): def heuristic(self, next_pos, tx, ty): return abs(next_pos[0] - tx) + abs(next_pos[1] - ty) - # checks if the current position of pacman is either a dot, big dot or free + def is_valid(self, maze, x, y): - if x >= 0 and x < 30: + if x >= 0 and x < 30: # Necessary to make portals work is_dot = maze[y][x] == Map.D is_big_dot = maze[y][x] == Map.BD is_free = maze[y][x] == 0 return (is_dot or is_free or is_big_dot) return True - # checks collision with pacman and obstacles returns false if there is a collision and true otherwise def check_collision(self, nx, ny, gx, gy, maze): direct_x = [1, 0, -1, 0, 1, 1, -1, -1] direct_y = [0, 1, 0, -1, -1, 1, -1, 1] - for i in range(len(direct_x)): px = nx + direct_x[i] * 14 py = ny + direct_y[i] * 14 x = px // gx y = py // gy - if not self.is_valid(maze, x, y): + if not self.is_valid(maze, x, y): return False return True - - def get_next_move(self, pacman, maze, screen): + def get_next_move(self, pacman, maze, screen, blinky): ret = len(dx) * [math.inf] forbidden = inv_dir[self.last_move] - + for i in range(len(dx)): nx = self.x + dx[i] * self.speed ny = self.y + dy[i] * self.speed if self.check_collision(nx, ny, 30, 30, maze): if i != forbidden: ret[i] = self.heuristic((nx, ny), pacman.x, pacman.y) + if settings.debug: + pygame.draw.line(screen, self.color, (pacman.x, pacman.y), + (self.x, self.y), 1) min_h = min(ret) + + # Favour going up when there is a conflict if min_h == ret[3] and min_h != math.inf: return 3 min_idx = ret.index(min_h) return min_idx - - def move(self, maze, pacman, screen, game_over): + def move(self, maze, pacman, screen, game_over, blinky): if abs(pacman.x - self.x) <= 15 and abs(pacman.y - self.y) <= 15: game_over[0] = True - min_idx = self.get_next_move(pacman, maze, screen) + min_idx = self.get_next_move(pacman, maze, screen, blinky) new_dx = dx[min_idx] * self.speed new_dy = dy[min_idx] * self.speed self.x += new_dx self.y += new_dy - self.x %= 900 + self.x %= 900 # The logic of the portal self.last_move = min_idx def draw(self, screen): radius = 30 // 2 - pos = (self.x - radius , self.y - radius) - image = pygame.transform.scale(self.sprite[sprite_sheet[self.last_move]], (40, 40)) + pos = (self.x - radius, self.y - radius) + image = pygame.transform.scale( + self.sprite[sprite_sheet[self.last_move]], (40, 40)) screen.blit(image, pos) diff --git a/src/inky.py b/src/inky.py index 2f886a6..e1c44b0 100644 --- a/src/inky.py +++ b/src/inky.py @@ -1,44 +1,17 @@ import math +from typing_extensions import override from direction import DIRECTION +from settings import settings +import pygame from ghost import Ghost + class Inky(Ghost): def __init__(self, sprite_sheet, x, y): - super().__init__(sprite_sheet, "cyan", x, y) - + super().__init__(sprite_sheet, "orange", x, y) - # def get_intermediate_tile(self, pacman): - # if pacman.direction == DIRECTION.UP: - # new_target = (pacman.x - 30 * 2, pacman.y - 30 * 4) - # if self.in_bounds(new_target): - # return new_target - # else: - # return (pacman.x, pacman.y) - # elif pacman.direction == DIRECTION.DOWN: - # new_target = (pacman.x, pacman.y + 30 * 2) - # if self.in_bounds(new_target): - # return new_target - # else: - # return (pacman.x, pacman.y) - # elif pacman.direction == DIRECTION.RIGHT: - # new_target = (pacman.x + 30 * 2, pacman.y) - # if self.in_bounds(new_target): - # return new_target - # else: - # return (pacman.x, pacman.y) - # elif pacman.direction == DIRECTION.LEFT: - # new_target = (pacman.x - 30 * 2, pacman.y) - # if self.in_bounds(new_target): - # return new_target - # else: - # return (pacman.x, pacman.y) - # - # def get_vector_blinky_it(self, blinky, pacman): - # it = self.get_intermediate_tile(pacman) - # return (it[0] - blinky.x, it[1], blinky.y) - # # @override - # def get_next_move(self, target, maze, screen): + # def get_next_move(self, target, maze, screen, blinky): # dx = [1, 0, -1, 0] # dy = [0, 1, 0, -1] # @@ -55,19 +28,19 @@ class Inky(Ghost): # if self.last_move == 3: # forbidden = 1 # - # new_target = self.get_intermediate_tile(target) - # pygame.draw.circle(screen, self.color, (new_target[0], new_target[1]), 15) - # + # new_target = self.get_target(target, blinky) + # + # if settings.debug: + # pygame.draw.line(screen, self.color, (new_target), + # (blinky.x, blinky.y), 1) + # # for i in range(len(dx)): # if i != forbidden: # nx = self.x + dx[i] * self.speed # ny = self.y + dy[i] * self.speed # if self.check_collision(nx, ny, 30, 30, maze): - # ret[i] = self.heuristic((nx, ny), new_target[0], new_target[1]) + # ret[i] = self.heuristic( + # (nx, ny), new_target[0], new_target[1]) # # min_idx = ret.index(min(ret)) # return min_idx - # - # - # - # diff --git a/src/map.py b/src/map.py index 2ccfa1f..01c1258 100644 --- a/src/map.py +++ b/src/map.py @@ -3,17 +3,15 @@ import math import settings as Settings -H = 1 -V = 2 -D = 4 -BD = 8 -TR = 16 -TL = 32 -BL = 64 -BR = 128 -G = 256 -LP = 512 -RP = 1024 +H = 1 # Horitoznal wall +V = 2 # Vertical wall +D = 4 # Dot +BD = 8 # Big Dot +TR = 16 # TopRight wall +TL = 32 # TopLeft wall +BL = 64 # BottomLeft wall +BR = 128 # BottomrRight wall +G = 256 # Ghost PI = math.pi @@ -21,40 +19,73 @@ PI = math.pi class Map(): def __init__(self): self.maze = [ - [TL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H,H, H, H, H, H, H, H, H, H, H, H, H, H, TR], - [V, TL, H, H, H, H, H, H, H, H, H, H, H, H, TR, TL, H, H, H, H, H, H, H, H, H, H, H, H, TR, V], - [V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V], - [V, V, D, TL, H, H, TR, D, TL, H, H, H, TR, D, V, V, D, TL, H, H, H, TR, D, TL, H, H, TR, D, V, V], - [V, V, BD, V, 0, 0, V, D, V, 0, 0, 0, V, D, V, V, D, V, 0, 0, 0, V, D, V, 0, 0, V, BD, V, V], - [V, V, D, BL, H, H, BR, D, BL, H, H, H, BR, D, BL, BR, D, BL, H, H, H, BR, D, BL, H, H, BR, D, V, V], - [V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, V, V], - [V, V, D, TL, H, H, TR, D, TL, TR, D, TL, H, H, H, H, H, H, TR, D, TL, TR, D, TL, H, H, TR, D, V, V], - [V, V, D, BL, H, H, BR, D, V, V, D, BL, H, H, TR, TL, H, H, BR, D, V, V, D, BL, H, H, BR, D, V, V], - [V, V, D, D, D, D, D, D, V, V, D, D, D, D, V, V, D, D, D, D, V, V, D, D, D, D, D, D, V, V], - [V, BL, H, H, H, H, TR, D, V, BL, H, H, TR, 0, V, V, 0, TL, H, H, BR, V, D, TL, H, H, H, H, BR, V], - [V, 0, 0, 0, 0, 0, V, D, V, TL, H, H, BR, 0, BL, BR, 0, BL, H, H, TR, V, D, V, 0, 0, 0, 0, 0, V], - [V, 0, 0, 0, 0, 0, V, D, V, V, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, V, V, D, V, 0, 0, 0, 0, 0, V], - [BR, 0, 0, 0, 0, 0, V, D, V, V, 0, TL, H, H, G, G, H, H, TR, 0, V, V, D, V, 0, 0, 0, 0, 0, BL], - [H, H, H, H, H, H, BR, D, BL, BR, 0, V, 0, 0, 0, 0, 0, 0, V, 0, BL, BR, D, BL, H, H, H, H, H, H], - [0, 0, 0, 0, 0, 0, 0, D, 0, 0, 0, V, 0, 0, 0, 0, 0, 0, V, 0, 0, 0, D, 0, 0, 0, 0, 0, 0, 0], - [H, H, H, H, H, H, TR, D, TL, TR, 0, V, 0, 0, 0, 0, 0, 0, V, 0, TL, TR, D, TL, H, H, H, H, H, H], - [TR, 0, 0, 0, 0, 0, V, D, V, V, 0, BL, H, H, H, H, H, H, BR, 0, V, V, D, V, 0, 0, 0, 0, 0, TL], - [V, 0, 0, 0, 0, 0, V, D, V, V, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, V, V, D, V, 0, 0, 0, 0, 0, V], - [V, 0, 0, 0, 0, 0, V, D, V, V, 0, TL, H, H, H, H, H, H, TR, 0, V, V, D, V, 0, 0, 0, 0, 0, V], - [V, TL, H, H, H, H, BR, D, BL, BR, 0, BL, H, H, TR, TL, H, H, BR, 0, BL, BR, D, BL, H, H, H, H, TR, V], - [V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V], - [V, V, D, TL, H, H, TR, D, TL, H, H, H, TR, D, V, V, D, TL, H, H, H, TR, D, TL, H, H, TR, D, V, V], - [V, V, D, BL, H, TR, V, D, BL, H, H, H, BR, D, BL, BR, D, BL, H, H, H, BR, D, V, TL, H, BR, D, V, V], - [V, V, BD, D, D, V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, V, V, D, D, BD, V, V], - [V, BL, H, TR, D, V, V, D, TL, TR, D, TL, H, H, H, H, H, H, TR, D, TL, TR, D, V, V, D, TL, H, BR, V], - [V, TL, H, BR, D, BL, BR, D, V, V, D, BL, H, H, TR, TL, H, H, BR, D, V, V, D, BL, BR, D, BL, H, TR, V], - [V, V, D, D, D, D, D, D, V, V, D, D, D, D, V, V, D, D, D, D, V, V, D, D, D, D, D, D, V, V], - [V, V, D, TL, H, H, H, H, BR, BL, H, H, TR, D, V, V, D, TL, H, H, BR, BL, H, H, H, H, TR, D, V, V], - [V, V, D, BL, H, H, H, H, H, H, H, H, BR, D, BL, BR, D, BL, H, H, H, H, H, H, H, H, BR, D, V, V], - [V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, V, V], - [V, BL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, BR, V], - [BL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, BR] - ] + [TL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, + H, H, H, H, H, H, H, H, H, H, H, H, H, TR], + [V, TL, H, H, H, H, H, H, H, H, H, H, H, H, TR, + TL, H, H, H, H, H, H, H, H, H, H, H, H, TR, V], + [V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V, + D, D, D, D, D, D, D, D, D, D, D, D, V, V], + [V, V, D, TL, H, H, TR, D, TL, H, H, H, TR, D, V, V, + D, TL, H, H, H, TR, D, TL, H, H, TR, D, V, V], + [V, V, BD, V, 0, 0, V, D, V, 0, 0, 0, V, D, V, V, + D, V, 0, 0, 0, V, D, V, 0, 0, V, BD, V, V], + [V, V, D, BL, H, H, BR, D, BL, H, H, H, BR, D, BL, + BR, D, BL, H, H, H, BR, D, BL, H, H, BR, D, V, V], + [V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, + D, D, D, D, D, D, D, D, D, D, D, D, V, V], + [V, V, D, TL, H, H, TR, D, TL, TR, D, TL, H, H, H, H, + H, H, TR, D, TL, TR, D, TL, H, H, TR, D, V, V], + [V, V, D, BL, H, H, BR, D, V, V, D, BL, H, H, TR, + TL, H, H, BR, D, V, V, D, BL, H, H, BR, D, V, V], + [V, V, D, D, D, D, D, D, V, V, D, D, D, D, V, V, + D, D, D, D, V, V, D, D, D, D, D, D, V, V], + [V, BL, H, H, H, H, TR, D, V, BL, H, H, TR, 0, V, V, + 0, TL, H, H, BR, V, D, TL, H, H, H, H, BR, V], + [V, 0, 0, 0, 0, 0, V, D, V, TL, H, H, BR, 0, BL, + BR, 0, BL, H, H, TR, V, D, V, 0, 0, 0, 0, 0, V], + [V, 0, 0, 0, 0, 0, V, D, V, V, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, V, V, D, V, 0, 0, 0, 0, 0, V], + [BR, 0, 0, 0, 0, 0, V, D, V, V, 0, TL, H, H, G, G, + H, H, TR, 0, V, V, D, V, 0, 0, 0, 0, 0, BL], + [H, H, H, H, H, H, BR, D, BL, BR, 0, V, 0, 0, 0, 0, + 0, 0, V, 0, BL, BR, D, BL, H, H, H, H, H, H], + [0, 0, 0, 0, 0, 0, 0, D, 0, 0, 0, V, 0, 0, 0, 0, + 0, 0, V, 0, 0, 0, D, 0, 0, 0, 0, 0, 0, 0], + [H, H, H, H, H, H, TR, D, TL, TR, 0, V, 0, 0, 0, 0, + 0, 0, V, 0, TL, TR, D, TL, H, H, H, H, H, H], + [TR, 0, 0, 0, 0, 0, V, D, V, V, 0, BL, H, H, H, H, + H, H, BR, 0, V, V, D, V, 0, 0, 0, 0, 0, TL], + [V, 0, 0, 0, 0, 0, V, D, V, V, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, V, V, D, V, 0, 0, 0, 0, 0, V], + [V, 0, 0, 0, 0, 0, V, D, V, V, 0, TL, H, H, H, H, + H, H, TR, 0, V, V, D, V, 0, 0, 0, 0, 0, V], + [V, TL, H, H, H, H, BR, D, BL, BR, 0, BL, H, H, TR, + TL, H, H, BR, 0, BL, BR, D, BL, H, H, H, H, TR, V], + [V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V, + D, D, D, D, D, D, D, D, D, D, D, D, V, V], + [V, V, D, TL, H, H, TR, D, TL, H, H, H, TR, D, V, V, + D, TL, H, H, H, TR, D, TL, H, H, TR, D, V, V], + [V, V, D, BL, H, TR, V, D, BL, H, H, H, BR, D, BL, + BR, D, BL, H, H, H, BR, D, V, TL, H, BR, D, V, V], + [V, V, BD, D, D, V, V, D, D, D, D, D, D, D, D, D, + D, D, D, D, D, D, D, V, V, D, D, BD, V, V], + [V, BL, H, TR, D, V, V, D, TL, TR, D, TL, H, H, H, H, + H, H, TR, D, TL, TR, D, V, V, D, TL, H, BR, V], + [V, TL, H, BR, D, BL, BR, D, V, V, D, BL, H, H, TR, + TL, H, H, BR, D, V, V, D, BL, BR, D, BL, H, TR, V], + [V, V, D, D, D, D, D, D, V, V, D, D, D, D, V, V, + D, D, D, D, V, V, D, D, D, D, D, D, V, V], + [V, V, D, TL, H, H, H, H, BR, BL, H, H, TR, D, V, V, + D, TL, H, H, BR, BL, H, H, H, H, TR, D, V, V], + [V, V, D, BL, H, H, H, H, H, H, H, H, BR, D, BL, + BR, D, BL, H, H, H, H, H, H, H, H, BR, D, V, V], + [V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, + D, D, D, D, D, D, D, D, D, D, D, D, V, V], + [V, BL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, + H, H, H, H, H, H, H, H, H, H, H, H, BR, V], + [BL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, + H, H, H, H, H, H, H, H, H, H, H, H, H, H, BR] + ] self.dot_color = (255, 255, 255) # white self.small_dot_radius = 4 self.big_dot_radius = 8 @@ -63,9 +94,6 @@ class Map(): self.line_horizontal = Settings.settings.width // len(self.maze[0]) self.line_stroke = 1 - def consturct_map(self): - pass - def draw_wall(self, screen, flag, pos): if flag & V: pos1 = (pos[0] + self.line_vertical * 0.5, pos[1]) diff --git a/src/pinky.py b/src/pinky.py index 42b5d91..191ff61 100644 --- a/src/pinky.py +++ b/src/pinky.py @@ -1,13 +1,14 @@ import pygame from typing_extensions import override from direction import DIRECTION +from settings import settings import math from ghost import Ghost + class Pinky(Ghost): def __init__(self, sprite_sheet, x, y): - super().__init__(sprite_sheet,"pink", x, y) - + super().__init__(sprite_sheet, "pink", x, y) def get_four_tiles_ahead_of_pacman(self, pacman): if pacman.direction == DIRECTION.UP: @@ -36,7 +37,7 @@ class Pinky(Ghost): return (pacman.x, pacman.y) @override - def get_next_move(self, target, maze, screen): + def get_next_move(self, target, maze, screen, blinky): dx = [1, 0, -1, 0] dy = [0, 1, 0, -1] @@ -54,20 +55,20 @@ class Pinky(Ghost): forbidden = 1 new_target = self.get_four_tiles_ahead_of_pacman(target) - pygame.draw.circle(screen, self.color, (new_target[0], new_target[1]), 15) - + if settings.debug: + pygame.draw.circle(screen, self.color, + (new_target[0], new_target[1]), 15) + for i in range(len(dx)): if i != forbidden: nx = self.x + dx[i] * self.speed ny = self.y + dy[i] * self.speed if self.check_collision(nx, ny, 30, 30, maze): - ret[i] = self.heuristic((nx, ny), new_target[0], new_target[1]) + ret[i] = self.heuristic( + (nx, ny), new_target[0], new_target[1]) + if settings.debug: + pygame.draw.line(screen, self.color, (new_target), + (self.x, self.y), 1) min_idx = ret.index(min(ret)) return min_idx - - - - - - diff --git a/src/settings.py b/src/settings.py index 9f0f118..94eabd8 100644 --- a/src/settings.py +++ b/src/settings.py @@ -3,5 +3,7 @@ class Settings(): self.width = 900 self.height = 990 self.fps = 60 + self.debug = True + settings = Settings() -- cgit v1.2.3 From c7c473177086399a8fb97936b4c3c2b67a43fce0 Mon Sep 17 00:00:00 2001 From: omagdy7 Date: Mon, 8 May 2023 13:07:48 +0300 Subject: Finished inky's algorithm and also added an option in settings to disable audio --- src/game.py | 9 ++--- src/inky.py | 102 +++++++++++++++++++++++++++++++++++++------------------- src/player.py | 9 +++-- src/settings.py | 1 + 4 files changed, 78 insertions(+), 43 deletions(-) (limited to 'src') diff --git a/src/game.py b/src/game.py index 5a41f88..c4f5597 100644 --- a/src/game.py +++ b/src/game.py @@ -61,8 +61,9 @@ class Game(): siren_sound = pygame.mixer.Sound('../assets/sfx/siren_1.wav') munch_sound = pygame.mixer.Sound('../assets/sfx/munch_1.wav') - pygame.mixer.music.play() - siren_sound.play(-1) + if settings.sound: + pygame.mixer.music.play() + siren_sound.play(-1) is_game_over = [False] # Main game loop @@ -148,7 +149,7 @@ class Game(): # Move ghosts blinky.move(maze.maze, player, screen, is_game_over, blinky) pinky.move(maze.maze, player, screen, is_game_over, blinky) - # inky.move(maze.maze, player, screen, is_game_over, blinky) + inky.move(maze.maze, player, screen, is_game_over, blinky) clyde.move(maze.maze, player, screen, is_game_over, blinky) # Draw the map on each frame @@ -158,7 +159,7 @@ class Game(): player.draw(screen, counter) blinky.draw(screen) pinky.draw(screen) - # inky.draw(screen) + inky.draw(screen) clyde.draw(screen) # Update the screen diff --git a/src/inky.py b/src/inky.py index e1c44b0..86ac156 100644 --- a/src/inky.py +++ b/src/inky.py @@ -10,37 +10,71 @@ class Inky(Ghost): def __init__(self, sprite_sheet, x, y): super().__init__(sprite_sheet, "orange", x, y) - # @override - # def get_next_move(self, target, maze, screen, blinky): - # dx = [1, 0, -1, 0] - # dy = [0, 1, 0, -1] - # - # ret = len(dx) * [math.inf] - # - # forbidden = 0 - # - # if self.last_move == 0: - # forbidden = 2 - # if self.last_move == 1: - # forbidden = 3 - # if self.last_move == 2: - # forbidden = 0 - # if self.last_move == 3: - # forbidden = 1 - # - # new_target = self.get_target(target, blinky) - # - # if settings.debug: - # pygame.draw.line(screen, self.color, (new_target), - # (blinky.x, blinky.y), 1) - # - # for i in range(len(dx)): - # if i != forbidden: - # nx = self.x + dx[i] * self.speed - # ny = self.y + dy[i] * self.speed - # if self.check_collision(nx, ny, 30, 30, maze): - # ret[i] = self.heuristic( - # (nx, ny), new_target[0], new_target[1]) - # - # min_idx = ret.index(min(ret)) - # return min_idx + def get_intermediate_tile(self, pacman): + if pacman.direction == DIRECTION.UP: + new_target = (pacman.x - 30 * 2, pacman.y - 30 * 2) + if self.in_bounds(new_target): + return new_target + else: + return (pacman.x, pacman.y) + elif pacman.direction == DIRECTION.DOWN: + new_target = (pacman.x, pacman.y + 30 * 2) + if self.in_bounds(new_target): + return new_target + else: + return (pacman.x, pacman.y) + elif pacman.direction == DIRECTION.RIGHT: + new_target = (pacman.x + 30 * 2, pacman.y) + if self.in_bounds(new_target): + return new_target + else: + return (pacman.x, pacman.y) + elif pacman.direction == DIRECTION.LEFT: + new_target = (pacman.x - 30 * 2, pacman.y) + if self.in_bounds(new_target): + return new_target + else: + return (pacman.x, pacman.y) + + def get_target(self, inter_tile, blinky): + target = (inter_tile[0] - (blinky.x - inter_tile[0]), + inter_tile[1] - (blinky.y - inter_tile[1])) + return target + + @override + def get_next_move(self, target, maze, screen, blinky): + dx = [1, 0, -1, 0] + dy = [0, 1, 0, -1] + + ret = len(dx) * [math.inf] + + forbidden = 0 + + if self.last_move == 0: + forbidden = 2 + if self.last_move == 1: + forbidden = 3 + if self.last_move == 2: + forbidden = 0 + if self.last_move == 3: + forbidden = 1 + + inter_tile = self.get_intermediate_tile(target) + target = self.get_target(inter_tile, blinky) + + # y = mx + c + + if settings.debug: + pygame.draw.line(screen, self.color, (target), + (self.x, self.y), 1) + + for i in range(len(dx)): + if i != forbidden: + nx = self.x + dx[i] * self.speed + ny = self.y + dy[i] * self.speed + if self.check_collision(nx, ny, 30, 30, maze): + ret[i] = self.heuristic( + (nx, ny), target[0], target[1]) + + min_idx = ret.index(min(ret)) + return min_idx diff --git a/src/player.py b/src/player.py index 2c4c4f4..903c358 100644 --- a/src/player.py +++ b/src/player.py @@ -1,4 +1,3 @@ -from typing import List from direction import DIRECTION import map as Map from util import get_sprites @@ -14,7 +13,7 @@ class Player(): self.direction = DIRECTION.LEFT # checks if the current position of pacman is either a dot, big dot or free - def is_valid(self,maze, x, y): + def is_valid(self, maze, x, y): if x >= 0 and x < 30: is_dot = maze.maze[y][x] == Map.D is_big_dot = maze.maze[y][x] == Map.BD @@ -24,8 +23,8 @@ class Player(): return (is_dot or is_free or is_big_dot) return True - - # checks collision with pacman and obstacles returns false if there is a collision and true otherwise + # checks collision with pacman and obstacles returns false + # if there is a collision and true otherwise def check_collision(self, maze, dx, dy, tile_width, tile_height): direct_x = [1, 0, -1, 0, 1, 1, -1, -1] direct_y = [0, 1, 0, -1, -1, 1, -1, 1] @@ -44,7 +43,7 @@ class Player(): def draw(self, screen, counter): radius = 30 // 2 - pos = (self.x - radius , self.y - radius) + pos = (self.x - radius, self.y - radius) image = pygame.transform.scale(self.sprite[counter // 5], (35, 35)) if self.direction == DIRECTION.UP: screen.blit(pygame.transform.rotate(image, 270), pos) diff --git a/src/settings.py b/src/settings.py index 94eabd8..d1cc93c 100644 --- a/src/settings.py +++ b/src/settings.py @@ -4,6 +4,7 @@ class Settings(): self.height = 990 self.fps = 60 self.debug = True + self.sound = False settings = Settings() -- cgit v1.2.3 From 72aeff07de251f66c579405f0aecb0b9c4d4cfac Mon Sep 17 00:00:00 2001 From: omagdy7 Date: Mon, 8 May 2023 14:47:07 +0300 Subject: Added scattered mode for the ghosts --- src/clyde.py | 34 ++++++++++++++------ src/game.py | 27 +++++++++++++--- src/ghost.py | 20 ++++++++++-- src/inky.py | 25 +++++++++++++-- src/map.py | 99 +++++++++++++++++++-------------------------------------- src/mode.py | 7 ++++ src/pinky.py | 27 ++++++++++++++-- src/settings.py | 2 +- 8 files changed, 151 insertions(+), 90 deletions(-) create mode 100644 src/mode.py (limited to 'src') diff --git a/src/clyde.py b/src/clyde.py index 95de2cb..d17a583 100644 --- a/src/clyde.py +++ b/src/clyde.py @@ -1,6 +1,7 @@ from ghost import Ghost import pygame from settings import settings +from mode import MODE from typing_extensions import override import math @@ -15,8 +16,14 @@ class Clyde(Ghost): dy = self.y - pacman.y return math.sqrt(dx * dx + dy * dy) <= tile_width * 8 + @override + def get_default_tile(self): + return (27 * 30 + 15, 2 * 30 + 15) + @override def get_next_move(self, pacman, maze, screen, blinky): + default_tile = self.get_default_tile() + dx = [1, 0, -1, 0] # right, down, left, up dy = [0, 1, 0, -1] @@ -32,23 +39,30 @@ class Clyde(Ghost): ny = self.y + dy[i] * self.speed if self.check_collision(nx, ny, 30, 30, maze): if i != forbidden: - if self.is_eight_tiles_away(pacman): + if self.mode == MODE.SCATTERED: ret[i] = self.heuristic( - (nx, ny), bottom_left_corner[0], bottom_left_corner[1]) - if settings.debug: - pygame.draw.line(screen, self.color, (bottom_left_corner), - (self.x, self.y), 1) + (nx, ny), default_tile[0], default_tile[1]) else: - ret[i] = self.heuristic( - (nx, ny), pacman.x, pacman.y) - if settings.debug: - pygame.draw.line(screen, self.color, (pacman.x, pacman.y), - (self.x, self.y), 1) + if self.is_eight_tiles_away(pacman): + ret[i] = self.heuristic( + (nx, ny), bottom_left_corner[0], bottom_left_corner[1]) + if settings.debug: + pygame.draw.line(screen, self.color, (bottom_left_corner), + (self.x, self.y), 1) + else: + ret[i] = self.heuristic( + (nx, ny), pacman.x, pacman.y) + if settings.debug: + pygame.draw.line(screen, self.color, (pacman.x, pacman.y), + (self.x, self.y), 1) min_h = min(ret) # Favour going up when there is a conflict if min_h == ret[3] and min_h != math.inf: return 3 + # Favour going down than sideways when there is a conflict + if min_h == ret[1] and min_h != math.inf: + return 1 min_idx = ret.index(min_h) return min_idx diff --git a/src/game.py b/src/game.py index c4f5597..0478b28 100644 --- a/src/game.py +++ b/src/game.py @@ -1,6 +1,7 @@ from blinky import Blinky from clyde import Clyde from direction import DIRECTION +from mode import MODE from inky import Inky from pinky import Pinky from player import Player @@ -34,6 +35,10 @@ class Game(): clyde_sprite = pygame.image.load('../assets/clyde.png').convert_alpha() inky_sprite = pygame.image.load('../assets/inky.png').convert_alpha() + # Set the timer to trigger after 10,000 milliseconds (10 seconds) + timer_event = pygame.USEREVENT + 1 + pygame.time.set_timer(timer_event, 1000 * 10, 1) + # our beautiful maze maze = Map.Map() @@ -43,10 +48,14 @@ class Game(): # Initialize the player and the ghosts player = Player(sprite_sheet) - blinky = Blinky(blinky_sprite, 75, 75) - pinky = Pinky(pinky_sprite, 27 * 30, 30 * 30 + 15) - inky = Inky(inky_sprite, 75, 30 * 30 + 15) - clyde = Clyde(clyde_sprite, 27 * 30 + 15, 75) + blinky = Blinky(blinky_sprite, 12 * TILE_WIDTH + + 15, 12 * TILE_HEIGHT + 15) + pinky = Pinky(pinky_sprite, 11 * TILE_WIDTH + + 15, 12 * TILE_HEIGHT + 15) + inky = Inky(inky_sprite, 13 * TILE_WIDTH + + 15, 12 * TILE_HEIGHT + 15) + clyde = Clyde(clyde_sprite, 14 * TILE_WIDTH + + 15, 12 * TILE_HEIGHT + 15) # Set the pacman velocity dx = 0 @@ -105,6 +114,13 @@ class Game(): player.direction = DIRECTION.RIGHT tx = player.speed ty = 0 # Necssarry to move only horizontal or vertical + # Check for the timer event + if event.type == timer_event: + print("Finished") + pinky.mode = MODE.CHASING + inky.mode = MODE.CHASING + blinky.mode = MODE.CHASING + clyde.mode = MODE.CHASING keys = pygame.key.get_pressed() @@ -126,7 +142,8 @@ class Game(): tx = player.speed ty = 0 - # if tx and ty doesn't lead to colliding change the current dx and dy to them and other wise + # if tx and ty doesn't lead to colliding change the current + # dx and dy to them and other wise # let pacman move in his previous direction if player.check_collision(maze, tx, ty, TILE_WIDTH, TILE_HEIGHT): dx = tx diff --git a/src/ghost.py b/src/ghost.py index 301645b..43c0c4a 100644 --- a/src/ghost.py +++ b/src/ghost.py @@ -2,6 +2,7 @@ import pygame import math from util import get_sprites from settings import settings +from mode import MODE import map as Map dx = [1, 0, -1, 0] # right, down, left, up @@ -19,6 +20,7 @@ class Ghost(): self.color = color self.last_move = 3 # this represents the direction based on the dx, dy arrays self.speed = 3 + self.mode = MODE.SCATTERED def in_bounds(self, pos): (x, y) = pos @@ -37,7 +39,12 @@ class Ghost(): return (is_dot or is_free or is_big_dot) return True - # checks collision with pacman and obstacles returns false if there is a collision and true otherwise + def get_default_tile(self): + return (75, 75) + + # checks collision with pacman and obstacles returns false if there is + # a collision and true otherwise + def check_collision(self, nx, ny, gx, gy, maze): direct_x = [1, 0, -1, 0, 1, 1, -1, -1] direct_y = [0, 1, 0, -1, -1, 1, -1, 1] @@ -54,6 +61,8 @@ class Ghost(): def get_next_move(self, pacman, maze, screen, blinky): + default_tile = self.get_default_tile() + ret = len(dx) * [math.inf] forbidden = inv_dir[self.last_move] @@ -63,7 +72,11 @@ class Ghost(): ny = self.y + dy[i] * self.speed if self.check_collision(nx, ny, 30, 30, maze): if i != forbidden: - ret[i] = self.heuristic((nx, ny), pacman.x, pacman.y) + if self.mode == MODE.SCATTERED: + ret[i] = self.heuristic( + (nx, ny), default_tile[0], default_tile[1]) + else: + ret[i] = self.heuristic((nx, ny), pacman.x, pacman.y) if settings.debug: pygame.draw.line(screen, self.color, (pacman.x, pacman.y), (self.x, self.y), 1) @@ -73,6 +86,9 @@ class Ghost(): # Favour going up when there is a conflict if min_h == ret[3] and min_h != math.inf: return 3 + # Favour going down than sideways when there is a conflict + if min_h == ret[1] and min_h != math.inf: + return 1 min_idx = ret.index(min_h) return min_idx diff --git a/src/inky.py b/src/inky.py index 86ac156..070faa0 100644 --- a/src/inky.py +++ b/src/inky.py @@ -1,6 +1,7 @@ import math from typing_extensions import override from direction import DIRECTION +from mode import MODE from settings import settings import pygame from ghost import Ghost @@ -36,6 +37,10 @@ class Inky(Ghost): else: return (pacman.x, pacman.y) + @override + def get_default_tile(self): + return (2 * 30 + 15, 30 * 30 + 15) + def get_target(self, inter_tile, blinky): target = (inter_tile[0] - (blinky.x - inter_tile[0]), inter_tile[1] - (blinky.y - inter_tile[1])) @@ -43,6 +48,8 @@ class Inky(Ghost): @override def get_next_move(self, target, maze, screen, blinky): + default_tile = self.get_default_tile() + dx = [1, 0, -1, 0] dy = [0, 1, 0, -1] @@ -73,8 +80,20 @@ class Inky(Ghost): nx = self.x + dx[i] * self.speed ny = self.y + dy[i] * self.speed if self.check_collision(nx, ny, 30, 30, maze): - ret[i] = self.heuristic( - (nx, ny), target[0], target[1]) + if self.mode == MODE.SCATTERED: + ret[i] = self.heuristic( + (nx, ny), default_tile[0], default_tile[1]) + else: + ret[i] = self.heuristic( + (nx, ny), target[0], target[1]) + + min_h = min(ret) - min_idx = ret.index(min(ret)) + # Favour going up when there is a conflict + if min_h == ret[3] and min_h != math.inf: + return 3 + # Favour going down than sideways when there is a conflict + if min_h == ret[1] and min_h != math.inf: + return 1 + min_idx = ret.index(min_h) return min_idx diff --git a/src/map.py b/src/map.py index 01c1258..c37a438 100644 --- a/src/map.py +++ b/src/map.py @@ -19,72 +19,39 @@ PI = math.pi class Map(): def __init__(self): self.maze = [ - [TL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, - H, H, H, H, H, H, H, H, H, H, H, H, H, TR], - [V, TL, H, H, H, H, H, H, H, H, H, H, H, H, TR, - TL, H, H, H, H, H, H, H, H, H, H, H, H, TR, V], - [V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V, - D, D, D, D, D, D, D, D, D, D, D, D, V, V], - [V, V, D, TL, H, H, TR, D, TL, H, H, H, TR, D, V, V, - D, TL, H, H, H, TR, D, TL, H, H, TR, D, V, V], - [V, V, BD, V, 0, 0, V, D, V, 0, 0, 0, V, D, V, V, - D, V, 0, 0, 0, V, D, V, 0, 0, V, BD, V, V], - [V, V, D, BL, H, H, BR, D, BL, H, H, H, BR, D, BL, - BR, D, BL, H, H, H, BR, D, BL, H, H, BR, D, V, V], - [V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, - D, D, D, D, D, D, D, D, D, D, D, D, V, V], - [V, V, D, TL, H, H, TR, D, TL, TR, D, TL, H, H, H, H, - H, H, TR, D, TL, TR, D, TL, H, H, TR, D, V, V], - [V, V, D, BL, H, H, BR, D, V, V, D, BL, H, H, TR, - TL, H, H, BR, D, V, V, D, BL, H, H, BR, D, V, V], - [V, V, D, D, D, D, D, D, V, V, D, D, D, D, V, V, - D, D, D, D, V, V, D, D, D, D, D, D, V, V], - [V, BL, H, H, H, H, TR, D, V, BL, H, H, TR, 0, V, V, - 0, TL, H, H, BR, V, D, TL, H, H, H, H, BR, V], - [V, 0, 0, 0, 0, 0, V, D, V, TL, H, H, BR, 0, BL, - BR, 0, BL, H, H, TR, V, D, V, 0, 0, 0, 0, 0, V], - [V, 0, 0, 0, 0, 0, V, D, V, V, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, V, V, D, V, 0, 0, 0, 0, 0, V], - [BR, 0, 0, 0, 0, 0, V, D, V, V, 0, TL, H, H, G, G, - H, H, TR, 0, V, V, D, V, 0, 0, 0, 0, 0, BL], - [H, H, H, H, H, H, BR, D, BL, BR, 0, V, 0, 0, 0, 0, - 0, 0, V, 0, BL, BR, D, BL, H, H, H, H, H, H], - [0, 0, 0, 0, 0, 0, 0, D, 0, 0, 0, V, 0, 0, 0, 0, - 0, 0, V, 0, 0, 0, D, 0, 0, 0, 0, 0, 0, 0], - [H, H, H, H, H, H, TR, D, TL, TR, 0, V, 0, 0, 0, 0, - 0, 0, V, 0, TL, TR, D, TL, H, H, H, H, H, H], - [TR, 0, 0, 0, 0, 0, V, D, V, V, 0, BL, H, H, H, H, - H, H, BR, 0, V, V, D, V, 0, 0, 0, 0, 0, TL], - [V, 0, 0, 0, 0, 0, V, D, V, V, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, V, V, D, V, 0, 0, 0, 0, 0, V], - [V, 0, 0, 0, 0, 0, V, D, V, V, 0, TL, H, H, H, H, - H, H, TR, 0, V, V, D, V, 0, 0, 0, 0, 0, V], - [V, TL, H, H, H, H, BR, D, BL, BR, 0, BL, H, H, TR, - TL, H, H, BR, 0, BL, BR, D, BL, H, H, H, H, TR, V], - [V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V, - D, D, D, D, D, D, D, D, D, D, D, D, V, V], - [V, V, D, TL, H, H, TR, D, TL, H, H, H, TR, D, V, V, - D, TL, H, H, H, TR, D, TL, H, H, TR, D, V, V], - [V, V, D, BL, H, TR, V, D, BL, H, H, H, BR, D, BL, - BR, D, BL, H, H, H, BR, D, V, TL, H, BR, D, V, V], - [V, V, BD, D, D, V, V, D, D, D, D, D, D, D, D, D, - D, D, D, D, D, D, D, V, V, D, D, BD, V, V], - [V, BL, H, TR, D, V, V, D, TL, TR, D, TL, H, H, H, H, - H, H, TR, D, TL, TR, D, V, V, D, TL, H, BR, V], - [V, TL, H, BR, D, BL, BR, D, V, V, D, BL, H, H, TR, - TL, H, H, BR, D, V, V, D, BL, BR, D, BL, H, TR, V], - [V, V, D, D, D, D, D, D, V, V, D, D, D, D, V, V, - D, D, D, D, V, V, D, D, D, D, D, D, V, V], - [V, V, D, TL, H, H, H, H, BR, BL, H, H, TR, D, V, V, - D, TL, H, H, BR, BL, H, H, H, H, TR, D, V, V], - [V, V, D, BL, H, H, H, H, H, H, H, H, BR, D, BL, - BR, D, BL, H, H, H, H, H, H, H, H, BR, D, V, V], - [V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, - D, D, D, D, D, D, D, D, D, D, D, D, V, V], - [V, BL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, - H, H, H, H, H, H, H, H, H, H, H, H, BR, V], - [BL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, - H, H, H, H, H, H, H, H, H, H, H, H, H, H, BR] + [TL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, TR], + [V, TL, H, H, H, H, H, H, H, H, H, H, H, H, TR, TL, H, H, H, H, H, H, H, H, H, H, H, H, TR, V], + [V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V], + [V, V, D, TL, H, H, TR, D, TL, H, H, H, TR, D, V, V, D, TL, H, H, H, TR, D, TL, H, H, TR, D, V, V], + [V, V, BD, V, 0, 0, V, D, V, 0, 0, 0, V, D, V, V, D, V, 0, 0, 0, V, D, V, 0, 0, V, BD, V, V], + [V, V, D, BL, H, H, BR, D, BL, H, H, H, BR, D, BL, BR, D, BL, H, H, H, BR, D, BL, H, H, BR, D, V, V], + [V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, V, V], + [V, V, D, TL, H, H, TR, D, TL, TR, D, TL, H, H, H, H, H, H, TR, D, TL, TR, D, TL, H, H, TR, D, V, V], + [V, V, D, BL, H, H, BR, D, V, V, D, BL, H, H, TR, TL, H, H, BR, D, V, V, D, BL, H, H, BR, D, V, V], + [V, V, D, D, D, D, D, D, V, V, D, D, D, D, V, V, D, D, D, D, V, V, D, D, D, D, D, D, V, V], + [V, BL, H, H, H, H, TR, D, V, BL, H, H, TR, 0, V, V, 0, TL, H, H, BR, V, D, TL, H, H, H, H, BR, V], + [V, 0, 0, 0, 0, 0, V, D, V, TL, H, H, BR, 0, BL, BR, 0, BL, H, H, TR, V, D, V, 0, 0, 0, 0, 0, V], + [V, 0, 0, 0, 0, 0, V, D, V, V, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, V, V, D, V, 0, 0, 0, 0, 0, V], + [BR, 0, 0, 0, 0, 0, V, D, V, V, 0, TL, H, H, G, G, H, H, TR, 0, V, V, D, V, 0, 0, 0, 0, 0, BL], + [H, H, H, H, H, H, BR, D, BL, BR, 0, V, 0, 0, 0, 0, 0, 0, V, 0, BL, BR, D, BL, H, H, H, H, H, H], + [0, 0, 0, 0, 0, 0, 0, D, 0, 0, 0, V, 0, 0, 0, 0, 0, 0, V, 0, 0, 0, D, 0, 0, 0, 0, 0, 0, 0], + [H, H, H, H, H, H, TR, D, TL, TR, 0, V, 0, 0, 0, 0, 0, 0, V, 0, TL, TR, D, TL, H, H, H, H, H, H], + [TR, 0, 0, 0, 0, 0, V, D, V, V, 0, BL, H, H, H, H, H, H, BR, 0, V, V, D, V, 0, 0, 0, 0, 0, TL], + [V, 0, 0, 0, 0, 0, V, D, V, V, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, V, V, D, V, 0, 0, 0, 0, 0, V], + [V, 0, 0, 0, 0, 0, V, D, V, V, 0, TL, H, H, H, H, H, H, TR, 0, V, V, D, V, 0, 0, 0, 0, 0, V], + [V, TL, H, H, H, H, BR, D, BL, BR, 0, BL, H, H, TR, TL, H, H, BR, 0, BL, BR, D, BL, H, H, H, H, TR, V], + [V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V, D, D, D, D, D, D, D, D, D, D, D, D, V, V], + [V, V, D, TL, H, H, TR, D, TL, H, H, H, TR, D, V, V, D, TL, H, H, H, TR, D, TL, H, H, TR, D, V, V], + [V, V, D, BL, H, TR, V, D, BL, H, H, H, BR, D, BL, BR, D, BL, H, H, H, BR, D, V, TL, H, BR, D, V, V], + [V, V, BD, D, D, V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, V, V, D, D, BD, V, V], + [V, BL, H, TR, D, V, V, D, TL, TR, D, TL, H, H, H, H, H, H, TR, D, TL, TR, D, V, V, D, TL, H, BR, V], + [V, TL, H, BR, D, BL, BR, D, V, V, D, BL, H, H, TR, TL, H, H, BR, D, V, V, D, BL, BR, D, BL, H, TR, V], + [V, V, D, D, D, D, D, D, V, V, D, D, D, D, V, V, D, D, D, D, V, V, D, D, D, D, D, D, V, V], + [V, V, D, TL, H, H, H, H, BR, BL, H, H, TR, D, V, V, D, TL, H, H, BR, BL, H, H, H, H, TR, D, V, V], + [V, V, D, BL, H, H, H, H, H, H, H, H, BR, D, BL, BR, D, BL, H, H, H, H, H, H, H, H, BR, D, V, V], + [V, V, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, D, V, V], + [V, BL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, BR, V], + [BL, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, H, BR] ] self.dot_color = (255, 255, 255) # white self.small_dot_radius = 4 diff --git a/src/mode.py b/src/mode.py new file mode 100644 index 0000000..9e7f572 --- /dev/null +++ b/src/mode.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class MODE(Enum): + SCATTERED = 0 + FRIGHETENED = 1 + CHASING = 2 diff --git a/src/pinky.py b/src/pinky.py index 191ff61..688163b 100644 --- a/src/pinky.py +++ b/src/pinky.py @@ -1,6 +1,7 @@ import pygame from typing_extensions import override from direction import DIRECTION +from mode import MODE from settings import settings import math from ghost import Ghost @@ -36,8 +37,14 @@ class Pinky(Ghost): else: return (pacman.x, pacman.y) + @override + def get_default_tile(self): + return (27 * 30 + 15, 30 * 30 + 15) + @override def get_next_move(self, target, maze, screen, blinky): + default_tile = self.get_default_tile() + dx = [1, 0, -1, 0] dy = [0, 1, 0, -1] @@ -58,17 +65,31 @@ class Pinky(Ghost): if settings.debug: pygame.draw.circle(screen, self.color, (new_target[0], new_target[1]), 15) + pygame.draw.circle(screen, self.color, + default_tile, 15) for i in range(len(dx)): if i != forbidden: nx = self.x + dx[i] * self.speed ny = self.y + dy[i] * self.speed if self.check_collision(nx, ny, 30, 30, maze): - ret[i] = self.heuristic( - (nx, ny), new_target[0], new_target[1]) + if self.mode == MODE.SCATTERED: + ret[i] = self.heuristic( + (nx, ny), default_tile[0], default_tile[1]) + else: + ret[i] = self.heuristic( + (nx, ny), new_target[0], new_target[1]) if settings.debug: pygame.draw.line(screen, self.color, (new_target), (self.x, self.y), 1) - min_idx = ret.index(min(ret)) + min_h = min(ret) + + # Favour going up when there is a conflict + if min_h == ret[3] and min_h != math.inf: + return 3 + # Favour going down than sideways when there is a conflict + if min_h == ret[1] and min_h != math.inf: + return 1 + min_idx = ret.index(min_h) return min_idx diff --git a/src/settings.py b/src/settings.py index d1cc93c..a8dfa24 100644 --- a/src/settings.py +++ b/src/settings.py @@ -3,7 +3,7 @@ class Settings(): self.width = 900 self.height = 990 self.fps = 60 - self.debug = True + self.debug = False self.sound = False -- cgit v1.2.3 From 9621f880a03337a8a252cf5cd3993d5a1a29969c Mon Sep 17 00:00:00 2001 From: omagdy7 Date: Mon, 8 May 2023 17:59:55 +0300 Subject: Added frightened mode --- src/clyde.py | 12 +++++++++++- src/game.py | 9 ++++----- src/ghost.py | 39 ++++++++++++++++++++++++++++++++++----- src/inky.py | 16 +++++++++++++--- src/pinky.py | 12 +++++++++++- src/player.py | 37 +++++++++++++++++++++++++++++-------- src/timer.py | 7 +++++++ 7 files changed, 109 insertions(+), 23 deletions(-) create mode 100644 src/timer.py (limited to 'src') diff --git a/src/clyde.py b/src/clyde.py index d17a583..140ce16 100644 --- a/src/clyde.py +++ b/src/clyde.py @@ -1,4 +1,5 @@ from ghost import Ghost +import random import pygame from settings import settings from mode import MODE @@ -34,6 +35,12 @@ class Clyde(Ghost): forbidden = inv_dir[self.last_move] + rand_pos = (0, 0) + + if pacman.powerup: + self.mode = MODE.FRIGHETENED + rand_pos = random.randint(0, 900), random.randint(0, 990) + for i in range(len(dx)): nx = self.x + dx[i] * self.speed ny = self.y + dy[i] * self.speed @@ -42,7 +49,10 @@ class Clyde(Ghost): if self.mode == MODE.SCATTERED: ret[i] = self.heuristic( (nx, ny), default_tile[0], default_tile[1]) - else: + elif self.mode == MODE.FRIGHETENED: + ret[i] = self.heuristic( + (nx, ny), rand_pos[0], rand_pos[1]) + elif self.mode == MODE.CHASING: if self.is_eight_tiles_away(pacman): ret[i] = self.heuristic( (nx, ny), bottom_left_corner[0], bottom_left_corner[1]) diff --git a/src/game.py b/src/game.py index 0478b28..8e4ce37 100644 --- a/src/game.py +++ b/src/game.py @@ -116,7 +116,6 @@ class Game(): ty = 0 # Necssarry to move only horizontal or vertical # Check for the timer event if event.type == timer_event: - print("Finished") pinky.mode = MODE.CHASING inky.mode = MODE.CHASING blinky.mode = MODE.CHASING @@ -174,10 +173,10 @@ class Game(): # Draw the player and the ghosts player.draw(screen, counter) - blinky.draw(screen) - pinky.draw(screen) - inky.draw(screen) - clyde.draw(screen) + blinky.draw(screen, player.powerup, counter) + pinky.draw(screen, player.powerup, counter) + inky.draw(screen, player.powerup, counter) + clyde.draw(screen, player.powerup, counter) # Update the screen pygame.display.flip() diff --git a/src/ghost.py b/src/ghost.py index 43c0c4a..cfd3068 100644 --- a/src/ghost.py +++ b/src/ghost.py @@ -1,8 +1,10 @@ import pygame +import random import math from util import get_sprites from settings import settings from mode import MODE +from direction import DIRECTION import map as Map dx = [1, 0, -1, 0] # right, down, left, up @@ -16,6 +18,8 @@ class Ghost(): def __init__(self, sprite_sheet, color, x, y): self.x = x self.y = y + self.sprite_sheet = sprite_sheet + self.name = "blinky" self.sprite = get_sprites(sprite_sheet) self.color = color self.last_move = 3 # this represents the direction based on the dx, dy arrays @@ -67,6 +71,14 @@ class Ghost(): forbidden = inv_dir[self.last_move] + rand_pos = (0, 0) + + if pacman.powerup: + self.mode = MODE.FRIGHETENED + pacman.sprite = get_sprites(pygame.image.load( + '../assets/blinky.png').convert_alpha()) + rand_pos = random.randint(0, 900), random.randint(0, 990) + for i in range(len(dx)): nx = self.x + dx[i] * self.speed ny = self.y + dy[i] * self.speed @@ -75,8 +87,11 @@ class Ghost(): if self.mode == MODE.SCATTERED: ret[i] = self.heuristic( (nx, ny), default_tile[0], default_tile[1]) - else: + elif self.mode == MODE.CHASING: ret[i] = self.heuristic((nx, ny), pacman.x, pacman.y) + elif self.mode == MODE.FRIGHETENED: + ret[i] = self.heuristic( + (nx, ny), rand_pos[0], rand_pos[1]) if settings.debug: pygame.draw.line(screen, self.color, (pacman.x, pacman.y), (self.x, self.y), 1) @@ -103,9 +118,23 @@ class Ghost(): self.x %= 900 # The logic of the portal self.last_move = min_idx - def draw(self, screen): + def draw(self, screen, powerup, counter): radius = 30 // 2 pos = (self.x - radius, self.y - radius) - image = pygame.transform.scale( - self.sprite[sprite_sheet[self.last_move]], (40, 40)) - screen.blit(image, pos) + if powerup: + self.sprite = get_sprites(pygame.image.load( + f'../assets/pacman_{self.color}.png').convert_alpha()) + image = pygame.transform.scale(self.sprite[counter // 5], (35, 35)) + if self.last_move == DIRECTION.UP.value: + screen.blit(pygame.transform.rotate(image, 270), pos) + elif self.last_move == DIRECTION.DOWN.value: + screen.blit(pygame.transform.rotate(image, 90), pos) + elif self.last_move == DIRECTION.RIGHT.value: + screen.blit(pygame.transform.flip(image, True, False), pos) + elif self.last_move == DIRECTION.LEFT.value: + screen.blit(image, pos) + else: + self.sprite = get_sprites(self.sprite_sheet) + image = pygame.transform.scale( + self.sprite[sprite_sheet[self.last_move]], (40, 40)) + screen.blit(image, pos) diff --git a/src/inky.py b/src/inky.py index 070faa0..1989048 100644 --- a/src/inky.py +++ b/src/inky.py @@ -1,4 +1,5 @@ import math +import random from typing_extensions import override from direction import DIRECTION from mode import MODE @@ -47,7 +48,7 @@ class Inky(Ghost): return target @override - def get_next_move(self, target, maze, screen, blinky): + def get_next_move(self, pacman, maze, screen, blinky): default_tile = self.get_default_tile() dx = [1, 0, -1, 0] @@ -66,9 +67,15 @@ class Inky(Ghost): if self.last_move == 3: forbidden = 1 - inter_tile = self.get_intermediate_tile(target) + inter_tile = self.get_intermediate_tile(pacman) target = self.get_target(inter_tile, blinky) + rand_pos = (0, 0) + + if pacman.powerup: + self.mode = MODE.FRIGHETENED + rand_pos = random.randint(0, 900), random.randint(0, 990) + # y = mx + c if settings.debug: @@ -83,7 +90,10 @@ class Inky(Ghost): if self.mode == MODE.SCATTERED: ret[i] = self.heuristic( (nx, ny), default_tile[0], default_tile[1]) - else: + elif self.mode == MODE.FRIGHETENED: + ret[i] = self.heuristic( + (nx, ny), rand_pos[0], rand_pos[1]) + elif self.mode == MODE.CHASING: ret[i] = self.heuristic( (nx, ny), target[0], target[1]) diff --git a/src/pinky.py b/src/pinky.py index 688163b..416e7d8 100644 --- a/src/pinky.py +++ b/src/pinky.py @@ -1,4 +1,5 @@ import pygame +import random from typing_extensions import override from direction import DIRECTION from mode import MODE @@ -61,6 +62,8 @@ class Pinky(Ghost): if self.last_move == 3: forbidden = 1 + rand_pos = (0, 0) + new_target = self.get_four_tiles_ahead_of_pacman(target) if settings.debug: pygame.draw.circle(screen, self.color, @@ -68,6 +71,10 @@ class Pinky(Ghost): pygame.draw.circle(screen, self.color, default_tile, 15) + if target.powerup: + self.mode = MODE.FRIGHETENED + rand_pos = random.randint(0, 900), random.randint(0, 990) + for i in range(len(dx)): if i != forbidden: nx = self.x + dx[i] * self.speed @@ -76,7 +83,10 @@ class Pinky(Ghost): if self.mode == MODE.SCATTERED: ret[i] = self.heuristic( (nx, ny), default_tile[0], default_tile[1]) - else: + elif self.mode == MODE.FRIGHETENED: + ret[i] = self.heuristic( + (nx, ny), rand_pos[0], rand_pos[1]) + elif self.mode == MODE.CHASING: ret[i] = self.heuristic( (nx, ny), new_target[0], new_target[1]) if settings.debug: diff --git a/src/player.py b/src/player.py index 903c358..97d2ea5 100644 --- a/src/player.py +++ b/src/player.py @@ -1,4 +1,5 @@ from direction import DIRECTION +from timer import Timer import map as Map from util import get_sprites import pygame @@ -8,9 +9,12 @@ class Player(): def __init__(self, sprite_sheet): self.x = 30 * 17 - 15 self.y = 30 * 25 - 15 + self.sprite_sheet = sprite_sheet self.sprite = get_sprites(sprite_sheet) self.speed = 6 self.direction = DIRECTION.LEFT + self.powerup = False + self.timer = None # checks if the current position of pacman is either a dot, big dot or free def is_valid(self, maze, x, y): @@ -20,6 +24,9 @@ class Player(): is_free = maze.maze[y][x] == 0 if is_dot or is_big_dot: maze.maze[y][x] = 0 + if is_big_dot: + self.powerup = True + self.timer = Timer(5 * 1000) return (is_dot or is_free or is_big_dot) return True @@ -42,14 +49,28 @@ class Player(): return True def draw(self, screen, counter): + if self.timer is not None: + elapsed_time = pygame.time.get_ticks() - self.timer.start + if elapsed_time > self.timer.duration: + self.powerup = False + radius = 30 // 2 pos = (self.x - radius, self.y - radius) - image = pygame.transform.scale(self.sprite[counter // 5], (35, 35)) - if self.direction == DIRECTION.UP: - screen.blit(pygame.transform.rotate(image, 270), pos) - elif self.direction == DIRECTION.DOWN: - screen.blit(pygame.transform.rotate(image, 90), pos) - elif self.direction == DIRECTION.RIGHT: - screen.blit(pygame.transform.flip(image, True, False), pos) - elif self.direction == DIRECTION.LEFT: + sprite_sheet = [2, 0, 3, 1] + if self.powerup: + self.sprite = get_sprites(pygame.image.load( + '../assets/pacman_as_ghost.png').convert_alpha()) + image = pygame.transform.scale( + self.sprite[sprite_sheet[self.direction.value]], (40, 40)) screen.blit(image, pos) + else: + self.sprite = get_sprites(self.sprite_sheet) + image = pygame.transform.scale(self.sprite[counter // 5], (35, 35)) + if self.direction == DIRECTION.UP: + screen.blit(pygame.transform.rotate(image, 270), pos) + elif self.direction == DIRECTION.DOWN: + screen.blit(pygame.transform.rotate(image, 90), pos) + elif self.direction == DIRECTION.RIGHT: + screen.blit(pygame.transform.flip(image, True, False), pos) + elif self.direction == DIRECTION.LEFT: + screen.blit(image, pos) diff --git a/src/timer.py b/src/timer.py new file mode 100644 index 0000000..769534b --- /dev/null +++ b/src/timer.py @@ -0,0 +1,7 @@ +import pygame + + +class Timer(): + def __init__(self, durat