Initial commit
This commit is contained in:
190
tests/test_flow.py
Normal file
190
tests/test_flow.py
Normal file
@@ -0,0 +1,190 @@
|
||||
"""Level loading, death/respawn, counters, level-clear flow, window sizing."""
|
||||
|
||||
import pygame
|
||||
from conftest import step, run, place, hold, DT
|
||||
from game import settings as S
|
||||
from game.player import InputState
|
||||
|
||||
|
||||
SIMPLE = """
|
||||
name: My Level
|
||||
tile_size: 32
|
||||
battery_seconds: 30
|
||||
map: |
|
||||
#####
|
||||
#P.G#
|
||||
#####
|
||||
"""
|
||||
|
||||
|
||||
def test_level_parse(make_level):
|
||||
lvl = make_level(SIMPLE)
|
||||
assert lvl.name == "My Level"
|
||||
assert lvl.width == 5 * 32 and lvl.height == 3 * 32
|
||||
assert lvl.spawn == (1 * 32, 1 * 32)
|
||||
assert lvl.goal_rect.topleft == (3 * 32, 1 * 32)
|
||||
assert len(lvl.solids) == 5 + 5 + 2 # top row + bottom row + side walls
|
||||
|
||||
|
||||
def test_unknown_trap_type_is_skipped(make_level):
|
||||
lvl = make_level("""
|
||||
name: t
|
||||
tile_size: 32
|
||||
map: |
|
||||
###
|
||||
#P#
|
||||
###
|
||||
traps:
|
||||
- type: not_a_real_trap
|
||||
at: [1, 1]
|
||||
""")
|
||||
assert lvl.traps == [] # skipped, no crash
|
||||
|
||||
|
||||
def test_death_pause_then_respawn(make_game):
|
||||
g = make_game(SIMPLE)
|
||||
run(g, 5)
|
||||
d0 = g.deaths
|
||||
g._start_death()
|
||||
assert g.state == "dying" and g.deaths == d0 + 1
|
||||
frames = 0
|
||||
while g.state == "dying" and frames < 100:
|
||||
step(g)
|
||||
frames += 1
|
||||
assert g.state == "playing"
|
||||
assert abs(frames * DT - S.DEATH_PAUSE) < DT * 2
|
||||
|
||||
|
||||
def test_death_counters_per_level_and_total(make_game, tmp_path):
|
||||
# two-level game to test per-level reset vs session total
|
||||
a = tmp_path / "a.yaml"
|
||||
b = tmp_path / "b.yaml"
|
||||
a.write_text(SIMPLE)
|
||||
b.write_text(SIMPLE.replace("My Level", "Two"))
|
||||
from game.game import Game
|
||||
g = Game([str(a), str(b)], str(tmp_path / "noassets"))
|
||||
|
||||
def die():
|
||||
g._start_death()
|
||||
while g.state == "dying":
|
||||
step(g)
|
||||
|
||||
die(); die(); die()
|
||||
assert g.level_deaths == 3 and g.deaths == 3
|
||||
g._advance()
|
||||
assert g.level_deaths == 0 and g.deaths == 3 # per-level resets, total persists
|
||||
die()
|
||||
assert g.level_deaths == 1 and g.deaths == 4
|
||||
g._replay()
|
||||
assert g.index == 0 and g.deaths == 0 # replay resets the total
|
||||
|
||||
|
||||
def test_reach_goal_charges_then_wins(make_game):
|
||||
g = make_game(SIMPLE)
|
||||
g._reach_goal()
|
||||
assert g.state == "charging"
|
||||
assert 0 <= g.charge_from < 0.2 # near-empty start
|
||||
for _ in range(70):
|
||||
step(g)
|
||||
assert g.state == "won_all" # single level -> won_all
|
||||
|
||||
|
||||
def test_fade_swaps_level_at_black(make_game, tmp_path):
|
||||
a = tmp_path / "a.yaml"
|
||||
b = tmp_path / "b.yaml"
|
||||
a.write_text(SIMPLE)
|
||||
b.write_text(SIMPLE.replace("My Level", "Two"))
|
||||
from game.game import Game
|
||||
g = Game([str(a), str(b)], str(tmp_path / "noassets"))
|
||||
g.state = "won_level"
|
||||
g._start_fade(g._advance)
|
||||
out = swapped = done = 0
|
||||
for i in range(80):
|
||||
prev = g.index
|
||||
step(g)
|
||||
if g.fade_phase == "out" and g.state == "fading":
|
||||
out += 1
|
||||
if g.index != prev:
|
||||
swapped = i
|
||||
if g.state == "playing":
|
||||
done = i
|
||||
break
|
||||
assert swapped and done and g.index == 1
|
||||
assert done - swapped >= 15 # fade-in ~0.3s not skipped
|
||||
|
||||
|
||||
def test_window_sized_once_for_tallest_level(make_game, tmp_path, monkeypatch):
|
||||
a = tmp_path / "a.yaml"
|
||||
b = tmp_path / "b.yaml"
|
||||
a.write_text("name: A\ntile_size: 32\nmap: |\n #####\n #P.G#\n #####\n") # 3 rows
|
||||
b.write_text("name: B\ntile_size: 32\nmap: |\n #####\n #...#\n #P.G#\n #####\n") # 4 rows
|
||||
from game.game import Game
|
||||
calls = []
|
||||
orig = pygame.display.set_mode
|
||||
monkeypatch.setattr(pygame.display, "set_mode",
|
||||
lambda size, *a, **k: calls.append(size) or orig(size, *a, **k))
|
||||
g = Game([str(a), str(b)], str(tmp_path / "noassets"))
|
||||
g._advance(); g._replay()
|
||||
assert len(calls) == 1 # never recreated the window
|
||||
# sized to the tallest level (4 rows + HUD)
|
||||
from game.game import HUD_H
|
||||
assert g.win_h == 4 * 32 + HUD_H
|
||||
|
||||
|
||||
def test_battery_visual_scales_by_pct(make_level):
|
||||
lvl = make_level(SIMPLE + "battery_pct: 4\n")
|
||||
assert lvl.battery_pct == 4
|
||||
|
||||
|
||||
def test_cli_debug_forces_all_levels(make_game):
|
||||
g = make_game(SIMPLE, debug=True)
|
||||
assert g.level.debug is True
|
||||
g2 = make_game(SIMPLE)
|
||||
assert g2.level.debug is False
|
||||
|
||||
|
||||
def test_f5_hot_reload_picks_up_edits_and_counts_death(tmp_path):
|
||||
from game.game import Game
|
||||
p = tmp_path / "hot.yaml"
|
||||
p.write_text("name: A\ntile_size: 32\nmap: |\n #####\n #P.G#\n #####\n")
|
||||
g = Game([str(p)], str(tmp_path / "noassets"))
|
||||
assert g.level.name == "A" and len(g.level.traps) == 0
|
||||
d0 = g.deaths
|
||||
# edit the file on disk, then F5
|
||||
p.write_text("name: B\ntile_size: 32\nmap: |\n #####\n #P.G#\n #####\n"
|
||||
"traps:\n - type: block\n at: [2, 1]\n deadly: true\n")
|
||||
g._reload_level()
|
||||
assert g.state == "dying" and g.deaths == d0 + 1 # death beat + counter
|
||||
while g.state == "dying":
|
||||
step(g)
|
||||
assert g.level.name == "B" and len(g.level.traps) == 1 # picked up the edit
|
||||
assert g.level_deaths == 1 # counted, not reset to 0
|
||||
|
||||
|
||||
def test_f5_bad_yaml_does_not_crash(tmp_path, capsys):
|
||||
from game.game import Game
|
||||
p = tmp_path / "bad.yaml"
|
||||
p.write_text("name: A\ntile_size: 32\nmap: |\n #####\n #P.G#\n #####\n")
|
||||
g = Game([str(p)], str(tmp_path / "noassets"))
|
||||
p.write_text("name: A\n bad: [unclosed\n") # invalid YAML
|
||||
g._reload_level()
|
||||
while g.state == "dying":
|
||||
step(g)
|
||||
assert g.state == "playing" # fell back, no crash
|
||||
assert g.level.name == "A" # kept the old level
|
||||
|
||||
|
||||
def test_debug_grid_adds_pixels(make_level):
|
||||
import pygame
|
||||
from game.assets import AssetStore
|
||||
lvl = make_level("name: t\ntile_size: 32\nmap: |\n #####\n #...#\n #...#\n #####\n")
|
||||
a = AssetStore("noassets")
|
||||
|
||||
def painted(dbg):
|
||||
lvl.debug = dbg
|
||||
w = pygame.Surface((lvl.width, lvl.height))
|
||||
w.fill((0, 0, 0))
|
||||
lvl.draw(w, a)
|
||||
return sum(1 for y in range(lvl.height) for x in range(lvl.width)
|
||||
if w.get_at((x, y))[:3] != (0, 0, 0))
|
||||
assert painted(True) > painted(False) # grid + labels add ink
|
||||
Reference in New Issue
Block a user