Added Soundeffects

This commit is contained in:
2025-04-25 16:23:43 +02:00
parent 15635d7f96
commit c046995fdc
7 changed files with 123 additions and 112 deletions

BIN
_start.mp3 Normal file

Binary file not shown.

BIN
fail.mp3 Normal file

Binary file not shown.

235
game.py
View File

@@ -5,14 +5,15 @@ import json
import os import os
import threading import threading
import time import time
import serial.tools.list_ports # Import hinzugefügt import serial.tools.list_ports # Import hinzugefügt
import pygame
class TimeGuessGame: class TimeGuessGame:
def __init__(self, root): def __init__(self, root):
self.root = root self.root = root
self.root.title("Zeit-Schätz-Spiel") self.root.title("Zeit-Schätz-Spiel")
self.root.geometry("800x600") self.root.geometry("800x600")
self.root.resizable(True, True) # Resizable aktiviert self.root.resizable(True, True) # Resizable aktiviert
# Serielle Verbindung # Serielle Verbindung
self.serial_port = None self.serial_port = None
@@ -28,16 +29,17 @@ class TimeGuessGame:
self.load_leaderboard() self.load_leaderboard()
# UI-Elemente # UI-Elemente
self.canvas_frame_id = None # Hinzugefügt: ID für das Canvas-Fenster self.canvas_frame_id = None # Hinzugefügt: ID für das Canvas-Fenster
self.create_ui() self.create_ui()
pygame.mixer.init()
# Verbindung nicht automatisch beim Start herstellen # Verbindung nicht automatisch beim Start herstellen
# Serielle Kommunikation wird in setup_connection gestartet # Serielle Kommunikation wird in setup_connection gestartet
# Protokoll für das Schließen des Fensters # Protokoll für das Schließen des Fensters
self.root.protocol("WM_DELETE_WINDOW", self.on_closing) self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
def create_ui(self): def create_ui(self):
# ... (Restlicher Code von create_ui bleibt gleich bis zum Ranglisten-Teil) ... # ... (Restlicher Code von create_ui bleibt gleich bis zum Ranglisten-Teil) ...
@@ -58,7 +60,7 @@ class TimeGuessGame:
self.start_button = tk.Button(control_frame, text="Neues Spiel starten", self.start_button = tk.Button(control_frame, text="Neues Spiel starten",
command=self.start_new_game, font=("Arial", 14), command=self.start_new_game, font=("Arial", 14),
bg="#4CAF50", fg="white", padx=10, pady=5, bg="#4CAF50", fg="white", padx=10, pady=5,
state=tk.DISABLED) # Standardmäßig deaktiviert bis Verbindung steht state=tk.DISABLED) # Standardmäßig deaktiviert bis Verbindung steht
self.start_button.pack(pady=10) self.start_button.pack(pady=10)
# Frame für Spielinformationen # Frame für Spielinformationen
@@ -90,7 +92,7 @@ class TimeGuessGame:
self.name_label.pack(side=tk.LEFT) self.name_label.pack(side=tk.LEFT)
self.name_entry = tk.Entry(name_frame, font=("Arial", 12), width=20) self.name_entry = tk.Entry(name_frame, font=("Arial", 12), width=20)
self.name_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True) # Nimmt verfügbaren Platz ein self.name_entry.pack(side=tk.LEFT, padx=5, fill=tk.X, expand=True) # Nimmt verfügbaren Platz ein
button_frame = tk.Frame(name_frame) button_frame = tk.Frame(name_frame)
button_frame.pack(side=tk.LEFT) button_frame.pack(side=tk.LEFT)
@@ -105,24 +107,24 @@ class TimeGuessGame:
bg="#9E9E9E", fg="white") bg="#9E9E9E", fg="white")
self.skip_button.pack(side=tk.LEFT, padx=5) self.skip_button.pack(side=tk.LEFT, padx=5)
# --- Frame für Rangliste (Container) --- # --- Frame für Rangliste (Container) ---
leaderboard_outer_frame = tk.Frame(self.root, padx=10, pady=10) leaderboard_outer_frame = tk.Frame(self.root, padx=10, pady=10)
leaderboard_outer_frame.pack(fill=tk.BOTH, expand=True) # Füllt restlichen Platz leaderboard_outer_frame.pack(fill=tk.BOTH, expand=True) # Füllt restlichen Platz
tk.Label(leaderboard_outer_frame, text="Rangliste", font=("Arial", 16, "bold")).pack() tk.Label(leaderboard_outer_frame, text="Rangliste", font=("Arial", 16, "bold")).pack()
# --- Canvas und Scrollbar für die Rangliste --- # --- Canvas und Scrollbar für die Rangliste ---
self.leaderboard_canvas = tk.Canvas(leaderboard_outer_frame) self.leaderboard_canvas = tk.Canvas(leaderboard_outer_frame)
self.leaderboard_scrollbar = tk.Scrollbar(leaderboard_outer_frame, orient=tk.VERTICAL, command=self.leaderboard_canvas.yview) self.leaderboard_scrollbar = tk.Scrollbar(leaderboard_outer_frame, orient=tk.VERTICAL,
self.scrollable_leaderboard_frame = tk.Frame(self.leaderboard_canvas) # Frame *innerhalb* des Canvas command=self.leaderboard_canvas.yview)
self.scrollable_leaderboard_frame = tk.Frame(self.leaderboard_canvas) # Frame *innerhalb* des Canvas
# --- Änderung 1: ID speichern und Bindung an Canvas --- # --- Änderung 1: ID speichern und Bindung an Canvas ---
# Das Frame in den Canvas einbetten und die ID speichern # Das Frame in den Canvas einbetten und die ID speichern
self.canvas_frame_id = self.leaderboard_canvas.create_window( self.canvas_frame_id = self.leaderboard_canvas.create_window(
(0, 0), (0, 0),
window=self.scrollable_leaderboard_frame, window=self.scrollable_leaderboard_frame,
anchor="nw" # Wichtig: Anker oben links anchor="nw" # Wichtig: Anker oben links
) )
# Konfigurations-Event an den *Canvas* binden, um die Breite des Frames anzupassen # Konfigurations-Event an den *Canvas* binden, um die Breite des Frames anzupassen
@@ -145,12 +147,12 @@ class TimeGuessGame:
file_menu = tk.Menu(menubar, tearoff=0) file_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Datei", menu=file_menu) menubar.add_cascade(label="Datei", menu=file_menu)
file_menu.add_command(label="Rangliste zurücksetzen", command=self.reset_leaderboard) file_menu.add_command(label="Rangliste zurücksetzen", command=self.reset_leaderboard)
file_menu.add_command(label="Beenden", command=self.on_closing) # Geändert zu on_closing file_menu.add_command(label="Beenden", command=self.on_closing) # Geändert zu on_closing
connection_menu = tk.Menu(menubar, tearoff=0) connection_menu = tk.Menu(menubar, tearoff=0)
menubar.add_cascade(label="Verbindung", menu=connection_menu) menubar.add_cascade(label="Verbindung", menu=connection_menu)
connection_menu.add_command(label="Verbinden", command=self.setup_connection) connection_menu.add_command(label="Verbinden", command=self.setup_connection)
connection_menu.add_command(label="Trennen", command=self.disconnect_serial) # Trennen Option connection_menu.add_command(label="Trennen", command=self.disconnect_serial) # Trennen Option
# --- Änderung 2: Neue Methode zum Anpassen der Frame-Breite --- # --- Änderung 2: Neue Methode zum Anpassen der Frame-Breite ---
def _on_canvas_configure(self, event): def _on_canvas_configure(self, event):
@@ -163,7 +165,6 @@ class TimeGuessGame:
self.leaderboard_canvas.configure(scrollregion=self.leaderboard_canvas.bbox("all")) self.leaderboard_canvas.configure(scrollregion=self.leaderboard_canvas.bbox("all"))
# --- Ende Änderung 2 --- # --- Ende Änderung 2 ---
def update_leaderboard_display(self): def update_leaderboard_display(self):
# Bestehende Einträge im scrollbaren Frame löschen # Bestehende Einträge im scrollbaren Frame löschen
for widget in self.scrollable_leaderboard_frame.winfo_children(): for widget in self.scrollable_leaderboard_frame.winfo_children():
@@ -171,20 +172,20 @@ class TimeGuessGame:
# --- Spaltenkonfiguration mit Gewichten für dynamische Breite --- # --- Spaltenkonfiguration mit Gewichten für dynamische Breite ---
# Definieren Sie die relativen Breiten (Gewichte) der Spalten # Definieren Sie die relativen Breiten (Gewichte) der Spalten
column_weights = [1, 5, 3, 3, 3] # Name etwas breiter gemacht column_weights = [1, 5, 3, 3, 3] # Name etwas breiter gemacht
headers = ["Rang", "Name", "Zielzeit (s)", "Gemessene Zeit (s)", "Abweichung (s)"] headers = ["Rang", "Name", "Zielzeit (s)", "Gemessene Zeit (s)", "Abweichung (s)"]
# Konfiguriere die Spalten im scrollable_leaderboard_frame, damit sie sich anpassen # Konfiguriere die Spalten im scrollable_leaderboard_frame, damit sie sich anpassen
# Wichtig: Dies wirkt sich nur aus, wenn scrollable_leaderboard_frame # Wichtig: Dies wirkt sich nur aus, wenn scrollable_leaderboard_frame
# die Breite hat, um die Gewichte anzuwenden (daher _on_canvas_configure) # die Breite hat, um die Gewichte anzuwenden (daher _on_canvas_configure)
for i, weight in enumerate(column_weights): for i, weight in enumerate(column_weights):
self.scrollable_leaderboard_frame.columnconfigure(i, weight=weight, uniform='leaderboard') # 'uniform' kann helfen self.scrollable_leaderboard_frame.columnconfigure(i, weight=weight, uniform='leaderboard') # 'uniform' kann helfen
# --- Kopfzeile erstellen mit grid --- # --- Kopfzeile erstellen mit grid ---
for i, header in enumerate(headers): for i, header in enumerate(headers):
lbl = tk.Label(self.scrollable_leaderboard_frame, text=header, lbl = tk.Label(self.scrollable_leaderboard_frame, text=header,
font=("Arial", 11, "bold"), relief=tk.RIDGE, padx=5, pady=2, bd=1) # Etwas kleiner, mit Rand font=("Arial", 11, "bold"), relief=tk.RIDGE, padx=5, pady=2, bd=1) # Etwas kleiner, mit Rand
lbl.grid(row=0, column=i, sticky='nsew') # nsew füllt Zelle komplett lbl.grid(row=0, column=i, sticky='nsew') # nsew füllt Zelle komplett
# --- Einträge in die Rangliste einfügen mit grid --- # --- Einträge in die Rangliste einfügen mit grid ---
for i, entry in enumerate(self.leaderboard): for i, entry in enumerate(self.leaderboard):
@@ -193,28 +194,33 @@ class TimeGuessGame:
bg_color = "#f0f0f0" if i % 2 == 0 else "#ffffff" bg_color = "#f0f0f0" if i % 2 == 0 else "#ffffff"
# Zellen erstellen und platzieren # Zellen erstellen und platzieren
data_font = ("Arial", 10) # Kleinere Schrift für Daten data_font = ("Arial", 10) # Kleinere Schrift für Daten
# Rang # Rang
lbl_rank = tk.Label(self.scrollable_leaderboard_frame, text=str(i + 1), bg=bg_color, padx=5, pady=2, font=data_font) lbl_rank = tk.Label(self.scrollable_leaderboard_frame, text=str(i + 1), bg=bg_color, padx=5, pady=2,
font=data_font)
lbl_rank.grid(row=row_num, column=0, sticky='nsew') lbl_rank.grid(row=row_num, column=0, sticky='nsew')
# Name # Name
lbl_name = tk.Label(self.scrollable_leaderboard_frame, text=entry['name'], bg=bg_color, anchor='w', padx=5, pady=2, font=data_font) lbl_name = tk.Label(self.scrollable_leaderboard_frame, text=entry['name'], bg=bg_color, anchor='w', padx=5,
pady=2, font=data_font)
lbl_name.grid(row=row_num, column=1, sticky='nsew') lbl_name.grid(row=row_num, column=1, sticky='nsew')
# Zielzeit # Zielzeit
lbl_target = tk.Label(self.scrollable_leaderboard_frame, text=f"{entry['target_time'] / 1000:.3f}", bg=bg_color, padx=5, pady=2, font=data_font) lbl_target = tk.Label(self.scrollable_leaderboard_frame, text=f"{entry['target_time'] / 1000:.3f}",
bg=bg_color, padx=5, pady=2, font=data_font)
lbl_target.grid(row=row_num, column=2, sticky='nsew') lbl_target.grid(row=row_num, column=2, sticky='nsew')
# Gemessene Zeit # Gemessene Zeit
lbl_elapsed = tk.Label(self.scrollable_leaderboard_frame, text=f"{entry['elapsed_time'] / 1000:.3f}", bg=bg_color, padx=5, pady=2, font=data_font) lbl_elapsed = tk.Label(self.scrollable_leaderboard_frame, text=f"{entry['elapsed_time'] / 1000:.3f}",
bg=bg_color, padx=5, pady=2, font=data_font)
lbl_elapsed.grid(row=row_num, column=3, sticky='nsew') lbl_elapsed.grid(row=row_num, column=3, sticky='nsew')
# Abweichung # Abweichung
deviation = abs(entry['deviation'] / 1000) deviation = abs(entry['deviation'] / 1000)
color = "green" if deviation < 0.5 else "orange" if deviation < 1.0 else "red" color = "green" if deviation < 0.5 else "orange" if deviation < 1.0 else "red"
lbl_dev = tk.Label(self.scrollable_leaderboard_frame, text=f"{deviation:.3f}", fg=color, bg=bg_color, padx=5, pady=2, font=data_font) lbl_dev = tk.Label(self.scrollable_leaderboard_frame, text=f"{deviation:.3f}", fg=color, bg=bg_color,
padx=5, pady=2, font=data_font)
lbl_dev.grid(row=row_num, column=4, sticky='nsew') lbl_dev.grid(row=row_num, column=4, sticky='nsew')
# Wichtig: Nach dem Hinzufügen von Widgets und Konfigurieren der Spalten # Wichtig: Nach dem Hinzufügen von Widgets und Konfigurieren der Spalten
@@ -224,12 +230,11 @@ class TimeGuessGame:
self.scrollable_leaderboard_frame.update_idletasks() self.scrollable_leaderboard_frame.update_idletasks()
self.leaderboard_canvas.configure(scrollregion=self.leaderboard_canvas.bbox("all")) self.leaderboard_canvas.configure(scrollregion=self.leaderboard_canvas.bbox("all"))
# ... (Rest der Methoden: setup_connection, check_initial_connection, etc. bleiben unverändert) ... # ... (Rest der Methoden: setup_connection, check_initial_connection, etc. bleiben unverändert) ...
def setup_connection(self): def setup_connection(self):
if self.connected: if self.connected:
messagebox.showinfo("Info", "Bereits verbunden.") messagebox.showinfo("Info", "Bereits verbunden.")
return return
try: try:
ports = list(serial.tools.list_ports.comports()) ports = list(serial.tools.list_ports.comports())
@@ -238,7 +243,7 @@ class TimeGuessGame:
return return
port_names = [p.device for p in ports] port_names = [p.device for p in ports]
port_info = [f"{p.device} - {p.description}" for p in ports] # Mehr Infos anzeigen port_info = [f"{p.device} - {p.description}" for p in ports] # Mehr Infos anzeigen
if len(ports) == 1: if len(ports) == 1:
selected_port_device = ports[0].device selected_port_device = ports[0].device
@@ -249,12 +254,12 @@ class TimeGuessGame:
f"Verfügbare Ports:\n" + "\n".join(port_info) + "\n\nBitte Port eingeben (z.B. COM3 oder /dev/ttyACM0):", f"Verfügbare Ports:\n" + "\n".join(port_info) + "\n\nBitte Port eingeben (z.B. COM3 oder /dev/ttyACM0):",
parent=self.root) parent=self.root)
if not selected_port_str: if not selected_port_str:
return # Benutzer hat abgebrochen return # Benutzer hat abgebrochen
# Prüfen, ob der eingegebene Port gültig ist # Prüfen, ob der eingegebene Port gültig ist
if selected_port_str not in port_names: if selected_port_str not in port_names:
messagebox.showerror("Fehler", f"Ungültiger Port: {selected_port_str}") messagebox.showerror("Fehler", f"Ungültiger Port: {selected_port_str}")
return return
selected_port_device = selected_port_str selected_port_device = selected_port_str
# Verbindung herstellen # Verbindung herstellen
@@ -262,13 +267,13 @@ class TimeGuessGame:
self.serial_port.close() self.serial_port.close()
self.serial_port = serial.Serial(selected_port_device, 9600, timeout=0.1) self.serial_port = serial.Serial(selected_port_device, 9600, timeout=0.1)
self.root.after(2000, self.check_initial_connection) # Warte 2s und prüfe Verbindung self.root.after(2000, self.check_initial_connection) # Warte 2s und prüfe Verbindung
except serial.SerialException as e: except serial.SerialException as e:
messagebox.showerror("Verbindungsfehler", f"Konnte Port {selected_port_device} nicht öffnen:\n{e}") messagebox.showerror("Verbindungsfehler", f"Konnte Port {selected_port_device} nicht öffnen:\n{e}")
self.connected = False self.connected = False
self.connection_label.config(text="Nicht verbunden", fg="red") self.connection_label.config(text="Nicht verbunden", fg="red")
self.start_button.config(state=tk.DISABLED) self.start_button.config(state=tk.DISABLED)
except Exception as e: except Exception as e:
messagebox.showerror("Verbindungsfehler", str(e)) messagebox.showerror("Verbindungsfehler", str(e))
self.connected = False self.connected = False
@@ -280,41 +285,40 @@ class TimeGuessGame:
# (Optional: Sende einen PING oder erwarte eine READY Nachricht) # (Optional: Sende einen PING oder erwarte eine READY Nachricht)
try: try:
if self.serial_port and self.serial_port.is_open: if self.serial_port and self.serial_port.is_open:
# Optional: Warte auf eine "READY" Nachricht vom Arduino # Optional: Warte auf eine "READY" Nachricht vom Arduino
# self.serial_port.write(b"CHECK_READY\n") # Beispielbefehl # self.serial_port.write(b"CHECK_READY\n") # Beispielbefehl
# Oder gehe einfach davon aus, dass es geklappt hat # Oder gehe einfach davon aus, dass es geklappt hat
self.connected = True self.connected = True
self.connection_label.config(text=f"Verbunden mit {self.serial_port.port}", fg="green") self.connection_label.config(text=f"Verbunden mit {self.serial_port.port}", fg="green")
self.start_button.config(state=tk.NORMAL) self.start_button.config(state=tk.NORMAL)
self.instruction_label.config(text="Drücke 'Neues Spiel starten' oder den Taster") # Aktualisiert self.instruction_label.config(text="Drücke 'Neues Spiel starten' oder den Taster") # Aktualisiert
# Starte den Lese-Thread erst *nach* erfolgreicher Verbindung # Starte den Lese-Thread erst *nach* erfolgreicher Verbindung
if self.serial_thread is None or not self.serial_thread.is_alive(): if self.serial_thread is None or not self.serial_thread.is_alive():
self.serial_thread = threading.Thread(target=self.read_serial, daemon=True) self.serial_thread = threading.Thread(target=self.read_serial, daemon=True)
self.serial_thread.start() self.serial_thread.start()
messagebox.showinfo("Verbindung", f"Erfolgreich mit {self.serial_port.port} verbunden!") messagebox.showinfo("Verbindung", f"Erfolgreich mit {self.serial_port.port} verbunden!")
else: else:
# Dieser Fall sollte eigentlich nicht eintreten, wenn Serial() erfolgreich war # Dieser Fall sollte eigentlich nicht eintreten, wenn Serial() erfolgreich war
raise serial.SerialException("Port nicht mehr offen nach Wartezeit.") raise serial.SerialException("Port nicht mehr offen nach Wartezeit.")
except Exception as e: except Exception as e:
port_name = self.serial_port.port if self.serial_port else "Unbekannt" port_name = self.serial_port.port if self.serial_port else "Unbekannt"
messagebox.showerror("Verbindungsfehler", f"Keine Antwort vom Arduino auf {port_name}:\n{e}") messagebox.showerror("Verbindungsfehler", f"Keine Antwort vom Arduino auf {port_name}:\n{e}")
self.disconnect_serial() # Verbindung sauber trennen self.disconnect_serial() # Verbindung sauber trennen
def disconnect_serial(self): def disconnect_serial(self):
"""Trennt die serielle Verbindung sauber.""" """Trennt die serielle Verbindung sauber."""
self.game_active = False # Spiel stoppen, falls aktiv self.game_active = False # Spiel stoppen, falls aktiv
if self.serial_port and self.serial_port.is_open: if self.serial_port and self.serial_port.is_open:
try: try:
self.serial_port.close() self.serial_port.close()
except Exception as e: except Exception as e:
print(f"Fehler beim Schließen des Ports: {e}") print(f"Fehler beim Schließen des Ports: {e}")
self.serial_port = None self.serial_port = None
self.connected = False # Wichtig: Setze connected auf False self.connected = False # Wichtig: Setze connected auf False
# UI Updates im Hauptthread sicherstellen # UI Updates im Hauptthread sicherstellen
self.root.after(0, self._update_ui_disconnected) self.root.after(0, self._update_ui_disconnected)
print("Serielle Verbindung getrennt.") print("Serielle Verbindung getrennt.")
@@ -342,7 +346,7 @@ class TimeGuessGame:
# Starte ein neues Spiel # Starte ein neues Spiel
print("Sende START_GAME an Arduino") print("Sende START_GAME an Arduino")
self.serial_port.write(b"START_GAME\n") self.serial_port.write(b"START_GAME\n")
self.serial_port.flush() # Sicherstellen, dass Daten gesendet werden self.serial_port.flush() # Sicherstellen, dass Daten gesendet werden
self.game_active = True self.game_active = True
# UI zurücksetzen # UI zurücksetzen
@@ -351,7 +355,7 @@ class TimeGuessGame:
self.deviation_label.config(text="Abweichung: -") self.deviation_label.config(text="Abweichung: -")
self.status_label.config(text="Spiel wird vorbereitet...") self.status_label.config(text="Spiel wird vorbereitet...")
self.instruction_label.config(text="Warte auf Spielstart...") self.instruction_label.config(text="Warte auf Spielstart...")
self.deviation_label.config(fg="black") # Farbe zurücksetzen self.deviation_label.config(fg="black") # Farbe zurücksetzen
# Deaktiviere Start-Button während des Spiels # Deaktiviere Start-Button während des Spiels
self.start_button.config(state=tk.DISABLED) self.start_button.config(state=tk.DISABLED)
@@ -362,8 +366,9 @@ class TimeGuessGame:
self.name_entry.delete(0, tk.END) self.name_entry.delete(0, tk.END)
except serial.SerialException as e: except serial.SerialException as e:
messagebox.showerror("Kommunikationsfehler", f"Fehler beim Senden an Arduino:\n{e}\nVerbindung wird getrennt.", parent=self.root) messagebox.showerror("Kommunikationsfehler",
self.disconnect_serial() # Bei Sendefehler Verbindung trennen f"Fehler beim Senden an Arduino:\n{e}\nVerbindung wird getrennt.", parent=self.root)
self.disconnect_serial() # Bei Sendefehler Verbindung trennen
except Exception as e: except Exception as e:
messagebox.showerror("Fehler", f"Fehler beim Starten des Spiels: {e}", parent=self.root) messagebox.showerror("Fehler", f"Fehler beim Starten des Spiels: {e}", parent=self.root)
self.game_active = False self.game_active = False
@@ -371,11 +376,10 @@ class TimeGuessGame:
if self.connected: if self.connected:
self.start_button.config(state=tk.NORMAL) self.start_button.config(state=tk.NORMAL)
def read_serial(self): def read_serial(self):
print("Serial Read-Thread gestartet.") print("Serial Read-Thread gestartet.")
buffer = "" buffer = ""
while self.connected: # Schleife läuft nur solange connected=True while self.connected: # Schleife läuft nur solange connected=True
if self.serial_port and self.serial_port.is_open: if self.serial_port and self.serial_port.is_open:
try: try:
# Daten lesen, wenn verfügbar # Daten lesen, wenn verfügbar
@@ -387,7 +391,7 @@ class TimeGuessGame:
# Verarbeite vollständige Zeilen im Puffer # Verarbeite vollständige Zeilen im Puffer
while '\n' in buffer: while '\n' in buffer:
line, buffer = buffer.split('\n', 1) line, buffer = buffer.split('\n', 1)
line = line.strip() # Entferne Leerzeichen und ggf. \r line = line.strip() # Entferne Leerzeichen und ggf. \r
if line: if line:
print(f"Arduino: {line}") # Debug-Ausgabe print(f"Arduino: {line}") # Debug-Ausgabe
# Stelle sicher, dass UI-Updates im Hauptthread laufen # Stelle sicher, dass UI-Updates im Hauptthread laufen
@@ -397,32 +401,30 @@ class TimeGuessGame:
print(f"Serieller Fehler im Lesethread: {e}") print(f"Serieller Fehler im Lesethread: {e}")
# Bei Lesefehler Verbindung als verloren markieren # Bei Lesefehler Verbindung als verloren markieren
# Stelle sicher, dass dies im Hauptthread passiert # Stelle sicher, dass dies im Hauptthread passiert
if self.connected: # Nur wenn wir dachten, wir wären verbunden if self.connected: # Nur wenn wir dachten, wir wären verbunden
self.root.after(0, self.handle_connection_loss) self.root.after(0, self.handle_connection_loss)
break # Thread beenden break # Thread beenden
except Exception as e: except Exception as e:
# Verhindert Absturz bei unerwarteten Dekodierungsfehlern etc. # Verhindert Absturz bei unerwarteten Dekodierungsfehlern etc.
print(f"Allgemeiner Fehler im Lesethread: {e}") print(f"Allgemeiner Fehler im Lesethread: {e}")
# Optional: Hier auch Verbindung trennen? # Optional: Hier auch Verbindung trennen?
time.sleep(0.1) # Kurze Pause bei unerwartetem Fehler time.sleep(0.1) # Kurze Pause bei unerwartetem Fehler
else: else:
# Wenn Port nicht mehr offen ist oder None, Thread beenden # Wenn Port nicht mehr offen ist oder None, Thread beenden
print("Port nicht mehr offen oder nicht vorhanden. Read-Thread wird beendet.") print("Port nicht mehr offen oder nicht vorhanden. Read-Thread wird beendet.")
break # Beendet die while self.connected Schleife break # Beendet die while self.connected Schleife
time.sleep(0.05) # Kurze Pause, um CPU zu schonen time.sleep(0.05) # Kurze Pause, um CPU zu schonen
print("Serial Read-Thread beendet.") print("Serial Read-Thread beendet.")
def handle_connection_loss(self): def handle_connection_loss(self):
"""Wird aufgerufen, wenn ein serieller Fehler auftritt (im Hauptthread).""" """Wird aufgerufen, wenn ein serieller Fehler auftritt (im Hauptthread)."""
if self.connected: # Nur handeln, wenn wir dachten, wir wären verbunden if self.connected: # Nur handeln, wenn wir dachten, wir wären verbunden
# Verhindert mehrere Fehlermeldungen bei schnellen Fehlern # Verhindert mehrere Fehlermeldungen bei schnellen Fehlern
self.connected = False # Sofort auf False setzen self.connected = False # Sofort auf False setzen
messagebox.showerror("Verbindungsfehler", "Verbindung zum Arduino verloren!", parent=self.root) messagebox.showerror("Verbindungsfehler", "Verbindung zum Arduino verloren!", parent=self.root)
self.disconnect_serial() # Ruft UI-Updates etc. auf self.disconnect_serial() # Ruft UI-Updates etc. auf
def process_arduino_message(self, message): def process_arduino_message(self, message):
# Diese Funktion wird jetzt über self.root.after() im Hauptthread aufgerufen # Diese Funktion wird jetzt über self.root.after() im Hauptthread aufgerufen
@@ -440,19 +442,22 @@ class TimeGuessGame:
'elapsed_time': 0, 'elapsed_time': 0,
'deviation': 0 'deviation': 0
} }
self.target_time_label.config(text=f"Zielzeit: {target_time/1000:.1f} Sekunden") self.target_time_label.config(text=f"Zielzeit: {target_time / 1000:.1f} Sekunden")
self.status_label.config(text="Spiel läuft") # Status hier anpassen self.status_label.config(text="Spiel läuft") # Status hier anpassen
self.instruction_label.config(text="Drücke Taster zum Starten...") # Erste Anweisung nach Zielzeit self.instruction_label.config(text="Drücke Taster zum Starten...") # Erste Anweisung nach Zielzeit
elif message == "WAITING_FOR_BUTTON": elif message == "WAITING_FOR_BUTTON":
self.instruction_label.config( self.instruction_label.config(
text="Drücke den Taster zum Starten der Zeitmessung") text="Drücke den Taster zum Starten der Zeitmessung")
self.status_label.config(text="Bereit zum Start") # Klarerer Status self.status_label.config(text="Bereit zum Start") # Klarerer Status
elif message == "TIME_STARTED": elif message == "TIME_STARTED":
startsound = pygame.mixer.Sound("start.mp3")
pygame.mixer.Sound.play(startsound)
self.status_label.config(text="Zeitmessung läuft...") self.status_label.config(text="Zeitmessung läuft...")
self.instruction_label.config( self.instruction_label.config(
text="Drücke den Taster erneut zum Stoppen") # Verkürzt text="Drücke den Taster erneut zum Stoppen") # Verkürzt
elif message.startswith("ELAPSED_TIME:"): elif message.startswith("ELAPSED_TIME:"):
# Nur verarbeiten, wenn ein Spiel aktiv ist # Nur verarbeiten, wenn ein Spiel aktiv ist
@@ -460,7 +465,7 @@ class TimeGuessGame:
elapsed_time = int(message.split(":")[1].strip()) elapsed_time = int(message.split(":")[1].strip())
self.current_game['elapsed_time'] = elapsed_time self.current_game['elapsed_time'] = elapsed_time
self.elapsed_time_label.config( self.elapsed_time_label.config(
text=f"Deine Zeit: {elapsed_time/1000:.3f} Sekunden") text=f"Deine Zeit: {elapsed_time / 1000:.3f} Sekunden")
elif message.startswith("DEVIATION:"): elif message.startswith("DEVIATION:"):
# Nur verarbeiten, wenn ein Spiel aktiv ist # Nur verarbeiten, wenn ein Spiel aktiv ist
@@ -475,32 +480,40 @@ class TimeGuessGame:
color = "green" if deviation_abs_sec < 0.5 else "orange" if deviation_abs_sec < 1.0 else "red" color = "green" if deviation_abs_sec < 0.5 else "orange" if deviation_abs_sec < 1.0 else "red"
self.deviation_label.config(fg=color) self.deviation_label.config(fg=color)
if deviation_abs_sec >= 10:
fail = pygame.mixer.Sound("fail.mp3")
pygame.mixer.Sound.play(fail)
elif deviation_abs_sec == 0:
winner = pygame.mixer.Sound("winner.mp3")
pygame.mixer.Sound.play(winner)
else:
fail = pygame.mixer.Sound("good.mp3")
pygame.mixer.Sound.play(fail)
elif message == "GAME_ENDED": elif message == "GAME_ENDED":
self.game_active = False # Wichtig: Spielstatus aktualisieren self.game_active = False # Wichtig: Spielstatus aktualisieren
self.status_label.config(text="Spiel beendet") self.status_label.config(text="Spiel beendet")
self.instruction_label.config( self.instruction_label.config(
text="Trage Namen ein oder starte neu") # Verkürzt text="Trage Namen ein oder starte neu") # Verkürzt
if self.connected: # Nur aktivieren, wenn noch verbunden if self.connected: # Nur aktivieren, wenn noch verbunden
self.start_button.config(state=tk.NORMAL) self.start_button.config(state=tk.NORMAL)
self.submit_button.config(state=tk.NORMAL) # Buttons aktivieren self.submit_button.config(state=tk.NORMAL) # Buttons aktivieren
self.skip_button.config(state=tk.NORMAL) self.skip_button.config(state=tk.NORMAL)
else: else:
# Sicherstellen, dass Buttons deaktiviert bleiben, wenn keine Verbindung # Sicherstellen, dass Buttons deaktiviert bleiben, wenn keine Verbindung
self.submit_button.config(state=tk.DISABLED) self.submit_button.config(state=tk.DISABLED)
self.skip_button.config(state=tk.DISABLED) self.skip_button.config(state=tk.DISABLED)
elif message == "PC_READY": # Arduino meldet Bereitschaft nach Neustart/Init
print("Arduino meldet PC_READY")
# Wenn wir verbunden sind oder versuchen zu verbinden, UI aktualisieren
if self.connected or (self.serial_port and self.serial_port.is_open):
self.status_label.config(text="Arduino bereit")
self.start_button.config(state=tk.NORMAL)
self.instruction_label.config(text="Drücke 'Neues Spiel starten'")
else:
# Wenn keine Verbindung (mehr) besteht, nur loggen
print("PC_READY empfangen, aber nicht verbunden.")
elif message == "PC_READY": # Arduino meldet Bereitschaft nach Neustart/Init
print("Arduino meldet PC_READY")
# Wenn wir verbunden sind oder versuchen zu verbinden, UI aktualisieren
if self.connected or (self.serial_port and self.serial_port.is_open):
self.status_label.config(text="Arduino bereit")
self.start_button.config(state=tk.NORMAL)
self.instruction_label.config(text="Drücke 'Neues Spiel starten'")
else:
# Wenn keine Verbindung (mehr) besteht, nur loggen
print("PC_READY empfangen, aber nicht verbunden.")
# Handle andere unerwartete Nachrichten (optional) # Handle andere unerwartete Nachrichten (optional)
# else: # else:
@@ -511,10 +524,9 @@ class TimeGuessGame:
except IndexError as e: except IndexError as e:
print(f"Fehler beim Splitten der Nachricht '{message}': {e}") print(f"Fehler beim Splitten der Nachricht '{message}': {e}")
except Exception as e: except Exception as e:
# Catch-all für unerwartete Fehler während der Nachrichtenverarbeitung
import traceback import traceback
print(f"Unerwarteter Fehler beim Verarbeiten der Nachricht '{message}': {e}") traceback.print_exc()
traceback.print_exc() # Gibt detaillierten Traceback aus print(f"Unerwarteter Fehler bei der Nachrichtenverarbeitung: {e}")
def submit_score(self): def submit_score(self):
name = self.name_entry.get().strip() name = self.name_entry.get().strip()
@@ -644,8 +656,7 @@ class TimeGuessGame:
print("Tkinter-Fenster wird zerstört.") print("Tkinter-Fenster wird zerstört.")
self.root.destroy() # Tkinter-Fenster zerstören self.root.destroy() # Tkinter-Fenster zerstören
if __name__ == "__main__": if __name__ == "__main__":
root = tk.Tk() root = tk.Tk()
app = TimeGuessGame(root) game = TimeGuessGame(root)
root.mainloop() root.mainloop()

BIN
good.mp3 Normal file

Binary file not shown.

BIN
start.mp3 Normal file

Binary file not shown.

BIN
starten.mp3 Normal file

Binary file not shown.

BIN
winner.mp3 Normal file

Binary file not shown.