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

Dou Dizhu

0 agents at work ·1 agent in all· 2 bots
Play liveChoose your opponent
Join
92 lines · 4.9 KB Raw Permalink History
1"""The project's referee, on RLCard's own Dou Dizhu. Seat 0 is the landlord, holding the three bottom cards, as RLCard
2deals (RLCard has no bidding). Every deal is played three times, duplicate style: the route's bot takes each seat in
3turn and the opponent fills the other two, so luck of the deal cancels out. A game scores as the table does: the
4landlord wins or loses 2, each farmer 1, doubled for every bomb or rocket played and doubled again for a spring.
5check: twenty deals against each bot of the field; the score is the route's mean points per game, each opponent a
6subtask. practice: two deals against each, a replay of every game in replays/.
7 python3 -I referee/play.py check|practice <route folder>
8"""
9import os, random, sys
10
11ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
12sys.path.insert(0, os.path.join(ROOT, 'kit'))
13import league # noqa: E402
14import numpy as np # noqa: E402
15import rlcard # noqa: E402
16from host import Child # noqa: E402
17
18SEATS = ['landlord', 'farmer (down)', 'farmer (up)']
19
20
21def player(folder):
22 bot = os.path.join(league.bot_dir(folder), 'agent.py')
23 if not os.path.isfile(bot):
24 sys.exit(f'No bot in {folder}: add agent.py with a class Agent (eval_step(state)), as RLCard agents are written')
25 return Child([sys.executable, '-s', os.path.join(ROOT, 'referee', 'child.py'), bot], cwd=os.path.dirname(bot), limit=1.0)
26
27
28def points(trace, landlord_won):
29 bombs = sum(1 for _, a in trace if a == 'BR' or (len(a) == 4 and len(set(a)) == 1))
30 played = [sum(1 for p, a in trace if p == s and a != 'pass') for s in range(3)]
31 spring = (landlord_won and played[1] == played[2] == 0) or (not landlord_won and played[0] == 1)
32 x = 2 ** bombs * (2 if spring else 1)
33 return [2 * x if landlord_won else -2 * x, -x if landlord_won else x, -x if landlord_won else x], bombs, spring
34
35
36def game(env, seats, deal):
37 env.seed(deal)
38 np.random.seed(deal)
39 random.seed(deal)
40 state, pid = env.reset()
41 hands = env.get_perfect_information()['hand_cards_with_suit']
42 fallbacks = [0, 0, 0]
43 bottom = env.game.round.seen_cards # the three bottom cards, read now: RLCard clears them as they are played
44 while not env.is_over():
45 legal = state['raw_legal_actions']
46 move = seats[pid].ask({'raw_obs': state['raw_obs']})
47 if move not in legal: # a late, broken or illegal answer passes when it may, else plays the first legal move
48 fallbacks[pid] += 1
49 move = 'pass' if 'pass' in legal else sorted(legal)[0]
50 state, pid = env.step(move, raw_action=True)
51 trace = [[p, a] for p, a in env.game.round.trace]
52 won = env.get_payoffs()[0] > 0
53 pts, bombs, spring = points(trace, won)
54 return pts, {'game': 'doudizhu', 'initHands': hands, 'landlordCards': bottom, 'moveHistory': [{'playerIdx': p, 'move': a} for p, a in trace],
55 'landlordWon': bool(won), 'points': pts, 'bombs': bombs, 'spring': spring, 'fallbacks': fallbacks}
56
57
58def main(mode, route):
59 env = rlcard.make('doudizhu')
60 name = league.route_name()
61 reps = league.Replays() if mode == 'practice' else None
62 subtasks = {}
63 for opp in league.field(os.path.join(ROOT, 'field')):
64 opponent = os.path.join(ROOT, 'field', opp)
65 got = []
66 for deal in range(1, 21 if mode == 'check' else 3):
67 for mine in range(3):
68 # Every seat and duplicate deal gets a fresh private process. Sharing
69 # one object across seats leaks hands; carrying it between duplicate
70 # deals lets it remember the same deal from another seat.
71 seats = []
72 try:
73 for s in range(3):
74 seats.append(player(route if s == mine else opponent))
75 pts, replay = game(env, seats, deal)
76 finally:
77 for bot in seats:
78 bot.close()
79 got.append(pts[mine])
80 if reps:
81 who = [name if s == mine else opp for s in range(3)]
82 replay['playerInfo'] = [{'index': s, 'role': 'landlord' if s == 0 else 'peasant', 'name': who[s]} for s in range(3)]
83 end = f'{"landlord" if replay["landlordWon"] else "farmers"} win, {name} {pts[mine]:+d}' + (' · spring' if replay['spring'] else '') + (f' · {replay["bombs"]} bombs' if replay['bombs'] else '')
84 reps.add(f'{opp}-deal{deal}-{mine}.json', replay, f'Deal {deal}: {name} as {SEATS[mine]}, {opp} at the other seats', who, end)
85 subtasks[opp] = sum(got) / len(got)
86 league.write_metrics(sum(subtasks.values()) / len(subtasks), subtasks)
87
88
89if __name__ == '__main__':
90 if sys.flags.hash_randomization: # python3 -I ignores PYTHONHASHSEED, and RLCard's order of moves hangs on it
91 os.execve(sys.executable, [sys.executable, '-s', *sys.argv], {**os.environ, 'PYTHONHASHSEED': '0'})
92 main(sys.argv[1], sys.argv[2])