Files
2026-11-12 13:33:44 +01:00

358 lines
8.6 KiB
Python

import asyncio
import turtle
import math
# ==========================================
# 1. BILDSCHIRM UND GLOBALE VARIABLEN
# ==========================================
# Wir erstellen das Turtle-Fenster.
screen = turtle.Screen()
screen.setup(900, 650) # Fenstergröße
screen.bgcolor("black") # Schwarzer Hintergrund
screen.title("Rennbahn - Reeborg & Sophia")
# tracer(0) deaktiviert die automatische Bildschirmaktualisierung.
# Wir aktualisieren den Bildschirm später selbst mit screen.update().
screen.tracer(0)
# Die aktuelle Ampelphase wird in einer globalen Variable gespeichert.
# OL_UR bedeutet: oben links und unten rechts haben Grün.
# OR_UL bedeutet: oben rechts und unten links haben Grün.
ampel_phase = "OL_UR"
# Ein Lock verhindert, dass beide Autos gleichzeitig
# die Kreuzung überqueren.
kreuzung = asyncio.Lock()
# ==========================================
# 2. STRECKE ZEICHNEN
# ==========================================
def position(t, spur):
# Mathematische Berechnung der Achterbahn (Unendlichkeitszeichen).
x = 250 * math.sin(t)
y = 145 * math.sin(2 * t)
# Berechnung der Fahrtrichtung an der aktuellen Position.
dx = 250 * math.cos(t)
dy = 290 * math.cos(2 * t)
# Länge des Richtungsvektors berechnen.
laenge = math.sqrt(dx**2 + dy**2)
# Die beiden Fahrspuren werden leicht versetzt.
# spur = 1 oder -1 unterscheidet die beiden Fahrspuren.
abstand = 10 * spur * math.sin(t)**2
# Versatz senkrecht zur Fahrtrichtung berechnen.
x += abstand * (-dy / laenge)
y += abstand * (dx / laenge)
return x, y
def zeichne_strecke():
# Eine zusätzliche Turtle zeichnet die Rennbahn.
strecke = turtle.Turtle()
strecke.hideturtle()
strecke.speed(0)
strecke.color("#777777")
strecke.pensize(3)
# Wir zeichnen die beiden Fahrspuren der Achterbahn.
for spur in [-1, 1]:
strecke.penup()
# Die Strecke wird aus 1001 kleinen Punkten aufgebaut.
for i in range(1001):
t = i * 2 * math.pi / 1000
# Berechnet die Koordinaten für den aktuellen Punkt.
x, y = position(t, spur)
if i == 0:
strecke.goto(x, y)
strecke.pendown()
else:
strecke.goto(x, y)
strecke.penup()
screen.update()
# ==========================================
# 3. AMPEL ANZEIGEN
# ==========================================
anzeige = turtle.Turtle()
anzeige.hideturtle()
anzeige.penup()
anzeige.color("white")
anzeige.goto(0, 260)
def zeige_ampel():
# Alte Ampelanzeige löschen.
anzeige.clear()
# Aktuelle Ampelphase auf dem Bildschirm anzeigen.
anzeige.write(
"AMPEL: " + ampel_phase,
align="center",
font=("Arial", 18, "bold")
)
screen.update()
# ==========================================
# 4. AMPEL-COROUTINE
# ==========================================
async def ampel():
global ampel_phase
# Die Ampel läuft dauerhaft.
while True:
# Die Ampel wartet 5 Sekunden.
# asyncio.sleep blockiert nicht die Event Loop.
await asyncio.sleep(5)
# Die Ampel wartet, bis die Kreuzung frei ist.
async with kreuzung:
# Zwischen den beiden Ampelphasen wechseln.
if ampel_phase == "OL_UR":
ampel_phase = "OR_UL"
else:
ampel_phase = "OL_UR"
# Änderung in der Konsole protokollieren.
print("Ampel schaltet auf:", ampel_phase)
# Neue Ampelphase grafisch anzeigen.
zeige_ampel()
# ==========================================
# 5. AUTOS BEWEGEN
# ==========================================
async def fahren(auto, start, ende, spur):
# Wir teilen die Bewegung in viele kleine Schritte.
# Dadurch wird die Event Loop nicht blockiert.
schritte = max(1, int(abs(ende - start) * 60))
for i in range(1, schritte + 1):
# Berechnung des nächsten Punktes auf der Strecke.
t = start + (ende - start) * i / schritte
x, y = position(t, spur)
# Aktuelle Position des Autos speichern.
alte_x, alte_y = auto.position()
# Fahrtrichtung aus den alten und neuen Koordinaten berechnen.
winkel = math.degrees(
math.atan2(y - alte_y, x - alte_x)
)
# Turtle in Fahrtrichtung drehen.
auto.setheading(winkel)
# Auto zum nächsten Punkt bewegen.
auto.goto(x, y)
# Bildschirm aktualisieren.
screen.update()
# Kurze Pause zwischen den Bewegungsschritten.
await asyncio.sleep(0.02)
# ==========================================
# 6. KREUZUNG UND AMPEL ABFRAGEN
# ==========================================
async def kreuzung_ueberqueren(auto, name, spur, t, gruen):
# Das Auto fährt zunächst bis kurz vor die Kreuzung.
einstieg = t - 0.12
await fahren(auto, t - 0.5, einstieg, spur)
# Solange die Ampel rot ist, wartet das Auto.
while True:
# Prüfen, ob die Ampel für dieses Auto grün ist.
if ampel_phase == gruen:
# Kreuzung reservieren.
await kreuzung.acquire()
# Nach dem Reservieren erneut prüfen,
# ob die Ampel noch immer grün ist.
if ampel_phase == gruen:
break
# Falls nicht mehr grün, Lock wieder freigeben.
kreuzung.release()
# Log-Meldung ausgeben.
print(name, "wartet auf Grün...")
# Eine Sekunde warten und erneut prüfen.
await asyncio.sleep(1)
print(name, "hat Grün und überquert die Kreuzung!")
try:
# Das Auto überquert die Kreuzung in kleinen Schritten.
await fahren(auto, einstieg, t + 0.12, spur)
finally:
# Kreuzung nach der Überquerung wieder freigeben.
kreuzung.release()
# ==========================================
# 7. REEBORG - BLAUES AUTO
# ==========================================
async def reeborg():
# Neues Turtle-Objekt für Reeborg erstellen.
auto = turtle.Turtle()
auto.shape("turtle")
auto.color("#008cff")
auto.pensize(4)
auto.speed(0)
# Reeborg bekommt seine Fahrspur.
spur = 1
# Startposition oben links.
t = -3 * math.pi / 4
x, y = position(t, spur)
auto.penup()
auto.goto(x, y)
print("Reeborg startet!")
# Das Auto fährt dauerhaft im Kreis.
while True:
# Erste Kreuzung: Reeborg benötigt OL_UR-Grün.
await kreuzung_ueberqueren(
auto, "Reeborg", spur, 0, "OL_UR"
)
# Danach fährt Reeborg durch die rechte Kehre.
await fahren(auto, 0.12, math.pi - 0.12, spur)
# Zweite Kreuzung: Hier benötigt Reeborg OR_UL-Grün.
await kreuzung_ueberqueren(
auto, "Reeborg", spur, math.pi, "OR_UL"
)
# Anschließend fährt Reeborg durch die linke Kehre.
await fahren(
auto,
math.pi + 0.12,
2 * math.pi - 0.12,
spur
)
# ==========================================
# 8. SOPHIA - ROTES AUTO
# ==========================================
async def sophia():
# Neues Turtle-Objekt für Sophia erstellen.
auto = turtle.Turtle()
auto.shape("turtle")
auto.color("#ff3030")
auto.pensize(4)
auto.speed(0)
# Sophia bekommt die andere Fahrspur.
spur = -1
# Startposition unten links.
t = -math.pi / 4
x, y = position(t, spur)
auto.penup()
auto.goto(x, y)
print("Sophia startet!")
# Auch Sophia fährt dauerhaft.
while True:
# Erste Kreuzung: Sophia benötigt OR_UL-Grün.
await kreuzung_ueberqueren(
auto, "Sophia", spur, 0, "OR_UL"
)
# Danach fährt Sophia durch die rechte Kehre.
await fahren(auto, 0.12, math.pi - 0.12, spur)
# Zweite Kreuzung: Sophia benötigt OL_UR-Grün.
await kreuzung_ueberqueren(
auto, "Sophia", spur, math.pi, "OL_UR"
)
# Anschließend fährt Sophia durch die linke Kehre.
await fahren(
auto,
math.pi + 0.12,
2 * math.pi - 0.12,
spur
)
# ==========================================
# 9. HAUPTPROGRAMM
# ==========================================
async def main():
# Zuerst die Rennbahn zeichnen.
zeichne_strecke()
# Ampelanzeige initialisieren.
zeige_ampel()
# Alle drei Coroutinen nebenläufig starten.
# Das Programm wartet, bis alle Coroutinen beendet sind.
await asyncio.gather(
ampel(),
reeborg(),
sophia()
)
# Startet die Event Loop und damit das Programm.
asyncio.run(main())