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:
Project Bridge committed 2026-11-01 11:38:16 +00:00
1 parent d970185e75
commit e3a0c5a57d
11 files changed
+319 -37

No files matched your search

+84 -4
View File
@@ -20,6 +20,7 @@ from loader_game.difficulty import DEFAULT_DIFFICULTY, DIFFICULTIES, by_key
from loader_game.game import Game, HandleResult, State
from loader_game.input_map import Action
from loader_game.menu import DifficultyMenu
from loader_game.sayings import saying_for
from loader_game.sequences import CaseSpec, load_sequences, parse_sequences
from loader_game.trailer import Trailer
@@ -131,8 +132,8 @@ class CaseTests(unittest.TestCase):
case.move(1 / 60, wall_x=0)
self.assertFalse(case.allow_snap)
case = self.make_case(x=config.SNAP_THRESHOLD + 2)
case.move(1 / 60, wall_x=0)
case = self.make_case(x=config.SNAP_WINDOW + 2)
case.move(1 / 60, wall_x=0, difficulty=DIFFICULTIES[0])
self.assertTrue(case.allow_snap)
def test_placed_case_does_not_move(self) -> None:
@@ -207,7 +208,7 @@ class TrailerTests(unittest.TestCase):
def test_gap_placement_leaves_hole(self) -> None:
case = self.make_case(2, 1)
self.trailer.stack_with_gap(case)
self.assertEqual(case.x, self.trailer.x + config.SNAP_THRESHOLD)
self.assertEqual(case.x, self.trailer.x + config.FAIL_GAP)
self.assertFalse(case.snapped)
def test_platform_on_flat_ground_case_is_allowed(self) -> None:
@@ -321,7 +322,7 @@ class GameTests(unittest.TestCase):
rng=random.Random(1),
)
case = game.current
case.x = float(game.trailer.wall_x + config.SNAP_THRESHOLD + 1)
case.x = float(game.trailer.wall_x + config.SNAP_WINDOW + 1)
case.allow_snap = True
game.handle(Action.LEFT)
self.update_until(game, lambda: game.animation is None)
@@ -393,6 +394,10 @@ class GameTests(unittest.TestCase):
game.handle(Action.PRIMARY)
game.update(1 / 60)
self.assertIs(game.state, State.FINISHED)
# Cooldown: erst nach Ablauf darf die nächste Runde starten
self.assertIs(game.handle(Action.PRIMARY), HandleResult.CONTINUE)
for _ in range(int(config.RESULT_COOLDOWN * 60) + 5):
game.update(1 / 60)
self.assertIs(game.handle(Action.PRIMARY), HandleResult.NEW_ROUND)
def test_result_lines_show_difficulty_and_percent(self) -> None:
@@ -404,6 +409,81 @@ class GameTests(unittest.TestCase):
self.assertTrue(any("ESC" in line for line in lines))
class SayingTests(unittest.TestCase):
def test_full_load_gets_the_contract_line(self) -> None:
self.assertIn("Vertrag", saying_for(100))
self.assertIn("Vertrag", saying_for(100.0))
def test_every_percent_gets_a_saying(self) -> None:
rng = random.Random(5)
for percent in range(0, 101, 5):
self.assertTrue(saying_for(percent, rng).strip())
def test_high_and_low_use_different_pools(self) -> None:
rng = random.Random(1)
high = {saying_for(90, rng) for _ in range(40)}
low = {saying_for(10, rng) for _ in range(40)}
self.assertFalse(high & low)
def test_no_harsh_words(self) -> None:
# Zielgruppe Jugendliche: frech ja, beleidigend nein.
bad = ("dumm", "idiot", "blöd", "bloed", "versager", "looser", "hässlich")
rng = random.Random(4)
for percent in range(0, 101, 5):
text = saying_for(percent, rng).lower()
for word in bad:
self.assertNotIn(word, text)
class ResultCooldownTests(unittest.TestCase):
def finish_a_round(self) -> Game:
game = Game(
None,
[[CaseSpec(1, 1)]],
difficulty=DIFFICULTIES[1],
rng=random.Random(2),
)
for _ in range(4000):
if game.state is State.FINISHED:
break
game.update(1 / 60)
self.assertIs(game.state, State.FINISHED)
return game
def test_countdown_then_hint(self) -> None:
game = self.finish_a_round()
lines = game.result_lines()
self.assertEqual(len(lines), 3)
self.assertIn("Neue Runde in", lines[2])
self.assertIn("%", lines[0])
for _ in range(int(config.RESULT_COOLDOWN * 60) + 5):
game.update(1 / 60)
self.assertIn("ESC", game.result_lines()[2])
def test_primary_is_ignored_during_cooldown(self) -> None:
game = self.finish_a_round()
self.assertIs(game.handle(Action.PRIMARY), HandleResult.CONTINUE)
game.update(config.RESULT_COOLDOWN / 2)
self.assertIs(game.handle(Action.PRIMARY), HandleResult.CONTINUE)
class SequenceChoiceTests(unittest.TestCase):
def test_never_repeats_the_previous_sequence(self) -> None:
sequences = [[CaseSpec(1, 1)] for _ in range(4)]
rng = random.Random(11)
previous = None
for _ in range(40):
game = Game(None, sequences, rng=rng, previous_sequence=previous)
self.assertNotEqual(game.sequence_index, previous)
previous = game.sequence_index
def test_single_sequence_is_allowed(self) -> None:
game = Game(None, [[CaseSpec(1, 1)]], rng=random.Random(1), previous_sequence=0)
self.assertEqual(game.sequence_index, 0)
class RenderTests(unittest.TestCase):
"""Rendert je ein Bild von Intro, Menü und Spiel (ohne Fenster).