174 lines
5.5 KiB
Python
Executable File
174 lines
5.5 KiB
Python
Executable File
#!/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())
|