Files
pacmanpythondhbw/Game_Package/Ghost.py
T
2024-10-29 17:18:30 +01:00

211 lines
9.8 KiB
Python

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)