358 lines
8.6 KiB
Python
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())
|