first prototype

This commit is contained in:
2026-09-27 09:52:00 +02:00
parent 4635199a41
commit 6d011d474f
109 changed files with 6354 additions and 507 deletions
+80
View File
@@ -0,0 +1,80 @@
import json
import random
import pytest
from wizard_game import GameOptions, WizardGame
from wizard_ai.agents import HeuristicAgent, ModelAgent, RandomAgent, make_agent
from wizard_ai.model import FEATURE_SIZE, ActionSpace, UniformPolicy, encode_observation, load_policy
from wizard_ai.training import SelfPlayTrainer, TrainingConfig, WizardEnv, evaluate
@pytest.mark.parametrize("num_players", [3, 4, 5, 6])
def test_encoding_has_fixed_size_and_masks_match(num_players):
game = WizardGame(num_players, GameOptions(hidden_bids=True), seed=num_players)
rng = random.Random(0)
while not game.is_finished:
obs = game.observation(game.current_player)
assert len(encode_observation(obs)) == FEATURE_SIZE
mask = ActionSpace.mask(obs)
legal = {ActionSpace.index(a) for a in obs.legal_actions}
assert {i for i, allowed in enumerate(mask) if allowed} == legal
game.apply(obs.player, rng.choice(obs.legal_actions))
def test_action_space_round_trip():
for index in range(ActionSpace.SIZE):
assert ActionSpace.index(ActionSpace.action(index)) == index
@pytest.mark.parametrize("agent", [HeuristicAgent(), RandomAgent(1), ModelAgent(UniformPolicy(1))])
def test_agents_play_legal_games(agent):
game = WizardGame(4, GameOptions(not_equal=True), seed=3)
while not game.is_finished:
obs = game.observation(game.current_player)
action = agent.act(obs)
assert action in obs.legal_actions
game.apply(obs.player, action)
def test_env_rewards_add_up_to_scores():
env = WizardEnv(3, seed=5)
obs = env.reset()
totals = [0.0] * env.num_players
rng = random.Random(5)
while obs is not None:
result = env.step(rng.choice(obs.legal_actions))
totals = [t + r for t, r in zip(totals, result.rewards)]
obs = result.observation
assert [round(t * 100) for t in totals] == env.game.scores
def test_trainer_runs_and_checkpoints(tmp_path):
config = TrainingConfig(episodes=4, player_counts=(3, 4), episodes_per_update=2,
checkpoint_dir=tmp_path / "ckpt", seed=1)
trainer = SelfPlayTrainer(UniformPolicy(1), config)
trajectories, scores = trainer.play_episode()
assert len(trajectories) == len(scores)
assert all(t[-1].done for t in trajectories)
assert sum(sum(tr.reward for tr in t) for t in trajectories) == pytest.approx(sum(scores) / 100)
trainer.train()
assert json.loads((tmp_path / "ckpt" / "policy.json").read_text())["type"] == "uniform"
assert isinstance(load_policy(tmp_path / "ckpt"), UniformPolicy)
def test_trainer_against_heuristic_opponents():
config = TrainingConfig(episodes=2, player_counts=(5,), opponents="heuristic", seed=2)
trajectories, _ = SelfPlayTrainer(UniformPolicy(2), config).play_episode()
assert len(trajectories) == 1
def test_make_agent_falls_back_without_checkpoint(tmp_path):
assert isinstance(make_agent("model", tmp_path / "missing"), HeuristicAgent)
UniformPolicy().save(tmp_path / "ckpt")
assert isinstance(make_agent("model", tmp_path / "ckpt"), ModelAgent)
def test_heuristic_beats_random():
result = evaluate([HeuristicAgent(), RandomAgent(1), RandomAgent(2), RandomAgent(3)], games=40, seed=0)
assert result.mean_scores[0] > max(result.mean_scores[1:])