from github
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
@@ -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)
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
class DrawType:
|
||||
rect = 0
|
||||
circle = 1
|
||||
image = 3
|
||||
@@ -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))
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -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)
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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.
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -0,0 +1 @@
|
||||
36
|
||||
@@ -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
|
||||
@@ -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
|
||||