Files
2024-10-29 17:18:30 +01:00

379 lines
18 KiB
Python

import os
import time
import pygame as pg
from pygame import mixer
from Blinky import Blinky
from Clyde import Clyde
from DrawType import DrawType
from Inky import Inky
from Maze import Maze
from Pinky import Pinky
from Player import Player
from PointGrid import PointGrid
from Vec import Vec
class Game:
def __init__(self):
# Variables
self.screen_width = self.screen_height = 780
self.cell_count = 30
self.grid_cell_size = 26
self.screen = pg.display.set_mode((self.screen_width, self.screen_height), pg.RESIZABLE + pg.SCALED, vsync=1)
self.pause = False
self.highscore_out = 0
self.ghosteaten = 0
self.relative_start_time = self.absolute_start_time = time.time()
self.delta_start_time = 0
self.clock = pg.time.Clock()
# Maze and Points
self.maze = Maze(self.grid_cell_size)
self.maze.read_file("../Game_Package/maze.pac")
self.points = PointGrid(self.grid_cell_size, self.cell_count, self.maze, 3)
self.points_to_win = self.calc_points_to_win()
# Init Pygame
pg.init()
pg.display.set_icon(pg.image.load("../Game_Package/PacMan.png"))
pg.display.set_caption("PacMan")
mixer.init()
mixer.music.load("../Game_Package/pacman-background-music.wav")
mixer.music.queue("../Game_Package/pacman-background-music.wav", loops=100)
mixer.music.set_volume(0.05)
mixer.music.play()
self.click_sound = pg.mixer.Sound("../Game_Package/click.wav")
self.click_sound.set_volume(0.05)
# Players and Ghost
self.player = Player(Vec(0, 0), Vec(self.grid_cell_size / 2 - 1, self.grid_cell_size / 2 - 1), 2,
self.grid_cell_size, self.maze)
self.player.auto_place()
self.last_time_ghosts_afraid = 0
self.blinky = Blinky(Vec(12 * self.grid_cell_size, 15 * self.grid_cell_size),
Vec(self.grid_cell_size, self.grid_cell_size), self.cell_count, self.grid_cell_size,
self.player, self.maze.maze, 2)
self.pinky = Pinky(Vec(13 * self.grid_cell_size, 15 * self.grid_cell_size),
Vec(self.grid_cell_size, self.grid_cell_size), self.cell_count, self.grid_cell_size,
self.player, self.maze.maze, 5.5)
self.inky = Inky(Vec(14 * self.grid_cell_size, 15 * self.grid_cell_size),
Vec(self.grid_cell_size, self.grid_cell_size), self.cell_count, self.grid_cell_size,
self.player, self.maze.maze, self.blinky.position, 9)
self.clyde = Clyde(Vec(15 * self.grid_cell_size, 15 * self.grid_cell_size),
Vec(self.grid_cell_size, self.grid_cell_size), self.cell_count, self.grid_cell_size,
self.player, self.maze.maze, 12.5)
# Images
self.player_image1 = pg.image.load("../Game_Package/PacMan.png")
self.player_image2 = pg.image.load("../Game_Package/PacMan2.png")
self.blinky_image = pg.image.load("../Game_Package/Blinky.png")
self.pinky_image = pg.image.load("../Game_Package/Pinky.png")
self.inky_image = pg.image.load("../Game_Package/Inky.png")
self.clyde_image = pg.image.load("../Game_Package/Clyde.png")
self.ghost_afraid_image = pg.image.load("../Game_Package/GhostAfraid.png")
# Scale Images
self.player_image1 = pg.transform.scale(self.player_image1, (self.grid_cell_size, self.grid_cell_size))
self.player_image2 = pg.transform.scale(self.player_image2, (self.grid_cell_size, self.grid_cell_size))
self.blinky_image = pg.transform.scale(self.blinky_image, (self.grid_cell_size, self.grid_cell_size))
self.pinky_image = pg.transform.scale(self.pinky_image, (self.grid_cell_size, self.grid_cell_size))
self.inky_image = pg.transform.scale(self.inky_image, (self.grid_cell_size, self.grid_cell_size))
self.clyde_image = pg.transform.scale(self.clyde_image, (self.grid_cell_size, self.grid_cell_size))
self.ghost_afraid_image = pg.transform.scale(self.ghost_afraid_image,
(self.grid_cell_size, self.grid_cell_size))
# Call the Game Loop
self.game_loop()
# Highscore des Spielers wird in der highscore.pac gespeichert bzw. überschrieben
def save_highscore(self):
if os.path.getsize("highscore.pac") == 0:
with open("highscore.pac", "w") as overwrite:
overwrite.write(str(int(self.delta_start_time)))
self.highscore_out = self.delta_start_time
else:
with open("highscore.pac", "r") as getScore:
if self.delta_start_time < int(getScore.read()):
with open("highscore.pac", "w") as overwrite:
overwrite.write(str(int(self.delta_start_time)))
self.highscore_out = self.delta_start_time
# Geister werden gezeichnet, wenn sie nicht gefressen wurden
def draw_ghosts(self, afraid):
if not self.blinky.eaten:
img_blinky = pg.transform.flip(self.ghost_afraid_image if afraid else self.blinky_image,
self.blinky.is_mirrored(), False)
self.blinky.draw(self.screen, (0, 0, 0), DrawType.image, self.grid_cell_size, img_blinky)
if not self.pinky.eaten:
img_pinky = pg.transform.flip(self.ghost_afraid_image if afraid else self.pinky_image,
self.pinky.is_mirrored(), False)
self.pinky.draw(self.screen, (0, 0, 0), DrawType.image, self.grid_cell_size, img_pinky)
if not self.inky.eaten:
img_inky = pg.transform.flip(self.ghost_afraid_image if afraid else self.inky_image,
self.inky.is_mirrored(), False)
self.inky.draw(self.screen, (0, 0, 0), DrawType.image, self.grid_cell_size, img_inky)
if not self.clyde.eaten:
img_clyde = pg.transform.flip(self.ghost_afraid_image if afraid else self.clyde_image,
self.clyde.is_mirrored(), False)
self.clyde.draw(self.screen, (0, 0, 0), DrawType.image, self.grid_cell_size, img_clyde)
# Ausgabe der Punkteanzahl für den Spieler
def draw_overlay_points(self):
surface = pg.font.SysFont("Comic Sans", 22).render("Punkte: " + str(self.player.score + self.ghosteaten), True,
(0, 0, 0))
rect = surface.get_rect()
rect.center = (self.screen_width // 2, self.screen_height - 30)
self.screen.blit(surface, rect)
# Ausgabe der übrigen Lebensanzahl des Spielers
def draw_overlay_hp(self):
surface = pg.font.SysFont("Comic Sans", 22).render("Übrige Leben: " + str(self.player.hp - 1), True, (0, 0, 0))
rect = surface.get_rect()
rect.center = (self.screen_width, self.screen_height - 30)
rect.right = (self.screen_width - 10)
self.screen.blit(surface, rect)
# Berechnung und Ausgabe der Rundenzeit in Minuten und Sekunden
def draw_overlay_time(self):
delta_time_sec = int(time.time() - self.absolute_start_time)
rest = delta_time_sec % 60
delta_time_minute = delta_time_sec - rest
surface = pg.font.SysFont("Comic Sans", 22).render(str(delta_time_minute // 60) + " min " + str(rest) + " sek",
True, (0, 0, 0))
rect = surface.get_rect()
rect.center = (0, self.screen_height - 30)
rect.left = 10
self.screen.blit(surface, rect)
# Ausgabe des abgespeicherten Highscores
def draw_high_score(self):
# Wenn die highscore.pac Datei leer ist, wird ein vorgeschriebener Text ausgegeben.
if os.path.getsize("../Game_Package/highscore.pac") == 0:
text = "Schnellste gewonnene Runde: Keine vorhanden :P"
else: # Ansonsten wird die Datei ausgelesen und der Highscore angezeigt.
with open("../Game_Package/highscore.pac", "r") as myTest:
content = myTest.read()
sec = int(content) % 60
minute = int(content) / 60
text = "Schnellste gewonnene Runde: " + str(int(minute)) + " Minuten " + str(sec) + " Sekunden"
highscore_screen = pg.font.SysFont("Comic Sans", 22).render(text, True, (0, 0, 0))
highscore_rect = highscore_screen.get_rect()
highscore_rect.center = (self.screen_width // 2, self.screen_height // 2 + 80)
self.screen.blit(highscore_screen, highscore_rect)
def draw_current_score(self):
sec = int(self.delta_start_time % 60)
minute = int(self.delta_start_time / 60)
currentscore_screen = pg.font.SysFont("Comic Sans", 22).render(
"Aktuelle Runde: " + str(minute) + " Minuten " + str(sec) + " Sekunden",
True,
(0, 0, 0))
currentscore_rect = currentscore_screen.get_rect()
currentscore_rect.center = (self.screen_width // 2, self.screen_height // 2 + 110)
self.screen.blit(currentscore_screen, currentscore_rect)
# Alle Attribute des Spielers, der Geister, sowie die Zeit werden zurückgesetzt
def restart(self):
self.player.hp = 3
self.player.score = 0
self.ghosteaten = 0
self.absolute_start_time = self.relative_start_time = time.time()
self.reset_positions()
self.blinky.stateChange = self.pinky.stateChange = self.inky.stateChange = self.clyde.stateChange = time.time()
self.last_time_ghosts_afraid = 0
self.blinky.chase = self.pinky.chase = self.inky.chase = self.clyde.chase = True
self.points = PointGrid(self.grid_cell_size, self.cell_count, self.maze, 3)
self.pause = False
# Ausgabe des Win-Fensters für den Spieler bei gewonnener Runde
def win_screen(self):
self.save_highscore() # Highscore wird abgespeichert
win_screen = pg.font.SysFont("Comic Sans", 70).render("Gewonnen!", True, (0, 0, 0))
win_rect = win_screen.get_rect()
win_rect.center = (self.screen_width // 2, self.screen_height // 2)
self.screen.blit(win_screen, win_rect)
restart_screen = pg.font.SysFont("Comic Sans", 22).render("Drücke 'R' um noch eine Runde zu spielen!", True,
(0, 0, 0))
restart_rect = restart_screen.get_rect()
restart_rect.center = (self.screen_width // 2, self.screen_height // 2 + 50)
self.screen.blit(restart_screen, restart_rect)
self.draw_high_score()
self.draw_current_score()
keys = pg.key.get_pressed()
self.pause = True
if keys[pg.K_r]:
self.restart()
# Ausgabe des Loose-Fensters für den Spieler bei verlorener Runde, Highscore wird hier nicht abgespeichert
def loose_screen(self):
win_screen = pg.font.SysFont("Comic Sans", 70).render("Game Over!", True, (0, 0, 0))
win_rect = win_screen.get_rect()
win_rect.center = (self.screen_width // 2, self.screen_height // 2)
self.screen.blit(win_screen, win_rect)
restart_screen = pg.font.SysFont("Comic Sans", 22).render("Drücke 'R' um es wieder zu versuchen!", True,
(0, 0, 0))
restart_rect = restart_screen.get_rect()
restart_rect.center = (self.screen_width // 2, self.screen_height // 2 + 50)
self.screen.blit(restart_screen, restart_rect)
self.draw_high_score()
self.draw_current_score()
keys = pg.key.get_pressed()
self.pause = True
if keys[pg.K_r]:
self.restart()
def game_loop(self):
should_close = False
while not should_close:
for e in pg.event.get():
if e.type == pg.QUIT:
should_close = True
# Clear the screen
self.screen.fill((255, 255, 255))
# Draw the maze
self.maze.draw(self.screen, (200, 200, 200), (150, 150, 150))
# Draw the points
self.points.draw(self.screen, (255, 100, 0), DrawType.circle)
if not self.pause:
self.delta_start_time = time.time() - self.absolute_start_time
# Player
self.player.poll_keys(self.maze)
self.player.move()
self.player.check_bounds_move(self.screen_width, self.screen_height)
self.player.draw(self.screen, (0, 0, 0), DrawType.image, self.grid_cell_size, pg.transform.rotate(
self.player_image1 if self.player.get_state() else self.player_image2, self.player.get_rotation()))
self.check_points_possible_to_eat()
afraid = time.time() - self.last_time_ghosts_afraid < 8
self.blinky.run(afraid, self.relative_start_time)
self.pinky.run(afraid, self.relative_start_time)
self.inky.run(afraid, self.relative_start_time)
self.clyde.run(afraid, self.relative_start_time)
self.draw_ghosts(afraid)
self.draw_overlay_points()
self.draw_overlay_hp()
self.draw_overlay_time()
self.check_ghosts_collisions()
# Wenn der Spieler mehr oder gleich viele Punkte hat, wie es gezeichnete Punkte gibt,
# soll der Win-Screen ausgegeben werden.
if self.player.score >= self.points_to_win:
self.save_highscore()
self.win_screen()
# Wenn der Spieler keine Leben mehr hat, wird der loose-Screen angezeigt.
elif self.player.hp <= 0:
self.loose_screen()
pg.display.flip()
self.clock.tick(60)
self.on_game_exit()
# Berechnet die Punkte, die der Spieler benötigt, um das Spiel zu gewinnen.
def calc_points_to_win(self):
count = 0
# Es wird über das Maze-Array iteriert
for i in self.maze.maze:
for j in i:
# wenn an die Stelle maze[i][j] begehbar, also Null ist, existiert an dieser Stelle ein Punkt,
# der gefressen werden muss.
if j == 0:
count += 1
count -= 4
count *= 10
count += 4 * 50 # Idle-Punkte werden extra berechnet.
return count
@staticmethod
def on_game_exit():
pg.quit()
# Punkte die nah genug an Pac Man sind um gefressen zu werden
def check_points_possible_to_eat(self):
check = Vec(self.player.position.x + self.grid_cell_size / 2, self.player.position.y + self.grid_cell_size / 2)
surrounding_points = self.points.get_surrounding_points(check) # Punkte um Pac Man herum werden abgespeichert
for point in surrounding_points: # diese werden nun durchiteriert
# wenn die Punkte nah genug sind und noch nicht gegessen wurden, wird ein Sound abgespielt
# und die Punkte werden gegessen.
if check.distance(point.position) < 3:
if not point.eaten:
pg.mixer.Sound.play(self.click_sound)
if point.big:
self.player.score += 50
self.last_time_ghosts_afraid = time.time()
else:
self.player.score += 10
point.eat()
# Prüft Kollision zwischen Pac Man und den Geistern im fressbaren Zustand
def check_ghosts_collisions(self):
global ghosteaten
# Wenn Geister im fressbaren Zustand sind, aber ihre Position nicht nah genug an Pac Man dran ist,
# passiert nichts.
if self.blinky.eaten and self.blinky.position.distance(self.player.position) < 20:
pass
elif self.pinky.eaten and self.pinky.position.distance(self.player.position) < 20:
pass
elif self.inky.eaten and self.inky.position.distance(self.player.position) < 20:
pass
elif self.clyde.eaten and self.clyde.position.distance(self.player.position) < 20:
pass
elif (self.blinky.position.distance(self.player.position) < 20 or self.pinky.position.distance(
self.player.position) < 20 or self.inky.position.distance(
self.player.position) < 20 or self.clyde.position.distance(self.player.position) < 20) and \
time.time() - self.player.last_time_hit > 1 and time.time() - self.last_time_ghosts_afraid > 8:
self.relative_start_time = time.time()
self.player.last_time_hit = time.time()
self.player.hp -= 1
self.reset_positions()
self.blinky.eaten = False
self.pinky.eaten = False
self.inky.eaten = False
self.clyde.eaten = False
# Wenn Geister im fressbaren Zustand sind und ihre Position nah genug an Pac Man ist,
# wird ihr Zustand auf gefressen gesetzt und der Spieler bekommt Bonus Punkte
elif self.blinky.position.distance(self.player.position) < 20:
self.blinky.eaten = True
self.ghosteaten = self.ghosteaten + 200
elif self.pinky.position.distance(self.player.position) < 20:
self.pinky.eaten = True
self.ghosteaten = self.ghosteaten + 200
elif self.inky.position.distance(self.player.position) < 20:
self.inky.eaten = True
self.ghosteaten = self.ghosteaten + 200
elif self.clyde.position.distance(self.player.position) < 20:
self.clyde.eaten = True
self.ghosteaten = self.ghosteaten + 200
def reset_positions(self):
self.player.auto_place()
self.blinky.reset_position()
self.pinky.reset_position()
self.inky.reset_position()
self.clyde.reset_position()