aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md12
-rw-r--r--assets/pacman_as_ghost.pngbin0 -> 5849 bytes
-rw-r--r--assets/pacman_cyan.pngbin0 -> 4713 bytes
-rw-r--r--assets/pacman_orange.pngbin0 -> 4679 bytes
-rw-r--r--assets/pacman_pink.pngbin0 -> 4695 bytes
-rw-r--r--assets/pacman_red.pngbin0 -> 4754 bytes
-rw-r--r--src/blinky.py4
-rw-r--r--src/clyde.py86
-rw-r--r--src/game.py246
-rw-r--r--src/game_state.py50
-rw-r--r--src/ghost.py120
-rw-r--r--src/inky.py179
-rw-r--r--src/map.py29
-rw-r--r--src/mode.py8
-rw-r--r--src/pinky.py68
-rw-r--r--src/player.py62
-rw-r--r--src/settings.py3
-rw-r--r--src/timer.py7
18 files changed, 643 insertions, 231 deletions
diff --git a/README.md b/README.md
index 5ecf749..3c627a9 100644
--- a/README.md
+++ b/README.md
@@ -26,17 +26,19 @@ python3 macpan.py
- [X] Add the Algorithm for Pinky (Normal A* + 4 spaces ahead of pacman)
- [X] Add the ghosts in the game
- [X] Setup the sprite animation for the ghosts
-- [ ] Add the Algorithm for Inky (Ambush)
-- [ ] Add the Algorithm for Clyde (Same as Blinky excepts when he gets 8 tiles close to pacman he retreats, So he is basically useless)
+- [X] Add the Algorithm for Clyde (Same as Blinky excepts when he gets 8 tiles close to pacman he retreats, So he is basically useless)
+- [X] Add the Algorithm for Inky (Ambush)
## Optional
+- [X] Add scattered mode for the ghosts
+- [X] Add firghtened mode for the ghosts
+- [X] Add Eaten mode for the ghosts
+- [X] Add powerups
- [ ] Setup a menu for the game
- [ ] Setup a simple score system
- [ ] Setup a proper sfx/audio for the game
-- [ ] Add powerups
-- [ ] Add firghtening mode for the ghosts
-# EXTRA BONUS
+## EXTRA BONUS
- [ ] Make a nueral network agent that fully plays the game alone using reinforcment learning and PyTorch
diff --git a/assets/pacman_as_ghost.png b/assets/pacman_as_ghost.png
new file mode 100644
index 0000000..c30c099
--- /dev/null
+++ b/assets/pacman_as_ghost.png
Binary files differ
diff --git a/assets/pacman_cyan.png b/assets/pacman_cyan.png
new file mode 100644
index 0000000..be3bc47
--- /dev/null
+++ b/assets/pacman_cyan.png
Binary files differ
diff --git a/assets/pacman_orange.png b/assets/pacman_orange.png
new file mode 100644
index 0000000..2606cb5
--- /dev/null
+++ b/assets/pacman_orange.png
Binary files differ
diff --git a/assets/pacman_pink.png b/assets/pacman_pink.png
new file mode 100644
index 0000000..ebb9537
--- /dev/null
+++ b/assets/pacman_pink.png
Binary files differ
diff --git a/assets/pacman_red.png b/assets/pacman_red.png
new file mode 100644
index 0000000..70621e2
--- /dev/null
+++ b/assets/pacman_red.png
Binary files differ
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..2fd4aee 100644
--- a/src/clyde.py
+++ b/src/clyde.py
@@ -1,9 +1,93 @@
from ghost import Ghost
+import random
+import pygame
+from settings import settings
+from mode import MODE
+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_default_tile(self):
+ return (2 * 30 + 15, 30 * 30 + 15)
+
+ @override
+ def get_intial_tile(self):
+ return (14 * 30 + 15, 12 * 30 + 15)
+
+ @override
+ def get_next_move(self, game_state, screen):
+ default_tile = self.get_default_tile()
+
+ 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(game_state.map.maze) - 1 - 1 - 0.5) * 30)
+
+ forbidden = inv_dir[self.last_move]
+
+ rand_pos = (0, 0)
+
+ if game_state.pacman.powerup and self.mode != MODE.EATEN:
+ self.mode = MODE.FRIGHETENED
+ rand_pos = random.randint(0, 900), random.randint(0, 990)
+ if game_state.pacman.powerup is False and self.mode == MODE.FRIGHETENED:
+ self.mode = MODE.CHASING
+ if settings.debug:
+ pygame.draw.line(screen, self.color, (game_state.pacman.x, game_state.pacman.y),
+ (self.x, self.y), 1)
+ 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, game_state.map.maze):
+ if i != forbidden:
+ if self.mode == MODE.SCATTERED:
+ ret[i] = self.heuristic(
+ (nx, ny), default_tile[0], default_tile[1])
+ 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(game_state.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), game_state.pacman.x, game_state.pacman.y)
+ if settings.debug:
+ pygame.draw.line(screen, self.color, (game_state.pacman.x, game_state.pacman.y),
+ (self.x, self.y), 1)
+ elif self.mode == MODE.EATEN:
+ pos = self.get_intial_tile()
+ self.x = pos[0]
+ self.y = pos[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 37739e3..a7efb67 100644
--- a/src/game.py
+++ b/src/game.py
@@ -1,21 +1,102 @@
-from blinky import Blinky
-from clyde import Clyde
from direction import DIRECTION
-from inky import Inky
-from pinky import Pinky
-from player import Player
+from game_state import GameState
+from mode import MODE
from settings import settings
-import map as Map
+from game_state import WIDTH, HEIGHT, TILE_WIDTH, TILE_HEIGHT
import pygame
-WIDTH = settings.width
-HEIGHT = settings.height
-
class Game():
def __init__(self):
self.settings = settings
+ def show_gameover_screen(self, screen, game_state, sprites):
+ font = pygame.font.SysFont(None, 64)
+
+ # Render the "Game Over" text to a surface
+ game_over_text_1 = font.render(
+ "Game Over", True, (255, 255, 255))
+ game_over_text_2 = font.render(
+ "Press R to try again or Q to quit.", True, (255, 255, 255))
+
+ # Blit the "Game Over" text onto the screen
+ text_rect_1 = game_over_text_1.get_rect(
+ center=(WIDTH/2, HEIGHT/2 - 75))
+ text_rect_2 = game_over_text_2.get_rect(
+ center=(WIDTH/2, HEIGHT/2))
+
+ screen.blit(game_over_text_1, text_rect_1)
+ screen.blit(game_over_text_2, text_rect_2)
+
+ # Update the display
+ pygame.display.flip()
+
+ quit_game = False # Initialize the flag variable
+ while not quit_game:
+ for event in pygame.event.get():
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_r:
+ # Reset the game and start again
+ # Add your own code here to reset the game state
+ self.reset_game(game_state, sprites)
+ game_state.game_over = False
+ quit_game = True # Set the flag to True to break out of both loops
+ break
+ elif event.type == pygame.KEYDOWN and event.key == pygame.K_q:
+ game_state.game_over = True
+ quit_game = True
+ break
+ elif event.type == pygame.QUIT:
+ game_state.game_over = True
+ quit_game = True # Set the flag to True to break out of both loops
+ break
+
+ def show_wining_screen(self, screen, game_state, sprites):
+ font = pygame.font.SysFont(None, 64)
+
+ # Render the "Game Over" text to a surface
+ wining_text_1 = font.render(
+ "Congratulation You Won!!", True, (255, 255, 255))
+ wining_text_2 = font.render(
+ "Press R to play again or Q to quit", True, (255, 255, 255))
+
+ text_rect_1 = wining_text_1.get_rect(
+ center=(WIDTH/2, HEIGHT/2 - 75))
+ text_rect_2 = wining_text_2.get_rect(
+ center=(WIDTH/2, HEIGHT/2))
+
+ # Blit the "Game Over" text onto the screen
+ text_rect_1 = wining_text_1.get_rect(
+ center=(WIDTH/2, HEIGHT/2))
+ text_rect_2 = wining_text_2.get_rect(
+ center=(WIDTH/2, HEIGHT/2 + 100))
+ screen.blit(wining_text_1, text_rect_1)
+ screen.blit(wining_text_2, text_rect_2)
+
+ # Update the display
+ pygame.display.flip()
+
+ quit_game = False # Initialize the flag variable
+ while not quit_game:
+ for event in pygame.event.get():
+ if event.type == pygame.KEYDOWN and event.key == pygame.K_r:
+ # Reset the game and start again
+ # Add your own code here to reset the game state
+ self.reset_game(game_state, sprites)
+ game_state.game_over = False
+ quit_game = True # Set the flag to True to break out of both loops
+ break
+ elif event.type == pygame.KEYDOWN and event.key == pygame.K_q:
+ game_state.game_over = True
+ quit_game = True
+ break
+ elif event.type == pygame.QUIT:
+ game_state.game_over = True
+ quit_game = True # Set the flag to True to break out of both loops
+ break
+
+ def reset_game(self, game_state, sprites):
+ game_state.reset(sprites)
+
def run(self):
# Initialize Pygame
pygame.init()
@@ -24,27 +105,24 @@ 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()
+ sprites = [sprite_sheet, blinky_sprite,
+ pinky_sprite, inky_sprite, clyde_sprite]
- # length of the map grid size
- TILE_WIDTH = WIDTH // len(maze.maze[0])
- TILE_HEIGHT = HEIGHT // len(maze.maze)
+ # 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)
- # 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)
+ game_state = GameState(sprites)
# Set the pacman velocity
dx = 0
@@ -57,15 +135,13 @@ class Game():
pygame.mixer.music.load('../assets/sfx/game_start.wav')
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)
- is_game_over = [False]
+ if settings.sound:
+ pygame.mixer.music.play()
+ siren_sound.play(-1)
# Main game loop
- while not is_game_over[0]:
+ while not game_state.game_over:
# setting game fps
clock.tick(settings.fps)
@@ -84,95 +160,101 @@ class Game():
# Handling events
for event in pygame.event.get():
if event.type == pygame.QUIT:
- is_game_over = False
+ game_state.game_over = True
elif event.type == pygame.KEYDOWN:
# Move the circle based on the pressed key
if event.key == pygame.K_w:
- player.direction = DIRECTION.UP
- ty = -player.speed
+ game_state.pacman.direction = DIRECTION.UP
+ ty = -game_state.pacman.speed
tx = 0 # Necssarry to move only horizontal or vertical
elif event.key == pygame.K_s:
- player.direction = DIRECTION.DOWN
- ty = player.speed
+ game_state.pacman.direction = DIRECTION.DOWN
+ ty = game_state.pacman.speed
tx = 0 # Necssarry to move only horizontal or vertical
elif event.key == pygame.K_a:
- player.direction = DIRECTION.LEFT
- tx = -player.speed
+ game_state.pacman.direction = DIRECTION.LEFT
+ tx = -game_state.pacman.speed
ty = 0 # Necssarry to move only horizontal or vertical
elif event.key == pygame.K_d:
- player.direction = DIRECTION.RIGHT
- tx = player.speed
+ game_state.pacman.direction = DIRECTION.RIGHT
+ tx = game_state.pacman.speed
ty = 0 # Necssarry to move only horizontal or vertical
+ # Check for the timer event
+ if event.type == timer_event:
+ game_state.pinky.mode = MODE.CHASING
+ game_state.inky.mode = MODE.CHASING
+ game_state.blinky.mode = MODE.CHASING
+ game_state.clyde.mode = MODE.CHASING
keys = pygame.key.get_pressed()
# Simulates holding the key which adds better playability for pacman
if keys[pygame.K_w]:
- player.direction = DIRECTION.UP
- ty = -player.speed
+ game_state.pacman.direction = DIRECTION.UP
+ ty = -game_state.pacman.speed
tx = 0
elif keys[pygame.K_s]:
- player.direction = DIRECTION.DOWN
- ty = player.speed
+ game_state.pacman.direction = DIRECTION.DOWN
+ ty = game_state.pacman.speed
tx = 0
elif keys[pygame.K_a]:
- player.direction = DIRECTION.LEFT
- tx = -player.speed
+ game_state.pacman.direction = DIRECTION.LEFT
+ tx = -game_state.pacman.speed
ty = 0
elif keys[pygame.K_d]:
- player.direction = DIRECTION.RIGHT
- tx = player.speed
+ game_state.pacman.direction = DIRECTION.RIGHT
+ tx = game_state.pacman.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):
+ if game_state.pacman.check_collision(game_state, tx, ty, TILE_WIDTH, TILE_HEIGHT):
dx = tx
dy = ty
if dx < 0:
- player.direction = DIRECTION.LEFT
+ game_state.pacman.direction = DIRECTION.LEFT
elif dx > 0:
- player.direction = DIRECTION.RIGHT
+ game_state.pacman.direction = DIRECTION.RIGHT
elif dy < 0:
- player.direction = DIRECTION.UP
+ game_state.pacman.direction = DIRECTION.UP
elif dy > 0:
- player.direction = DIRECTION.DOWN
-
- if player.check_collision(maze, dx, dy, TILE_WIDTH, TILE_HEIGHT):
- player.x += dx
- player.y += dy
- player.x %= 900
+ game_state.pacman.direction = DIRECTION.DOWN
+ if game_state.pacman.check_collision(game_state, dx, dy, TILE_WIDTH, TILE_HEIGHT):
+ game_state.pacman.x += dx
+ game_state.pacman.y += dy
+ game_state.pacman.x %= 900 # logic for portal
# 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)
+ game_state.blinky.move(game_state, screen)
+ game_state.pinky.move(game_state, screen)
+ game_state.inky.move(game_state, screen)
+ game_state.clyde.move(game_state, screen)
# Draw the map on each frame
- maze.draw_map(screen)
-
- # Draw the player and the ghosts
- player.draw(screen, counter)
- blinky.draw(screen)
- pinky.draw(screen)
- inky.draw(screen)
- clyde.draw(screen)
+ game_state.map.draw_map(screen)
+ # Draw the game_state.pacman and the ghosts
+ game_state.pacman.draw(screen, counter)
+ game_state.blinky.draw(screen, game_state.pacman.powerup, counter)
+ game_state.pinky.draw(screen, game_state.pacman.powerup, counter)
+ game_state.inky.draw(screen, game_state.pacman.powerup, counter)
+ game_state.clyde.draw(screen, game_state.pacman.powerup, counter)
- # Update the screen
- pygame.display.flip()
-
- # Quit Pygame
-
- #pygame.quit()
- screen = pygame.display.set_mode((1280, 720))
- pygame.mixer.music.stop()
- siren_sound.stop()
-
+ if game_state.food == 246:
+ self.show_wining_screen(screen, game_state, sprites)
+ if not game_state.is_pacman_alive:
+ self.show_gameover_screen(
+ screen, game_state, sprites)
+ game_state.is_pacman_alive = True
+ else:
+ # Update the screen
+ pygame.display.flip()
+ # Quit Pygame
+ print(game_state.score)
+ pygame.quit()
diff --git a/src/game_state.py b/src/game_state.py
new file mode 100644
index 0000000..5ed98c0
--- /dev/null
+++ b/src/game_state.py
@@ -0,0 +1,50 @@
+from blinky import Blinky
+from clyde import Clyde
+from inky import Inky
+from pinky import Pinky
+from player import Player
+from settings import settings
+import map as Map
+import pygame
+
+WIDTH = settings.width
+HEIGHT = settings.height
+maze = Map.Map()
+TILE_WIDTH = WIDTH // len(maze.maze[0])
+TILE_HEIGHT = HEIGHT // len(maze.maze)
+
+
+class GameState():
+ def __init__(self, sprites):
+ self.pacman = Player(sprites[0])
+ self.blinky = Blinky(sprites[1], 12 * TILE_WIDTH +
+ 15, 12 * TILE_HEIGHT + 15)
+ self.pinky = Pinky(sprites[2], 11 * TILE_WIDTH +
+ 15, 12 * TILE_HEIGHT + 15)
+ self.inky = Inky(sprites[3], 13 * TILE_WIDTH +
+ 15, 12 * TILE_HEIGHT + 15)
+ self.clyde = Clyde(sprites[4], 14 * TILE_WIDTH +
+ 15, 12 * TILE_HEIGHT + 15)
+ self.map = Map.Map()
+ self.food = 0
+ self.game_over = False
+ self.score = 0
+ self.is_pacman_alive = True
+
+ def reset(self, sprites):
+ self.pacman = Player(sprites[0])
+ self.blinky = Blinky(sprites[1], 12 * TILE_WIDTH +
+ 15, 12 * TILE_HEIGHT + 15)
+ self.pinky = Pinky(sprites[2], 11 * TILE_WIDTH +
+ 15, 12 * TILE_HEIGHT + 15)
+ self.inky = Inky(sprites[3], 13 * TILE_WIDTH +
+ 15, 12 * TILE_HEIGHT + 15)
+ self.clyde = Clyde(sprites[4], 14 * TILE_WIDTH +
+ 15, 12 * TILE_HEIGHT + 15)
+ self.map = Map.Map()
+ self.food = 0
+ self.game_over = False
+ self.is_pacman_alive = True
+ self.score = 0
+ timer_event = pygame.USEREVENT + 1
+ pygame.time.set_timer(timer_event, 1000 * 10, 1)
diff --git a/src/ghost.py b/src/ghost.py
index e810bfe..8568ff4 100644
--- a/src/ghost.py
+++ b/src/ghost.py
@@ -1,23 +1,33 @@
import pygame
+import time
+import random
import math
from util import get_sprites
+from timer import Timer
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
+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_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
+ self.last_move = 3 # this represents the direction based on the dx, dy arrays
self.speed = 3
+ self.timer = None
+ self.mode = MODE.SCATTERED
def in_bounds(self, pos):
(x, y) = pos
@@ -26,67 +36,133 @@ 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
+ def get_default_tile(self):
+ return (75, 75)
+
+ def get_intial_tile(self):
+ return (12 * 30 + 15, 12 * 30 + 15)
+
+ # 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, 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 check_pacman_collision(self, game_state):
+ if game_state.pacman.powerup and abs(game_state.pacman.x - self.x) <= 30 and abs(game_state.pacman.y - self.y) <= 30:
+ initial_position = self.get_intial_tile()
+ self.mode = MODE.EATEN
+ self.timer = Timer(2 * 1000)
+ time.sleep(1)
+ game_state.score += 200
+ self.x = initial_position[0]
+ self.y = initial_position[1]
+ elif not game_state.pacman.powerup and abs(game_state.pacman.x - self.x) <= 30 and abs(game_state.pacman.y - self.y) <= 30:
+ if abs(game_state.pacman.x - self.x) <= 30 and abs(game_state.pacman.y - self.y) <= 30:
+ game_state.is_pacman_alive = False
+
+ def get_next_move(self, game_state, screen):
- def get_next_move(self, pacman, maze, screen):
+ default_tile = self.get_default_tile()
ret = len(dx) * [math.inf]
forbidden = inv_dir[self.last_move]
-
+
+ rand_pos = (0, 0)
+
+ if game_state.pacman.powerup and self.mode != MODE.EATEN:
+ self.mode = MODE.FRIGHETENED
+ rand_pos = random.randint(0, 900), random.randint(0, 990)
+
+ if game_state.pacman.powerup is False and self.mode == MODE.FRIGHETENED:
+ self.mode = MODE.CHASING
+
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 self.check_collision(nx, ny, 30, 30, game_state.map.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])
+ elif self.mode == MODE.CHASING:
+ ret[i] = self.heuristic(
+ (nx, ny), game_state.pacman.x, game_state.pacman.y)
+ elif self.mode == MODE.FRIGHETENED:
+ ret[i] = self.heuristic(
+ (nx, ny), rand_pos[0], rand_pos[1])
+ elif self.mode == MODE.EATEN:
+ pos = self.get_intial_tile()
+ self.x = pos[0]
+ self.y = pos[1]
+ if settings.debug:
+ pygame.draw.line(screen, self.color, (game_state.pacman.x, game_state.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
-
- def move(self, maze, pacman, screen, game_over):
- 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)
+ def move(self, game_state, screen):
+ self.check_pacman_collision(game_state)
+ min_idx = self.get_next_move(game_state, screen)
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):
+ def draw(self, screen, powerup, counter):
+ if self.timer is not None:
+ elapsed_time = pygame.time.get_ticks() - self.timer.start
+ if elapsed_time > self.timer.duration:
+ self.mode = MODE.CHASING
+
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)
+ pos = (self.x - radius, self.y - radius)
+ 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 2f886a6..be2e62c 100644
--- a/src/inky.py
+++ b/src/inky.py
@@ -1,73 +1,118 @@
import math
+import random
+from typing_extensions import override
from direction import DIRECTION
+from mode import MODE
+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)
-
-
- # 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):
- # 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_intermediate_tile(target)
- # 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])
- #
- # min_idx = ret.index(min(ret))
- # return min_idx
- #
- #
- #
- #
+ 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 * 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)
+
+ @override
+ def get_default_tile(self):
+ return (27 * 30 + 15, 2 * 30 + 15)
+
+ @override
+ def get_intial_tile(self):
+ return (13 * 30 + 15, 12 * 30 + 15)
+
+ def get_target(self, inter_tile, blinky):
+ target = (max(inter_tile[0] - (blinky.x - inter_tile[0]) % 900, 0),
+ max(inter_tile[1] - (blinky.y - inter_tile[1]) % 990, 0))
+ return target
+
+ @override
+ def get_next_move(self, game_state, screen):
+ default_tile = self.get_default_tile()
+
+ 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(game_state.pacman)
+ target = self.get_target(inter_tile, game_state.blinky)
+
+ rand_pos = (0, 0)
+
+ if game_state.pacman.powerup and self.mode != MODE.EATEN:
+ self.mode = MODE.FRIGHETENED
+ rand_pos = random.randint(0, 900), random.randint(0, 990)
+
+ if game_state.pacman.powerup is False and self.mode == MODE.FRIGHETENED:
+ self.mode = MODE.CHASING
+
+ if settings.debug: