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>
This commit is contained in:
+89
-31
@@ -22,7 +22,7 @@ from dojo.events import (
|
||||
TrackChanged,
|
||||
TurnSkipped,
|
||||
)
|
||||
from dojo.rules import VARIANT_A, VARIANT_B, Card
|
||||
from dojo.rules import BLACK, DEFAULT_RULES, Card, RulesConfig
|
||||
|
||||
START_0 = Pos(0, 0)
|
||||
START_1 = Pos(0, 13)
|
||||
@@ -58,7 +58,7 @@ def test_seats_up_to_four_take_corners_clockwise() -> None:
|
||||
def test_players_start_in_their_own_safe_room() -> None:
|
||||
from dataclasses import replace
|
||||
|
||||
game, _, _ = make_game(players=5, config=replace(VARIANT_A, start_position=3))
|
||||
game, _, _ = make_game(players=5, config=replace(DEFAULT_RULES, start_position=3))
|
||||
|
||||
for player in game.players:
|
||||
assert player.position in game.board.safe_room(player.corner)
|
||||
@@ -104,16 +104,15 @@ def test_upset_swaps_a_belt() -> None:
|
||||
assert (game.players[0].track, game.players[1].track) == (3, 3)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("config", "gain"), [(VARIANT_A, 0), (VARIANT_B, 1)])
|
||||
def test_higher_belt_beating_lower_gains_the_variant_amount(config, gain: int) -> None:
|
||||
game, agents, _ = make_game(config=config, dice=[1, 1, 1, 2])
|
||||
def test_higher_belt_beating_lower_gains_nothing() -> None:
|
||||
game, agents, _ = make_game(dice=[1, 1, 1, 2])
|
||||
put(game, 0, Pos(4, 3), track=6)
|
||||
put(game, 1, Pos(4, 5), track=0)
|
||||
|
||||
turn(game, agents, 0, Pos(4, 5))
|
||||
|
||||
assert only_fight(game).kind is FightKind.EXPECTED
|
||||
assert game.players[0].track == 6 + gain
|
||||
assert game.players[0].track == 6
|
||||
assert game.players[1].track == 0
|
||||
assert game.players[1].position == START_1
|
||||
|
||||
@@ -311,18 +310,8 @@ def test_allenamento_and_fatica() -> None:
|
||||
assert game.players[0].track == 3
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("before", "after"), [(14, 14), (13, 14), (15, 16), (17, 17)])
|
||||
def test_variant_b_squares_cannot_make_a_player_brown(before: int, after: int) -> None:
|
||||
game, agents, _ = make_game(config=VARIANT_B, dice=[1, 1])
|
||||
put(game, 0, Pos(0, 3), track=before)
|
||||
|
||||
turn(game, agents, 0, Pos(0, 5))
|
||||
|
||||
assert game.players[0].track == after
|
||||
|
||||
|
||||
def test_variant_a_squares_can_make_a_player_brown() -> None:
|
||||
game, agents, _ = make_game(config=VARIANT_A, dice=[1, 1])
|
||||
def test_squares_can_make_a_player_brown() -> None:
|
||||
game, agents, _ = make_game(dice=[1, 1])
|
||||
put(game, 0, Pos(0, 3), track=14)
|
||||
|
||||
turn(game, agents, 0, Pos(0, 5))
|
||||
@@ -330,16 +319,6 @@ def test_variant_a_squares_can_make_a_player_brown() -> None:
|
||||
assert game.players[0].track == 15
|
||||
|
||||
|
||||
def test_variant_b_kumite_can_make_a_player_brown() -> None:
|
||||
game, agents, _ = make_game(config=VARIANT_B, dice=[1, 1, 3, 3])
|
||||
put(game, 0, Pos(4, 3), track=14)
|
||||
put(game, 1, Pos(4, 5), track=12)
|
||||
|
||||
turn(game, agents, 0, Pos(4, 5))
|
||||
|
||||
assert game.players[0].track == 17
|
||||
|
||||
|
||||
def test_passo_falso_returns_to_the_move_start_and_fights_stand() -> None:
|
||||
game, agents, _ = make_game(dice=[1, 1, 6, 1])
|
||||
put(game, 0, Pos(4, 3), track=3)
|
||||
@@ -545,8 +524,10 @@ def test_only_one_tecnica_can_be_held(keep_drawn: bool, kept: Card) -> None:
|
||||
# Central Dojo and Final Exam --------------------------------------------------------------
|
||||
|
||||
|
||||
def dojo_game(dice: list[int], players: int = 3, **tracks: int) -> tuple[Game, list]:
|
||||
game, agents, _ = make_game(players=players, dice=dice)
|
||||
def dojo_game(
|
||||
dice: list[int], players: int = 3, config: RulesConfig = DEFAULT_RULES, **tracks: int
|
||||
) -> tuple[Game, list]:
|
||||
game, agents, _ = make_game(players=players, config=config, dice=dice)
|
||||
put(game, 0, Pos(4, 7), track=15)
|
||||
for name, track in tracks.items():
|
||||
game.players[int(name[1:])].track = track
|
||||
@@ -614,6 +595,83 @@ def test_meditating_players_cannot_defend() -> None:
|
||||
assert events_of(game, ExamTaken)[0].defender == 1
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("ties_to_defender", "passed"), [(False, True), (True, False)])
|
||||
def test_exam_ties_can_go_to_the_defender(ties_to_defender: bool, passed: bool) -> None:
|
||||
config = RulesConfig(exam_ties_to_defender=ties_to_defender)
|
||||
# Black belt defender: +1, so 4 vs 3 is a tie.
|
||||
game, agents = dojo_game([1, 1, 4, 3], config=config, p1=9, p2=15)
|
||||
|
||||
turn(game, agents, 0, Pos(6, 7))
|
||||
|
||||
assert events_of(game, ExamTaken)[0].passed is passed
|
||||
|
||||
|
||||
def test_exam_ties_go_to_the_defender_only_in_the_exam() -> None:
|
||||
game, agents, _ = make_game(config=RulesConfig(exam_ties_to_defender=True), dice=[1, 1, 3, 3])
|
||||
put(game, 0, Pos(4, 3), track=3)
|
||||
put(game, 1, Pos(4, 5), track=3)
|
||||
|
||||
turn(game, agents, 0, Pos(4, 5))
|
||||
|
||||
assert only_fight(game).attacker_won
|
||||
|
||||
|
||||
@pytest.mark.parametrize(("black", "passed"), [(False, True), (True, False)])
|
||||
def test_exam_defender_may_fight_as_a_black_belt(black: bool, passed: bool) -> None:
|
||||
config = RulesConfig(exam_defender_black=black)
|
||||
game, agents = dojo_game([1, 1, 4, 4], config=config, p1=9, p2=12)
|
||||
|
||||
turn(game, agents, 0, Pos(6, 7))
|
||||
|
||||
fight = only_fight(game)
|
||||
assert fight.defender_belt == (BLACK if black else 4)
|
||||
assert (fight.attacker_total, fight.defender_total) == ((4, 5) if black else (5, 4))
|
||||
assert events_of(game, ExamTaken)[0].passed is passed
|
||||
|
||||
|
||||
def test_best_of_three_exam_is_passed_on_two_rounds() -> None:
|
||||
game, agents = dojo_game(
|
||||
[1, 1, 6, 1, 1, 6, 6, 1], config=RulesConfig(exam_rounds=3), p1=9, p2=15
|
||||
)
|
||||
|
||||
turn(game, agents, 0, Pos(6, 7))
|
||||
|
||||
rounds = events_of(game, KumiteFought)
|
||||
assert [r.attacker_won for r in rounds] == [True, False, True]
|
||||
assert all(r.exam for r in rounds)
|
||||
assert events_of(game, ExamTaken) == [ExamTaken(challenger=0, defender=2, passed=True)]
|
||||
assert game.winner == 0
|
||||
|
||||
|
||||
def test_best_of_three_exam_stops_once_decided() -> None:
|
||||
game, agents = dojo_game([1, 1, 1, 6, 1, 6], config=RulesConfig(exam_rounds=3), p1=9, p2=15)
|
||||
|
||||
turn(game, agents, 0, Pos(6, 7))
|
||||
|
||||
assert len(events_of(game, KumiteFought)) == 2
|
||||
assert game.winner is None
|
||||
assert game.players[0].track == 12
|
||||
assert game.players[0].position == START_0
|
||||
|
||||
|
||||
def test_uke_played_in_any_exam_round_saves_the_challenger() -> None:
|
||||
game, agents = dojo_game([1, 1, 1, 6, 1, 6], config=RulesConfig(exam_rounds=3), p1=9, p2=15)
|
||||
game.players[0].tecnica = Card.UKE
|
||||
agents[0].plays = frozenset({Card.UKE})
|
||||
|
||||
turn(game, agents, 0, Pos(6, 7))
|
||||
|
||||
assert events_of(game, KumiteFought)[0].cards == ((0, Card.UKE),)
|
||||
assert game.players[0].position == Pos(5, 7)
|
||||
assert game.players[0].track == 12
|
||||
|
||||
|
||||
@pytest.mark.parametrize("rounds", [0, 2])
|
||||
def test_exam_rounds_must_be_odd(rounds: int) -> None:
|
||||
with pytest.raises(ValueError, match="odd"):
|
||||
RulesConfig(exam_rounds=rounds)
|
||||
|
||||
|
||||
def test_kumite_won_mid_move_can_open_the_dojo() -> None:
|
||||
game, agents, dice = make_game(dice=[1, 1, 3, 3, 6, 1])
|
||||
put(game, 0, Pos(4, 7), track=14)
|
||||
@@ -635,7 +693,7 @@ def test_a_game_runs_to_completion() -> None:
|
||||
|
||||
from scripted import ScriptedAgent, fixture_board
|
||||
|
||||
config = replace(VARIANT_A, max_rounds=30)
|
||||
config = replace(DEFAULT_RULES, max_rounds=30)
|
||||
game = Game(fixture_board(), config, [ScriptedAgent() for _ in range(4)], RandomSource(7))
|
||||
|
||||
game.run()
|
||||
|
||||
Reference in New Issue
Block a user