# 2. Game rules as implemented This chapter lists every rule the code enforces and where. The original rules are in `wizard_instructions/`; where this implementation deliberately differs from them, it is marked **(house rule)**. All rule code lives in the game engine: stateless helpers in `wizard_server/game_logic/wizard_game/rules.py`, flow in `game.py`. ## Cards 60 cards (`cards.py`): | Cards | Count | Notes | |-------|-------|-------| | Number cards 1–13 in red, yellow, green, blue | 52 | the only cards with a suit | | Wizards | 4 | always highest | | Jesters | 4 | always lowest | Every card has a fixed **id 0–59**, used in JSON and in the AI encoding: | Ids | Cards | |-----|-------| | 0–12 | red 1–13 | | 13–25 | yellow 1–13 | | 26–38 | green 1–13 | | 39–51 | blue 1–13 | | 52–55 | wizards | | 56–59 | jesters | Formula for number cards: `id = suit_index * 13 + (rank - 1)` with suit order red, yellow, green, blue. ## Players and rounds - 3 to 6 players (`rules.MIN_PLAYERS`, `rules.MAX_PLAYERS`). - Number of rounds = `60 // players` (`rules.total_rounds`): 3 → 20, 4 → 15, 5 → 12, 6 → 10. - In round *n* every player gets *n* cards, so round *n* has *n* tricks. ## Dealing, dealer and starting player `WizardGame._deal`: 1. A fresh 60-card stack is shuffled every round. 2. Cards are dealt one at a time from the top of the stack, starting with the player left of the dealer. 3. The **next card of the stack is turned up as the trump card**. It is taken off the stack: it is in nobody's hand and can never be played. 4. In the last round all 60 cards are dealt, so there is **no trump card**. The first dealer is random (or `first_dealer=` in tests). The dealer moves one seat to the left every round. The player left of the dealer (the *starter*) bids first and leads the first trick. ## Trump `rules.trump_suit(trump_card)`: | Trump card | Trump suit | |------------|------------| | number card | its suit | | wizard | **no trump** (house rule — the original rules let the dealer choose) | | jester | no trump | | none (last round) | no trump | ## Bidding `WizardGame._allowed_bids` and `_bid`: - Players bid in turn, starting with the starter; the dealer bids last. - A bid is any number from 0 to the number of cards in hand. - **The sum of all bids may never equal the number of cards in hand** (house rule — in the original rules this is the optional "notequal" variant; here it is always on). Only the last bidder is restricted: the one value that would make the sum equal the round number is removed from their legal bids. Example: round 5, the others bid 1, 2 and 0 → the last player may bid anything except 2. - Option **hidden bids** ("hiddentip", `GameOptions.hidden_bids`): while bidding is in progress, each player only sees their own bid; everyone can see *who* has already bid (`Observation.has_bid`). All bids are revealed as soon as the last bid is placed (`WizardGame.observation`). Note: because of the sum rule, the last bidder can deduce the sum of the hidden bids from the forbidden value. ## Playing a trick The starter leads the first trick; afterwards the winner of the previous trick leads. ### Which suit must be followed — `rules.lead_suit(trick)` Look at the cards of the trick in playing order: - Jesters are skipped. - If the first non-jester card is a **wizard**, there is no suit to follow for the whole trick. - If the first non-jester card is a **number card**, its suit must be followed. - If only jesters were played so far, there is no suit yet (the next number card will set it). ### Which cards may be played — `rules.legal_cards(hand, trick)` - Wizards and jesters may always be played. - If there is a lead suit and the player holds at least one number card of that suit, they must play a card of that suit (or a wizard/jester). - Otherwise any card may be played. ### Who wins the trick — `rules.trick_winner(trick, trump)` In this order: 1. The **first wizard** played wins. 2. Otherwise the **highest trump** wins. 3. Otherwise the **highest card of the lead suit** wins. 4. If only jesters were played, the **first jester** wins. ## Scoring `rules.round_score(bid, tricks)` at the end of each round: | Result | Points | |--------|--------| | tricks == bid | 20 + 10 × tricks | | tricks ≠ bid | −10 × \|tricks − bid\| | Examples: bid 0, took 0 → +20. Bid 3, took 3 → +50. Bid 2, took 0 → −20. ## End of the game After the last round (`WizardGame._complete_round`) the phase becomes `finished`. All players with the highest total score win (ties are possible, `WizardGame.winners`). ## Rule variants from the original that are not implemented - `dmmixed` and `wiztournament` (tournament card distributions). - The dealer choosing trump when a wizard is turned up (replaced by the house rule "wizard = no trump").