added docs

This commit is contained in:
2026-09-27 11:26:10 +02:00
parent 87151c2cc9
commit 905574e8f1
10 changed files with 1736 additions and 1 deletions
+140
View File
@@ -0,0 +1,140 @@
# 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").