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>
This commit is contained in:
2026-09-27 10:15:25 +02:00
co-authored by Claude Opus 5.5
parent 90176e1cca
commit fde8e2fb2e
14 changed files with 35 additions and 41 deletions
+1 -1
View File
@@ -30,7 +30,7 @@ def test_action_space_round_trip():
@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)
game = WizardGame(4, seed=3)
while not game.is_finished:
obs = game.observation(game.current_player)
action = agent.act(obs)
+1 -2
View File
@@ -33,7 +33,6 @@ def main() -> None:
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("--not-equal", action="store_true")
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")
@@ -65,7 +64,7 @@ def main() -> None:
training_config = TrainingConfig(
episodes=args.episodes,
player_counts=tuple(int(n) for n in args.players.split(",")),
options=GameOptions(not_equal=args.not_equal, hidden_bids=args.hidden_bids),
options=GameOptions(hidden_bids=args.hidden_bids),
opponents=args.opponents,
checkpoint_dir=out,
seed=args.seed,
+2 -2
View File
@@ -104,7 +104,7 @@ def encode_observation(obs: Observation) -> list[float]:
features += _one_hot(MAX_PLAYERS - MIN_PLAYERS + 1, n - MIN_PLAYERS)
features += _one_hot(len(PHASES), PHASES.index(obs.phase) if obs.phase in PHASES else None)
features += [obs.round_number / MAX_BID, obs.round_number / obs.total_rounds]
features += [float(obs.options.not_equal), float(obs.options.hidden_bids)]
features += [float(obs.options.hidden_bids)]
features += _one_hot(MAX_PLAYERS, (me - obs.dealer) % n) # seat relative to dealer
features += _one_hot(MAX_PLAYERS, len(obs.current_trick)) # position in the trick
@@ -150,7 +150,7 @@ def encode_observation(obs: Observation) -> list[float]:
FEATURE_SIZE = (
(MAX_PLAYERS - MIN_PLAYERS + 1) + len(PHASES) + 2 + 2 + MAX_PLAYERS + MAX_PLAYERS
(MAX_PLAYERS - MIN_PLAYERS + 1) + len(PHASES) + 2 + 1 + MAX_PLAYERS + MAX_PLAYERS
+ DECK_SIZE * 3 + (len(SUITS) + 1) * 2 + DECK_SIZE
+ 5
+ MAX_OPPONENTS * 7