"""Shared pytest fixtures + helpers for the Dying Phone engine tests. Everything runs headless via SDL's dummy drivers, so no window is needed. """ import os os.environ.setdefault("SDL_VIDEODRIVER", "dummy") os.environ.setdefault("SDL_AUDIODRIVER", "dummy") import pygame import pytest from game import settings as S from game.level import Level from game.game import Game from game.player import InputState DT = 1 / 60.0 @pytest.fixture(scope="session", autouse=True) def _pygame(): pygame.init() pygame.display.set_mode((64, 64)) # a display so convert_alpha() works yield pygame.quit() # --- level/game factories ---------------------------------------------------- @pytest.fixture def make_level(tmp_path): n = [0] def _make(yaml_text): n[0] += 1 path = tmp_path / f"level_{n[0]}.yaml" path.write_text(yaml_text) return Level(str(path)) return _make @pytest.fixture def make_game(tmp_path): n = [0] def _make(yaml_text, **kw): n[0] += 1 path = tmp_path / f"game_{n[0]}.yaml" path.write_text(yaml_text) # nonexistent assets dir -> placeholder rectangles (deterministic) return Game([str(path)], str(tmp_path / "no_assets"), **kw) return _make # --- run-loop helpers --------------------------------------------------------- def step(game, inp=None): """Advance one frame, mirroring Game.run()'s per-state dispatch.""" inp = inp or InputState() if game.state == "playing": game._update_play(DT, inp) elif game.state == "dying": game.death_timer -= DT if game.death_timer <= 0: game._respawn() elif game.state == "charging": game.charge_timer += DT if game.charge_timer >= S.CHARGE_TIME: game.state = "won_all" if game._final else "won_level" elif game.state == "fading": game._update_fade(DT) def run(game, frames, inp_fn=None): for i in range(frames): step(game, inp_fn(i) if inp_fn else None) def place(game, col, row): """Put the player's feet at the bottom of cell (col,row), centred.""" t = game.level.tile game.player.fx = float(col * t + (t - game.player.w) / 2) game.player.fy = float(row * t + (t - game.player.h)) game.player.vx = game.player.vy = 0.0 game.player._sync_rect() def hold(**flags): inp = InputState() for k, v in flags.items(): setattr(inp, k, v) return inp # --- fakes for isolated trap unit tests -------------------------------------- class FakeLevel: tile = 32 debug = False width = 800 height = 480 def cell_rect(self, c, r): return pygame.Rect(c * 32, r * 32, 32, 32) class FakePlayer: def __init__(self, rect): self.rect = rect self.w = rect.w self.h = rect.h self.fx = float(rect.x) self.fy = float(rect.y) self.vx = self.vy = 0.0 def _sync_rect(self): self.rect.x = round(self.fx) self.rect.y = round(self.fy) class FakeGame: def __init__(self, rect): self.player = FakePlayer(rect) def reversals(values): """Count direction reversals in a numeric sequence (for jitter tests).""" rev, prev = 0, 0 for i in range(1, len(values)): d = (values[i] > values[i - 1]) - (values[i] < values[i - 1]) if d and prev and d != prev: rev += 1 if d: prev = d return rev