141 lines
4.8 KiB
Markdown
141 lines
4.8 KiB
Markdown
# 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").
|