Files
eisterman 52d8af620b Add failed-Exam loss switch; keep -3 and accept the length (6.11, D50)
RulesConfig.exam_failure_loss (bots aware) with the sim axis --exam-loss.
Losing 1 or 0 stripes saves 3-5 minutes but weakens comebacks, so the
rules keep -3. Default games are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-09-27 17:51:13 +02:00

90 lines
2.9 KiB
Python

from pathlib import Path
import pytest
from dojo.rules import RulesConfig
from dojo.sim import BoardFiles, Scenario, matrix, run
BOARD = Path(__file__).parent.parent / "board"
FILES = BoardFiles(BOARD / "quadrant.txt", BOARD / "legend.toml")
LINEUP = ("balanced", "random")
def test_matrix_crosses_every_axis() -> None:
scenarios = matrix(["none", "acd"], [3, 4], [2, 1], [0], [LINEUP], [3, 0])
assert len(scenarios) == 16
assert scenarios[0].name == "exam:none p3 kiai+2 start0 fail-3 balanced,random"
assert scenarios[0].config == RulesConfig(exam_defender_black=False)
assert scenarios[-1].config == RulesConfig(
exam_ties_to_defender=True,
exam_defender_black=True,
exam_rounds=3,
exam_failure_loss=0,
kiai_bonus=1,
)
@pytest.mark.parametrize(
("exam", "ties", "black", "rounds"),
[
("none", False, False, 1),
("a", True, False, 1),
("c", False, True, 1),
("d", False, False, 3),
("cd", False, True, 3),
],
)
def test_exam_options_map_to_rules(exam: str, ties: bool, black: bool, rounds: int) -> None:
config = Scenario(exam, 4, 2, 0, LINEUP).config
assert (config.exam_ties_to_defender, config.exam_defender_black) == (ties, black)
assert config.exam_rounds == rounds
def test_lineup_fills_the_seats_and_rotates_each_game() -> None:
scenario = Scenario("none", 3, 2, 0, LINEUP)
assert scenario.seating(0) == ("balanced", "random", "balanced")
assert scenario.seating(1) == ("random", "balanced", "balanced")
assert scenario.seating(3) == scenario.seating(0)
@pytest.mark.parametrize(
("exam", "players", "lineup", "message"),
[
("b", 4, LINEUP, "unknown Exam options"),
("aa", 4, LINEUP, "unknown Exam options"),
("", 4, LINEUP, "unknown Exam options"),
("none", 4, ("balanced", "cheater"), "unknown bots"),
("none", 4, (), "unknown bots"),
("none", 9, LINEUP, "supported range"),
],
)
def test_invalid_scenarios_are_rejected(
exam: str, players: int, lineup: tuple[str, ...], message: str
) -> None:
with pytest.raises(ValueError, match=message):
Scenario(exam, players, 2, 0, lineup)
def test_invalid_exam_loss_is_rejected() -> None:
with pytest.raises(ValueError, match="exam_failure_loss"):
Scenario("c", 4, 2, 0, LINEUP, exam_loss=-1)
def test_runs_are_reproducible_and_independent_of_worker_count() -> None:
scenarios = matrix(["none", "acd"], [3], [2], [0], [LINEUP])
serial = run(FILES, scenarios, games=2, seed=5, jobs=1)
parallel = run(FILES, scenarios, games=2, seed=5, jobs=2)
assert serial == parallel
assert [(g.scenario, g.seed) for g in serial] == [
(scenarios[0].name, 5),
(scenarios[0].name, 6),
(scenarios[1].name, 5),
(scenarios[1].name, 6),
]
assert serial[1].lineup == "random,balanced,balanced"