#!/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())