Files
Habegger_loader_game/loader_game/sequences.py
T

108 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""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