from github

This commit is contained in:
jonas
2024-10-29 17:18:30 +01:00
commit b0f85b5c87
63 changed files with 1781 additions and 0 deletions
BIN
View File
Binary file not shown.
+2
View File
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

+24
View File
@@ -0,0 +1,24 @@
import math
from Ghost import Ghost
from Vec import Vec
class Blinky(Ghost):
def __init__(self, start_position: Vec, start_size: Vec, cell_count,
grid_cell_size, player, maze_grid, start_delay):
super().__init__(start_position, start_size, cell_count, grid_cell_size, player, maze_grid, start_delay)
self.current_idle_point = Vec(2, 1)
# Aktiviert den Idle-Mode von Blinky:
# Er bewegt sich zu seinem definierten Idle-Point.
def idle(self):
self.move_to_point(self.current_idle_point)
# Aktiviert den Hunting-Mode von Blinky:
# Er bewegt sich zu der aktuellen Position des Spielers.
def hunting(self):
player_pos = Vec(math.floor((self.player.position.x + self.grid_cell_size / 2) / self.grid_cell_size),
math.floor((self.player.position.y + self.grid_cell_size / 2) / self.grid_cell_size))
self.move_to_point(player_pos)
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+43
View File
@@ -0,0 +1,43 @@
import math
import time
from Ghost import Ghost
from Vec import Vec
class Clyde(Ghost):
def __init__(self, start_position: Vec, start_size: Vec, cell_count,
grid_cell_size, player, maze_grid, start_delay):
super().__init__(start_position, start_size, cell_count, grid_cell_size, player, maze_grid, start_delay)
self.idle_points = [Vec(12, 12), Vec(17, 12), Vec(17, 15), Vec(12, 15)]
self.idle_index = 0
# Aktiviert den Idle-Mode von Clyde:
# Er begibt sich zurück in das Haus und läuft dort im Kreis.
def idle(self):
self.move_to_point(self.idle_points[self.idle_index])
if abs(self.idle_points[self.idle_index].mul_ret(self.grid_cell_size).distance(self.position)) <= self.speed:
self.idle_index += 1
if self.idle_index > 3:
self.idle_index = 0
# Aktiviert den Hunting-Mode von Clyde:
# Solange der Spieler mehr als 2 Felder von Clyde entfernt ist
# bewegt er sich zu der aktuellen Position des Spielers.
# Ist er näher als 2 Felder hört er auf PacMan zu jagen
def hunting(self):
if self.position.distance(self.player.position) > 2.5 * self.grid_cell_size:
player_pos = Vec(math.floor((self.player.position.x + self.grid_cell_size / 2) / self.grid_cell_size),
math.floor((self.player.position.y + self.grid_cell_size / 2) / self.grid_cell_size))
self.move_to_point(player_pos)
else:
self.stateChange = time.time()
self.chase = False
# Überschreibt den Timer aus der Ghost-Klasse.
# Wenn er den Spieler nicht jagt, dann wird self.chase nach 6 Sekunden wieder auf True gesetzt
def timer(self):
if not self.chase:
if time.time() - self.stateChange > 6:
self.chase = True
+4
View File
@@ -0,0 +1,4 @@
class DrawType:
rect = 0
circle = 1
image = 3
+28
View File
@@ -0,0 +1,28 @@
import pygame as pg
from DrawType import DrawType
from Vec import Vec
class Entity:
def __init__(self, start_position: Vec, start_size: Vec):
self.position = Vec(start_position.x, start_position.y)
self.size = start_size
def draw(self, screen, color, draw_type: DrawType = DrawType.rect, grid_cell_size=0, image=None):
# Either draw a...
match draw_type:
# ...Rect...
case DrawType.rect:
pg.draw.rect(screen, color, (self.position.x, self.position.y, self.size.x, self.size.y))
# ... or a circle
case DrawType.circle:
pg.draw.circle(screen, color, (self.position.x + grid_cell_size / 2,
self.position.y + grid_cell_size / 2), (self.size.x + self.size.y) / 2)
case DrawType.image:
screen.blit(image, (self.position.x, self.position.y))
+378
View File
@@ -0,0 +1,378 @@
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()
+390
View File
@@ -0,0 +1,390 @@
import sys
import pygame as pg
from pygame import mixer
from Blinky import Blinky
from Clyde import Clyde
from Inky import Inky
from Clyde import Clyde
from DrawType import DrawType
from Maze import Maze
from Pinky import Pinky
from Player import Player
from PointGrid import PointGrid
from Vec import Vec
import time
import os
# Variables
screen_width: int = 780
screen_height: int = 780
grid_cell_size: int = 26
startTime = time.time()
relativeStartTime = time.time()
delta_Time_Sec = 0
highscore = 0
clock = pg.time.Clock()
pointsToWin = 0
def auto_place_player(p: Player, m: Maze):
global default_pos_player
x = 15
for y in range(0, 29):
if m.is_free(Vec(x, y)):
p.position = Vec(x * grid_cell_size, y * grid_cell_size)
default_pos_player = Vec(x * grid_cell_size, y * grid_cell_size)
# Init Pygame
pg.init()
screen = pg.display.set_mode((screen_width, screen_height), pg.RESIZABLE + pg.SCALED, vsync=1)
overlayFont = pg.font.SysFont("Comic Sans", 22)
finishFont = pg.font.SysFont("Comic Sans", 70)
restartFont = pg.font.SysFont("Comic Sans", 22)
pg.display.set_icon(pg.image.load("/PacMan.png"))
pg.display.set_caption("PacMan")
pause = False
mixer.init()
mixer.music.load("/pacman-background-music.wav")
mixer.music.queue("/pacman-background-music.wav", loops=100)
mixer.music.set_volume(0.05)
mixer.music.play()
click_sound = pg.mixer.Sound("/click.wav")
click_sound.set_volume(0.05)
# Init Player
default_pos_player: Vec = None
player = Player(Vec(0, 0), Vec(grid_cell_size / 2 - 1, grid_cell_size / 2 - 1), 2, grid_cell_size)
player.velocity = Vec(0, 0)
# Generate Maze
maze = Maze(grid_cell_size)
maze.read_file("../Game/maze.pac")
auto_place_player(player, maze)
# Init Points
points = PointGrid(grid_cell_size, screen_width, screen_height, maze, 3)
# Ghosts
last_time_ghosts_afraid = 0
# Blinky
blinky = Blinky(Vec(12 * grid_cell_size, 15 * grid_cell_size), Vec(grid_cell_size / 2 - 1, grid_cell_size / 2 - 1),
screen_width, grid_cell_size, player, maze.maze)
blinky_start_delay = 2
# Pinky
pinky = Pinky(Vec(13 * grid_cell_size, 15 * grid_cell_size), Vec(grid_cell_size / 2 - 1, grid_cell_size / 2 - 1),
screen_width, grid_cell_size, player, maze.maze)
pinky_start_delay = 5.5
# Inky
inky = Inky(Vec(14 * grid_cell_size, 15 * grid_cell_size), Vec(grid_cell_size / 2 - 1, grid_cell_size / 2 - 1),
screen_width, grid_cell_size, player, maze.maze, blinky.position, screen)
inky_start_delay = 9
# Clyde
clyde = Clyde(Vec(15 * grid_cell_size, 15 * grid_cell_size), Vec(grid_cell_size / 2 - 1, grid_cell_size / 2 - 1),
screen_width, grid_cell_size, player, maze.maze)
clyde_start_delay = 12.5
# Images
player_image = pg.image.load("/PacMan.png")
player_image = pg.transform.scale(player_image, (grid_cell_size, grid_cell_size))
player_image2 = pg.image.load("/PacMan2.png")
player_image2 = pg.transform.scale(player_image2, (grid_cell_size, grid_cell_size))
ghost_afraid_image = pg.image.load("/GhostAfraid.png")
ghost_afraid_image = pg.transform.scale(ghost_afraid_image, (grid_cell_size, grid_cell_size))
blinky_image = pg.image.load("/Blinky.png")
blinky_image = pg.transform.scale(blinky_image, (grid_cell_size, grid_cell_size))
pinky_image = pg.image.load("/Pinky.png")
pinky_image = pg.transform.scale(pinky_image, (grid_cell_size, grid_cell_size))
inky_image = pg.image.load("/Inky.png")
inky_image = pg.transform.scale(inky_image, (grid_cell_size, grid_cell_size))
clyde_image = pg.image.load("/Clyde.png")
clyde_image = pg.transform.scale(clyde_image, (grid_cell_size, grid_cell_size))
# Function called when game should close
def on_game_exit():
pg.quit()
os.system("python3 ../Start/Start.py")
# function to clear the screen
def clear_screen():
screen.fill((255, 255, 255))
def check_points_possible_to_eat():
global last_time_ghosts_afraid
global pause
global finishFont
check = Vec(player.position.x + grid_cell_size / 2, player.position.y + grid_cell_size / 2)
surrounding_point = points.get_surrounding_points(check)
for point in surrounding_point:
if check.distance(point.position) < 3:
if not point.eaten:
pg.mixer.Sound.play(click_sound)
if point.big:
player.score += 50
last_time_ghosts_afraid = time.time()
else:
player.score += 10
point.eat()
# Highscore Overlay
def draw_highscore():
if os.path.getsize("/highscore.pac") == 0:
text = "Schnellste gewonnene Runde: Keine vorhanden :P"
else:
with open("/highscore.pac", "r") as myTest:
content = myTest.read()
sec = int(content) % 60
min = int(content) / 60
text = "Schnellste gewonnene Runde: " + str(int(min)) + " Minuten " + str(sec) + " Sekunden"
highscore_screen = restartFont.render(text, True, (0, 0, 0))
highscore_rect = highscore_screen.get_rect()
highscore_rect.center = (screen_width // 2, screen_height // 2 + 80)
screen.blit(highscore_screen, highscore_rect)
# Aktueller Score Overlay
def draw_currentscore():
global delta_Time_Sec
sec = delta_Time_Sec % 60
min = int(delta_Time_Sec / 60)
currentscore_screen = restartFont.render("Aktuelle Runde: " + str(min) + " Minuten " + str(sec) + " Sekunden", True,
(0, 0, 0))
currentscore_rect = currentscore_screen.get_rect()
currentscore_rect.center = (screen_width // 2, screen_height // 2 + 110)
screen.blit(currentscore_screen, currentscore_rect)
# Punkte Overlay
def draw_overlayPoints():
global overlayFont
pointSurface = overlayFont.render("Punkte: " + str(player.score), True, (0, 0, 0))
textrect = pointSurface.get_rect()
textrect.center = (screen_width // 2, screen_height - 30)
screen.blit(pointSurface, textrect)
hp_surface = overlayFont.render("Übrige Leben: " + str(player.hp - 1), True, (0, 0, 0))
hp_rect = hp_surface.get_rect()
hp_rect.center = (screen_width, screen_height - 30)
hp_rect.right = (screen_width - 10)
screen.blit(hp_surface, hp_rect)
# Timer Overlay
def draw_overlayTime():
global overlayFont
global delta_Time_Sec
global delta_Time_Minute
delta_Time_Sec = int(time.time() - startTime)
rest = delta_Time_Sec % 60
delta_Time_Minute = delta_Time_Sec - rest
timeSurface = overlayFont.render(str(delta_Time_Minute // 60) + " min " + str(rest) + " sek", True, (0, 0, 0))
timerect = timeSurface.get_rect()
timerect.center = (0, screen_height - 30)
timerect.left = 10
screen.blit(timeSurface, timerect)
def reset_positions():
player.position = default_pos_player.get_instance()
blinky.set_position(blinky.default_pos.get_instance())
pinky.set_position(pinky.default_pos.get_instance())
inky.set_position(inky.default_pos.get_instance())
clyde.set_position(clyde.default_pos.get_instance())
def restart():
global pause
global startTime
global relativeStartTime
global last_time_ghosts_afraid
player.hp = 3
player.score = 0
startTime = time.time()
relativeStartTime = time.time()
reset_positions()
blinky.stateChange = time.time()
pinky.stateChange = time.time()
inky.stateChange = time.time()
clyde.stateChange = time.time()
last_time_ghosts_afraid = 0
blinky.chase = True
pinky.chase = True
inky.chase = True
clyde.chase = True
for x in range(0, 30):
for y in range(0, 30):
points.points[x][y].eaten = maze.maze[x][y] != 0
pause = False
def save_highscore():
global highscore
global delta_Time_Sec
if os.path.getsize("/highscore.pac") == 0:
with open("/highscore.pac", "w") as overwrite:
overwrite.write(str(delta_Time_Sec))
highscore = delta_Time_Sec
else:
with open("/highscore.pac", "r") as getScore:
if delta_Time_Sec < int(getScore.read()):
with open("/highscore.pac", "w") as overwrite:
overwrite.write(str(delta_Time_Sec))
highscore = delta_Time_Sec
def check_ghost_collisions():
global relativeStartTime
global pause
global finishFont
global restartFont
global last_time_ghosts_afraid
if (blinky.position.distance(player.position) < 20 or pinky.position.distance(
player.position) < 20 or inky.position.distance(player.position) < 20 or clyde.position.distance(
player.position) < 20) \
and time.time() - player.last_time_hit > 1 and time.time() - last_time_ghosts_afraid > 8:
relativeStartTime = time.time()
player.last_time_hit = time.time()
player.hp -= 1
reset_positions()
def calc_points_to_win():
global maze
count = 0
for i in maze.maze:
for j in i:
if j == 0:
count += 1
count -= 4
count *= 10
count += 4 * 50
return count
pointsToWin = calc_points_to_win()
while True:
for e in pg.event.get():
if e.type == pg.QUIT:
on_game_exit()
break
# Clear the screen
clear_screen()
maze.draw(screen, (200, 200, 200), (150, 150, 150))
# Draw the points
points.draw(screen, (255, 100, 0), DrawType.circle)
if not pause:
# Player actions
player.poll_keys(maze)
player.move(maze)
player.check_bounds_move(screen_width, screen_height)
player.draw(screen, (252, 186, 3), DrawType.image, grid_cell_size,
image=pg.transform.rotate(player_image if player.get_state() else player_image2,
player.get_rotation()))
check_points_possible_to_eat()
draw_overlayPoints()
draw_overlayTime()
# Check Ghosts
if time.time() - last_time_ghosts_afraid < 8:
if time.time() - relativeStartTime > pinky_start_delay:
pinky.idle()
if time.time() - relativeStartTime > blinky_start_delay:
blinky.idle()
if time.time() - relativeStartTime > inky_start_delay:
inky.idle()
if time.time() - relativeStartTime > clyde_start_delay:
clyde.idle()
blinky.draw(screen, (0, 0, 0), DrawType.image, grid_cell_size,
image=pg.transform.flip(ghost_afraid_image, blinky.is_mirrored(), False))
pinky.draw(screen, (0, 0, 0), DrawType.image, grid_cell_size,
image=pg.transform.flip(ghost_afraid_image, pinky.is_mirrored(), False))
inky.draw(screen, (0, 0, 0), DrawType.image, grid_cell_size,
image=pg.transform.flip(ghost_afraid_image, inky.is_mirrored(), False))
clyde.draw(screen, (0, 0, 0), DrawType.image, grid_cell_size,
image=pg.transform.flip(ghost_afraid_image, clyde.is_mirrored(), False))
else:
if time.time() - relativeStartTime > blinky_start_delay:
blinky.run()
if time.time() - relativeStartTime > pinky_start_delay:
pinky.run()
if time.time() - relativeStartTime > inky_start_delay:
inky.run()
if time.time() - relativeStartTime > clyde_start_delay:
clyde.run()
blinky.draw(screen, (0, 0, 0), DrawType.image, grid_cell_size,
image=pg.transform.flip(blinky_image, blinky.is_mirrored(), False))
pinky.draw(screen, (0, 0, 0), DrawType.image, grid_cell_size,
image=pg.transform.flip(pinky_image, pinky.is_mirrored(), False))
inky.draw(screen, (0, 0, 0), DrawType.image, grid_cell_size,
image=pg.transform.flip(inky_image, inky.is_mirrored(), False))
clyde.draw(screen, (0, 0, 0), DrawType.image, grid_cell_size,
image=pg.transform.flip(clyde_image, clyde.is_mirrored(), False))
check_ghost_collisions()
if player.score >= pointsToWin:
save_highscore()
win_screen = finishFont.render("Gewonnen!", True, (0, 0, 0))
win_rect = win_screen.get_rect()
win_rect.center = (screen_width // 2, screen_height // 2)
screen.blit(win_screen, win_rect)
restart_screen = restartFont.render("Drücke 'R' um noch eine Runde zu spielen!", True, (0, 0, 0))
restart_rect = restart_screen.get_rect()
restart_rect.center = (screen_width // 2, screen_height // 2 + 50)
screen.blit(restart_screen, restart_rect)
draw_highscore()
draw_currentscore()
keys = pg.key.get_pressed()
pause = True
if keys[pg.K_r]:
restart()
elif player.hp == 0:
lose_screen = finishFont.render("Game Over!", True, (0, 0, 0))
lose_rect = lose_screen.get_rect()
lose_rect.center = (screen_width // 2, screen_height // 2)
screen.blit(lose_screen, lose_rect)
restart_screen = restartFont.render("Drücke 'R' um es wieder zu versuchen!", True, (0, 0, 0))
restart_rect = restart_screen.get_rect()
restart_rect.center = (screen_width // 2, screen_height // 2 + 50)
screen.blit(restart_screen, restart_rect)
draw_highscore()
keys = pg.key.get_pressed()
pause = True
if keys[pg.K_r]:
restart()
# Update the display
pg.display.flip()
clock.tick(60)
+210
View File
@@ -0,0 +1,210 @@
import sys
import time
from MoveAble import MoveAble
from Node import Node
from Vec import Vec
# Funktion für den Pathfinding Algorithmus, welche überprüft, ob der Parent eines Nodes neu gesetzt werden muss.
def handle_node_cost_check(temp_node, open_nodes, current_node, end_node):
if temp_node not in open_nodes:
temp_node.parent = current_node
temp_node.calculate_cost(end_node)
open_nodes.append(temp_node)
elif current_node.p_cost + 1 + temp_node.h_cost < temp_node.g_cost:
temp_node.parent = current_node
temp_node.calculate_cost(end_node)
class Ghost(MoveAble):
def __init__(self, start_position: Vec, start_size: Vec, cell_count, grid_cell_size, player, maze_grid,
start_delay):
super().__init__(start_position, start_size, 2)
self.cell_count = cell_count
self.start_delay = start_delay
self.chase = True
self.stateChange = time.time()
self.grid = []
self.grid_cell_size = grid_cell_size
self.player = player
self.maze_grid = maze_grid
self.res = []
self.generate_nodes()
self.distance = 0
self.direction = None
self.default_pos = start_position.get_instance()
self.eaten = False
def reset_position(self):
self.distance = 0
self.position = self.default_pos.get_instance()
def idle(self):
pass
def hunting(self):
pass
# Diese Funktion wird in jedem Update des Spiels (GameLoop) aufgerufen
def run(self, afraid, relative_start_time):
if time.time() - relative_start_time > self.start_delay:
# Sollte der Geist von PacMan gefressen worden sein, bewegt sich dieser ins Geisterhaus zurück.
if self.eaten:
self.move_to_point(Vec(15, 14))
if int((self.position.x + self.grid_cell_size / 2) / self.grid_cell_size) == 15 and \
int((self.position.y + self.grid_cell_size / 2) / self.grid_cell_size) == 14:
self.eaten = False
else:
# Sollte der Geist verängstigt / Blau sein, führt dieser seine Idle routine aus.
if afraid:
self.idle()
else:
# Hier wird anhand des aktuellen States (siehe Timer) zwischen Jagd und Idle gewechselt
self.timer()
if self.chase:
self.hunting()
else:
self.idle()
# Diese Funktion wechselt in Zeitabständen von 16 und 4,5 Sekunden zwischen dem Jagd- und dem Idle-Modus
def timer(self):
if time.time() - self.stateChange >= (16 if self.chase else 4.5):
self.chase = not self.chase
self.stateChange = time.time()
# Diese Funktion gibt an, ob das Bild des Geistes gespiegelt werden muss.
def is_mirrored(self):
return self.velocity.x < 0
# Diese Funktion setzt die Kosten aller Nodes zurück (dadurch müssen keine neuen Objekte generiert werden)
def clear_nodes(self):
for x in range(0, self.cell_count):
for y in range(0, self.cell_count):
self.grid[x][y].g_cost = sys.float_info.max
# Generiere für jedes Feld (Wand oder Frei oder Geist) einen Knoten, welcher Informationen wie Parent und Kosten
# beinhaltet die Nodes müssen nur zu Beginn des Spiels einmal generiert werden.
def generate_nodes(self):
for x in range(0, self.cell_count):
self.grid.append([])
for y in range(0, self.cell_count):
self.grid[x].append(Node(x, y))
# Diese Funktion bewegt den Geist in Richtung eines Gegeben End Punktes v
def move_to_point(self, v):
if self.distance <= self.speed:
# Rufe den Pathfinding Algorithmus
self.res = self.find_path(v)
if len(self.res) > 0:
x_pos = int((self.position.x + self.grid_cell_size / 2) / self.grid_cell_size)
y_pos = int((self.position.y + self.grid_cell_size / 2) / self.grid_cell_size)
# Bilden den Velocity Vektor (Richtung und Geschwindigkeit), wohin der Geist sich bewegen soll
self.direction = Vec(self.res[0].x - x_pos, self.res[0].y - y_pos)
self.direction.mul(self.speed)
self.velocity = self.direction
if len(self.res) > 0:
# Bestimme die Distanz zum nächsten Node (resultiert aus dem Pathfinding) um oben in der Funktion zu
# bestimmen, ob das Pathfinding erneut ausgeführt werden soll
self.distance = abs(
self.position.distance(Vec(self.res[0].x * self.grid_cell_size, self.res[0].y * self.grid_cell_size)))
# Bewege den Geist
self.move()
# In dieser Funktion werden alle Parents eines Nodes in eine Liste abgespeichert
@staticmethod
def path_node_to_list(node, start_node):
result = []
# Iteratives aufbauen der Liste
while node is not start_node:
result.append(node)
node = node.parent
# Liste Invertieren, da diese noch in verkehrter Reihenfolge ist. (End -> Start, muss ja Start -> End sein)
result.reverse()
return result
# Eine Methode, welche einen Pfad von der aktuellen Position des Geistes zu einem EndPunkt berechnet
def find_path(self, end_point):
# Alle Nodes werde zu Beginn zurückgesetzt bzw. ihre Kosten werden auf "unendlich" gesetzt.
self.clear_nodes()
# Zwei listen, in welchen die zu überprüfenden Nodes und die bereits überprüften Nodes abgespeichert werden.
checked_nodes = []
open_nodes = []
# umrechnen der Positionen von 0 - bildschirmbreite, 0 - bildschirmhöhe in 0 - 30, 0 - 30
x_pos = int((self.position.x + self.grid_cell_size / 2) / self.grid_cell_size)
y_pos = int((self.position.y + self.grid_cell_size / 2) / self.grid_cell_size)
x_pos_end_point = end_point.x
y_pos_end_point = end_point.y
# Positionen gegebenenfalls (falls über die Array-Grenzen hinaus) "abschneiden"
if x_pos_end_point >= 30:
x_pos_end_point = 29
if x_pos_end_point < 0:
x_pos_end_point = 0
# Das Start Node bestimmen und dessen Kosten auf 0 setzen (dort wo man schon ist, muss man nicht hinlaufen)
start_node = self.grid[x_pos][y_pos]
start_node.g_cost = 0
start_node.h_cost = 0
start_node.p_cost = 0
# Das End Node merken und das Start Node in die Liste der zu überprüfenden Nodes einfügen.
# (Es wird als Erstes zum current Node)
end_node = self.grid[x_pos_end_point][y_pos_end_point]
current_node: Node = None
open_nodes.append(start_node)
# Den Algorithmus so lange ausführen, bis das aktuelle Node dem End Node entspricht (=> Weg wurde gefunden)
while current_node is not end_node:
# Sollte die Liste der zu überprüfenden Nodes Elemente enthalten,
# wird das Node mit den aktuell geringsten Kosten zum Current Node
if len(open_nodes) > 0:
open_nodes.sort(key=lambda x: x.g_cost)
current_node = open_nodes.pop(0)
else:
break
# Das Aktuelle Node wurde mit diesem Schleifendurchlauf überprüft, und ist hiermit fest
# (es hatten in der Liste open_nodes die geringsten Kosten)
checked_nodes.append(current_node)
# Hier werden alle Nachbarknoten des aktuellen Knotens überprüft und eine Funktion aufgerufen,
# die gegebenenfalls den Parent dieses Nachbar Nodes umsetzt.
if current_node.x + 1 < 30 and (
self.maze_grid[current_node.x + 1][current_node.y] == 0 or self.maze_grid[current_node.x + 1][
current_node.y] == 2) and \
(not self.grid[current_node.x + 1][current_node.y] in checked_nodes):
# Nachbar Node vom aktuellen Node (eins nach rechts) zwischen speichern
temp_node = self.grid[current_node.x + 1][current_node.y]
# Hier wird der Parent des Nachbar Node gegebenenfalls umgesetzt
handle_node_cost_check(temp_node, open_nodes, current_node, end_node)
if current_node.x - 1 >= 0 and (
self.maze_grid[current_node.x - 1][current_node.y] == 0 or self.maze_grid[current_node.x - 1][
current_node.y] == 2) and \
(not self.grid[current_node.x - 1][current_node.y] in checked_nodes):
temp_node = self.grid[current_node.x - 1][current_node.y]
handle_node_cost_check(temp_node, open_nodes, current_node, end_node)
if current_node.y + 1 < 30 and (
self.maze_grid[current_node.x][current_node.y + 1] == 0 or self.maze_grid[current_node.x][
current_node.y + 1] == 2) and \
(not self.grid[current_node.x][current_node.y + 1] in checked_nodes):
temp_node = self.grid[current_node.x][current_node.y + 1]
handle_node_cost_check(temp_node, open_nodes, current_node, end_node)
if current_node.y - 1 >= 0 and (
self.maze_grid[current_node.x][current_node.y - 1] == 0 or self.maze_grid[current_node.x][
current_node.y - 1] == 2) and \
(not self.grid[current_node.x][current_node.y - 1] in checked_nodes):
temp_node = self.grid[current_node.x][current_node.y - 1]
handle_node_cost_check(temp_node, open_nodes, current_node, end_node)
# Hier wird eine Liste in der richtigen Reihenfolge basierend auf allen Parents des End Nodes zurückgegeben
return self.path_node_to_list(current_node, start_node)
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+34
View File
@@ -0,0 +1,34 @@
from Ghost import Ghost
from Vec import Vec
class Inky(Ghost):
def __init__(self, start_position: Vec, start_size: Vec, cell_count, grid_cell_size, player, maze_grid, blinky_pos,
start_delay):
super().__init__(start_position, start_size, cell_count, grid_cell_size, player, maze_grid, start_delay)
self.blinky_pos = blinky_pos
self.int_vec = None
# Aktiviert den Idle-Mode von Inky:
# Wird der Idle-Mode aktiviert springt er direkt wieder in den Hunting-Mode.
# Inky hat somit de facto keinen Idle-Mode.
def idle(self):
self.hunting()
# Aktiviert den Hunting-Mode von Inky:
# Er bewegt sich zu einem Punkt der sich aus PacMan und Blinkys Position berechnet.
def hunting(self):
vec_blinky_pac: Vec = self.player.position.add_ret(self.blinky_pos.mul_ret(-1))
direction = vec_blinky_pac.get_normalized()
vec_blinky_pac = self.player.position.add_ret(vec_blinky_pac)
direction.mul(-20)
while True:
self.int_vec = vec_blinky_pac.mul_ret(1 / self.grid_cell_size).to_int_ret()
if 30 > self.int_vec.x >= 0 and 30 > self.int_vec.y >= 0:
if self.maze_grid[self.int_vec.x][self.int_vec.y] == 0:
break
vec_blinky_pac.add(direction)
self.move_to_point(self.int_vec)
+60
View File
@@ -0,0 +1,60 @@
import pygame as pg
from Vec import Vec
class Maze:
def __init__(self, grid_cell_size):
self.grid_cell_size = grid_cell_size
self.maze = []
# jeweilige .pac Datei wird ausgelesen
def read_file(self, filename):
try:
with open(filename, "r") as file:
data = file.read()
lines = data.split("\n")
for line in lines:
inner_list = []
self.maze.append(inner_list)
for f in line:
inner_list.append(int(f))
# rotate
temp = []
for i in range(len(self.maze)):
temp.append([])
for j in range(len(self.maze)):
temp[i].append(
self.maze[j][i]) # füllt das 2d Array an richtiger Stelle mit den Werten aus der .pac Datei
self.maze = temp
except FileNotFoundError:
print("File Not Found")
# Labyrinth wird für den Spieler gezeichnet
def draw(self, screen, color1, color2):
for x, l in enumerate(self.maze):
for y, f in enumerate(l):
# wenn die Zelle begehbar ist, bekommt sie die Farbe Weiß, ansonsten grau.
color = color1 if f == 1 else color2
if f > 0:
# Zelle wird mit ihrer Farbe gezeichnet
pg.draw.rect(screen, color, (x * self.grid_cell_size,
y * self.grid_cell_size, self.grid_cell_size,
self.grid_cell_size),
border_bottom_left_radius=10 if self.is_free(Vec(x - 1, y)) and self.is_free(
Vec(x, y + 1)) else 0,
border_bottom_right_radius=10 if self.is_free(Vec(x + 1, y)) and self.is_free(
Vec(x, y + 1)) else 0,
border_top_left_radius=10 if self.is_free(Vec(x - 1, y)) and self.is_free(
Vec(x, y - 1)) else 0,
border_top_right_radius=10 if self.is_free(Vec(x + 1, y)) and self.is_free(
Vec(x, y - 1)) else 0,
)
def is_free(self, v):
if len(self.maze) > v.x >= 0 and len(self.maze[v.x]) > v.y >= 0:
return self.maze[v.x][v.y] == 0
return True
+24
View File
@@ -0,0 +1,24 @@
from Entity import Entity
from Vec import Vec
class MoveAble(Entity):
def __init__(self, start_position: Vec, start_size: Vec, speed):
super().__init__(start_position, start_size)
self.velocity = Vec(0, 0)
self.speed = speed
def move(self):
self.position.add(self.velocity)
def check_bounds_move(self, screen_width, screen_height):
if self.position.x < 0:
self.position.x = screen_width
elif self.position.x > screen_width:
self.position.x = 0
if self.position.y < 0:
self.position.y = screen_height
elif self.position.y > screen_height:
self.position.y = 0
+19
View File
@@ -0,0 +1,19 @@
import math
import sys
class Node:
def __init__(self, x, y):
self.x: int = x
self.y: int = y
self.h_cost = self.p_cost = self.g_cost = sys.float_info.max
self.parent = None
# Berechnet alle Kosten (Entfernungen) für den Pathfinding-Algorithmus der Geister.
def calculate_cost(self, end_node):
self.h_cost = math.sqrt(math.pow(end_node.x - self.x, 2) + math.pow(end_node.y - self.y, 2))
self.p_cost = self.parent.p_cost + 1
self.g_cost = self.h_cost + self.p_cost
def __str__(self):
return str(self.x) + " " + str(self.y)
Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+47
View File
@@ -0,0 +1,47 @@
from Ghost import Ghost
from Vec import Vec
class Pinky(Ghost):
def __init__(self, start_position: Vec, start_size: Vec, cell_count, grid_cell_size, player, maze_grid,
start_delay):
super().__init__(start_position, start_size, cell_count, grid_cell_size, player, maze_grid, start_delay)
self.current_idle_point = Vec(27, 1)
# Aktiviert den Idle-Mode von Pinky:
# Er bewegt sich zu seinem definierten Idle-Point.
def idle(self):
self.move_to_point(self.current_idle_point)
# Aktiviert den Hunting-Mode von Pinky:
# Er bewegt sich zu der aktuellen Target-Position.
def hunting(self):
fieldy = self.get_target_position()
self.move_to_point(fieldy)
# Berechnet das aktuelle Ziel von Pinky:
# Es wird der Punkt vier Felder vor PacMan berechnet.
# Wenn dieses eine Wand ist, ist das Ziel 3 Felder vor PacMan,
# wenn dieses wiederum eine Wand ist 2 Felder, usw.
def get_target_position(self):
direct = self.player.velocity.get_normalized()
fieldy = None
if abs(self.player.position.distance(self.position)) <= 4 * self.grid_cell_size:
fieldy = self.player.position.mul_ret(1 / self.grid_cell_size).to_int_ret()
if fieldy.x >= 30:
fieldy.x = 29
if fieldy.x < 0:
fieldy.x = 0
else:
for i in range(0, 5):
fieldy = self.player.position.mul_ret(1 / self.grid_cell_size).add_ret(direct.mul_ret(i)).to_int_ret()
if fieldy.x >= 30:
fieldy.x = 29
if fieldy.x < 0:
fieldy.x = 0
if self.maze_grid[fieldy.x][fieldy.y] == 1:
fieldy = self.player.position.mul_ret(1 / self.grid_cell_size).add_ret(
direct.mul_ret(i - 1)).to_int_ret()
break
return fieldy
+102
View File
@@ -0,0 +1,102 @@
import math
import time
from MoveAble import MoveAble
from Vec import Vec
import pygame as pg
class Player(MoveAble):
def __init__(self, start_position: Vec, start_size: Vec, speed, grid_cell_size, maze):
# Make the speed an even number
# speed += 0 if (speed % 2 == 0 or speed == 1) == 0 else 1
super().__init__(start_position, start_size, speed)
self.grid_cell_size = grid_cell_size
self.targetDirection = Vec(0, 0)
self.correct = False
self.score = 0
self.last_state_switched = time.time()
self.state = False
self.hp = 3
self.last_time_hit = 0
self.maze = maze
# Überschreibt den Vektor, der die Laufrichtung PacMans darstellt
def poll_keys(self, maze):
keys = pg.key.get_pressed()
free_y = self.position.x % self.grid_cell_size == 0
free_x = self.position.y % self.grid_cell_size == 0
if keys[pg.K_LEFT] or keys[pg.K_a]:
self.targetDirection = Vec(-1, 0)
if keys[pg.K_RIGHT] or keys[pg.K_d]:
self.targetDirection = Vec(1, 0)
if keys[pg.K_UP] or keys[pg.K_w]:
self.targetDirection = Vec(0, -1)
if keys[pg.K_DOWN] or keys[pg.K_s]:
self.targetDirection = Vec(0, 1)
allowed = maze.is_free(
Vec(round(self.position.x / self.grid_cell_size) + self.targetDirection.x,
round(self.position.y / self.grid_cell_size) + self.targetDirection.y))
# Erst wenn die gewünschte Laufrichtung begehbar ist, verändert sich die Laufrichtung PacMans.
if self.targetDirection.x != 0 and free_x and allowed:
self.velocity = Vec(self.targetDirection.x * self.speed, self.targetDirection.y * self.speed)
self.targetDirection = Vec(0, 0)
# Erst wenn die gewünschte Laufrichtung begehbar ist, verändert sich die Laufrichtung PacMans.
if self.targetDirection.y != 0 and free_y and allowed:
self.velocity = Vec(self.targetDirection.x * self.speed, self.targetDirection.y * self.speed)
self.targetDirection = Vec(0, 0)
def auto_place(self):
x = 15
for y in range(0, 29):
if self.maze.is_free(Vec(x, y)):
self.position = Vec(x * self.grid_cell_size, y * self.grid_cell_size)
break
self.velocity = Vec(0, 0)
def move(self):
# Rechne die Position von 0 - Bildschirmbreite, 0 - Bildschirmhöhe in 0-30, 0-30 um
test = Vec(self.position.x / self.grid_cell_size,
self.position.y / self.grid_cell_size)
# Mache die Komponenten (je nach richtung der Geschwindigkeit) zu Integer-Datentypen.
test.x = math.ceil(test.x) if self.velocity.x < 0 else math.floor(test.x)
test.y = math.ceil(test.y) if self.velocity.y < 0 else math.floor(test.y)
# Speichere die Richtung der Geschwindigkeit als Vektor der länge 1 ab
check_direction = self.velocity.get_normalized()
check_direction.to_int()
# Füge diese Richtung der Test (Position von Pacman im 30x30 Gitter) hinzu
test.add(check_direction)
# Überprüfe, ob das Labyrinth an der Stelle des resultierenden test Vektor frei ist (keine Wand bzw. 0)
if self.maze.is_free(test):
super().move()
def get_rotation(self):
player_rot = 0
if self.velocity.x < 0:
player_rot = 180
if self.velocity.y < 0:
player_rot = 90
if self.velocity.y > 0:
player_rot = -90
return player_rot
def get_state(self):
if time.time() - self.last_state_switched > 0.2:
self.state = not self.state
self.last_state_switched = time.time()
return self.state
+17
View File
@@ -0,0 +1,17 @@
from Entity import Entity
from Vec import Vec
class Point(Entity):
def __init__(self, start_position: Vec, start_size: Vec, big):
super().__init__(start_position, start_size)
self.eaten = False
self.big = big
# Wird ausgeführt, wenn der Punkt von PacMan berührt wird.
def eat(self):
self.eaten = True
def __str__(self):
return self.position
+51
View File
@@ -0,0 +1,51 @@
from DrawType import DrawType
from Point import Point
from Vec import Vec
class PointGrid:
def __init__(self, grid_cell_size, cell_count, maze, radius=8):
self.grid_cell_size = grid_cell_size
self.points = []
self.big_points_positions = [
(2, 1),
(27, 1),
(2, 27),
(27, 27)
] # Idle-Punkte bekommen feste Position
# Fill the points list based on size of one cell, screen height and width
for x in range(0, int(cell_count)):
self.points.append([])
for y in range(0, int(cell_count)):
point = Point(Vec(x * grid_cell_size + grid_cell_size / 2, y * grid_cell_size + grid_cell_size / 2),
Vec(radius + (5 if (x, y) in self.big_points_positions else 0),
radius + (5 if (x, y) in self.big_points_positions else 0)),
(x, y) in self.big_points_positions)
self.points[x].append(point)
if maze.maze[x][y] > 0:
point.eat()
def draw(self, screen, color, draw_type: DrawType = DrawType.circle):
# Loop through the points list
for xList in self.points:
for point in xList:
# Check if point is eaten
if not point.eaten:
point.draw(screen, color, draw_type)
def get_surrounding_points(self, player_position):
x = int(player_position.x / self.grid_cell_size)
y = int(player_position.y / self.grid_cell_size)
result = []
for ix in range(x - 1, x + 2):
for iy in range(y - 1, y + 2):
if 0 <= ix < len(self.points):
if 0 <= iy < len(self.points[ix]):
result.append(self.points[ix][iy])
return result
+40
View File
@@ -0,0 +1,40 @@
# PacManPython
Ein PacMan Spiel, dass in Python unter Verwendung von Pygame programmiert wurde.
Zum Ausführen des Spiels muss Python mit pygame (pip install pygame) auf dem Computer installiert sein.
Um das Spiel zu starten kann die Datei "Start.py" im Order Start ausgeführt werden. Z.b. im Terminal: python3 Game.py
Allgemeines:
- Der Spieler kann über die Pfeiltasten oder W, S, A, D gesteuert werden.
- Die Geistern werden nacheinander aus ihrem Haus kommen und versuchen den Spieler auf unterschiedlichen Arten zu Jagen. Mal jagen sie ihn direkt, mal versuchen sie ihm den Weg abzuschneiden, oder ziehen sich für einen Kurzen Moment zurück.
- Die Zeitanzeige zeigt an, wie lange der Spieler überlebt hat ohne von einem Geist getroffen zu werden.
- Die Punkteanzeige, zeigt die Punkte welche gefressen wurden. Ein kleiner Fresspunkt gibt +10 Punkte, ein großer +50.
- Wird ein großer Punkt gefressen "bekommen die Geister Angst" und ziehen sich größtenteils zurück.
- Das Spiel läuft so lange, bis die HP (Leben) < 0 sind.
- Ziel des Spiels ist es mit mindestens 0 leben alle Punkte im Spielfeld zu fressen.
- Das gewonnene oder verlorene Spiel kann über das Drücken der "R" Taste neu gestartet werden.
Funktionsweiße der Geister:
- Roter Geist (Blinky) verfolgt den Spieler immer auf dem schnellsten weg.
- Rosa Geist (Pinky) versucht immer auf das Feld 4 Felder in Laufrichtung des Spielers zu gelangen.
-> Gemeinsam schneiden sie dem Spieler immer wieder den Weg ab.
- Blauer Geist (Inky) berechnet den Vektor von Blinky zum Spieler, verdoppelt dessen Länge und verkürzt dessen Länge so lange wieder in kleinen Schritten, bis der Vektor auf ein freies Feld zeigt. Dieses wird von ihm anvisiert. Dadurch erhält er ein schwer vorhersehbares Verhalten.
- Oranger Geist (Clyde) verhält sich wie Blinky, aber zieht sich zurück wenn er in der unmittelbaren Nähe des Spielers ist. Dadurch ist er eher ungefährlich.
Technisch:
- Positionen, Richtungen und Geschwindigkeiten werden als Vektoren dargestellt.
- Geister verwenden für ihre Bewegung einen umgebauten AStar algorithmus. (https://de.wikipedia.org/wiki/A*-Algorithmus)
- Hierbei ergibt sich der Graph, auf welchem die Algorithmen arbeiten aus dem Labyrinth des Spiels.
- Zur Heuristischen Kostenberechnung des AStar werden Längen der Verbindungsvektoren zwischen einem Knoten des Graphen und dem Ziel verwendet.
- Jedes Element im Spiel (außer dem Labyrinth selbst) fügt sich in eine Vererbungsstruktur ein, um eine verallgemeinerung von Bewegungen oder Positionen zu gewährleisten.
- Intern (für das Pathfinding) wird ein 2D-Array mit 30x30 feldern verwendet.
- Um ein flüssiges Spielerlebnis zu ermöglichen, wird dieses 30x30 Array auf eine Bildschirmbreite von 780x780 hochskaliert. Positionen o.ä. werden demnach teilweiße auch im hochskalierten System berechnet und zwischen den beiden Systemen umgerechnet.
- Das Labyrinth wird aus einer selbst erstelltem Datei format ".pac" geladen. Hierbei kann in einem Rahmen von 30 Zeichen pro Zeilen und 30 Zeilen ein Labyrinth definiert werden. 0 ist hierbei ein freies Feld. 1 eine Wand. und 2 stellt das Geisterhaus dar. (Manche Position wie "Idle Loops" der Geister müssten bei einem anderen Labyrinth leicht angepasst werden)
- Die Kollissionserkennung zwischen dem Spieler und Wänden bzw. Geistern erfolgt über Linearkombinationen von Vektoren und Distanzbestimmung zwischen Ortsvektoren.
- Das Spiel unterstützt eine Skalierung auf die volle Bildschirmhöhe, hierbei muss aber ein Qualitätsverlust der Grafik in Kauf genommen werden.
+185
View File
@@ -0,0 +1,185 @@
from tkinter import *
from Game import Game
# maze.pac Datei wird ausgelesen
def read_current_maze(filename):
cells = []
try:
with open("../Game_Package/" + filename, "r") as file:
data = file.read()
lines = data.split("\n")
for line in lines:
inner_list = []
cells.append(inner_list)
for f in line:
inner_list.append(int(f))
# rotate
temp = []
for i in range(len(cells)):
temp.append([])
for j in range(len(cells)):
temp[i].append(
cells[j][i]) # füllt das 2d Array an richtiger Stelle mit den Werten aus der maze.pac Datei
return temp
except FileNotFoundError:
print("File Not Found")
# überschreibt aktuelles Labyrinth in maze.pac mit neu gezeichnetem Labyrinth
def write_maze():
output = ""
for x, i in enumerate(cells):
for y, j in enumerate(i):
if cells[y][x] == 3:
cells[y][x] = 1
if cells[y][x] == 4:
cells[y][x] = 0
output += str(cells[y][x])
if x < 29:
output += "\n"
with open("../Game_Package/maze.pac", "w") as file:
file.write(output)
cells = read_current_maze("maze.pac")
def on_mouse_down(e, left):
x = e.x // 26
y = e.y // 26
if cells[x][y] == 0 or cells[x][y] == 1:
if left:
cells[x][y] = 1
canvas.create_rectangle(x * 26 + 1, y * 26 + 1, x * 26 + 26, y * 26 + 26, fill="#969696", width=0)
else:
cells[x][y] = 0
canvas.create_rectangle(x * 26 + 1, y * 26 + 1, x * 26 + 26, y * 26 + 26, fill="#FFFFFF", width=0)
def draw_canvas_content():
canvas.delete("all")
canvas.create_rectangle(0, 0, 780, 780, fill="#FFFFFF", width=0)
for x, i in enumerate(cells):
for y, j in enumerate(i):
if cells[x][y] == 1:
canvas.create_rectangle(x * 26, y * 26, x * 26 + 26, y * 26 + 26, fill="#969696", width=0)
if cells[x][y] == 2 or cells[x][y] == 3:
canvas.create_rectangle(x * 26, y * 26, x * 26 + 26, y * 26 + 26, fill="#556dad", width=0)
if cells[x][y] == 4:
canvas.create_rectangle(x * 26, y * 26, x * 26 + 26, y * 26 + 26, fill="#55ad68", width=0)
canvas.create_line(0, y * 26, 780, y * 26, fill="#919191", width=1)
canvas.create_line(x * 26, 0, x * 26, 780, fill="#919191", width=1)
def on_start_clicked():
write_maze()
root.withdraw()
Game()
root.deiconify()
block_important_cells()
def reload_standard_maze():
global cells
cells = read_current_maze("standardmaze.pac")
block_important_cells()
draw_canvas_content()
def block_important_cells():
# Idle-Points werden blockiert
cells[2][1] = cells[2][27] = cells[27][1] = cells[27][27] = 4
for x, i in enumerate(cells):
for y, j in enumerate(i):
if x <= 1 or y < 1 or y >= 28 or x >= 28:
cells[x][y] = 3
# Labyrinth-Öffnung an den Seiten wird blockiert.
cells[0][13] = cells[1][13] = cells[28][13] = cells[29][13] = 4
# Labyrinth wird geleert
def clear_all():
for x, i in enumerate(cells):
for y, j in enumerate(i):
cells[x][y] = 0 if cells[x][y] != 2 else 2
# wichtige Zellen werden blockiert
block_important_cells()
draw_canvas_content()
root = Tk()
# Center Window
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
x = (screen_width / 2) - (1000 / 2)
y = (screen_height / 2) - (780 / 2)
root.geometry('%dx%d+%d+%d' % (1000, 780, x, y))
# End Center Window
# Tkinter wird angepasst
root.resizable(False, False)
root.title("PacMan")
root.iconphoto(False, PhotoImage(file="../Game_Package/PacMan.png"))
canvas = Canvas(root, width=780, height=780)
canvas.pack(side=LEFT)
canvas.bind("<B1-Motion>", lambda x: on_mouse_down(x, True))
canvas.bind("<B3-Motion>", lambda x: on_mouse_down(x, False))
canvas.bind("<1>", lambda x: on_mouse_down(x, True))
canvas.bind("<3>", lambda x: on_mouse_down(x, False))
font = ("Comic Sans MS", 15)
# Buttons werden generiert und mit Funktionen ausgestattet
button = Button(root, text="Start", width=10, height=1, command=on_start_clicked, borderwidth=0, background="#55ad68",
font=font)
button.pack(anchor=CENTER, side=TOP, pady=10)
button_reload = Button(root, text="Standard", width=10, height=1, command=reload_standard_maze, borderwidth=0,
background="#e08c70", font=font)
button_reload.pack(anchor=CENTER, side=TOP, pady=10)
button_reload = Button(root, text="Speichern", width=10, height=1,
command=lambda: [write_maze(), block_important_cells(), draw_canvas_content()], borderwidth=0,
background="#55ad68", font=font)
button_reload.pack(anchor=CENTER, side=TOP, pady=10)
button_reload = Button(root, text="Clear", width=10, height=1, command=clear_all, borderwidth=0, background="#e08c70",
font=font)
button_reload.pack(anchor=CENTER, side=TOP, pady=10)
label_hello = Label(root, font=font,
text="Willkommen bei PacMan! \n male dein eigenes Labyrinth oder verwende das Vorhandene",
wraplength=220)
label_hello.pack(anchor=CENTER, side=TOP, pady=10)
label = Label(root, font=font,
text="Verwende \n 'Linke Maustaste' \n um eine Wand zu malen, \n 'Rechte Maustaste'"
" um eine Wand zu löschen \n <--",
wraplength=220)
label.pack(anchor=CENTER, side=TOP, pady=10)
block_important_cells()
draw_canvas_content()
root.mainloop()
+62
View File
@@ -0,0 +1,62 @@
import math
class Vec:
def __init__(self, x, y):
self.x = x
self.y = y
# Diese Funktion addiert einen Vektor auf den aktuellen Vektor
def add(self, v):
self.x += v.x
self.y += v.y
# Diese Funktion multipliziert den aktuellen Vektor mit einem Skalar und gibt diesen zurück
def mul_ret(self, s):
return Vec(self.x * s, self.y * s)
# Diese Funktion multipliziert ein Skalar auf den aktuellen Vektor
def mul(self, s):
self.x *= s
self.y *= s
# Diese Funktion addiert einen Vektor v auf den aktuellen und gibt diesen zurück
def add_ret(self, v):
return Vec(self.x + v.x, self.y + v.y)
# Berechnet die Distanz zwischen dem aktuellen Vektor und einem Vektor V und gibt diese zurück
def distance(self, v):
# Verbindungsvektor v2 bilden
v2 = Vec(v.x - self.x, v.y - self.y)
# v2 = Verbindungsvektor; distanz = Wurzel(v2.x² + v2.y²)
return abs(math.sqrt(math.pow(v2.x, 2) + math.pow(v2.y, 2)))
def __str__(self):
return f"x:{self.x} y:{self.y}"
# Bringt den aktuellen Vektor auf die länge 1 und gibt diesen zurück. Die Richtung wird dabei beibehalten.
def get_normalized(self):
# länge des Vektors durch Wurzel(x² + y²) berechnen.
length = math.sqrt(math.pow(self.x, 2) + math.pow(self.y, 2))
# Überprüfen, ob die länge 0 ist (sonst division by zero error)
length = length if length != 0 else 1
# einen neuen, skalierten Vektor zurückgeben
return Vec(self.x / length, self.y / length)
# Castet alle Komponenten des aktuellen Vektors zu Integer-Datentypen.
def to_int(self):
self.x = int(self.x)
self.y = int(self.y)
# Castet alle Komponenten des aktuellen Vektors zu Integer-Datentypen und gibt den Vektor zurück.
def to_int_ret(self):
return Vec(int(self.x), int(self.y))
# Eine Funktion, welche überprüft, ob der aktuelle Vektor und ein Vektor v die gleichen Komponenten haben.
def equals(self, v):
return v.x == self.x and v.y == self.y
# Gibt eine Instanz, des aktuellen Vektor-Objekts zurück.
def get_instance(self):
return Vec(self.x, self.y)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1
View File
@@ -0,0 +1 @@
36
+30
View File
@@ -0,0 +1,30 @@
111111111111111111111111111111
110000000000001100000000000011
110111101111101101111101111011
110111101111101101111101111011
110111101111101101111101111011
110000000000000000000000000011
110111101101111111101101111011
110000001100001100001100000011
111111101111101101111101111111
110001101111101101111101100011
110111101100000000001101111011
110000001101112211101100000011
111111101101222222101101111111
000000000001222222100000000000
111111101101222222101101111111
110001101101222222101101100011
110101101101111111101101101011
110111101100000000001101111011
110111101101111111101101111011
110000000000001100000000000011
110111101111101101111101111011
110111101111101101111101111011
110001100000000000000001100011
111101101101111111101101101111
110000001100001100001100000011
110111111111101101111111111011
110111111111101101111111111011
110000000000000000000000000011
111111111111111111111111111111
111111111111111111111111111111
Binary file not shown.
+30
View File
@@ -0,0 +1,30 @@
111111111111111111111111111111
110000000000001100000000000011
110111101111101101111101111011
110111101111101101111101111011
110111101111101101111101111011
110000000000000000000000000011
110111101101111111101101111011
110000001100001100001100000011
111111101111101101111101111111
110001101111101101111101100011
110111101100000000001101111011
110000001101112211101100000011
111111101101222222101101111111
000000000001222222100000000000
111111101101222222101101111111
110001101101222222101101100011
110101101101111111101101101011
110111101100000000001101111011
110111101101111111101101111011
110000000000001100000000000011
110111101111101101111101111011
110111101111101101111101111011
110001100000000000000001100011
111101101101111111101101101111
110000001100001100001100000011
110111111111101101111111111011
110111111111101101111111111011
110000000000000000000000000011
111111111111111111111111111111
111111111111111111111111111111