Files
Wizard/wizard_ai/wizard_ai/__main__.py
T
marvinandClaude Opus 5.5 fde8e2fb2e Always forbid bids that add up to the cards in hand
The sum of all bids may never equal the number of cards each player
holds in the round; the last bidder has to adjust their bid. This was an
optional "not equal" lobby setting and is now a fixed rule of the game
engine, so it applies to humans, AI players and training alike.

- Remove GameOptions.not_equal, the set_options field, the lobby toggle
  and the --not-equal training flag
- Explain the rule in the lobby and show the blocked bid while bidding
- Drop the option from the AI observation encoding
- Tests check that no round ends with bids summing to the cards in hand

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 10:15:25 +02:00

83 lines
3.4 KiB
Python

"""Command line entry point.
python -m wizard_ai serve # production: connect to the game server
python -m wizard_ai train --episodes 5000 # self-play training
python -m wizard_ai evaluate --agents model,heuristic,random,random
"""
from __future__ import annotations
import argparse
import asyncio
import logging
from pathlib import Path
from wizard_game import GameOptions
from .agents import AGENT_NAMES, make_agent
from .config import Config
def main() -> None:
config = Config()
parser = argparse.ArgumentParser(prog="wizard_ai")
parser.add_argument("--log-level", default=config.log_level)
commands = parser.add_subparsers(dest="command", required=True)
serve = commands.add_parser("serve", help="serve AI decisions to the game server")
serve.add_argument("--server-url", default=config.server_url)
serve.add_argument("--agent", default=config.agent, choices=AGENT_NAMES)
serve.add_argument("--model-path", default=config.model_path)
train = commands.add_parser("train", help="train the policy with self-play")
train.add_argument("--episodes", type=int, default=1000)
train.add_argument("--players", default="3,4,5,6", help="comma separated player counts")
train.add_argument("--opponents", default="self", choices=("self", "heuristic", "random"))
train.add_argument("--hidden-bids", action="store_true")
train.add_argument("--resume", action="store_true", help="continue from --out if it exists")
train.add_argument("--out", default=config.model_path, help="checkpoint directory")
train.add_argument("--seed", type=int)
evaluation = commands.add_parser("evaluate", help="let agents play against each other")
evaluation.add_argument("--agents", default="model,heuristic,random,random",
help=f"comma separated, 3-6 of {AGENT_NAMES}")
evaluation.add_argument("--games", type=int, default=200)
evaluation.add_argument("--model-path", default=config.model_path)
evaluation.add_argument("--seed", type=int)
args = parser.parse_args()
logging.basicConfig(level=args.log_level.upper(), format="%(asctime)s %(levelname)s %(name)s: %(message)s")
if args.command == "serve":
from .service import AIService
agent = make_agent(args.agent, args.model_path)
asyncio.run(AIService(args.server_url, agent, config.secret).run_forever())
elif args.command == "train":
from .model import UniformPolicy, checkpoint_exists, load_policy
from .training import SelfPlayTrainer, TrainingConfig
out = Path(args.out)
# TODO: replace UniformPolicy with the real model once it is implemented.
policy = load_policy(out) if args.resume and checkpoint_exists(out) else UniformPolicy(args.seed)
training_config = TrainingConfig(
episodes=args.episodes,
player_counts=tuple(int(n) for n in args.players.split(",")),
options=GameOptions(hidden_bids=args.hidden_bids),
opponents=args.opponents,
checkpoint_dir=out,
seed=args.seed,
)
SelfPlayTrainer(policy, training_config).train()
elif args.command == "evaluate":
from .training import evaluate
agents = [make_agent(name.strip(), args.model_path) for name in args.agents.split(",")]
print(evaluate(agents, args.games, seed=args.seed))
if __name__ == "__main__":
main()