Speed up the simulator 5-7x with identical games (6.10)
Precompute board adjacency and square sets, look up opponents once per move, and cache walks that exclude only the start square. Every game is unchanged for the same seed, checked with agents/scripts/fingerprint_games.py. Plan: skip catch-up 6.9 (D44), stop running the random lineup (D45). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""Check that an engine change leaves every game identical (events hashed per game).
|
||||
|
||||
Record before the change, check after (a rule change is expected to differ):
|
||||
|
||||
uv run python agents/scripts/fingerprint_games.py write /tmp/fingerprints.json
|
||||
uv run python agents/scripts/fingerprint_games.py check /tmp/fingerprints.json
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from concurrent.futures import ProcessPoolExecutor
|
||||
from pathlib import Path
|
||||
|
||||
from dojo.board import load_board
|
||||
from dojo.engine import Game, RandomSource
|
||||
from dojo.rules import RulesConfig
|
||||
from dojo.sim import make_agent
|
||||
|
||||
BOARD = Path(__file__).resolve().parents[2] / "board"
|
||||
GAMES = 60
|
||||
CASES = [
|
||||
("mixed4", RulesConfig(), ("balanced", "farmer", "aggressive", "balanced"), GAMES),
|
||||
("casual4", RulesConfig(), ("casual",) * 4, GAMES),
|
||||
(
|
||||
"mixed6",
|
||||
RulesConfig(),
|
||||
("casual", "farmer", "aggressive", "balanced", "casual", "farmer"),
|
||||
30,
|
||||
),
|
||||
(
|
||||
"stop-r1",
|
||||
RulesConfig(fatica_on_crossing=False, first_kumite_round=1),
|
||||
("casual", "balanced", "farmer"),
|
||||
30,
|
||||
),
|
||||
("random", RulesConfig(max_rounds=60), ("random", "casual", "random", "balanced"), 30),
|
||||
]
|
||||
|
||||
|
||||
def play(case: int, seed: int) -> tuple[str, int, str]:
|
||||
name, config, bots, _ = CASES[case]
|
||||
board = load_board(BOARD / "quadrant.txt", BOARD / "legend.toml")
|
||||
agents = [make_agent(bot, seed, seat) for seat, bot in enumerate(bots)]
|
||||
game = Game(board, config, agents, RandomSource(seed))
|
||||
game.run()
|
||||
digest = hashlib.sha256("\n".join(map(repr, game.events)).encode()).hexdigest()
|
||||
return name, seed, digest
|
||||
|
||||
|
||||
def main() -> None:
|
||||
mode, path = sys.argv[1], Path(sys.argv[2])
|
||||
tasks = [(i, seed) for i, case in enumerate(CASES) for seed in range(case[3])]
|
||||
with ProcessPoolExecutor(8) as pool:
|
||||
results = {f"{n}:{s}": d for n, s, d in pool.map(play, *zip(*tasks, strict=True))}
|
||||
if mode == "write":
|
||||
path.write_text(json.dumps(results, indent=0))
|
||||
print(f"wrote {len(results)} games")
|
||||
return
|
||||
expected = json.loads(path.read_text())
|
||||
bad = [k for k in expected if results.get(k) != expected[k]]
|
||||
print(f"{len(expected) - len(bad)}/{len(expected)} games identical")
|
||||
if bad:
|
||||
print("differ:", bad[:10])
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user