211 lines
9.8 KiB
Python
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)
|