Auto-assignment (Board.add_target/add_ally with no explicit id_) now scopes its 1/2/3... sequence per TYPE within each group, not one sequence shared across every type in the group -- Tank#1/Infantry#1 rather than Tank#A/Infantry#B, matching the game's own numbering. Reverses the type-scoping half of an earlier fix in this file (see TODO.md's "Allies and enemies seem to share indices" entry) per explicit user direction; the targets-vs-allies namespace split that fix also made is untouched, still correct. _next_free_id (letters, rolling over to "AA" past 26) is replaced by _next_free_numeric_id -- a plain counter can't run out the way a fixed alphabet could, so there's no equivalent rollover concern. test_models.py updated to match (one test asserts the opposite of before, renamed accordingly). detected_id (map_vision.read_marker_id) was being logged but never shown anywhere a human could actually check it against the screenshot before now: added to the proposal popover's heading and the pending-proposal's own on-map label. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
121 lines
4.8 KiB
Python
121 lines
4.8 KiB
Python
"""Regression coverage for Board's bulk-mutation helpers (clear/clear_units)
|
|
and the id namespaces Target/Ally are supposed to keep separate."""
|
|
from fenigma.models import Board, Coord, TargetType
|
|
|
|
|
|
def _coord(x=0, y=0):
|
|
return Coord(X="A", Y=1, x=x, y=y)
|
|
|
|
|
|
def test_clear_drops_allies_too():
|
|
"""Board.clear() used to leave self.allies untouched -- the "clear
|
|
board" action then reported success but a previously-placed ally
|
|
stayed on the map."""
|
|
board = Board()
|
|
board.nest.coord = _coord()
|
|
board.add_spotter(_coord())
|
|
board.add_reference_point(_coord())
|
|
board.add_target(TargetType.TANK, _coord())
|
|
board.add_ally(TargetType.TANK, _coord())
|
|
board.add_scout_flight((1.0, 1.0), 45.0)
|
|
|
|
board.clear()
|
|
|
|
assert board.nest.coord is None
|
|
assert board.spotters == []
|
|
assert board.reference_points == []
|
|
assert board.targets == []
|
|
assert board.allies == []
|
|
assert board.scout_flights == []
|
|
|
|
|
|
def test_clear_units_keeps_recon_infrastructure():
|
|
"""The Clear button's right-click "Clear enemies, units & flights"
|
|
option: drops targets/allies/scout flights but keeps the Nest,
|
|
spotters, and reference points."""
|
|
board = Board()
|
|
board.nest.coord = _coord()
|
|
sp = board.add_spotter(_coord())
|
|
rp = board.add_reference_point(_coord())
|
|
board.add_target(TargetType.TANK, _coord())
|
|
board.add_ally(TargetType.TANK, _coord())
|
|
board.add_scout_flight((1.0, 1.0), 45.0)
|
|
|
|
board.clear_units()
|
|
|
|
assert board.nest.coord is not None
|
|
assert board.spotters == [sp]
|
|
assert board.reference_points == [rp]
|
|
assert board.targets == []
|
|
assert board.allies == []
|
|
assert board.scout_flights == []
|
|
|
|
|
|
def test_ally_and_target_ids_are_independent_namespaces():
|
|
"""An ally Tank#1 and a hostile Target Tank#1 are unrelated -- adding
|
|
one must never be influenced by the other's ids, and auto-assignment
|
|
on each side starts from '1' independently."""
|
|
board = Board()
|
|
t1 = board.add_target(TargetType.TANK, _coord(), id_="1")
|
|
a1 = board.add_ally(TargetType.TANK, _coord(), id_="1")
|
|
assert t1.id == a1.id == "1"
|
|
assert t1 is not a1
|
|
|
|
# '1' is already taken (explicitly) for TANK on each side, so the next
|
|
# auto-assigned Tank on each side must skip it and land on '2'.
|
|
t_auto = board.add_target(TargetType.TANK, _coord())
|
|
a_auto = board.add_ally(TargetType.TANK, _coord())
|
|
assert t_auto.id == "2" # first free number among *target* Tanks only
|
|
assert a_auto.id == "2" # first free number among *ally* Tanks only, unaffected by the target above
|
|
|
|
|
|
def test_auto_id_is_per_type_within_targets_and_within_allies():
|
|
"""Each TYPE gets its own independent 1/2/3... sequence within a group
|
|
(all targets, or all allies) -- a Tank and an Infantry auto-assigned
|
|
back to back both start at '1' (Tank#1, Infantry#1), matching how the
|
|
game itself numbers units, rather than sharing one sequence across
|
|
every type in the group."""
|
|
board = Board()
|
|
tank = board.add_target(TargetType.TANK, _coord())
|
|
infantry = board.add_target(TargetType.INFANTRY, _coord())
|
|
assert tank.id == "1"
|
|
assert infantry.id == "1" # own sequence, not '2' just because a Tank came first
|
|
|
|
second_tank = board.add_target(TargetType.TANK, _coord())
|
|
assert second_tank.id == "2" # but a SECOND Tank does advance the Tank sequence
|
|
|
|
ally_tank = board.add_ally(TargetType.TANK, _coord())
|
|
ally_infantry = board.add_ally(TargetType.INFANTRY, _coord())
|
|
assert ally_tank.id == "1"
|
|
assert ally_infantry.id == "1"
|
|
|
|
|
|
def test_auto_id_keeps_counting_past_26_entities_of_one_type():
|
|
"""The id sequence is a plain integer counter now (see
|
|
_next_free_numeric_id), not the old letter sequence that could raise
|
|
StopIteration past 26 (see TODO.md/git history) -- nothing special
|
|
should happen crossing 26, it just keeps counting."""
|
|
board = Board()
|
|
for _ in range(26):
|
|
board.add_target(TargetType.TANK, _coord())
|
|
twenty_seventh = board.add_target(TargetType.TANK, _coord())
|
|
assert twenty_seventh.id == "27"
|
|
|
|
board2 = Board()
|
|
for _ in range(26):
|
|
board2.add_ally(TargetType.TANK, _coord())
|
|
twenty_seventh_ally = board2.add_ally(TargetType.TANK, _coord())
|
|
assert twenty_seventh_ally.id == "27"
|
|
|
|
|
|
def test_find_by_name_prefers_target_over_same_named_ally():
|
|
"""find_by_name() (used to resolve Clue references) checks targets
|
|
before allies -- documented, deliberate priority, not a namespace
|
|
collision: an ally and a same-typed/same-id target are still two
|
|
distinct objects, this only matters when something's Clue names one
|
|
ambiguously by the shared display name."""
|
|
board = Board()
|
|
target = board.add_target(TargetType.TANK, _coord(x=1), id_="1")
|
|
board.add_ally(TargetType.TANK, _coord(x=2), id_="1")
|
|
assert board.find_by_name("Tank#1") is target
|