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

Executable
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env python3
"""Einstiegspunkt des Habegger Loader Games.
Beispiele::
./run.sh # Fenster, letzter Spielername
./run.sh --fullscreen # Vollbild (Messe-/Eventbetrieb)
./run.sh --name "Anna" # Name direkt setzen
./run.sh --smoke 10 # Selbsttest ohne Eingabegeräte
"""
from __future__ import annotations
import argparse
import random
import sys
import pygame
from loader_game import config, storage
from loader_game.assets import Assets
from loader_game.game import Game, State
from loader_game.input_map import Action, actions_from_event, controller_hint_lines
from loader_game.intro import Intro
from loader_game.sequences import load_sequences
def parse_args(argv: list[str] | None = None) -> argparse.Namespace:
parser = argparse.ArgumentParser(description="Habegger Loader Game")
parser.add_argument(
"--fullscreen",
action="store_true",
help="Vollbild starten (Standard: Fenster; F11 schaltet jederzeit um)",
)
parser.add_argument(
"--name",
default=None,
help="Spielername (sonst der zuletzt gespeicherte aus runtime/)",
)
parser.add_argument(
"--seed",
type=int,
default=None,
help="Zufalls-Seed für reproduzierbare Koffer-Sequenzen",
)
parser.add_argument(
"--smoke",
type=float,
default=None,
metavar="SEKUNDEN",
help="Selbsttest: bestätigt das Intro automatisch, spielt ohne Eingaben "
"und beendet sich nach N Sekunden",
)
return parser.parse_args(argv)
def create_screen(fullscreen: bool) -> pygame.Surface:
"""Fenster mit virtueller 1920x1080-Auflösung anlegen.
``pygame.SCALED`` sorgt dafür, dass alles automatisch auf die tatsächliche
Fenster-/Bildschirmgrösse skaliert wird (Retina, 1440x900, 4K …).
"""
flags = pygame.SCALED | (pygame.FULLSCREEN if fullscreen else pygame.RESIZABLE)
size = (config.SCREEN_WIDTH, config.SCREEN_HEIGHT)
try:
return pygame.display.set_mode(size, flags, vsync=1)
except pygame.error:
return pygame.display.set_mode(size, flags)
def _demo_action(game: Game, rng: random.Random) -> Action | None:
"""Einfache Auto-Strategie für den Selbsttest."""
case = game.current
if case is None or game.animation is not None or not case.allow_snap:
return None
return rng.choice([Action.PRIMARY, Action.PRIMARY, Action.PRIMARY, Action.UP, Action.LEFT])
def main(argv: list[str] | None = None) -> int:
args = parse_args(argv)
pygame.mixer.pre_init(config.AUDIO_SAMPLE_RATE, -16, 2, 512)
pygame.init()
if not pygame.display.get_init():
print("Kein Display verfügbar (ohne Bildschirm gestartet?).", file=sys.stderr)
return 1
pygame.display.set_caption(config.WINDOW_TITLE)
screen = create_screen(fullscreen=args.fullscreen)
assets = Assets()
assets.load()
for line in controller_hint_lines():
print(line)
sequences, warnings = load_sequences()
for warning in warnings:
print("Sequenz-Warnung:", warning, file=sys.stderr)
name = (args.name or storage.load_last_player() or config.DEFAULT_PLAYER_NAME).strip()
name = name[: config.MAX_PLAYER_NAME_LENGTH] or config.DEFAULT_PLAYER_NAME
storage.save_last_player(name)
rng = random.Random(args.seed)
intro: Intro | None = Intro(assets)
game: Game | None = None
clock = pygame.time.Clock()
running = True
smoke_remaining = args.smoke
smoke_timer = 0.0
while running:
dt = min(clock.tick(config.FPS) / 1000.0, 0.1)
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
break
if event.type == pygame.KEYDOWN and event.key == pygame.K_F11:
pygame.display.toggle_fullscreen()
continue
for action in actions_from_event(event):
if intro is not None:
intro.handle(action)
if action is Action.BACK:
running = False
elif game is not None:
running = game.handle(action)
if not running:
break
if intro is not None:
if intro.update(dt):
intro = None
game = Game(assets, sequences, name=name, rng=rng)
elif smoke_remaining is not None and intro.phase == "instruction":
intro.handle(Action.PRIMARY)
elif game is not None:
game.update(dt)
if smoke_remaining is not None:
smoke_remaining -= dt
if smoke_remaining <= 0:
running = False
elif game is not None and intro is None:
smoke_timer -= dt
if smoke_timer <= 0:
smoke_timer = 0.25
action = _demo_action(game, rng)
if action is not None:
running = game.handle(action)
if intro is not None:
intro.draw(screen)
elif game is not None:
game.draw(screen)
pygame.display.flip()
if game is not None:
percent = game.percent if game.state is State.GAME_OVER else game.trailer.load_percent()
print(
f"Spiel beendet: Zustand={game.state.value}, "
f"Beladung={percent:.0f}%, Koffer im Auflieger={len(game.trailer.stack)}"
)
pygame.quit()
return 0
if __name__ == "__main__":
sys.exit(main())