| """Parser + engine smoke tests. Run with: python -m tests.test_parser |
| |
| These use the mock backend, so no model weights or network are required.""" |
|
|
| import sys |
| import os |
|
|
| sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) |
|
|
| from engine.game_state import GameState, Enemy |
| from engine import parser |
| from engine.engine import GameEngine |
| from engine.llm import build_backend |
|
|
|
|
| def check(name, cond): |
| status = "ok " if cond else "FAIL" |
| print(f"[{status}] {name}") |
| if not cond: |
| raise AssertionError(name) |
|
|
|
|
| def test_parse_blocks(): |
| raw = ( |
| "<narrative>You enter a dim hall.</narrative>" |
| "<state>\nHP: -5\nGOLD: +10\nITEM_ADD: Torch\nLOCATION: Dim Hall\n</state>" |
| "<choices>\n1. Go north.\n2. Light the torch.\n</choices>" |
| ) |
| narrative, choices, lines = parser.parse(raw) |
| check("narrative extracted", narrative == "You enter a dim hall.") |
| check("choices extracted", choices == ["Go north.", "Light the torch."]) |
| check("state lines count", len(lines) == 4) |
|
|
|
|
| def test_apply_changes_clamped(): |
| state = GameState(hp=20, max_hp=20, gold=10) |
| parser.apply_state_changes(state, ["HP: -5", "GOLD: +10", "ITEM_ADD: Torch"]) |
| check("hp reduced", state.hp == 15) |
| check("gold added", state.gold == 20) |
| check("item added", state.has_item("Torch")) |
|
|
| |
| parser.apply_state_changes(state, ["HP: +999"]) |
| check("heal clamped to max", state.hp == 20) |
|
|
| |
| parser.apply_state_changes(state, ["GOLD: -9999"]) |
| check("gold floored at 0", state.gold == 0) |
|
|
|
|
| def test_death(): |
| state = GameState(hp=5) |
| parser.apply_state_changes(state, ["HP: -50"]) |
| check("hp floored at 0", state.hp == 0) |
| check("game over on death", state.game_over) |
|
|
|
|
| def test_combat_flow(): |
| state = GameState() |
| parser.apply_state_changes(state, ["ENEMY: Goblin|hp=10|atk=4"]) |
| check("combat started", state.enemy is not None and state.enemy.name == "Goblin") |
| parser.apply_state_changes(state, ["ENEMY_HP: -6"]) |
| check("enemy damaged", state.enemy.hp == 4) |
| parser.apply_state_changes(state, ["ENEMY_HP: -10"]) |
| check("combat ended on death", state.enemy is None) |
|
|
|
|
| def test_leveling(): |
| state = GameState(level=1, xp=0, max_hp=20) |
| parser.apply_state_changes(state, ["XP: +10"]) |
| check("leveled up", state.level == 2) |
| check("max hp grew", state.max_hp == 25) |
|
|
|
|
| def test_unparseable_ignored(): |
| state = GameState(hp=20) |
| before = state.to_dict() |
| parser.apply_state_changes(state, ["HP: lots", "WUT: 5", "random gibberish"]) |
| check("garbage ignored", state.to_dict() == before) |
|
|
|
|
| def test_full_engine_mock(): |
| engine = GameEngine(build_backend("mock")) |
| opening = engine.start() |
| check("opening has narrative", len(opening.narrative) > 0) |
| check("opening has choices", len(opening.choices) == 3) |
|
|
| |
| engine.state.start_combat(Enemy("Wraith", hp=10, max_hp=10, attack=3)) |
| res = engine.act("I attack the wraith with my dagger") |
| check("attack damaged enemy or ended combat", |
| engine.state.enemy is None or engine.state.enemy.hp < 10) |
|
|
|
|
| def main(): |
| tests = [v for k, v in sorted(globals().items()) if k.startswith("test_")] |
| for t in tests: |
| t() |
| print(f"\nAll {len(tests)} test groups passed.") |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|