harness.pyΒΆ
Part of Tanks of Freedom.
1"""Scripted runtime harness: drives the world headless and verifies state.
2
3Run:
4 uv run python examples/ports/tanks_of_freedom/harness.py
5
6Validates:
7- The iso tile map builds with the expected mode and bounds.
8- The BFS pathfinder returns a viable path between two passable cells.
9- The AP flood fill only reports in-bounds cells.
10- Combat resolution actually deals damage.
11- Turn manager alternates blue / red and counts full rounds.
12- The world boots with all starting units / buildings.
13"""
14
15from __future__ import annotations
16
17import sys
18from pathlib import Path
19
20_PORT_DIR = Path(__file__).parent
21if str(_PORT_DIR) not in sys.path:
22 sys.path.insert(0, str(_PORT_DIR))
23
24from main import TanksRoot # noqa: E402
25from nodes.combat import resolve_attack # noqa: E402
26from nodes.data import ( # noqa: E402
27 PLAYER_BLUE,
28 PLAYER_RED,
29 STARTING_BUILDINGS,
30 STARTING_UNITS,
31)
32from nodes.pathfinder import find_path, reachable_cells # noqa: E402
33from nodes.tile_map import TankTileMap # noqa: E402
34from nodes.turn import TurnManager # noqa: E402
35
36from simvx.core.testing import SceneRunner # noqa: E402
37
38
39def main() -> int:
40 print("--- Tanks of Freedom port harness ---")
41
42 # 1) Tile map terrain layout
43 tm = TankTileMap()
44 assert tm.mode == "isometric"
45 assert tm.in_bounds(0, 0)
46 assert not tm.in_bounds(99, 99)
47 print(f"OK tile map ready in mode={tm.mode!r} cell_size={tm.cell_size}")
48
49 # 2) Pathfinder finds a route between two passable cells
50 def passable(x, y):
51 return tm.is_passable(x, y, is_air=False)
52
53 def unblocked(x, y):
54 return False
55
56 path = find_path((1, 10), (10, 1), passable=passable, blocked=unblocked)
57 assert path, "no path blue-HQ -> red-HQ"
58 assert path[0] == (1, 10) and path[-1] == (10, 1)
59 print(f"OK ground path length={len(path)}")
60
61 # 3) Reachable flood works with AP=4
62 reach = reachable_cells((2, 10), 4, passable=passable, blocked=unblocked)
63 assert (2, 10) in reach, "start cell missing from reachable set"
64 assert all(0 <= c[0] < 12 and 0 <= c[1] < 12 for c in reach)
65 print(f"OK reachable from (2,10) AP=4 -> {len(reach)} cells")
66
67 # 4) Combat resolves with damage applied
68 class _Stub:
69 def __init__(self, type_, hp, max_hp):
70 self.type = type_
71 self.life = hp
72 self.max_life = max_hp
73
74 a = _Stub(1, 15, 15) # tank
75 b = _Stub(0, 10, 10) # soldier
76 pre = b.life
77 resolve_attack(a, b)
78 assert b.life < pre
79 print(f"OK combat: tank vs soldier, soldier hp {pre}->{b.life}")
80
81 # 5) Turn manager alternates and refreshes turn counter every two passes
82 tm_state = TurnManager()
83 assert tm_state.current == PLAYER_BLUE
84 tm_state.end_turn()
85 assert tm_state.current == PLAYER_RED
86 tm_state.end_turn()
87 assert tm_state.current == PLAYER_BLUE
88 assert tm_state.turn_number == 2
89 print("OK turn manager alternates blue->red->blue, turn_number 2")
90
91 # 6) End-to-end scene boot via SceneRunner
92 runner = SceneRunner()
93 runner.load(TanksRoot(autostart=True))
94 runner.advance_frames(60)
95 root = runner.root
96 world = root.children[0]
97 assert len(world.units) == len(STARTING_UNITS)
98 assert len(world.buildings) == len(STARTING_BUILDINGS)
99 print(f"OK world boots: {len(world.units)} units, {len(world.buildings)} buildings")
100
101 print("--- ALL CHECKS PASSED ---")
102 return 0
103
104
105if __name__ == "__main__":
106 sys.exit(main())