Trump card: draw from the stack, no trump for wizards and jesters
- Deal the hands from the shuffled stack and draw the trump card from it, so the trump card is in nobody's hand and cannot be played - A wizard or jester as trump card means no trump suit (previously a wizard let the dealer choose); remove the trump selection phase, the ChooseTrump action, the trump picker UI and the AI trump actions - Completed tricks carry their round's trump card, so the table keeps showing the matching trump card while the last trick of a round is displayed - Card corners are no longer rotated, so 6 and 9 cannot be confused - Trump label explains why there is no trump (wizard, jester, last round) - Tests: trump card never dealt, special cards give no trump, tricks keep their trump card Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+1
-1
@@ -6,7 +6,7 @@ provides everything around it.
|
||||
- `agents/` — `Agent` interface (`act(observation) -> action`) plus `RandomAgent`,
|
||||
`HeuristicAgent` (baseline / fallback) and `ModelAgent` (wraps a `Policy`)
|
||||
- `model/encoding.py` — observation → fixed-size feature vector (`FEATURE_SIZE`), and the fixed
|
||||
action space (21 bids + 60 cards + 4 trump suits) with legal-action masks; works for 3–6 players
|
||||
action space (21 bids + 60 cards) with legal-action masks; works for 3–6 players
|
||||
- `model/policy.py` — **the place to plug in the model**: subclass `Policy`, implement
|
||||
`select`, `update`, `save_weights`/`load`, register with `@register_policy`.
|
||||
`UniformPolicy` is a placeholder so the pipeline runs end to end.
|
||||
|
||||
@@ -6,9 +6,7 @@ as the fallback in production while no trained model is available.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from collections import Counter
|
||||
|
||||
from wizard_game import SUITS, Action, Bid, Card, ChooseTrump, Observation, Phase, PlayCard, Suit
|
||||
from wizard_game import Action, Bid, Card, Observation, Phase, PlayCard, Suit
|
||||
from wizard_game.rules import trick_winner
|
||||
|
||||
from .base import Agent
|
||||
@@ -18,21 +16,11 @@ class HeuristicAgent(Agent):
|
||||
name = "heuristic"
|
||||
|
||||
def act(self, observation: Observation) -> Action:
|
||||
if observation.phase is Phase.TRUMP_SELECTION:
|
||||
return ChooseTrump(self._best_trump(observation.hand))
|
||||
if observation.phase is Phase.BIDDING:
|
||||
return self._bid(observation)
|
||||
return self._play(observation)
|
||||
|
||||
# --- Trump & bidding ----------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _best_trump(hand: tuple[Card, ...]) -> Suit:
|
||||
strength = Counter({suit: 0.0 for suit in SUITS})
|
||||
for card in hand:
|
||||
if card.is_number and card.suit is not None:
|
||||
strength[card.suit] += 1 + card.rank / 13
|
||||
return strength.most_common(1)[0][0]
|
||||
# --- Bidding ------------------------------------------------------------
|
||||
|
||||
@staticmethod
|
||||
def _expected_tricks(observation: Observation) -> float:
|
||||
|
||||
@@ -17,7 +17,6 @@ from wizard_game import (
|
||||
SUITS,
|
||||
Action,
|
||||
Bid,
|
||||
ChooseTrump,
|
||||
Observation,
|
||||
Phase,
|
||||
PlayCard,
|
||||
@@ -32,12 +31,11 @@ SCORE_SCALE = 100.0
|
||||
|
||||
|
||||
class ActionSpace:
|
||||
"""Fixed action indexing: bids 0-20, then the 60 cards, then 4 trump suits."""
|
||||
"""Fixed action indexing: bids 0-20, then the 60 cards."""
|
||||
|
||||
BID_OFFSET = 0
|
||||
CARD_OFFSET = BID_OFFSET + MAX_BID + 1
|
||||
TRUMP_OFFSET = CARD_OFFSET + DECK_SIZE
|
||||
SIZE = TRUMP_OFFSET + len(SUITS)
|
||||
SIZE = CARD_OFFSET + DECK_SIZE
|
||||
|
||||
@classmethod
|
||||
def index(cls, action: Action) -> int:
|
||||
@@ -45,16 +43,12 @@ class ActionSpace:
|
||||
return cls.BID_OFFSET + action.value
|
||||
if isinstance(action, PlayCard):
|
||||
return cls.CARD_OFFSET + action.card.id
|
||||
if isinstance(action, ChooseTrump):
|
||||
return cls.TRUMP_OFFSET + action.suit.index
|
||||
raise TypeError(f"Unknown action {action!r}")
|
||||
|
||||
@classmethod
|
||||
def action(cls, index: int) -> Action:
|
||||
if not 0 <= index < cls.SIZE:
|
||||
raise ValueError(f"Action index out of range: {index}")
|
||||
if index >= cls.TRUMP_OFFSET:
|
||||
return ChooseTrump(SUITS[index - cls.TRUMP_OFFSET])
|
||||
if index >= cls.CARD_OFFSET:
|
||||
return PlayCard(card_by_id(index - cls.CARD_OFFSET))
|
||||
return Bid(index - cls.BID_OFFSET)
|
||||
@@ -86,7 +80,7 @@ def _cards(cards) -> list[float]:
|
||||
return vector
|
||||
|
||||
|
||||
PHASES = (Phase.TRUMP_SELECTION, Phase.BIDDING, Phase.PLAYING)
|
||||
PHASES = (Phase.BIDDING, Phase.PLAYING)
|
||||
|
||||
|
||||
def encode_observation(obs: Observation) -> list[float]:
|
||||
|
||||
Reference in New Issue
Block a user