Altstand nach old_version/ eingefroren (untracked). Monolith ersetzt durch main.py + Paket loader_game/. Neu: run.sh/run.command mit venv+requirements, .gitignore, docs/ANALYSE.md, 35 Tests. Secrets entfernt. Gelöscht: loader_game.py, qr_scan*.py, INSTALL_README.txt.

This commit is contained in:
Project Bridge committed 2026-11-01 10:25:13 +00:00
1 parent 6a2c891c3e
commit de550e1d4d
24 files changed
+2269 -1008

No files matched your search

+107
View File
@@ -0,0 +1,107 @@
"""Einlesen der Koffersequenzen aus ``case_sequences.txt``.
Dateiformat
-----------
Sequenzen sind durch Leerzeilen getrennt. Jede Zeile beschreibt einen Koffer::
breite,höhe,rotiert
* ``breite`` / ``höhe`` sind Kasten-Einheiten (1 Einheit = 100x100 Pixel).
* ``rotiert=True`` heisst: der Koffer wird beim Einlaufen um 90° gedreht,
Breite und Höhe werden also getauscht.
Beispiel::
3,1,False
1,2,True -> Koffer ist effektiv 2 Einheiten breit und 1 hoch
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from pathlib import Path
from . import config
_TRUE_VALUES = {"true", "1", "ja", "yes", "x"}
@dataclass(frozen=True, slots=True)
class CaseSpec:
"""Ein Koffer aus der Sequenzdatei – bereits in Einlauf-Orientierung."""
units_w: int
units_h: int
def __post_init__(self) -> None:
if self.units_w < 1 or self.units_h < 1:
raise ValueError(f"Koffer muss mindestens 1x1 Einheiten gross sein: {self!r}")
@property
def label(self) -> str:
return f"{self.units_w}x{self.units_h}"
#: Notfall-Sequenz, falls die Datei fehlt oder unlesbar ist.
FALLBACK_SEQUENCE: tuple[CaseSpec, ...] = (CaseSpec(2, 1), CaseSpec(1, 2))
def parse_sequences(raw: str) -> tuple[list[list[CaseSpec]], list[str]]:
"""Zerlegt den Dateiinhalt in Sequenzen.
Rückgabe: ``(sequenzen, warnungen)``. Ungültige Zeilen werden gemeldet und
übersprungen – ein Tippfehler in der Datei soll das Spiel nicht verhindern.
"""
sequences: list[list[CaseSpec]] = []
warnings: list[str] = []
for block_number, block in enumerate(re.split(r"\n\s*\n", raw.strip()), start=1):
specs: list[CaseSpec] = []
for line_number, line in enumerate(block.splitlines(), start=1):
text = line.strip()
if not text:
continue
parts = [part.strip() for part in text.split(",")]
if len(parts) != 3:
warnings.append(
f"Block {block_number}, Zeile {line_number}: "
f"'breite,höhe,rotiert' erwartet, gefunden {text!r}"
)
continue
try:
units_w, units_h = int(parts[0]), int(parts[1])
except ValueError:
warnings.append(
f"Block {block_number}, Zeile {line_number}: keine ganzen Zahlen in {text!r}"
)
continue
rotated = parts[2].lower() in _TRUE_VALUES
if rotated:
units_w, units_h = units_h, units_w
if units_w < 1 or units_h < 1:
warnings.append(
f"Block {block_number}, Zeile {line_number}: ungültige Masse {text!r}"
)
continue
specs.append(CaseSpec(units_w, units_h))
if specs:
sequences.append(specs)
if not sequences:
warnings.append("Keine gültige Sequenz gefunden – es wird die Notfall-Sequenz benutzt.")
return sequences, warnings
def load_sequences(path: Path | None = None) -> tuple[list[list[CaseSpec]], list[str]]:
"""Liest die Sequenzdatei und liefert immer mindestens eine Sequenz."""
path = path or config.SEQUENCES_FILE
try:
raw = path.read_text(encoding="utf-8")
except OSError as exc:
return [list(FALLBACK_SEQUENCE)], [f"Sequenzdatei {path} nicht lesbar: {exc}"]
sequences, warnings = parse_sequences(raw)
if not sequences:
sequences = [list(FALLBACK_SEQUENCE)]
return sequences, warnings