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()