"""Tastatur und Controller → abstrakte Aktionen. Die Aktionen sind absichtlich generisch gehalten. Die spätere Namenseingabe per Controller benutzt dieselben Richtungen (Buchstabe weiter/zurück, Position wechseln), deshalb gibt es hier keine spielspezifischen Namen. """ from __future__ import annotations import enum import pygame from . import config class Action(enum.Enum): """Abstrakte Eingabe – unabhängig davon, ob Tastatur oder Controller.""" PRIMARY = "primary" # Space / Enter / Controller A LEFT = "left" # Pfeil links / D-Pad links RIGHT = "right" # Pfeil rechts / D-Pad rechts UP = "up" # Pfeil hoch / D-Pad hoch DOWN = "down" # Pfeil runter / D-Pad runter BACK = "back" # Esc / Controller B #: Tastaturbelegung KEY_ACTIONS: dict[int, Action] = { pygame.K_SPACE: Action.PRIMARY, pygame.K_RETURN: Action.PRIMARY, pygame.K_KP_ENTER: Action.PRIMARY, pygame.K_LEFT: Action.LEFT, pygame.K_RIGHT: Action.RIGHT, pygame.K_UP: Action.UP, pygame.K_DOWN: Action.DOWN, pygame.K_ESCAPE: Action.BACK, } #: Controller-Tasten. Xbox-Layout: 0=A, 1=B, 2=X, 3=Y. #: Das D-Pad meldet macOS bei Xbox-Controllern nicht als Hat, sondern als #: Tasten 11-14 (Reihenfolge hoch, runter, links, rechts). BUTTON_ACTIONS: dict[int, Action] = { 0: Action.PRIMARY, 1: Action.BACK, 2: Action.LEFT, 3: Action.UP, 11: Action.UP, 12: Action.DOWN, 13: Action.LEFT, 14: Action.RIGHT, } #: D-Pad, das als Hat gemeldet wird HAT_ACTIONS: dict[tuple[int, int], Action] = { (-1, 0): Action.LEFT, (1, 0): Action.RIGHT, (0, -1): Action.UP, (0, 1): Action.DOWN, } #: Analogstick: ab diesem Ausschlag gilt eine Richtung als gewollt AXIS_DEADZONE = 0.6 #: Offene Joystick-Objekte. Sie müssen am Leben bleiben: gibt Python das #: Objekt frei, schliesst pygame das Gerät – und SDL liefert danach **keine** #: Joystick-Events mehr. Genau das war der Grund, weshalb ein erkannter #: Controller im Spiel nichts bewirkte. _OPEN_CONTROLLERS: list[pygame.joystick.Joystick] = [] def open_controllers() -> list[str]: """Öffnet alle angeschlossenen Controller und hält sie offen. Nach ``pygame.init()`` aufrufen, und erneut, wenn ein Controller an- oder abgesteckt wird (``JOYDEVICEADDED`` / ``JOYDEVICEREMOVED``). """ _OPEN_CONTROLLERS.clear() names: list[str] = [] if not pygame.joystick.get_init(): try: pygame.joystick.init() except pygame.error: return names try: count = pygame.joystick.get_count() except pygame.error: return names for index in range(count): try: stick = pygame.joystick.Joystick(index) stick.init() except pygame.error: continue _OPEN_CONTROLLERS.append(stick) names.append(stick.get_name()) return names def opened_controllers() -> list[pygame.joystick.Joystick]: """Die aktuell offenen Controller (für Diagnose-Tools).""" return list(_OPEN_CONTROLLERS) def controller_names() -> list[str]: return [stick.get_name() for stick in _OPEN_CONTROLLERS] def is_device_event(event: pygame.event.Event) -> bool: return event.type in (pygame.JOYDEVICEADDED, pygame.JOYDEVICEREMOVED) def actions_from_event(event: pygame.event.Event) -> list[Action]: """Übersetzt ein pygame-Event in null bis eine Aktion.""" if event.type == pygame.KEYDOWN: action = KEY_ACTIONS.get(event.key) return [action] if action else [] if event.type == pygame.JOYBUTTONDOWN: action = BUTTON_ACTIONS.get(event.button) return [action] if action else [] if event.type == pygame.JOYHATMOTION: action = HAT_ACTIONS.get(tuple(event.value)) return [action] if action else [] if event.type == pygame.JOYAXISMOTION: if event.axis == 0 and abs(event.value) >= AXIS_DEADZONE: return [Action.RIGHT if event.value > 0 else Action.LEFT] if event.axis == 1 and abs(event.value) >= AXIS_DEADZONE: return [Action.DOWN if event.value > 0 else Action.UP] return [] def describe_controller_setup() -> str: names = controller_names() if not names: return "Controller: keiner erkannt (Tastatur: Space=Snap, ←=Drehen, ↑=OnTop, Esc=Ende)" return "Controller: " + ", ".join(names) def controller_hint_lines() -> list[str]: """Kurzhilfe fürs Log beim Start.""" return [ describe_controller_setup(), f"Zielauflösung {config.SCREEN_WIDTH}x{config.SCREEN_HEIGHT}, " f"{config.FPS} fps", ]