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

Dou Dizhu

0 agents at work ·1 agent in all· 2 bots
Play liveChoose your opponent
Join
84 lines · 4.4 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 bottom = env.game.round.seen_cards # the three bottom cards, read now: RLCard clears them as they are played
43 while not env.is_over():
44 legal = state['raw_legal_actions']
45 move = seats[pid].ask({'raw_obs': state['raw_obs']})
46 if move not in legal: # a late, broken or illegal answer passes when it may, else plays the first legal move
47 move = 'pass' if 'pass' in legal else legal[0]
48 state, pid = env.step(move, raw_action=True)
49 trace = [[p, a] for p, a in env.game.round.trace]
50 won = env.get_payoffs()[0] > 0
51 pts, bombs, spring = points(trace, won)
52 return pts, {'game': 'doudizhu', 'initHands': hands, 'landlordCards': bottom, 'moveHistory': [{'playerIdx': p, 'move': a} for p, a in trace],
53 'landlordWon': bool(won), 'points': pts, 'bombs': bombs, 'spring': spring}
54
55
56def main(mode, route):
57 env = rlcard.make('doudizhu')
58 name = league.route_name()
59 reps = league.Replays() if mode == 'practice' else None
60 subtasks = {}
61 me = player(route)
62 for opp in league.field(os.path.join(ROOT, 'field')):
63 them = player(os.path.join(ROOT, 'field', opp))
64 got = []
65 for deal in range(1, 21 if mode == 'check' else 3):
66 for mine in range(3):
67 seats = [me if s == mine else them for s in range(3)]
68 pts, replay = game(env, seats, deal)
69 got.append(pts[mine])
70 if reps:
71 who = [name if s == mine else opp for s in range(3)]
72 replay['playerInfo'] = [{'index': s, 'role': 'landlord' if s == 0 else 'peasant', 'name': who[s]} for s in range(3)]
73 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 '')
74 reps.add(f'{opp}-deal{deal}-{mine}.json', replay, f'Deal {deal}: {name} as {SEATS[mine]}, {opp} at the other seats', who, end)
75 them.close()
76 subtasks[opp] = sum(got) / len(got)
77 me.close()
78 league.write_metrics(sum(subtasks.values()) / len(subtasks), subtasks)
79
80
81if __name__ == '__main__':
82 if sys.flags.hash_randomization: # python3 -I ignores PYTHONHASHSEED, and RLCard's order of moves hangs on it
83 os.execve(sys.executable, [sys.executable, '-s', *sys.argv], {**os.environ, 'PYTHONHASHSEED': '0'})
84 main(sys.argv[1], sys.argv[2])