Files
dojo_cate/tests/test_bots.py
T
eisterman 4998931cb0 Drop variant B, adopt Black-belt Exam defender, add one-sidedness metrics
- Remove variant B (Brown cap, +1 for higher beating lower) from rules,
  engine, bots and simulator; a higher belt beating a lower one gains 0.
- Final Exam: the defender fights as a Black belt (RULES.md v0.7). Ties to
  the defender and best-of-3 stay as simulator switches (`--exam`).
- Report stripe income per source as gains and losses.
- Add ten one-sidedness measures sampled at turn ends (lead gap, winner
  margin and lead share, halfway leader, last lead change, contention,
  rank swaps, leader Kumite losses, Exam challengers, winner pace).
- Plan: decisions D28-D33 and the detailed Phase 6.

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

132 lines
4.2 KiB
Python

"""Bot decisions on the fixture board (positions as in test_engine.py)."""
import random
import pytest
from scripted import make_game, put
from dojo.board import Board, Pos
from dojo.bots import (
AGGRESSIVE,
BALANCED,
FARMER,
PROFILES,
HeuristicAgent,
Profile,
RandomAgent,
series_probability,
win_probability,
)
from dojo.engine import Agent, FightView, Game, RandomSource
from dojo.rules import DEFAULT_RULES, Card, RulesConfig
ALLENAMENTO = Pos(0, 5)
FATICA = Pos(3, 6)
TATAMI = Pos(4, 4)
DOJO_ENTRANCE = Pos(5, 7)
DOJO = Pos(6, 7)
def test_win_probability_counts_senshu_and_bonuses() -> None:
assert win_probability(0, 0) == pytest.approx(21 / 36)
assert win_probability(2, 0) == pytest.approx(30 / 36)
assert win_probability(0, 2) == pytest.approx(10 / 36)
assert win_probability(0, 0, senshu=False) == pytest.approx(15 / 36)
def test_series_probability_is_a_best_of_n() -> None:
assert series_probability(0.3, 1) == pytest.approx(0.3)
assert series_probability(0.5, 3) == pytest.approx(0.5)
assert series_probability(0.6, 3) == pytest.approx(0.6**2 * (3 - 2 * 0.6))
def test_kiai_that_only_ties_the_exam_is_kept_when_ties_go_to_the_defender() -> None:
game, _, _ = make_game(config=RulesConfig(exam_ties_to_defender=True))
game.players[0].tecnica = Card.KIAI
exam = FightView(0, 1, True, 3, 5, played=())
assert not HeuristicAgent().play_tecnica(game.view(0), exam)
assert HeuristicAgent().play_tecnica(game.view(0), fight(3, 5))
def test_heuristic_prefers_allenamento_to_fatica() -> None:
game, _, _ = make_game()
choice = HeuristicAgent().choose_path(game.view(0), [(FATICA,), (ALLENAMENTO,)])
assert choice == (ALLENAMENTO,)
def test_brown_heuristic_takes_the_exam_when_it_can() -> None:
game, _, _ = make_game()
put(game, 0, DOJO_ENTRANCE, track=15)
choice = HeuristicAgent().choose_path(game.view(0), [(ALLENAMENTO,), (DOJO,)])
assert choice == (DOJO,)
@pytest.mark.parametrize(("profile", "fights"), [(FARMER, False), (AGGRESSIVE, True)])
def test_profiles_differ_on_an_even_fight(profile: Profile, fights: bool) -> None:
game, _, _ = make_game()
put(game, 0, Pos(4, 3), track=3)
put(game, 1, TATAMI, track=3)
choice = HeuristicAgent(profile).choose_path(game.view(0), [(Pos(5, 3),), (TATAMI,)])
assert (choice == (TATAMI,)) is fights
def fight(attacker_total: int, defender_total: int) -> FightView:
return FightView(0, 1, False, attacker_total, defender_total, played=())
@pytest.mark.parametrize(
("me", "card", "totals", "plays"),
[
(0, Card.KIAI, (3, 5), True), # 5 ties 5: Senshu gives it to the attacker.
(0, Card.KIAI, (2, 5), False), # Kiai cannot turn it: keep it.
(0, Card.KIAI, (5, 5), False), # Already winning.
(1, Card.KIAI, (5, 4), True), # The defender needs to beat, not tie.
(1, Card.KIAI, (6, 4), False),
(0, Card.KAESHI, (1, 6), True), # Losing: a reroll can only help.
(1, Card.UKE, (4, 4), True),
],
)
def test_heuristic_plays_a_tecnica_only_when_losing(
me: int, card: Card, totals: tuple[int, int], plays: bool
) -> None:
game, _, _ = make_game()
game.players[me].tecnica = card
assert HeuristicAgent().play_tecnica(game.view(me), fight(*totals)) is plays
def test_heuristic_keeps_the_tecnica_its_profile_ranks_first() -> None:
game, _, _ = make_game()
assert HeuristicAgent(BALANCED).keep_tecnica(game.view(0), Card.UKE, Card.KIAI) is Card.KIAI
assert HeuristicAgent(FARMER).keep_tecnica(game.view(0), Card.UKE, Card.KIAI) is Card.UKE
def play(board: Board, config: RulesConfig, seed: int) -> Game:
rng = random.Random(seed)
agents: list[Agent] = [HeuristicAgent(p) for p in PROFILES.values()]
agents.append(RandomAgent(rng))
game = Game(board, config, agents, RandomSource(seed))
game.run()
return game
@pytest.mark.parametrize(
"config",
[
DEFAULT_RULES,
RulesConfig(exam_ties_to_defender=True, exam_defender_black=True, exam_rounds=3),
],
)
def test_bots_play_full_games_reproducibly(board: Board, config: RulesConfig) -> None:
first, again = play(board, config, seed=7), play(board, config, seed=7)
assert first.winner is not None
assert first.events == again.events