11 KiB
11 KiB
Analyse Altstand (01.11.2026)
Grundlage: old_version/loader_game.py (656 Zeilen), old_version/qr_scan.py
(188 Zeilen), old_version/qr_scan_old.py (160 Zeilen),
old_version/case_sequences.txt (6 Sequenzen).
1. Wie der Altstand funktioniert hat
qr_scan.py ──► last_user.txt ──► subprocess: python loader_game.py
│ │
└ Kamera + pyzbar + HTTP-API └ pygame-Spiel, ein Durchgang, dann sys.exit()
qr_scan.py: öffnet die Webcam, liest QR-Codes, fragt eine externe API (hag.putschgi.ch/.../qr.php?action=verify_hash) nach Gültigkeit und Name, schreibtmail/nameinlast_user.txtund startet das Spiel als Unterprozess. Danach wieder Scanner – so kam der nächste Spieler dran.loader_game.py: ein Durchgang. Ein Koffer läuft von rechts ein; der Spieler kann ihn an die Barriere snappen (Space/A), drehen (←/X) oder auf einen anderen Koffer stapeln (↑/Y). Kollidiert er ungebremst, wird er mit einer Lücke eingereiht. Ist die Barriere hinter dem Heck, ist Schluss. Punktzahl = belegte Fläche im Auflieger, wird percurlansubmit_points.phpgeschickt.case_sequences.txt: 6 Koffersequenzen, Zeilenbreite,höhe,rotiert, Sequenzen durch Leerzeilen getrennt. Das Spiel zieht sich zufällig eine.
2. Gefundene Fehler und Risiken
| # | Fund | Wirkung | Status im Neustand |
|---|---|---|---|
| 1 | Secrets im Quellcode: API_SECRET = "ddlK…" in qr_scan.py, api_key=hag-trailer-8051 in loader_game.py |
Zugangsdaten liegen in der Git-Historie | entfernt; Endpunkt + Schlüssel nur noch per HAG_SCORE_URL / HAG_SCORE_API_KEY aus der Umgebung → alte Schlüssel rotieren |
| 2 | pygame.event.set_grab(True) |
Maus/Tastatur werden gefangen; auf macOS unangenehm, Esc kommt nicht mehr durch | entfernt |
| 3 | Feste 1920×1080-Vollbildannahme | MacBook (Retina/1440×900) → Fenster grösser als der Bildschirm | virtuelle Auflösung + pygame.SCALED, Fenster/--fullscreen, F11 |
| 4 | Windows-Annahmen: subprocess.run(["python", …]), pygetwindow/pyautogui, python.exe in der Install-Doku |
läuft so nicht auf macOS | kein Subprozess mehr, Scanner gestrichen |
| 5 | Doppelte Drehlogik: Case.rotated und getauschte Masse; draw() skaliert bei rotated auf (height, width) und dreht zusätzlich um 90° |
verzerrte/falsch gedrehte Koffer, schwer nachvollziehbar | nur noch units_w/units_h; Grafik wird einmalig passend erzeugt |
| 6 | Zufällige Zusatzdrehung in Case.__init__ (random.choice([True, False])) |
die „Next"-Vorschau zeigt etwas anderes als der Koffer, der wirklich kommt | Zufallsdrehung entfernt, Vorschau stimmt jetzt |
| 7 | can_place_on_top() und animate_place_on_top() prüfen unterschiedliche Regeln (Barriere-Ausrichtung fehlt in der Animation) |
„OnTop möglich!" wird angekündigt, dann landet der Koffer woanders oder gar nicht | eine einzige Regel: Trailer.find_platform() |
| 8 | Zwei verschiedene Fehlschlag-Pfade (fail_snap einmal mit sound_place, einmal mit sound_fail) |
inkonsistentes Feedback, verwirrende Punktevergabe | ein Pfad (_stack_with_gap), definierter Sound + Shake |
| 9 | Blockierende Animationsschleifen (for + pygame.time.delay mitten im Event-Loop) |
Events stauen sich, keine dt-Basis, Ruckler; 120-fps-Ziel war so nicht erreichbar |
Animationen laufen über elapsed/duration im Update, 60 fps |
| 10 | Case.move() startet den Roll-Sound und kehrt zurück; Sound hängt an Channel(1) |
erster Frame ohne Bewegung; Kanal-Kollisionen mit anderen Sounds | Roll-Sound beim Spawn, Stopp beim Platzieren, eigener Kanal |
| 11 | Blankes except: an 6 Stellen (Bilder, Sequenzen, Punkte-Upload) |
Fehler bleiben unsichtbar, z. B. Tippfehler in case_sequences.txt fallen still auf die Notfall-Sequenz zurück |
gezielte Exceptions + Warnungen beim Start |
| 12 | last_user.txt wird mit utf-8-sig geschrieben und mit utf-8 gelesen |
BOM landet im Spielernamen; Datei liegt im Programmordner | runtime/last_player.json, Ordner ist in .gitignore |
| 13 | calculate_load_percent() summiert die Fläche aller Koffer, auch der aufgesetzten |
Beladung ist „gefühlt" zu hoch; min(100, …) verdeckt das nur |
bewusst beibehalten (Spielbalance), aber dokumentiert und getestet |
| 14 | Toter/doppelter Code: global current_case zweimal in draw_game, can_use_on_top in spawn_case, transition_counter, state/PLAYING, entry_angle (immer 0), frame_color, game_started |
unnötige Verwirrung | entfernt |
| 15 | Kein Neustart möglich: Spiel endet mit sys.exit() |
jeder Spieler braucht einen neuen Prozess (Aufgabe des Scanners) | Spiel ist eine Klasse; Aufrufer entscheidet (Neustart/Beenden) |
| 16 | cap = cv2.VideoCapture(0) in der Endlosschleife, cv2.namedWindow() pro Frame |
Kamerahandle-Leak, Flackern | mit dem QR-Scanner entfallen (pyzbar/opencv nicht mehr nötig) |
| 17 | Keine requirements.txt, kein venv, Installation nur als Windows-Befehlskette |
auf dem Mac nicht reproduzierbar | requirements.txt + run.sh (venv + Auto-Install) |
| 18 | Spieler kam nur über die API, ohne Server kein Spielstart | Abhängigkeit von einem fremden Server | Name kommt künftig vom Controller (Schritt 2) |
3. Was der Neustand anders macht
- Struktur statt Monolith:
main.py+ Paketloader_game/mit je einem Modul pro Aufgabe (sieheloader_game/__init__.py). - Logik ohne pygame:
case.py,trailer.py,sequences.pylassen sich ohne Fenster testen (35 Tests). - Zeitbasiert: alles rechnet in Sekunden (
dt), nicht in Frames. - Eine Wahrheit für Regeln: Stapelregeln leben ausschliesslich in
Trailer, Zeichnen ausschliesslich inscene.py. - Konfiguration an einer Stelle:
config.py(Masse, Tempi, Farben, Texte). - Eingabe abstrahiert:
input_map.pybildet Tastatur und Controller aufAction.PRIMARY/LEFT/RIGHT/UP/DOWN/BACKab – die Basis für die Controller-Namenseingabe. - Nur noch eine Abhängigkeit:
pygame(kein numpy, kein opencv, kein pyzbar, kein requests, kein pyautogui).
4. Bewusst nicht geändert
- Spielmechanik und Balance (Tempo 720/240 px/s entspricht den alten 6/2 px pro Frame bei 120 fps; Snap-Schwelle, Lücken-Logik, Beladungsformel).
case_sequences.txt(Datenbestand bleibt unverändert).- Grafiken, Sounds, Fonts.
- Der Punkte-Upload existiert weiter, ist aber ohne konfigurierten Endpunkt stumm geschaltet.
5. Offene Punkte (nächste Schritte)
- Controller-Namenseingabe: Buchstabe für Buchstabe per D-Pad/Stick,
alternative Eingabe über die Tastatur. Ersetzt
last_user.txt/QR-Scanner. - Mehrere Durchgänge ohne Prozess-Neustart (Rückkehr zur Namenseingabe statt Beenden).
- Entscheiden, ob der Punkte-Upload wieder aktiv genutzt wird – und wohin.
images/*.pnghaben ein defektes sRGB-Profil (libpng-Warnung beim Laden, rein kosmetisch).
6. Änderungen nach dem ersten Test (v1.1)
Rückmeldungen aus dem ersten Durchlauf auf dem MacBook und was daraus wurde:
| Rückmeldung | Umsetzung |
|---|---|
| „läuft viel zu schnell" | Stufen Easy / Normal / Hard (difficulty.py), Hard entspricht exakt dem Altstand-Tempo (720/240 px pro Sekunde). Auswahl im Startmenü mit ←/→ und A/Space; das Tempo steht jetzt an einer Stelle. |
| „beim Tippen plötzlich andere Textur" | Der Koffer merkt sich in Case.texture die Orientierung, in der er eingelaufen ist. Gedreht wird nur die Grafik (90°), nie gegen eine andere Bilddatei getauscht. |
| „Spieler klingt doof" | Name und Punkteliste komplett entfernt (storage.py, net.py, --name gelöscht). Angezeigt werden nur noch Stufe und Beladung in Prozent. |
| „soll nicht enden, sondern fragen: nochmals?" | Neue Phasenkette in main.py: Tutorial → Menü → Ampel → Runde → Ergebnis → Menü → … Beendet wird nur mit Esc/B. Game.handle() liefert dafür HandleResult.CONTINUE / NEW_ROUND / QUIT. |
| „lädst du von einer Map/CSV?" | Ja: case_sequences.txt im Projektwurzelverzeichnis, 6 Sequenzen als breite,höhe,rotiert. Pro Runde wird zufällig eine Sequenz gezogen. |
7. Änderungen nach dem zweiten Test (v1.2)
| Rückmeldung | Umsetzung |
|---|---|
| „zeig im Log, welche Map gewählt wurde" | Beim Start jeder Runde: Runde n: Sequenz x/6 (m Koffer, Start: …), Stufe …. Zusätzlich wird dieselbe Sequenz nie zweimal direkt hintereinander gezogen (Game._pick_sequence). |
| „Reihenfolge: Anleitung → Moduswahl → Sequenz" | Phasenkette korrigiert. Die Anleitung kommt jetzt jede Runde zuerst, danach die Stufenauswahl, dann Ampel/Reissverschluss, dann das Spiel. |
| „kein Roll-Sound bis zum ersten Klick" | Ursache: der Roll-Sound lief auf einem Kanal, den die automatische Kanalvergabe für Einzelklänge (Ampel, Reissverschluss) belegen konnte; war er belegt, wurde der Roll übersprungen. Jetzt: pygame.mixer.set_reserved(2), eigener Kanal 0 für den Roll, unpause() nach dem Laden und kein „skip when busy" mehr. Lautstärke 0.3 → 0.5. Beim Start steht Audio: … im Log. |
| „Verlangsamen und Snap-Fenster früher" | FAST_TO_SLOW_DISTANCE 120 → 260 px, neues SNAP_WINDOW 160 px (vorher 60). Die Strafe bei einer Kollision bleibt unverändert bei FAIL_GAP 60 px. Snap-Animation dauert jetzt entfernungsabhängig 0.08–0.30 s. |
| „Cooldown am Ende" | RESULT_COOLDOWN = 10 s. Ergebnis bleibt stehen, zeigt „Neue Runde in n s …", A wird erst danach angenommen. Esc beendet jederzeit. |
| „lustige Sprüche" | Neues Modul sayings.py mit Stufen (100 % = Vertrag-im-Lager-Spruch, 85+, 70+, 55+, 40+, 20+, darunter). Bewusst jugendtauglich: frech, aber nicht beleidigend – ein Test prüft das. |
8. Ablauf-Korrektur (v1.3)
Der Ablauf war doppelt verdrahtet: die Klasse Intro enthielt sowohl das
Anleitungsbild als auch die Ampel-/Reissverschluss-Sequenz. Wer die Anleitung
bestätigte, startete damit ungewollt die Startsequenz – sie lief also vor der
Moduswahl und danach noch einmal.
Jetzt sind es zwei getrennte Bausteine (intro.py): Tutorial (nur Bild,
wartet auf A/Space, keine Töne) und StartSequence (Ampel + Reissverschluss).
Die Phasenkette im Hauptprogramm ist damit eindeutig und gilt für jede
Runde:
ANLEITUNG → MODUSWAHL → STARTSEQUENZ → SPIEL → ABSCHLUSS (10 s) → ANLEITUNG → …
9. Tempo nachgezogen (v1.4)
| Rückmeldung | Umsetzung |
|---|---|
| „Normal und Hard sind zu einfach, aber die Zeit für die Befehle soll bleiben" | Tempo erhöht: Normal 570 → 740 px/s (+30 %), Hard 720 → 1080 px/s (+50 %), Easy unverändert 420. Gebremst entsprechend 250 bzw. 360 px/s. Damit die Reaktionszeit nicht mitschrumpft, wurde das Snap-Fenster von 160 auf 200 px vergrössert: rund 1,4 s (Easy), 0,8 s (Normal) und 0,55 s (Hard) zwischen „Befehl möglich" und Barriere. |