v1.2: Log zeigt gewaehlte Sequenz (nie zweimal dieselbe), Anleitung vor Moduswahl (jede Runde), Roll-Sound-Kanal reserviert (Fix kein Sound), frueheres Abbremsen + Snap-Fenster 160px, 10s Cooldown am Ende, Sprueche nach Prozent (sayings.py), 56 Tests.
This commit is contained in:
1 parent
d970185e75
commit
e3a0c5a57d
11 files changed
+319
-37
No files matched your search
+41
-8
@@ -3,6 +3,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import enum
|
||||
import math
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
|
||||
@@ -10,6 +11,7 @@ from . import config, scene
|
||||
from .case import Case
|
||||
from .difficulty import DEFAULT_DIFFICULTY, Difficulty
|
||||
from .input_map import Action
|
||||
from .sayings import saying_for
|
||||
from .sequences import CaseSpec, FALLBACK_SEQUENCE
|
||||
from .trailer import Trailer
|
||||
|
||||
@@ -23,7 +25,7 @@ class HandleResult(enum.Enum):
|
||||
"""Was der Aufrufer nach einer Aktion tun soll."""
|
||||
|
||||
CONTINUE = "continue"
|
||||
NEW_ROUND = "new_round" # zurück zur Stufenauswahl
|
||||
NEW_ROUND = "new_round" # zurück zur Anleitung/Stufenauswahl
|
||||
QUIT = "quit" # Programm beenden (Esc / B)
|
||||
|
||||
|
||||
@@ -55,6 +57,7 @@ class Game:
|
||||
*,
|
||||
difficulty: Difficulty = DEFAULT_DIFFICULTY,
|
||||
rng: random.Random | None = None,
|
||||
previous_sequence: int | None = None,
|
||||
) -> None:
|
||||
self.assets = assets
|
||||
self.rng = rng or random.Random()
|
||||
@@ -62,20 +65,33 @@ class Game:
|
||||
|
||||
self.state = State.PLAYING
|
||||
self.trailer = Trailer()
|
||||
|
||||
source = sequences if sequences else [list(FALLBACK_SEQUENCE)]
|
||||
self.sequence: list[CaseSpec] = list(self.rng.choice(source))
|
||||
self.sequence_index = self._pick_sequence(len(source), previous_sequence)
|
||||
self.sequence: list[CaseSpec] = list(source[self.sequence_index])
|
||||
|
||||
self.index = 0
|
||||
self.current: Case | None = None
|
||||
self.animation: Animation | None = None
|
||||
self.shake = 0.0
|
||||
self.percent = 0.0
|
||||
self.result_timer = 0.0
|
||||
self.result_saying = ""
|
||||
|
||||
self._spawn()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Sequenz / Nachschub
|
||||
# ------------------------------------------------------------------
|
||||
def _pick_sequence(self, count: int, previous: int | None) -> int:
|
||||
"""Zufällige Sequenz – aber nie zweimal direkt hintereinander dieselbe."""
|
||||
if count <= 1:
|
||||
return 0
|
||||
if previous is None or not 0 <= previous < count:
|
||||
return self.rng.randrange(count)
|
||||
choice = self.rng.randrange(count - 1)
|
||||
return choice if choice < previous else choice + 1
|
||||
|
||||
def has_next(self) -> bool:
|
||||
return self.index < len(self.sequence)
|
||||
|
||||
@@ -107,7 +123,9 @@ class Game:
|
||||
return HandleResult.QUIT
|
||||
|
||||
if self.state is State.FINISHED:
|
||||
if action is Action.PRIMARY:
|
||||
# Erst nach dem Cooldown darf die nächste Runde starten – sonst
|
||||
# springt man beim Weggehen versehentlich weiter.
|
||||
if action is Action.PRIMARY and self.result_timer <= 0:
|
||||
return HandleResult.NEW_ROUND
|
||||
return HandleResult.CONTINUE
|
||||
|
||||
@@ -128,9 +146,10 @@ class Game:
|
||||
|
||||
def _start_snap(self, case: Case) -> None:
|
||||
self._stop_roll()
|
||||
distance = max(1.0, case.x - self.trailer.wall_x)
|
||||
self.animation = Animation(
|
||||
kind="snap",
|
||||
duration=config.ANIM_SNAP,
|
||||
duration=min(0.30, max(0.08, distance / 1200.0)),
|
||||
start_x=case.x,
|
||||
start_y=case.y,
|
||||
target_x=self.trailer.wall_x,
|
||||
@@ -172,7 +191,9 @@ class Game:
|
||||
def update(self, dt: float) -> None:
|
||||
if self.shake > 0:
|
||||
self.shake = max(0.0, self.shake - dt)
|
||||
if self.state is not State.PLAYING:
|
||||
|
||||
if self.state is State.FINISHED:
|
||||
self.result_timer = max(0.0, self.result_timer - dt)
|
||||
return
|
||||
|
||||
if self.animation is not None:
|
||||
@@ -223,7 +244,7 @@ class Game:
|
||||
if animation.kind == "rotate":
|
||||
case.rotate_footprint()
|
||||
case.y = float(config.TRAILER_BOTTOM - case.height)
|
||||
case.x = max(self.trailer.wall_x + config.SNAP_THRESHOLD // 2, case.x)
|
||||
case.x = max(self.trailer.wall_x + config.SNAP_WINDOW // 2, case.x)
|
||||
case.can_rotate = False
|
||||
return
|
||||
|
||||
@@ -270,6 +291,8 @@ class Game:
|
||||
self.percent = self.trailer.load_percent()
|
||||
if self.percent >= 99.9:
|
||||
self.percent = 100.0
|
||||
self.result_timer = config.RESULT_COOLDOWN
|
||||
self.result_saying = saying_for(self.percent, self.rng)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Zeichnen
|
||||
@@ -313,12 +336,22 @@ class Game:
|
||||
scene.draw_overlay_lines(surface, assets, self.result_lines())
|
||||
|
||||
def result_lines(self) -> list[str]:
|
||||
return [
|
||||
"""Ergebniszeilen: Stufe+Prozent, Spruch, dann Cooldown bzw. Hinweis."""
|
||||
lines = [
|
||||
config.RESULT_TEMPLATE.format(
|
||||
difficulty=self.difficulty.label, percent=int(self.percent)
|
||||
),
|
||||
config.RESULT_HINT,
|
||||
self.result_saying,
|
||||
]
|
||||
if self.result_timer > 0:
|
||||
lines.append(
|
||||
config.RESULT_COOLDOWN_TEMPLATE.format(
|
||||
seconds=math.ceil(self.result_timer)
|
||||
)
|
||||
)
|
||||
else:
|
||||
lines.append(config.RESULT_HINT)
|
||||
return lines
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Hilfen
|
||||
|
||||
Reference in new issue
Block a user