576 lines
22 KiB
Python
576 lines
22 KiB
Python
"""Tests für die Spiellogik (ohne Fenster, ohne Ton).
|
|
|
|
Aufruf::
|
|
|
|
.venv/bin/python -m unittest discover -s tests -v
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import random
|
|
import unittest
|
|
|
|
os.environ.setdefault("SDL_VIDEODRIVER", "dummy")
|
|
os.environ.setdefault("SDL_AUDIODRIVER", "dummy")
|
|
|
|
from loader_game import config
|
|
from loader_game.case import Case
|
|
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.intro import StartSequence, Tutorial
|
|
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
|
|
|
|
|
|
class SequenceTests(unittest.TestCase):
|
|
def test_real_file_parses_into_six_sequences(self) -> None:
|
|
sequences, warnings = load_sequences()
|
|
self.assertEqual(warnings, [])
|
|
self.assertEqual(len(sequences), 6)
|
|
self.assertEqual(sequences[0][0], CaseSpec(3, 1))
|
|
self.assertEqual(sequences[0][3], CaseSpec(2, 1)) # aus "1,2,True"
|
|
|
|
def test_rotated_flag_swaps_dimensions(self) -> None:
|
|
sequences, _ = parse_sequences("1,3,True\n2,1,False")
|
|
self.assertEqual(sequences, [[CaseSpec(3, 1), CaseSpec(2, 1)]])
|
|
|
|
def test_blank_lines_separate_sequences(self) -> None:
|
|
sequences, _ = parse_sequences("1,1,False\n\n2,2,False\n\n")
|
|
self.assertEqual(len(sequences), 2)
|
|
|
|
def test_invalid_lines_warn_and_are_skipped(self) -> None:
|
|
sequences, warnings = parse_sequences("1,1\n1,1,False,extra\n0,1,False\n2,2,False")
|
|
self.assertEqual(sequences, [[CaseSpec(2, 2)]])
|
|
self.assertEqual(len(warnings), 3)
|
|
|
|
def test_garbage_falls_back_to_default(self) -> None:
|
|
sequences, warnings = parse_sequences("nur müll")
|
|
self.assertEqual(sequences, [])
|
|
# eine Meldung für die kaputte Zeile, eine für die fehlende Sequenz
|
|
self.assertEqual(len(warnings), 2)
|
|
|
|
def test_all_specs_fit_available_images(self) -> None:
|
|
sequences, _ = load_sequences()
|
|
for specs in sequences:
|
|
for spec in specs:
|
|
self.assertLessEqual(spec.units_w, config.MAX_UNITS_W, spec)
|
|
self.assertLessEqual(spec.units_h, config.MAX_UNITS_H, spec)
|
|
|
|
|
|
class LayoutTests(unittest.TestCase):
|
|
def test_bands_add_up_to_screen_height(self) -> None:
|
|
self.assertEqual(
|
|
config.TOP_BAR_HEIGHT + config.GAME_AREA_HEIGHT + config.BOTTOM_BAR_HEIGHT,
|
|
config.SCREEN_HEIGHT,
|
|
)
|
|
|
|
def test_trailer_fits_into_game_area(self) -> None:
|
|
self.assertGreaterEqual(config.TRAILER_X, 0)
|
|
self.assertLessEqual(config.TRAILER_RIGHT, config.SCREEN_WIDTH)
|
|
self.assertGreaterEqual(config.TRAILER_Y, config.TOP_BAR_HEIGHT)
|
|
self.assertLessEqual(
|
|
config.TRAILER_BOTTOM, config.TOP_BAR_HEIGHT + config.GAME_AREA_HEIGHT
|
|
)
|
|
|
|
def test_case_unit_is_square(self) -> None:
|
|
self.assertEqual(config.UNIT_LENGTH, config.UNIT_HEIGHT)
|
|
|
|
|
|
class DifficultyTests(unittest.TestCase):
|
|
def test_three_levels_in_order(self) -> None:
|
|
self.assertEqual([d.key for d in DIFFICULTIES], ["easy", "normal", "hard"])
|
|
self.assertEqual([d.label for d in DIFFICULTIES], ["Easy", "Normal", "Hard"])
|
|
|
|
def test_speeds_match_the_requested_levels(self) -> None:
|
|
self.assertEqual(
|
|
[(d.speed_fast, d.speed_slow) for d in DIFFICULTIES],
|
|
[(420.0, 140.0), (740.0, 250.0), (1080.0, 360.0)],
|
|
)
|
|
# Gegenüber der ersten Fassung (Normal 570, Hard 720):
|
|
# Normal ~30 % schneller, Hard ~50 % schneller
|
|
self.assertAlmostEqual(by_key("normal").speed_fast / 570.0, 1.30, delta=0.02)
|
|
self.assertAlmostEqual(by_key("hard").speed_fast / 720.0, 1.50, delta=0.02)
|
|
self.assertAlmostEqual(by_key("normal").speed_slow / 190.0, 1.30, delta=0.02)
|
|
self.assertAlmostEqual(by_key("hard").speed_slow / 240.0, 1.50, delta=0.02)
|
|
|
|
def test_speeds_increase_with_difficulty(self) -> None:
|
|
fast = [d.speed_fast for d in DIFFICULTIES]
|
|
slow = [d.speed_slow for d in DIFFICULTIES]
|
|
self.assertEqual(fast, sorted(fast))
|
|
self.assertEqual(slow, sorted(slow))
|
|
|
|
def test_unknown_key_falls_back_to_default(self) -> None:
|
|
self.assertIs(by_key("gibtsnicht"), DEFAULT_DIFFICULTY)
|
|
|
|
|
|
class CaseTests(unittest.TestCase):
|
|
def make_case(self, units_w: int = 2, units_h: int = 1, x: float | None = None) -> Case:
|
|
return Case(
|
|
units_w=units_w,
|
|
units_h=units_h,
|
|
x=float(config.SCREEN_WIDTH if x is None else x),
|
|
y=float(config.TRAILER_BOTTOM - units_h * config.UNIT_HEIGHT),
|
|
)
|
|
|
|
def test_moves_left_and_brakes_near_wall(self) -> None:
|
|
case = self.make_case()
|
|
start = case.x
|
|
case.move(1 / 60, wall_x=0)
|
|
fast_step = start - case.x
|
|
self.assertGreater(fast_step, 0)
|
|
|
|
case.x = float(config.FAST_TO_SLOW_DISTANCE - 20) # schon im Bremsbereich
|
|
before = case.x
|
|
case.move(1 / 60, wall_x=0)
|
|
slow_step = before - case.x
|
|
self.assertLess(slow_step, fast_step)
|
|
|
|
def test_difficulty_scales_speed(self) -> None:
|
|
easy = self.make_case()
|
|
hard = self.make_case()
|
|
easy.move(0.1, wall_x=0, difficulty=DIFFICULTIES[0])
|
|
hard.move(0.1, wall_x=0, difficulty=DIFFICULTIES[2])
|
|
self.assertGreater(easy.x, hard.x) # easy ist langsamer, bleibt weiter rechts
|
|
|
|
def test_allow_snap_only_near_wall(self) -> None:
|
|
case = self.make_case()
|
|
case.move(1 / 60, wall_x=0)
|
|
self.assertFalse(case.allow_snap)
|
|
|
|
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:
|
|
case = self.make_case()
|
|
case.placed = True
|
|
start = case.x
|
|
case.move(1 / 60, wall_x=0)
|
|
self.assertEqual(case.x, start)
|
|
|
|
def test_case_keeps_its_texture_when_rotated(self) -> None:
|
|
case = self.make_case(units_w=1, units_h=2)
|
|
self.assertEqual(case.texture, (1, 2))
|
|
self.assertFalse(case.is_rotated)
|
|
|
|
case.rotate_footprint()
|
|
self.assertEqual((case.units_w, case.units_h), (2, 1))
|
|
self.assertEqual(case.texture, (1, 2)) # Textur bleibt
|
|
self.assertTrue(case.is_rotated)
|
|
|
|
def test_rotate_respects_trailer_height(self) -> None:
|
|
self.assertTrue(self.make_case(units_w=3, units_h=1).fits_trailer_height())
|
|
self.assertFalse(self.make_case(units_w=4, units_h=1).fits_trailer_height())
|
|
|
|
|
|
class TrailerTests(unittest.TestCase):
|
|
WALL = config.TRAILER_X + 500
|
|
|
|
def setUp(self) -> None:
|
|
self.trailer = Trailer()
|
|
|
|
def make_case(self, units_w: int, units_h: int) -> Case:
|
|
return Case(
|
|
units_w=units_w,
|
|
units_h=units_h,
|
|
x=float(config.TRAILER_RIGHT),
|
|
y=float(config.TRAILER_BOTTOM - units_h * config.UNIT_HEIGHT),
|
|
)
|
|
|
|
def add_base(self, units_w: int = 4, units_h: int = 1, y: float | None = None) -> Case:
|
|
"""Schwebender Koffer, rechtsbündig an der Barriere."""
|
|
base = Case(
|
|
units_w=units_w,
|
|
units_h=units_h,
|
|
x=0.0,
|
|
y=float(config.TRAILER_Y + 100 if y is None else y),
|
|
)
|
|
base.x = self.WALL - base.width
|
|
base.placed = True
|
|
base.snapped = True
|
|
self.trailer.stack.append(base)
|
|
self.trailer.wall_x = base.right
|
|
return base
|
|
|
|
# ------------------------------------------------------------------
|
|
def test_empty_trailer(self) -> None:
|
|
self.assertEqual(self.trailer.wall_x, self.trailer.x)
|
|
self.assertEqual(self.trailer.load_percent(), 0.0)
|
|
self.assertFalse(self.trailer.overflow())
|
|
self.assertIsNone(self.trailer.find_platform(self.make_case(1, 1)))
|
|
|
|
def test_snap_advances_wall_and_load(self) -> None:
|
|
case = self.make_case(4, 1)
|
|
self.trailer.stack_at_wall(case)
|
|
self.assertTrue(case.placed)
|
|
self.assertTrue(case.snapped)
|
|
self.assertEqual(self.trailer.wall_x, self.trailer.x + case.width)
|
|
expected = (
|
|
case.width * case.height / (self.trailer.width * self.trailer.height) * 100
|
|
)
|
|
self.assertAlmostEqual(self.trailer.load_percent(), expected)
|
|
|
|
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.FAIL_GAP)
|
|
self.assertFalse(case.snapped)
|
|
|
|
def test_platform_on_flat_ground_case_is_allowed(self) -> None:
|
|
# Ein flacher Koffer am Boden hat noch Platz darüber -> darf Plattform sein.
|
|
ground = self.make_case(4, 1)
|
|
self.trailer.stack_at_wall(ground)
|
|
self.assertIs(self.trailer.find_platform(self.make_case(1, 1)), ground)
|
|
|
|
def test_no_platform_when_case_reaches_top(self) -> None:
|
|
# 3 Einheiten hoch = Oberkante des Aufliegers -> kein Platz darüber.
|
|
ground = self.make_case(2, 3)
|
|
self.trailer.stack_at_wall(ground)
|
|
self.assertEqual(ground.y, config.TRAILER_Y)
|
|
self.assertIsNone(self.trailer.find_platform(self.make_case(1, 1)))
|
|
|
|
def test_platform_found_for_free_top(self) -> None:
|
|
base = self.add_base(units_w=4, units_h=1)
|
|
self.assertIs(self.trailer.find_platform(self.make_case(1, 1)), base)
|
|
|
|
def test_platform_rejects_too_wide_case(self) -> None:
|
|
self.add_base(units_w=2, units_h=1)
|
|
self.assertIsNone(self.trailer.find_platform(self.make_case(3, 1)))
|
|
|
|
def test_platform_rejects_case_not_aligned_with_wall(self) -> None:
|
|
base = self.add_base(units_w=4, units_h=1)
|
|
base.x -= 50 # nicht mehr bündig an der Barriere
|
|
self.assertIsNone(self.trailer.find_platform(self.make_case(1, 1)))
|
|
|
|
def test_platform_rejects_no_space_above(self) -> None:
|
|
self.add_base(units_w=4, units_h=1, y=config.TRAILER_Y + 50)
|
|
self.assertIsNone(self.trailer.find_platform(self.make_case(1, 1)))
|
|
|
|
def test_top_can_only_be_used_once(self) -> None:
|
|
base = self.add_base(units_w=4, units_h=1)
|
|
top = self.make_case(1, 1)
|
|
self.trailer.place_on_top(top, base)
|
|
self.assertEqual(top.bottom, base.y)
|
|
self.assertEqual(top.right, base.right)
|
|
self.assertIsNone(self.trailer.find_platform(self.make_case(1, 1)))
|
|
|
|
def test_overflow(self) -> None:
|
|
self.trailer.wall_x = self.trailer.right + 1
|
|
self.assertTrue(self.trailer.overflow())
|
|
|
|
|
|
class MenuTests(unittest.TestCase):
|
|
def test_navigation_wraps_around(self) -> None:
|
|
menu = DifficultyMenu(None, selected=DIFFICULTIES[1])
|
|
self.assertIs(menu.selected, DIFFICULTIES[1])
|
|
|
|
self.assertIsNone(menu.handle(Action.LEFT))
|
|
self.assertIs(menu.selected, DIFFICULTIES[0])
|
|
|
|
menu.handle(Action.LEFT) # Umlauf ans Ende
|
|
self.assertIs(menu.selected, DIFFICULTIES[-1])
|
|
|
|
menu.handle(Action.RIGHT) # Umlauf an den Anfang
|
|
self.assertIs(menu.selected, DIFFICULTIES[0])
|
|
|
|
def test_confirm_returns_selection(self) -> None:
|
|
menu = DifficultyMenu(None, selected=DIFFICULTIES[2])
|
|
self.assertIs(menu.handle(Action.PRIMARY), DIFFICULTIES[2])
|
|
|
|
def test_other_actions_do_not_select(self) -> None:
|
|
menu = DifficultyMenu(None)
|
|
self.assertIsNone(menu.handle(Action.UP))
|
|
self.assertIsNone(menu.handle(Action.DOWN))
|
|
self.assertIsNone(menu.handle(Action.BACK))
|
|
|
|
|
|
class GameTests(unittest.TestCase):
|
|
def make_game(self, sequence_length: int = 30, difficulty=None) -> Game:
|
|
sequences = [[CaseSpec(2, 1)] * sequence_length]
|
|
return Game(
|
|
None,
|
|
sequences,
|
|
difficulty=difficulty or DIFFICULTIES[2],
|
|
rng=random.Random(7),
|
|
)
|
|
|
|
def update_until(self, game: Game, predicate, steps: int = 5000) -> bool:
|
|
for _ in range(steps):
|
|
if predicate():
|
|
return True
|
|
game.update(1 / 60)
|
|
return predicate()
|
|
|
|
def test_starts_with_a_case(self) -> None:
|
|
game = self.make_game()
|
|
self.assertIsNotNone(game.current)
|
|
self.assertEqual(game.index, 1)
|
|
self.assertEqual(len(game.upcoming()), 3)
|
|
|
|
def test_snapping_places_case_and_advances_wall(self) -> None:
|
|
game = self.make_game()
|
|
self.assertTrue(
|
|
self.update_until(game, lambda: bool(game.current and game.current.allow_snap))
|
|
)
|
|
game.handle(Action.PRIMARY)
|
|
self.update_until(game, lambda: game.animation is None)
|
|
self.assertEqual(len(game.trailer.stack), 1)
|
|
self.assertAlmostEqual(
|
|
game.trailer.wall_x, config.TRAILER_X + 2 * config.UNIT_LENGTH
|
|
)
|
|
|
|
def test_rotation_keeps_texture(self) -> None:
|
|
game = Game(
|
|
None,
|
|
[[CaseSpec(1, 2)] * 5],
|
|
difficulty=DIFFICULTIES[2],
|
|
rng=random.Random(1),
|
|
)
|
|
case = game.current
|
|
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)
|
|
self.assertEqual((case.units_w, case.units_h), (2, 1))
|
|
self.assertEqual(case.texture, (1, 2))
|
|
self.assertTrue(case.is_rotated)
|
|
self.assertFalse(case.can_rotate)
|
|
|
|
def test_rotation_ignored_for_wide_cases(self) -> None:
|
|
game = Game(
|
|
None,
|
|
[[CaseSpec(4, 1)] * 5],
|
|
difficulty=DIFFICULTIES[2],
|
|
rng=random.Random(1),
|
|
)
|
|
case = game.current
|
|
case.allow_snap = True
|
|
game.handle(Action.LEFT)
|
|
self.assertIsNone(game.animation)
|
|
self.assertEqual((case.units_w, case.units_h), (4, 1))
|
|
|
|
def test_missing_platform_stacks_with_gap(self) -> None:
|
|
game = self.make_game()
|
|
self.assertTrue(
|
|
self.update_until(game, lambda: bool(game.current and game.current.allow_snap))
|
|
)
|
|
game.handle(Action.UP) # es gibt nichts, worauf man stapeln könnte
|
|
self.assertEqual(len(game.trailer.stack), 1)
|
|
self.assertFalse(game.trailer.stack[0].snapped)
|
|
self.assertGreater(game.shake, 0)
|
|
|
|
def test_easy_is_slower_than_hard(self) -> None:
|
|
easy = self.make_game(difficulty=DIFFICULTIES[0])
|
|
hard = self.make_game(difficulty=DIFFICULTIES[2])
|
|
for _ in range(30):
|
|
easy.update(1 / 60)
|
|
hard.update(1 / 60)
|
|
self.assertGreater(easy.current.x, hard.current.x)
|
|
|
|
def test_game_ends_when_trailer_overflows(self) -> None:
|
|
game = self.make_game(sequence_length=40)
|
|
self.assertTrue(
|
|
self.update_until(game, lambda: game.state is State.FINISHED, steps=20000)
|
|
)
|
|
self.assertGreater(game.percent, 0)
|
|
self.assertLessEqual(game.percent, 100)
|
|
|
|
def test_game_ends_when_sequence_is_exhausted(self) -> None:
|
|
game = self.make_game(sequence_length=3)
|
|
for _ in range(20000):
|
|
if game.state is State.FINISHED:
|
|
break
|
|
if game.current is not None and game.current.allow_snap:
|
|
game.handle(Action.PRIMARY)
|
|
game.update(1 / 60)
|
|
self.assertIs(State.FINISHED, game.state)
|
|
self.assertEqual(len(game.trailer.stack), 3)
|
|
|
|
def test_back_quits(self) -> None:
|
|
game = self.make_game()
|
|
self.assertIs(game.handle(Action.BACK), HandleResult.QUIT)
|
|
|
|
def test_primary_after_finish_starts_new_round(self) -> None:
|
|
game = self.make_game(sequence_length=2)
|
|
for _ in range(20000):
|
|
if game.state is State.FINISHED:
|
|
break
|
|
if game.current is not None and game.current.allow_snap:
|
|
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:
|
|
game = self.make_game(difficulty=DIFFICULTIES[2])
|
|
game.percent = 82
|
|
lines = game.result_lines()
|
|
self.assertIn("Hard", lines[0])
|
|
self.assertIn("82%", lines[0])
|
|
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 Anleitung, Startsequenz, Menü und Spiel (ohne Fenster).
|
|
|
|
Fängt fehlende config-Konstanten und Zeichenfehler ab, die reine
|
|
Logiktests nicht bemerken.
|
|
"""
|
|
|
|
@classmethod
|
|
def setUpClass(cls) -> None:
|
|
import pygame
|
|
|
|
from loader_game.assets import Assets
|
|
|
|
pygame.mixer.pre_init(44100, -16, 2, 512)
|
|
pygame.init()
|
|
cls.surface = pygame.display.set_mode(
|
|
(config.SCREEN_WIDTH, config.SCREEN_HEIGHT)
|
|
)
|
|
cls.assets = Assets()
|
|
cls.assets.load()
|
|
|
|
@classmethod
|
|
def tearDownClass(cls) -> None:
|
|
import pygame
|
|
|
|
pygame.quit()
|
|
|
|
def make_game(self) -> Game:
|
|
return Game(
|
|
self.assets,
|
|
[[CaseSpec(3, 1), CaseSpec(1, 2), CaseSpec(2, 2)]],
|
|
difficulty=DIFFICULTIES[1],
|
|
rng=random.Random(3),
|
|
)
|
|
|
|
def test_tutorial_renders_and_waits(self) -> None:
|
|
tutorial = Tutorial(self.assets)
|
|
tutorial.draw(self.surface)
|
|
# Das Tutorial zeigt nur das Bild und wartet - keine Startsequenz.
|
|
self.assertFalse(tutorial.update(1 / 60))
|
|
self.assertFalse(tutorial.update(1 / 60))
|
|
tutorial.handle(Action.PRIMARY)
|
|
self.assertTrue(tutorial.update(1 / 60))
|
|
|
|
def test_start_sequence_renders_and_finishes(self) -> None:
|
|
start = StartSequence(self.assets)
|
|
start.draw(self.surface)
|
|
elapsed = 0.0
|
|
while not start.update(0.05) and elapsed < 10:
|
|
elapsed += 0.05
|
|
expected = (
|
|
len(config.INTRO_DOT_COLORS) * config.INTRO_DOT_DELAY
|
|
+ config.INTRO_ZIP_DURATION
|
|
)
|
|
self.assertAlmostEqual(elapsed, expected, delta=0.3)
|
|
start.draw(self.surface)
|
|
|
|
def test_menu_renders(self) -> None:
|
|
menu = DifficultyMenu(self.assets, selected=DIFFICULTIES[0])
|
|
menu.handle(Action.RIGHT)
|
|
menu.draw(self.surface)
|
|
|
|
def test_game_and_result_render(self) -> None:
|
|
game = self.make_game()
|
|
game.draw(self.surface)
|
|
game.percent = 61.5
|
|
game.state = State.FINISHED
|
|
game.draw(self.surface)
|
|
|
|
def test_rotated_case_draws_something(self) -> None:
|
|
from loader_game import scene
|
|
|
|
case = Case(units_w=1, units_h=2, x=200.0, y=400.0)
|
|
case.rotate_footprint()
|
|
self.assertTrue(case.is_rotated)
|
|
scene.draw_case(self.surface, self.assets, case)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|