Rules v0.8: Fatica on crossing, no Kumite in round 1, board draft 2

- Fatica costs a stripe for every Fatica walked onto; a route whose Fatica
  drops a Brown player below 15 no longer enters the Dojo
- No Kumite in the first round: opponents are treated like meditating pawns
- Board draft 2: two Fatica tolls per quadrant, Allenamento moved to the ring
- Casual bot, --summary-csv, per-seat breakdown and move-options metric
- Out of contention measured at 3 belts
- Plan: Phase 6 baselines, decisions D34-D43

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-27 15:59:45 +02:00
parent 4998931cb0
commit bbc95b6722
18 changed files with 566 additions and 80 deletions
+43
View File
@@ -11,6 +11,7 @@ from dojo.bots import (
BALANCED,
FARMER,
PROFILES,
CasualAgent,
HeuristicAgent,
Profile,
RandomAgent,
@@ -57,6 +58,26 @@ def test_heuristic_prefers_allenamento_to_fatica() -> None:
assert choice == (ALLENAMENTO,)
def test_heuristic_walks_around_fatica() -> None:
game, _, _ = make_game()
put(game, 0, Pos(2, 6), track=3)
paths = [(FATICA, Pos(3, 7)), (Pos(2, 7), Pos(3, 7))]
assert HeuristicAgent().choose_path(game.view(0), paths) == paths[1]
def test_heuristic_sees_no_opponent_to_fight_in_round_one() -> None:
game, _, _ = make_game()
game.round = 1
put(game, 0, Pos(4, 3), track=3)
put(game, 1, TATAMI, track=3)
# In round 2 the aggressive bot picks the fight (test_profiles_differ_on_an_even_fight).
choice = HeuristicAgent(AGGRESSIVE).choose_path(game.view(0), [(Pos(5, 3),), (TATAMI,)])
assert choice == (Pos(5, 3),)
def test_brown_heuristic_takes_the_exam_when_it_can() -> None:
game, _, _ = make_game()
put(game, 0, DOJO_ENTRANCE, track=15)
@@ -108,6 +129,28 @@ def test_heuristic_keeps_the_tecnica_its_profile_ranks_first() -> None:
assert HeuristicAgent(FARMER).keep_tecnica(game.view(0), Card.UKE, Card.KIAI) is Card.UKE
def test_casual_bot_compares_every_move_when_offered_few() -> None:
game, _, _ = make_game()
choice = CasualAgent(random.Random(0)).choose_path(game.view(0), [(FATICA,), (ALLENAMENTO,)])
assert choice == (ALLENAMENTO,)
def test_casual_bot_compares_only_a_sample_of_many_moves() -> None:
game, _, _ = make_game()
paths = [(Pos(4, col),) for col in range(8)]
choice = CasualAgent(random.Random(3), sample=1).choose_path(game.view(0), paths)
assert choice == random.Random(3).sample(paths, 1)[0]
def test_casual_bot_needs_a_sample() -> None:
with pytest.raises(ValueError, match="sample size 0"):
CasualAgent(random.Random(0), sample=0)
def play(board: Board, config: RulesConfig, seed: int) -> Game:
rng = random.Random(seed)
agents: list[Agent] = [HeuristicAgent(p) for p in PROFILES.values()]