fadiinf-sose26/game.py
glmacedo 3378f35c77 Sitzung 4
Sitzung 4: Erstellen eines Spiels mithilfe von KI.

Signed-off-by: glmacedo <gian.mocerinomacedo@stud.ph-weingarten.de>
2026-05-05 17:22:16 +02:00

474 lines
No EOL
19 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
=============================================================
LABYRINTH-QUEST Ein Pygame-Lernspiel für Klasse 11
=============================================================
Didaktische Ziele (Kompetenzbereiche nach Du Boulay 1989):
- Schleifen (for, while)
- Bedingungen (if/elif/else)
- Listen & 2D-Listen (Labyrinth-Grid)
- Funktionen & Modularisierung
- Event-Handling (Tastatureingabe)
- Kollisionserkennung
- Spielzustand & Logik (State Machine)
- Rekursion (Maze-Generierung via Backtracking)
=============================================================
"""
import pygame
import random
import time
# ─────────────────────────────────────────
# KONSTANTEN
# ─────────────────────────────────────────
TILE = 40 # Größe einer Zelle in Pixel
COLS = 19 # Spalten (ungerade!)
ROWS = 15 # Zeilen (ungerade!)
WIDTH = COLS * TILE
HEIGHT = ROWS * TILE + 80 # +80 für HUD-Leiste
FPS = 60
# Farben
C_BG = (15, 20, 35)
C_WALL = (30, 50, 90)
C_WALL_HL = (50, 80, 130)
C_PATH = (20, 30, 55)
C_PLAYER = (80, 200, 120)
C_PLAYER_EYE= (15, 20, 35)
C_EXIT = (255, 200, 50)
C_EXIT_GLOW = (255, 230, 100)
C_COIN = (255, 215, 0)
C_TRAP = (220, 60, 60)
C_HUD_BG = (10, 15, 28)
C_TEXT = (200, 220, 255)
C_TITLE = (100, 180, 255)
C_WHITE = (255, 255, 255)
C_GREEN = ( 60, 200, 100)
C_RED = (220, 60, 60)
C_OVERLAY = (0, 0, 0, 180)
# Zell-Typen
WALL = 1
PATH = 0
# ─────────────────────────────────────────
# LABYRINTH GENERIEREN (Recursive Backtracking)
# ─────────────────────────────────────────
def erstelle_labyrinth(cols, rows):
"""
Generiert ein perfektes Labyrinth mit rekursivem Backtracking.
Gibt eine 2D-Liste zurück: WALL=1, PATH=0
"""
# Alles mit Wänden füllen
gitter = [[WALL] * cols for _ in range(rows)]
def ist_gueltig(r, c):
return 0 < r < rows - 1 and 0 < c < cols - 1
def grabe(r, c):
gitter[r][c] = PATH
richtungen = [(0, 2), (0, -2), (2, 0), (-2, 0)]
random.shuffle(richtungen)
for dr, dc in richtungen:
nr, nc = r + dr, c + dc
if ist_gueltig(nr, nc) and gitter[nr][nc] == WALL:
# Wand zwischen aktuellem und nächstem Feld entfernen
gitter[r + dr // 2][c + dc // 2] = PATH
grabe(nr, nc)
grabe(1, 1)
# Start & Ziel öffnen
gitter[1][1] = PATH
gitter[rows-2][cols-2] = PATH
return gitter
# ─────────────────────────────────────────
# MÜNZEN & FALLEN PLATZIEREN
# ─────────────────────────────────────────
def platziere_items(gitter, anzahl_muenzen=8, anzahl_fallen=5):
"""
Platziert Münzen und Fallen auf freien Pfadzellen.
Gibt zwei Listen mit (row, col)-Tupeln zurück.
"""
freie_felder = []
for r in range(len(gitter)):
for c in range(len(gitter[0])):
if gitter[r][c] == PATH:
# Start und Ziel aussparen
if (r, c) not in [(1, 1), (len(gitter)-2, len(gitter[0])-2)]:
freie_felder.append((r, c))
random.shuffle(freie_felder)
muenzen = freie_felder[:anzahl_muenzen]
fallen = freie_felder[anzahl_muenzen:anzahl_muenzen + anzahl_fallen]
return muenzen, fallen
# ─────────────────────────────────────────
# SPIELER-KLASSE
# ─────────────────────────────────────────
class Spieler:
def __init__(self, reihe, spalte):
self.reihe = reihe
self.spalte = spalte
self.punkte = 0
self.leben = 3
self.bewegt = False # für Animation
self.anim_x = 0.0 # interpoliertes X (Pixel)
self.anim_y = 0.0 # interpoliertes Y (Pixel)
self.ziel_x = float(spalte * TILE)
self.ziel_y = float(reihe * TILE)
self.anim_x = self.ziel_x
self.anim_y = self.ziel_y
self.blick = (0, 1) # Blickrichtung (dr, dc)
def bewege(self, dr, dc, gitter):
"""Bewegt den Spieler, wenn das Zielfeld kein WALL ist."""
nr = self.reihe + dr
nc = self.spalte + dc
self.blick = (dr, dc)
if 0 <= nr < len(gitter) and 0 <= nc < len(gitter[0]):
if gitter[nr][nc] == PATH:
self.reihe = nr
self.spalte = nc
self.ziel_x = float(nc * TILE)
self.ziel_y = float(nr * TILE)
return True
return False
def update(self, dt):
"""Weiche Interpolation zur Zielposition."""
speed = 12.0
self.anim_x += (self.ziel_x - self.anim_x) * speed * dt
self.anim_y += (self.ziel_y - self.anim_y) * speed * dt
def zeichne(self, surface):
cx = int(self.anim_x) + TILE // 2
cy = int(self.anim_y) + TILE // 2
r = TILE // 2 - 4
# Körper
pygame.draw.circle(surface, C_PLAYER, (cx, cy), r)
# Glanz
pygame.draw.circle(surface, (130, 240, 160), (cx - r//4, cy - r//4), r//3)
# Augen (folgen Blickrichtung)
dr, dc = self.blick
ex = cx + dc * (r // 2)
ey = cy + dr * (r // 2)
pygame.draw.circle(surface, C_PLAYER_EYE, (ex, ey), 4)
pygame.draw.circle(surface, C_WHITE, (ex + 1, ey - 1), 2)
# ─────────────────────────────────────────
# ZEICHENFUNKTIONEN
# ─────────────────────────────────────────
def zeichne_labyrinth(surface, gitter, tick):
for r in range(len(gitter)):
for c in range(len(gitter[0])):
rect = pygame.Rect(c * TILE, r * TILE, TILE, TILE)
if gitter[r][c] == WALL:
pygame.draw.rect(surface, C_WALL, rect)
# Dezentes Highlight oben/links
pygame.draw.line(surface, C_WALL_HL,
(c*TILE, r*TILE), (c*TILE+TILE, r*TILE), 1)
pygame.draw.line(surface, C_WALL_HL,
(c*TILE, r*TILE), (c*TILE, r*TILE+TILE), 1)
else:
pygame.draw.rect(surface, C_PATH, rect)
def zeichne_exit(surface, gitter, tick):
er = len(gitter) - 2
ec = len(gitter[0]) - 2
puls = abs((tick % 60) - 30) / 30.0 # 0..1 pulsierend
glow_r = int(TILE // 2 - 4 + puls * 5)
cx = ec * TILE + TILE // 2
cy = er * TILE + TILE // 2
# Glüheffekt (größerer halbtransparenter Kreis)
glow_surf = pygame.Surface((TILE * 2, TILE * 2), pygame.SRCALPHA)
pygame.draw.circle(glow_surf, (*C_EXIT_GLOW, 60),
(TILE, TILE), glow_r + 6)
surface.blit(glow_surf, (cx - TILE, cy - TILE))
pygame.draw.circle(surface, C_EXIT, (cx, cy), TILE // 2 - 4)
pygame.draw.circle(surface, C_WHITE, (cx, cy), TILE // 2 - 10)
# "E" für Exit
font = pygame.font.SysFont("monospace", 18, bold=True)
txt = font.render("E", True, C_HUD_BG)
surface.blit(txt, txt.get_rect(center=(cx, cy)))
def zeichne_muenzen(surface, muenzen, tick):
puls = abs((tick % 40) - 20) / 20.0
r = int(TILE // 2 - 8 + puls * 3)
for (mr, mc) in muenzen:
cx = mc * TILE + TILE // 2
cy = mr * TILE + TILE // 2
pygame.draw.circle(surface, C_COIN, (cx, cy), r)
pygame.draw.circle(surface, C_WHITE, (cx - 2, cy - 2), r // 3)
def zeichne_fallen(surface, fallen, tick):
for (fr, fc) in fallen:
margin = 6
rect = pygame.Rect(fc * TILE + margin, fr * TILE + margin,
TILE - 2*margin, TILE - 2*margin)
pygame.draw.rect(surface, C_TRAP, rect, border_radius=4)
# Kreuz-Symbol
cx = fc * TILE + TILE // 2
cy = fr * TILE + TILE // 2
d = TILE // 2 - margin - 2
pygame.draw.line(surface, C_WHITE, (cx-d, cy-d), (cx+d, cy+d), 2)
pygame.draw.line(surface, C_WHITE, (cx+d, cy-d), (cx-d, cy+d), 2)
def zeichne_hud(surface, spieler, level, zeit_rest, font, font_klein):
hud_rect = pygame.Rect(0, ROWS * TILE, WIDTH, 80)
pygame.draw.rect(surface, C_HUD_BG, hud_rect)
pygame.draw.line(surface, C_WALL_HL, (0, ROWS*TILE), (WIDTH, ROWS*TILE), 2)
y = ROWS * TILE + 10
# Level
txt = font.render(f"Level {level}", True, C_TITLE)
surface.blit(txt, (10, y))
# Punkte
txt = font.render(f"Punkte: {spieler.punkte}", True, C_TEXT)
surface.blit(txt, (10, y + 30))
# Leben (Herzen)
herz_txt = font_klein.render("Leben: " + "" * spieler.leben, True, C_RED)
surface.blit(herz_txt, (WIDTH // 2 - herz_txt.get_width() // 2, y + 5))
# Zeit
farbe = C_GREEN if zeit_rest > 15 else C_RED
txt = font.render(f"Zeit: {int(zeit_rest)}s", True, farbe)
surface.blit(txt, (WIDTH - txt.get_width() - 10, y))
# Steuerung-Hinweis
hint = font_klein.render("Pfeiltasten / WASD zum Bewegen | ESC: Beenden", True, (80, 100, 140))
surface.blit(hint, (WIDTH // 2 - hint.get_width() // 2, y + 50))
def zeige_overlay(surface, zeilen, font_gross, font):
"""Halbtransparentes Overlay mit zentriertem Text."""
overlay = pygame.Surface((WIDTH, HEIGHT), pygame.SRCALPHA)
overlay.fill((0, 0, 0, 200))
surface.blit(overlay, (0, 0))
for i, (text, farbe, gross) in enumerate(zeilen):
f = font_gross if gross else font
txt = f.render(text, True, farbe)
y = HEIGHT // 2 - len(zeilen) * 20 + i * 50
surface.blit(txt, txt.get_rect(center=(WIDTH // 2, y)))
# ─────────────────────────────────────────
# SPIELZUSTÄNDE (State Machine)
# ─────────────────────────────────────────
ZUSTAND_TITEL = "titel"
ZUSTAND_SPIEL = "spiel"
ZUSTAND_GEWINN = "gewinn"
ZUSTAND_VERLOR = "verloren"
ZUSTAND_PAUSE = "pause"
ZEIT_LIMIT = 60 # Sekunden pro Level
def spiel_starten(level):
"""Gibt alle Spielvariablen für ein neues Level zurück."""
gitter = erstelle_labyrinth(COLS, ROWS)
muenzen, fallen = platziere_items(gitter,
anzahl_muenzen=6 + level * 2,
anzahl_fallen=3 + level)
spieler = Spieler(1, 1)
start_zeit = time.time()
return gitter, muenzen, fallen, spieler, start_zeit
# ─────────────────────────────────────────
# HAUPTPROGRAMM
# ─────────────────────────────────────────
def main():
pygame.init()
pygame.display.set_caption("Labyrinth-Quest Klasse 11 Informatik")
screen = pygame.display.set_mode((WIDTH, HEIGHT))
clock = pygame.time.Clock()
# Schriftarten
try:
font_gross = pygame.font.SysFont("monospace", 42, bold=True)
font = pygame.font.SysFont("monospace", 24, bold=True)
font_klein = pygame.font.SysFont("monospace", 16)
except:
font_gross = pygame.font.Font(None, 48)
font = pygame.font.Font(None, 28)
font_klein = pygame.font.Font(None, 20)
# Spielstart-Variablen
zustand = ZUSTAND_TITEL
level = 1
gesamt_pkt = 0
gitter = muenzen = fallen = spieler = start_zeit = None
tick = 0
running = True
while running:
dt = clock.tick(FPS) / 1000.0
tick += 1
# ── Events ──────────────────────────────────
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_ESCAPE:
if zustand == ZUSTAND_SPIEL:
zustand = ZUSTAND_PAUSE
elif zustand == ZUSTAND_PAUSE:
zustand = ZUSTAND_SPIEL
else:
running = False
# Titelschirm → Spiel starten
elif zustand == ZUSTAND_TITEL and event.key in (
pygame.K_RETURN, pygame.K_SPACE):
level = 1
gitter, muenzen, fallen, spieler, start_zeit = spiel_starten(level)
zustand = ZUSTAND_SPIEL
# Nach Gewinn / Verlieren → weiter oder neu
elif zustand in (ZUSTAND_GEWINN, ZUSTAND_VERLOR):
if event.key == pygame.K_RETURN:
if zustand == ZUSTAND_GEWINN:
level += 1
gesamt_pkt += spieler.punkte
else:
level = 1
gesamt_pkt = 0
gitter, muenzen, fallen, spieler, start_zeit = spiel_starten(level)
zustand = ZUSTAND_SPIEL
elif event.key == pygame.K_ESCAPE:
zustand = ZUSTAND_TITEL
# Pause → weiter
elif zustand == ZUSTAND_PAUSE and event.key == pygame.K_RETURN:
zustand = ZUSTAND_SPIEL
# Spielerbewegung
elif zustand == ZUSTAND_SPIEL:
dr, dc = 0, 0
if event.key in (pygame.K_UP, pygame.K_w): dr, dc = -1, 0
if event.key in (pygame.K_DOWN, pygame.K_s): dr, dc = 1, 0
if event.key in (pygame.K_LEFT, pygame.K_a): dr, dc = 0, -1
if event.key in (pygame.K_RIGHT, pygame.K_d): dr, dc = 0, 1
if (dr, dc) != (0, 0):
spieler.bewege(dr, dc, gitter)
# ── Spiellogik ──────────────────────────────
if zustand == ZUSTAND_SPIEL and spieler:
spieler.update(dt)
pos = (spieler.reihe, spieler.spalte)
# Münze einsammeln
if pos in muenzen:
muenzen.remove(pos)
spieler.punkte += 10
# Falle auslösen
if pos in fallen:
fallen.remove(pos)
spieler.leben -= 1
spieler.punkte = max(0, spieler.punkte - 5)
if spieler.leben <= 0:
zustand = ZUSTAND_VERLOR
# Ziel erreicht
if pos == (ROWS - 2, COLS - 2):
spieler.punkte += 50 + len(muenzen) * 5 # Bonus für übrige Münzen
zustand = ZUSTAND_GEWINN
# Zeitlimit
zeit_vergangen = time.time() - start_zeit
zeit_rest = max(0, ZEIT_LIMIT - (level - 1) * 5 - zeit_vergangen)
if zeit_rest <= 0:
spieler.leben -= 1
if spieler.leben <= 0:
zustand = ZUSTAND_VERLOR
else:
# Neues Labyrinth, Leben-Abzug
gitter, muenzen, fallen, spieler_neu, start_zeit = spiel_starten(level)
spieler_neu.leben = spieler.leben
spieler_neu.punkte = spieler.punkte
spieler = spieler_neu
else:
zeit_rest = ZEIT_LIMIT
# ── Zeichnen ────────────────────────────────
screen.fill(C_BG)
if zustand == ZUSTAND_TITEL:
# Titelschirm
screen.fill(C_BG)
zeige_overlay(screen, [
("LABYRINTH-QUEST", C_TITLE, True),
("Finde den Ausgang!", C_TEXT, False),
("Sammle Münzen ♦ Meide Fallen ✕", C_COIN, False),
("", C_TEXT, False),
("[ENTER] Starten", C_GREEN, False),
("[ESC] Beenden", C_RED, False),
], font_gross, font)
elif zustand in (ZUSTAND_SPIEL, ZUSTAND_PAUSE):
zeichne_labyrinth(screen, gitter, tick)
zeichne_exit(screen, gitter, tick)
zeichne_muenzen(screen, muenzen, tick)
zeichne_fallen(screen, fallen, tick)
spieler.zeichne(screen)
zeichne_hud(screen, spieler, level, zeit_rest, font, font_klein)
if zustand == ZUSTAND_PAUSE:
zeige_overlay(screen, [
("PAUSE", C_TITLE, True),
("[ENTER] Weiter", C_GREEN, False),
("[ESC] Beenden", C_RED, False),
], font_gross, font)
elif zustand == ZUSTAND_GEWINN:
zeichne_labyrinth(screen, gitter, tick)
zeichne_exit(screen, gitter, tick)
spieler.zeichne(screen)
zeichne_hud(screen, spieler, level, 0, font, font_klein)
zeige_overlay(screen, [
("LEVEL GESCHAFFT! 🎉", C_COIN, True),
(f"Punkte: {spieler.punkte}", C_TEXT, False),
(f"Gesamt: {gesamt_pkt + spieler.punkte}", C_GREEN, False),
("[ENTER] Nächstes Level", C_TITLE, False),
("[ESC] Zum Titel", C_RED, False),
], font_gross, font)
elif zustand == ZUSTAND_VERLOR:
zeichne_labyrinth(screen, gitter, tick)
spieler.zeichne(screen)
zeichne_hud(screen, spieler, level, 0, font, font_klein)
zeige_overlay(screen, [
("GAME OVER", C_RED, True),
(f"Punkte: {spieler.punkte}", C_TEXT, False),
("[ENTER] Neu starten", C_GREEN, False),
("[ESC] Zum Titel", C_TITLE, False),
], font_gross, font)
pygame.display.flip()
pygame.quit()
if __name__ == "__main__":
main()