2026-07-21 19:36:02 -04:00
|
|
|
"""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()
|
2026-08-01 12:19:46 -04:00
|
|
|
pygame.display.set_mode((64, 64)) # a display so convert_alpha() works
|
2026-07-21 19:36:02 -04:00
|
|
|
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
|