From 084e9bbb2e7cb4e38bf0b840de4e1b2148847d23 Mon Sep 17 00:00:00 2001 From: mhagmann Date: Tue, 14 Jul 2026 10:05:37 +0200 Subject: [PATCH] Fertiges Spiel in Visual Studio Code --- fertigesSpiel/flucht_aus_der_tiefsee.py | 559 ++++++++++++++++++++++++ 1 file changed, 559 insertions(+) create mode 100644 fertigesSpiel/flucht_aus_der_tiefsee.py diff --git a/fertigesSpiel/flucht_aus_der_tiefsee.py b/fertigesSpiel/flucht_aus_der_tiefsee.py new file mode 100644 index 0000000..ab58e0b --- /dev/null +++ b/fertigesSpiel/flucht_aus_der_tiefsee.py @@ -0,0 +1,559 @@ +import sys +import math +import random +import pygame + +# ===================================================================== +# FLUCHT AUS DER TIEFSEE +# Steuerung: Pfeiltasten +# Start / Neustart: Leertaste +# ===================================================================== + +BREITE = 1100 +HOEHE = 760 +HUD_HOEHE = 125 +FPS = 60 + +PAPIER = (247, 240, 222) +TINTE = (25, 42, 52) +WASSER_OBEN = (173, 224, 245) +WASSER_UNTEN = (91, 183, 224) +WASSER_LICHT = (210, 239, 250) + +GRUEN = (117, 170, 105) +GRUEN_HELL = (151, 195, 126) +GRUEN_DUNKEL = (49, 104, 66) + +PERLE = (231, 63, 145) +PERLE_HELL = (255, 157, 206) + +QUALLE = (172, 139, 231) +QUALLE_HELL = (211, 195, 249) + +UBOOT = (246, 180, 45) +UBOOT_HELL = (255, 213, 87) +TAUCHER = (42, 163, 166) + +SPIELFELD = pygame.Rect(45, HUD_HOEHE + 18, BREITE - 90, HOEHE - HUD_HOEHE - 55) + +random.seed(8) + + +# ===================================================================== +# Hilfsfunktionen +# ===================================================================== + +def runde_box(flaeche, rect, farbe, randfarbe=TINTE, radius=14, rand=3): + pygame.draw.rect(flaeche, farbe, rect, border_radius=radius) + pygame.draw.rect(flaeche, randfarbe, rect, width=rand, border_radius=radius) + + +def text(flaeche, inhalt, schrift, farbe, pos, mitte=False): + bild = schrift.render(inhalt, True, farbe) + rect = bild.get_rect() + if mitte: + rect.center = pos + else: + rect.topleft = pos + flaeche.blit(bild, rect) + + +def zeichne_wellenlinie(flaeche, punkte, farbe, breite=3): + if len(punkte) >= 2: + pygame.draw.lines(flaeche, farbe, False, punkte, breite) + + +def organische_form(rect, seed): + """Erzeugt eine unregelmäßige, aber geschlossene Fels-/Algenform.""" + rng = random.Random(seed) + x, y, w, h = rect + punkte = [] + abstand = 22 + + for px in range(x, x + w + 1, abstand): + punkte.append((px, y + rng.randint(-7, 8))) + for py in range(y + abstand, y + h + 1, abstand): + punkte.append((x + w + rng.randint(-8, 8), py)) + for px in range(x + w - abstand, x - 1, -abstand): + punkte.append((px, y + h + rng.randint(-8, 8))) + for py in range(y + h - abstand, y, -abstand): + punkte.append((x + rng.randint(-8, 8), py)) + return punkte + + +# ===================================================================== +# Spielfiguren und Objekte +# ===================================================================== + +class Taucher: + def __init__(self, x, y): + self.start_x = x + self.start_y = y + self.x = x + self.y = y + self.breite = 42 + self.hoehe = 54 + self.geschwindigkeit = 3.5 + self.leben = 3 + self.sauerstoff = 100 + self.perlen_gesammelt = 0 + self.unverwundbar = False + self.unverwundbar_timer = 0 + + def rechteck(self): + return pygame.Rect(int(self.x), int(self.y), self.breite, self.hoehe) + + def bewegen(self, dx, dy, spielfeld, hindernisse): + neues_x = self.x + dx * self.geschwindigkeit + test = pygame.Rect(int(neues_x), int(self.y), self.breite, self.hoehe) + if spielfeld.contains(test) and not any(h.rechteck().colliderect(test) for h in hindernisse): + self.x = neues_x + + neues_y = self.y + dy * self.geschwindigkeit + test = pygame.Rect(int(self.x), int(neues_y), self.breite, self.hoehe) + if spielfeld.contains(test) and not any(h.rechteck().colliderect(test) for h in hindernisse): + self.y = neues_y + + def update(self): + if self.unverwundbar: + self.unverwundbar_timer -= 1 + if self.unverwundbar_timer <= 0: + self.unverwundbar = False + + def leben_verlieren(self): + if not self.unverwundbar: + self.leben -= 1 + self.unverwundbar = True + self.unverwundbar_timer = 90 + + def zuruecksetzen(self): + self.x = self.start_x + self.y = self.start_y + self.leben = 3 + self.sauerstoff = 100 + self.perlen_gesammelt = 0 + self.unverwundbar = False + self.unverwundbar_timer = 0 + + def zeichnen(self, flaeche): + if self.unverwundbar and (self.unverwundbar_timer // 5) % 2 == 0: + return + + x, y = int(self.x), int(self.y) + + # Sauerstoffflasche + pygame.draw.rect(flaeche, (241, 176, 43), (x - 6, y + 18, 10, 28), border_radius=4) + pygame.draw.rect(flaeche, TINTE, (x - 6, y + 18, 10, 28), 2, border_radius=4) + + # Körper + pygame.draw.rect(flaeche, TAUCHER, (x + 9, y + 25, 25, 23), border_radius=7) + pygame.draw.rect(flaeche, TINTE, (x + 9, y + 25, 25, 23), 2, border_radius=7) + + # Helm + pygame.draw.circle(flaeche, TAUCHER, (x + 22, y + 18), 17) + pygame.draw.circle(flaeche, TINTE, (x + 22, y + 18), 17, 3) + + # Visier + pygame.draw.ellipse(flaeche, (193, 231, 236), (x + 11, y + 9, 23, 17)) + pygame.draw.ellipse(flaeche, TINTE, (x + 11, y + 9, 23, 17), 2) + pygame.draw.circle(flaeche, TINTE, (x + 18, y + 17), 2) + pygame.draw.circle(flaeche, TINTE, (x + 27, y + 17), 2) + + # Arme + pygame.draw.line(flaeche, TINTE, (x + 10, y + 31), (x + 1, y + 38), 4) + pygame.draw.line(flaeche, TINTE, (x + 34, y + 31), (x + 42, y + 37), 4) + + # Beine und Flossen + pygame.draw.line(flaeche, TINTE, (x + 16, y + 46), (x + 12, y + 53), 4) + pygame.draw.line(flaeche, TINTE, (x + 28, y + 46), (x + 31, y + 53), 4) + pygame.draw.polygon(flaeche, TAUCHER, [(x + 12, y + 52), (x + 3, y + 55), (x + 12, y + 47)]) + pygame.draw.polygon(flaeche, TAUCHER, [(x + 31, y + 52), (x + 40, y + 55), (x + 31, y + 47)]) + + +class Perle: + def __init__(self, x, y): + self.x, self.y = x, y + self.groesse = 26 + self.eingesammelt = False + + def rechteck(self): + return pygame.Rect(self.x, self.y, self.groesse, self.groesse) + + def zeichnen(self, flaeche): + if self.eingesammelt: + return + mitte = (self.x + self.groesse // 2, self.y + self.groesse // 2) + pygame.draw.circle(flaeche, TINTE, mitte, 14) + pygame.draw.circle(flaeche, PERLE, mitte, 12) + pygame.draw.circle(flaeche, PERLE_HELL, (mitte[0] - 3, mitte[1] - 3), 7) + pygame.draw.circle(flaeche, (255, 245, 250), (mitte[0] - 5, mitte[1] - 6), 3) + + +class Hindernis: + def __init__(self, x, y, breite, hoehe, seed): + self._rechteck = pygame.Rect(x, y, breite, hoehe) + self.punkte = organische_form((x, y, breite, hoehe), seed) + self.seed = seed + + def rechteck(self): + return self._rechteck.inflate(-10, -10) + + def zeichnen(self, flaeche): + pygame.draw.polygon(flaeche, GRUEN_DUNKEL, self.punkte) + innen = [] + cx, cy = self._rechteck.center + for px, py in self.punkte: + innen.append((int(px + (cx - px) * 0.08), int(py + (cy - py) * 0.08))) + pygame.draw.polygon(flaeche, GRUEN, innen) + + rng = random.Random(self.seed) + for _ in range(max(5, self._rechteck.width * self._rechteck.height // 8000)): + px = rng.randint(self._rechteck.left + 12, self._rechteck.right - 12) + py = rng.randint(self._rechteck.top + 12, self._rechteck.bottom - 12) + pygame.draw.circle(flaeche, GRUEN_HELL, (px, py), rng.randint(3, 7)) + pygame.draw.circle(flaeche, GRUEN_DUNKEL, (px, py), rng.randint(1, 3), 1) + + # kleine Algenknubbel am Rand + for i, (px, py) in enumerate(self.punkte[::2]): + r = 5 + (i % 3) + pygame.draw.circle(flaeche, GRUEN_HELL, (px, py), r) + pygame.draw.circle(flaeche, GRUEN_DUNKEL, (px, py), r, 2) + + +class Qualle: + def __init__(self, start_x, end_x, y, geschwindigkeit): + self.start_x, self.end_x = start_x, end_x + self.x, self.y = start_x, y + self.groesse = 46 + self.geschwindigkeit = geschwindigkeit + self.richtung = 1 + self.phase = random.random() * math.tau + + def update(self): + self.x += self.geschwindigkeit * self.richtung + if self.x >= self.end_x or self.x <= self.start_x: + self.richtung *= -1 + self.phase += 0.06 + + def rechteck(self): + return pygame.Rect(int(self.x), int(self.y), self.groesse, self.groesse + 12) + + def zeichnen(self, flaeche): + x, y = int(self.x), int(self.y + math.sin(self.phase) * 4) + + kuppel = pygame.Rect(x, y, self.groesse, 31) + pygame.draw.ellipse(flaeche, TINTE, kuppel) + pygame.draw.ellipse(flaeche, QUALLE, kuppel.inflate(-4, -4)) + pygame.draw.ellipse(flaeche, QUALLE_HELL, (x + 8, y + 5, 19, 11)) + + # Unterkante etwas flacher abdecken + pygame.draw.rect(flaeche, QUALLE, (x + 3, y + 17, self.groesse - 6, 12)) + pygame.draw.line(flaeche, TINTE, (x + 3, y + 29), (x + self.groesse - 3, y + 29), 2) + + # Gesicht + pygame.draw.circle(flaeche, TINTE, (x + 16, y + 18), 2) + pygame.draw.circle(flaeche, TINTE, (x + 30, y + 18), 2) + pygame.draw.arc(flaeche, TINTE, (x + 19, y + 18, 9, 7), 0.1, math.pi - 0.1, 2) + + # gewellte Tentakel + for i in range(4): + tx = x + 8 + i * 10 + punkte = [] + for s in range(6): + punkte.append((tx + int(math.sin(self.phase + s) * 3), y + 30 + s * 5)) + zeichne_wellenlinie(flaeche, punkte, TINTE, 2) + + +class Uboot: + def __init__(self, x, y): + self._rechteck = pygame.Rect(x, y, 145, 78) + + def rechteck(self): + return self._rechteck + + def zeichnen(self, flaeche): + x, y, w, h = self._rechteck + + # Propeller + pygame.draw.line(flaeche, TINTE, (x + w - 2, y + h // 2), (x + w + 17, y + h // 2), 4) + pygame.draw.polygon(flaeche, UBOOT, [(x + w + 15, y + 17), (x + w + 25, y + 38), (x + w + 15, y + 58)]) + pygame.draw.polygon(flaeche, TINTE, [(x + w + 15, y + 17), (x + w + 25, y + 38), (x + w + 15, y + 58)], 2) + + # Körper + pygame.draw.ellipse(flaeche, TINTE, self._rechteck) + pygame.draw.ellipse(flaeche, UBOOT, self._rechteck.inflate(-5, -5)) + pygame.draw.ellipse(flaeche, UBOOT_HELL, (x + 24, y + 8, 80, 25)) + + # Nase + pygame.draw.circle(flaeche, (130, 196, 214), (x + 17, y + 39), 15) + pygame.draw.circle(flaeche, TINTE, (x + 17, y + 39), 15, 3) + + # Bullaugen + for px in (50, 77, 104): + pygame.draw.circle(flaeche, TINTE, (x + px, y + 39), 10) + pygame.draw.circle(flaeche, (213, 239, 244), (x + px, y + 39), 7) + + # Turm und Periskop + pygame.draw.rect(flaeche, UBOOT, (x + 59, y - 13, 31, 18), border_radius=6) + pygame.draw.rect(flaeche, TINTE, (x + 59, y - 13, 31, 18), 3, border_radius=6) + pygame.draw.line(flaeche, TINTE, (x + 74, y - 13), (x + 74, y - 31), 4) + pygame.draw.line(flaeche, TINTE, (x + 74, y - 31), (x + 90, y - 31), 4) + + # Flossen + pygame.draw.polygon(flaeche, UBOOT, [(x + 56, y + 66), (x + 43, y + 83), (x + 78, y + 80)]) + pygame.draw.polygon(flaeche, TINTE, [(x + 56, y + 66), (x + 43, y + 83), (x + 78, y + 80)], 2) + + +# ===================================================================== +# Level +# ===================================================================== + +def erstelle_level(): + taucher = Taucher(70, HOEHE - 110) + + hindernisse = [ + Hindernis(95, 165, 300, 55, 1), + Hindernis(350, 165, 55, 250, 2), + Hindernis(95, 345, 280, 55, 3), + Hindernis(95, 345, 55, 210, 4), + + Hindernis(390, 470, 300, 58, 5), + Hindernis(335, 505, 62, 135, 6), + + # Die beiden senkrechten Algenwände sind bewusst kürzer. + # Dadurch entstehen unten zwei ausreichend breite Durchgänge: + # einer zur oberen Perle und einer weiter zum U-Boot. + Hindernis(665, 165, 58, 170, 7), + Hindernis(665, 400, 240, 58, 8), + Hindernis(845, 165, 58, 170, 9), + + # Rechts bleibt neben dieser Wand ein freier Weg nach oben. + Hindernis(930, 455, 58, 130, 10), + ] + + perlen = [ + Perle(190, 225), + Perle(165, 430), + Perle(455, 235), + Perle(760, 310), + Perle(820, 585), + Perle(590, 575), + Perle(360, 650), + ] + + quallen = [ + Qualle(280, 470, 255, 1.4), + Qualle(745, 865, 300, 1.6), + Qualle(230, 340, 505, 1.2), + ] + + uboot = Uboot(895, 175) + return taucher, hindernisse, perlen, quallen, uboot + + +# ===================================================================== +# Hintergrund und HUD +# ===================================================================== + +def zeichne_wasser(flaeche, blasen, pflanzen): + for y in range(HUD_HOEHE, HOEHE): + t = (y - HUD_HOEHE) / (HOEHE - HUD_HOEHE) + farbe = tuple(int(WASSER_OBEN[i] + (WASSER_UNTEN[i] - WASSER_OBEN[i]) * t) for i in range(3)) + pygame.draw.line(flaeche, farbe, (0, y), (BREITE, y)) + + # dezente Papier-/Kreidestruktur + for bx, by, r in blasen: + pygame.draw.circle(flaeche, WASSER_LICHT, (bx, by), r, 1) + + for x, y, hoehe in pflanzen: + for versatz in (-8, 0, 8): + punkte = [] + for s in range(6): + punkte.append((x + versatz + int(math.sin(s * 0.9 + versatz) * 4), y - s * hoehe // 6)) + zeichne_wellenlinie(flaeche, punkte, (51, 149, 141), 3) + + +def zeichne_rahmen(flaeche): + pygame.draw.rect(flaeche, PAPIER, (0, 0, BREITE, HUD_HOEHE)) + pygame.draw.rect(flaeche, PAPIER, (0, HOEHE - 25, BREITE, 25)) + pygame.draw.rect(flaeche, TINTE, (10, 10, BREITE - 20, HOEHE - 20), 3) + pygame.draw.rect(flaeche, TINTE, SPIELFELD, 3) + + +def zeichne_hud(flaeche, taucher, gesamt, schrift_titel, schrift_hud, schrift_klein): + text(flaeche, "Flucht aus der Tiefsee", schrift_titel, TINTE, (BREITE // 2, 36), mitte=True) + + boxen = [ + (pygame.Rect(40, 72, 245, 45), f"● Perlen: {taucher.perlen_gesammelt} / {gesamt}", PERLE), + (pygame.Rect(305, 72, 245, 45), f"▣ Sauerstoff: {taucher.sauerstoff}", (45, 140, 220)), + (pygame.Rect(570, 72, 190, 45), f"♥ Leben: {taucher.leben}", (220, 63, 52)), + ] + for rect, beschriftung, farbe in boxen: + runde_box(flaeche, rect, PAPIER) + text(flaeche, beschriftung, schrift_hud, farbe, (rect.x + 14, rect.y + 9)) + + ziel_box = pygame.Rect(780, 72, 285, 45) + runde_box(flaeche, ziel_box, PAPIER) + text(flaeche, "★ Sammle alle Perlen!", schrift_klein, TINTE, (ziel_box.x + 13, ziel_box.y + 11)) + + +def zeichne_start_ziel(flaeche, schrift): + text(flaeche, "↑ START", schrift, (20, 108, 96), (72, HOEHE - 52)) + text(flaeche, "← ZIEL", schrift, (208, 79, 34), (992, 158)) + + +def zeichne_overlay(flaeche, titel, untertitel, hinweis, gross, mittel): + overlay = pygame.Surface((BREITE, HOEHE), pygame.SRCALPHA) + overlay.fill((13, 31, 44, 175)) + flaeche.blit(overlay, (0, 0)) + + box = pygame.Rect(BREITE // 2 - 330, HOEHE // 2 - 115, 660, 230) + runde_box(flaeche, box, PAPIER, TINTE, 22, 4) + text(flaeche, titel, gross, TINTE, (BREITE // 2, box.y + 55), mitte=True) + text(flaeche, untertitel, mittel, TINTE, (BREITE // 2, box.y + 115), mitte=True) + text(flaeche, hinweis, mittel, (30, 111, 143), (BREITE // 2, box.y + 165), mitte=True) + + +# ===================================================================== +# Hauptprogramm +# ===================================================================== + +def main(): + pygame.init() + pygame.display.set_caption("Flucht aus der Tiefsee") + bildschirm = pygame.display.set_mode((BREITE, HOEHE)) + uhr = pygame.time.Clock() + + schrift_titel = pygame.font.SysFont("Comic Sans MS", 38, bold=True) + schrift_gross = pygame.font.SysFont("Comic Sans MS", 44, bold=True) + schrift_mittel = pygame.font.SysFont("Comic Sans MS", 22) + schrift_hud = pygame.font.SysFont("Comic Sans MS", 21, bold=True) + schrift_klein = pygame.font.SysFont("Comic Sans MS", 18, bold=True) + + taucher, hindernisse, perlen, quallen, uboot = erstelle_level() + + blasen = [ + (random.randint(SPIELFELD.left + 10, SPIELFELD.right - 10), + random.randint(SPIELFELD.top + 10, SPIELFELD.bottom - 10), + random.choice((1, 1, 2, 2, 3))) + for _ in range(95) + ] + + pflanzen = [ + (140, 690, 50), (245, 655, 38), (525, 300, 52), + (735, 685, 45), (865, 635, 58), (965, 320, 42), + (610, 210, 40), (470, 655, 35) + ] + + zustand = "start" + end_nachricht = "" + sauerstoff_zaehler = 0 + + while True: + for event in pygame.event.get(): + if event.type == pygame.QUIT: + pygame.quit() + sys.exit() + + if event.type == pygame.KEYDOWN and event.key == pygame.K_SPACE: + if zustand == "start": + zustand = "laeuft" + elif zustand in ("gewonnen", "verloren"): + taucher.zuruecksetzen() + for perle in perlen: + perle.eingesammelt = False + for qualle in quallen: + qualle.x = qualle.start_x + qualle.richtung = 1 + sauerstoff_zaehler = 0 + end_nachricht = "" + zustand = "laeuft" + + if zustand == "laeuft": + tasten = pygame.key.get_pressed() + dx = tasten[pygame.K_RIGHT] - tasten[pygame.K_LEFT] + dy = tasten[pygame.K_DOWN] - tasten[pygame.K_UP] + + taucher.bewegen(dx, dy, SPIELFELD, hindernisse) + taucher.update() + + for qualle in quallen: + qualle.update() + + sauerstoff_zaehler += 1 + if sauerstoff_zaehler >= 42: + taucher.sauerstoff = max(0, taucher.sauerstoff - 1) + sauerstoff_zaehler = 0 + + for perle in perlen: + if not perle.eingesammelt and perle.rechteck().colliderect(taucher.rechteck()): + perle.eingesammelt = True + taucher.perlen_gesammelt += 1 + + for qualle in quallen: + if qualle.rechteck().colliderect(taucher.rechteck()): + taucher.leben_verlieren() + + alle_perlen = all(perle.eingesammelt for perle in perlen) + if alle_perlen and uboot.rechteck().colliderect(taucher.rechteck()): + zustand = "gewonnen" + end_nachricht = "Du bist sicher im U-Boot angekommen!" + + if taucher.sauerstoff <= 0: + zustand = "verloren" + end_nachricht = "Dir ist der Sauerstoff ausgegangen!" + elif taucher.leben <= 0: + zustand = "verloren" + end_nachricht = "Die Quallen waren dieses Mal schneller." + + bildschirm.fill(PAPIER) + zeichne_wasser(bildschirm, blasen, pflanzen) + + for hindernis in hindernisse: + hindernis.zeichnen(bildschirm) + for perle in perlen: + perle.zeichnen(bildschirm) + for qualle in quallen: + qualle.zeichnen(bildschirm) + + uboot.zeichnen(bildschirm) + taucher.zeichnen(bildschirm) + + zeichne_rahmen(bildschirm) + zeichne_hud(bildschirm, taucher, len(perlen), schrift_titel, schrift_hud, schrift_klein) + zeichne_start_ziel(bildschirm, schrift_klein) + + if zustand == "start": + zeichne_overlay( + bildschirm, + "FLUCHT AUS DER TIEFSEE", + "Sammle alle Perlen und erreiche danach das U-Boot.", + "Mit LEERTASTE starten – bewegen mit den PFEILTASTEN", + schrift_gross, + schrift_mittel, + ) + elif zustand == "gewonnen": + zeichne_overlay( + bildschirm, + "GESCHAFFT!", + end_nachricht, + "Leertaste für ein neues Spiel", + schrift_gross, + schrift_mittel, + ) + elif zustand == "verloren": + zeichne_overlay( + bildschirm, + "SPIEL VORBEI", + end_nachricht, + "Leertaste für einen neuen Versuch", + schrift_gross, + schrift_mittel, + ) + + pygame.display.flip() + uhr.tick(FPS) + + +if __name__ == "__main__": + main()