| 1 | """The project's referee, on RLCard's own Dou Dizhu. Seat 0 is the landlord, holding the three bottom cards, as RLCard |
| 2 | deals (RLCard has no bidding). Every deal is played three times, duplicate style: the route's bot takes each seat in |
| 3 | turn and the opponent fills the other two, so luck of the deal cancels out. A game scores as the table does: the |
| 4 | landlord wins or loses 2, each farmer 1, doubled for every bomb or rocket played and doubled again for a spring. |
| 5 | check: twenty deals against each bot of the field; the score is the route's mean points per game, each opponent a |
| 6 | subtask. practice: two deals against each, a replay of every game in replays/. |
| 7 | python3 -I referee/play.py check|practice <route folder> |
| 8 | """ |
| 9 | import os, random, sys |
| 10 | |
| 11 | ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
| 12 | sys.path.insert(0, os.path.join(ROOT, 'kit')) |
| 13 | import league |
| 14 | import numpy as np |
| 15 | import rlcard |
| 16 | from host import Child |
| 17 | |
| 18 | SEATS = ['landlord', 'farmer (down)', 'farmer (up)'] |
| 19 | |
| 20 | |
| 21 | def 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 | |
| 28 | def 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 | |
| 36 | def 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 |
| 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: |
| 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 | |
| 58 | def 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 | |
| 69 | |
| 70 | |
| 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 | |
| 89 | if __name__ == '__main__': |
| 90 | if sys.flags.hash_randomization: |
| 91 | os.execve(sys.executable, [sys.executable, '-s', *sys.argv], {**os.environ, 'PYTHONHASHSEED': '0'}) |
| 92 | main(sys.argv[1], sys.argv[2]) |