Files
Wizard/docs/02-game-rules.md
2026-09-27 11:26:10 +02:00

141 lines
4.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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").