379 lines
18 KiB
Python
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()
|