# Dou Dizhu China's most played card game, three players and one deck with two jokers: the landlord against two farmers who play as a team. It runs on [RLCard](https://github.com/datamllab/rlcard)'s own Dou Dizhu, the engine behind most research on the game, and your bot is written the way RLCard agents are. ## The game Seat 0 is the landlord: it holds 20 cards (its 17 and the three bottom cards, shown to all) and leads. The others are farmers with 17 each. Play goes round; each play must beat the last of the same kind (singles, pairs, triples with attachments, straights, planes …) or pass. A bomb (four of a rank) beats ordinary combinations; a higher bomb beats a lower bomb. The rocket (both jokers) beats every other play. The first to empty their hand wins for their side. As in RLCard, there is no bidding: the landlord seat is fixed. ## Scoring Every deal is played three times, duplicate style: your bot takes each seat in turn and the opponent fills the other two, to reduce deal and seat bias. Each seat in each replay of a deal has its own fresh process, so agent memory is not carried across seats or duplicate deals. A game scores as the table does: the landlord wins or loses 2 points, each farmer 1, doubled for each bomb or rocket played and doubled again for a spring (春天). Your score is your mean points per game over twenty deals against each bot of the field. ## Your bot An RLCard agent: `agent.py` with a class `Agent` that has `eval_step(state)` (or `step(state)`) and returns a move, written as RLCard writes them: `'3'`, `'33344'`, `'789TJQ'`, `'BR'` (the rocket), `'pass'`. `state['raw_obs']` has `self`, `landlord`, `current_hand`, `others_hand`, `played_cards`, `num_cards_left`, `seen_cards`, `trace` and `actions`; `state['raw_legal_actions']` lists the legal moves. Each move has 1 second; your bot is a process of its own. ## The field, verifier version 2 | Bot | What it does | |---|---| | `strategic` | default fixed computer: bounded hand decomposition, farmer cooperation, control-card conservation and one/two-card endgame defence | | `rule` | RLCard's original doudizhu-rule-v1, retained as a baseline | | `random` | any legal move | [DouZero](https://github.com/kwai/DouZero) (ICML 2021) is a learned self-play alternative for a future integration. It is not installed or used by the current fixed computers; no model-weight strength claim is made here. ## Play live A room is one deal played live, with people, agents and bots at the table. Seat `0` is the landlord (RLCard has no bidding, so seat 0 always is, and takes the three bottom cards), seat `1` the farmer who plays after it, seat `2` the farmer before it. A bot seat runs a route's bot or one of the field, as the checks do; a seat on auto, a player out of time and a bot that fails are played by the `strategic` rule computer. When it is your turn you get your hand, the cards each seat has played, the cards each has left, the bottom cards, the trick to beat, the bombs so far and the multiplier, and the legal moves as RLCard writes them. The finished deal is kept as a match. To play one on your machine, from this folder with the game's Python (answer for seat 0 in the terminal; `--auto` plays it for you): ``` SS_INPUT=$PWD python3 kit/roomcli.py --room '{"seats": {"0": {"kind": "human", "name": "me"}, "1": {"kind": "bot", "dir": "routes"}, "2": {"kind": "bot", "field": "rule"}}}' -- python3 -I referee/room.py ``` ## The table `viewer/` is the card table, adapted from [DeskPark](https://github.com/solitern/DeskPark) (MIT): it plays replays move by move, and in a room (`viewer/index.html?live`) it is where a person plays. Its source is `viewer-src/`; after a change, `cd viewer-src && npm install && npm run build` rewrites `viewer/app.js` and `viewer/app.css`. To watch a replay from a clone: `python3 -m http.server`, then open `viewer/index.html?replay=../replays/`. ## Credits The engine and the `rule` agent are RLCard 1.2.0's, © DATA Lab, under the MIT licence. The table is adapted from DeskPark, © 2026 solitern, under the MIT licence (`viewer/NOTICE`). The referee, the room and the rest of the viewer are this project's, under MIT. ## Rule and computer audit (2026-10-23) This is the installed **RLCard 1.2.0 fixed-landlord variant**, not a full bidding game. Its dealer fixes seat 0 even though some upstream documentation describes a hand-strength bidding heuristic. The table now says “固定地主 · 无叫牌” and no longer invents a “叫地主” action. RLCard's own legal-action generator remains authoritative, including its restriction against using both jokers together as airplane wings. Multipliers and spring scoring are this project's explicit scoring layer; upstream RLCard itself returns binary team payoffs. `strategic` uses only the ordinary seat observation. `others_hand` is the unassigned union of the two other hands, derivable from the public 54-card deck minus the current hand and exposed cards; it never identifies which opponent holds a card. It does not use a deal seed or perfect information. The original `rule` bot is kept unchanged for comparison. Verifier version 2 adds `strategic`, resets bot processes for every seat/deal, and records invalid/late fallback counts in replays. Old and new leaderboard scores are not interchangeable. Run `.venvs/doudizhu/bin/python -m unittest discover -s games/doudizhu/tests` from the repository root for engine, scoring and strategy regressions. The dated audit in `docs/research/2026-10-23-gomoku-doudizhu-audit.md` records the real-game comparisons and their limits.