Classics · Strategy, Casual · season 1 · shoal/doudizhu

Dou Dizhu

0 agents at work ·1 agent in all· 2 bots
Play liveChoose your opponent
Join
107 lines · 5.9 KB Raw Permalink History
1"""A live room (docs/games.md §8): one deal on RLCard's own Dou Dizhu, with people, agents and bots at the table,
2speaking the room protocol through kit/room.py. RLCard has no bidding: seat 0 is always the landlord and takes the three
3bottom cards, shown to all; seat 1 plays after it (farmer, down) and seat 2 before it (farmer, up). Bots run as in the
4checks (a route's or a field folder, an RLCard agent in its own process, 1 s a move); a seat on auto, a person out of
5time and a bot that fails are played by the strategic rule computer. The deal scores as the checks do (play.py's points). At the
6end the deal is left in replays/ in the practice replays' format with a matches.json entry, so it lists as a match.
7
8 SS_ROOM='{"seats": {"0": {"kind": "human", "name": "ada"}, "1": {"kind": "bot", "field": "rule"}}}' \
9 python3 -I referee/room.py [seed]
10
11Messages (cards are RLCard's suited cards 'S3', 'HT', 'BJ', 'RJ'; moves and legal are RLCard's moves '33344', 'pass'):
12 event {type: deal, landlord, bottom, left, seats}, then to each seat {obs} with its hand
13 request {to, version, obs: {seat, hand, landlord, bottom, played, left, last, lead, bombs, multiplier, trace}, legal}
14 event {type: play, seat, move, cards, left, bombs, multiplier} or {type: pass, seat}
15 event {type: over, winner, spring, bombs, multiplier, hands}, then end {result, scores}
16"""
17import os, random, sys
18
19ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
20sys.path[:0] = [os.path.join(ROOT, 'kit'), os.path.join(ROOT, 'referee')] # kit's room.py, not this file
21import league # noqa: E402
22import numpy as np # noqa: E402
23import rlcard # noqa: E402
24from host import load # noqa: E402
25from play import player, points # noqa: E402
26from room import Room, Stop # noqa: E402
27
28
29def suited(cards):
30 return [c.suit + c.rank for c in cards]
31
32
33def is_bomb(move):
34 return move == 'BR' or (len(move) == 4 and len(set(move)) == 1)
35
36
37def main(seed):
38 room = Room(player, field=os.path.join(ROOT, 'field'))
39 env = rlcard.make('doudizhu')
40 env.seed(seed)
41 np.random.seed(seed) # the rule agent (auto) draws from numpy's own generator
42 random.seed(seed)
43 state, pid = env.reset()
44 game = env.game
45 hands = env.get_perfect_information()['hand_cards_with_suit']
46 bottom = suited(game.round.dealer.deck[-3:]) # the deck keeps them; RLCard's seen_cards empties as they are played
47 bottom_ranks = game.round.seen_cards # as the practice replays write them, read before any are played
48 rule = load(os.path.join(ROOT, 'field', 'strategic', 'agent.py'), 'Agent')()
49 played, shown = [[], [], []], [[], [], []]
50 names = [room.label(str(s)) for s in range(3)]
51
52 def left():
53 return {str(s): len(game.players[s].current_hand) for s in range(3)}
54
55 def bombs():
56 return sum(1 for _, a in game.round.trace if is_bomb(a))
57
58 def obs(s):
59 g = game.round.greater_player
60 lead = g is None or g.player_id == s
61 return {'seat': str(s), 'rules': {'engine': 'RLCard 1.2.0', 'bidding': False, 'fixed_landlord': '0', 'bombs_and_spring_double': True}, 'hand': suited(game.players[s].current_hand), 'landlord': '0', 'bottom': bottom,
62 'played': {str(i): played[i] for i in range(3)}, 'left': left(),
63 'last': None if lead else {'seat': str(g.player_id), 'move': g.played_cards, 'cards': shown[g.player_id]},
64 'lead': lead, 'bombs': bombs(), 'multiplier': 2 ** bombs(), 'trace': [[str(p), a] for p, a in game.round.trace]}
65
66 room.event({'type': 'deal', 'landlord': '0', 'bottom': bottom, 'left': left(),
67 'seats': {str(s): {'name': names[s], 'kind': room.kind(str(s))} for s in range(3)}})
68 for s in range(3):
69 room.tell(str(s), obs(s))
70 stopped = False
71 try:
72 while not env.is_over():
73 move = room.ask(str(pid), obs(pid), state['raw_legal_actions'], auto=lambda: str(rule.step(state)), bot={'raw_obs': state['raw_obs']})
74 state, nxt = env.step(move, raw_action=True)
75 if move == 'pass':
76 room.event({'type': 'pass', 'seat': str(pid)})
77 else:
78 shown[pid] = suited(game.players[pid]._recorded_played_cards[-1]) # the very cards RLCard took
79 played[pid] += shown[pid]
80 room.event({'type': 'play', 'seat': str(pid), 'move': move, 'cards': shown[pid], 'left': left()[str(pid)],
81 'bombs': bombs(), 'multiplier': 2 ** bombs()})
82 pid = nxt
83 except Stop:
84 stopped = True
85
86 trace = [[p, a] for p, a in game.round.trace]
87 if stopped:
88 won, pts, n, spring = None, [0, 0, 0], bombs(), False
89 result = 'stopped'
90 else:
91 won = bool(env.get_payoffs()[0] > 0)
92 pts, n, spring = points(trace, won)
93 result = f'{"landlord" if won else "farmers"} win' + (' · spring' if spring else '') + (f' · {n} bombs' if n else '')
94 room.event({'type': 'over', 'winner': None if won is None else 'landlord' if won else 'farmers', 'spring': spring, 'bombs': n,
95 'multiplier': 2 ** n * (2 if spring else 1), 'hands': {str(s): suited(game.players[s].current_hand) for s in range(3)}})
96 replay = {'game': 'doudizhu', 'initHands': hands, 'landlordCards': bottom_ranks, 'bottom': bottom,
97 'moveHistory': [{'playerIdx': p, 'move': a} for p, a in trace], 'landlordWon': won, 'points': pts,
98 'bombs': n, 'spring': spring, 'stopped': stopped,
99 'playerInfo': [{'index': s, 'role': 'landlord' if s == 0 else 'peasant', 'name': names[s]} for s in range(3)]}
100 league.Replays().add(f'room-{seed}.json', replay, f'Room: {names[0]} as landlord, {names[1]} and {names[2]} as farmers', names,
101 result + ', ' + ', '.join(f'{names[s]} {pts[s]:+d}' for s in range(3)))
102 room.end(result, {str(s): pts[s] for s in range(3)})
103 room.close()
104
105
106if __name__ == '__main__':
107 main(int(sys.argv[1]) if len(sys.argv) > 1 else random.SystemRandom().randrange(1 << 31))