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:
+25
-12
@@ -31,7 +31,7 @@ from dojo.events import (
|
||||
TurnStarted,
|
||||
)
|
||||
from dojo.movement import Path, walks
|
||||
from dojo.rules import TECNICHE, Card, RulesConfig
|
||||
from dojo.rules import BLACK, TECNICHE, Card, RulesConfig
|
||||
|
||||
MIN_PLAYERS = 2
|
||||
MAX_PLAYERS = 8
|
||||
@@ -308,7 +308,10 @@ class Game:
|
||||
|
||||
def _kumite(self, attacker: Player, defender: Player, *, exam: bool = False) -> FightResult:
|
||||
config = self.config
|
||||
belts = (config.belt(attacker.track), config.belt(defender.track))
|
||||
defender_belt = config.belt(defender.track)
|
||||
if exam and config.exam_defender_black:
|
||||
defender_belt = BLACK
|
||||
belts = (config.belt(attacker.track), defender_belt)
|
||||
bonus = min(abs(belts[0] - belts[1]), config.max_belt_bonus)
|
||||
dice = [self._random.d6(), self._random.d6()]
|
||||
extra = [bonus if belts[0] > belts[1] else 0, bonus if belts[1] > belts[0] else 0]
|
||||
@@ -341,7 +344,10 @@ class Game:
|
||||
uke[side] = True
|
||||
|
||||
totals = (dice[0] + extra[0], dice[1] + extra[1])
|
||||
attacker_won = totals[0] >= totals[1] # Senshu: ties go to the attacker.
|
||||
if exam and config.exam_ties_to_defender:
|
||||
attacker_won = totals[0] > totals[1]
|
||||
else:
|
||||
attacker_won = totals[0] >= totals[1] # Senshu: ties go to the attacker.
|
||||
winner_belt, loser_belt = belts if attacker_won else belts[::-1]
|
||||
if winner_belt == loser_belt:
|
||||
kind = FightKind.EQUAL
|
||||
@@ -374,9 +380,7 @@ class Game:
|
||||
winner, loser, loser_uke = defender, attacker, result.attacker_uke
|
||||
|
||||
stripes = self.config.stripes_per_belt
|
||||
if result.kind is FightKind.EXPECTED:
|
||||
self._change_track(winner, self.config.expected_win_gain, Source.KUMITE)
|
||||
else:
|
||||
if result.kind is not FightKind.EXPECTED:
|
||||
self._change_track(winner, stripes, Source.KUMITE)
|
||||
self._change_track(loser, -stripes, Source.KUMITE)
|
||||
|
||||
@@ -402,13 +406,23 @@ class Game:
|
||||
)
|
||||
defender_id = self._pick(choice, candidates, "exam defender")
|
||||
|
||||
result = self._kumite(challenger, self.players[defender_id], exam=True)
|
||||
self._emit(ExamTaken(challenger.id, defender_id, result.attacker_won))
|
||||
if result.attacker_won:
|
||||
needed = self.config.exam_wins_needed
|
||||
won = lost = 0
|
||||
uke = False
|
||||
while won < needed and lost < needed:
|
||||
result = self._kumite(challenger, self.players[defender_id], exam=True)
|
||||
uke = uke or result.attacker_uke
|
||||
if result.attacker_won:
|
||||
won += 1
|
||||
else:
|
||||
lost += 1
|
||||
passed = won == needed
|
||||
self._emit(ExamTaken(challenger.id, defender_id, passed))
|
||||
if passed:
|
||||
self.winner = challenger.id
|
||||
return
|
||||
self._change_track(challenger, -self.config.stripes_per_belt, Source.EXAM)
|
||||
if not result.attacker_uke:
|
||||
if not uke:
|
||||
self._send_to_start(challenger)
|
||||
|
||||
# Squares ------------------------------------------------------------------------------
|
||||
@@ -525,8 +539,7 @@ class Game:
|
||||
# State helpers ------------------------------------------------------------------------
|
||||
|
||||
def _change_track(self, player: Player, delta: int, source: Source) -> None:
|
||||
from_board = source in (Source.SQUARE, Source.CARD)
|
||||
after = self.config.shifted(player.track, delta, from_board=from_board)
|
||||
after = self.config.shifted(player.track, delta)
|
||||
if after != player.track:
|
||||
self._emit(TrackChanged(player.id, player.track, after, source))
|
||||
player.track = after
|
||||
|
||||
Reference in New Issue
Block a user