Fertiges Spiel in Visual Studio Code

This commit is contained in:
mhagmann 2026-07-14 10:05:37 +02:00
parent 3b8bf8ace6
commit 084e9bbb2e

View file

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