"""The project's referee, on RLCard's own Dou Dizhu. Seat 0 is the landlord, holding the three bottom cards, as RLCard deals (RLCard has no bidding). Every deal is played three times, duplicate style: the route's bot takes each seat in turn and the opponent fills the other two, so luck of the deal cancels out. A game scores as the table does: the landlord wins or loses 2, each farmer 1, doubled for every bomb or rocket played and doubled again for a spring. check: twenty deals against each bot of the field; the score is the route's mean points per game, each opponent a subtask. practice: two deals against each, a replay of every game in replays/. python3 -I referee/play.py check|practice """ import os, random, sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.join(ROOT, 'kit')) import league # noqa: E402 import numpy as np # noqa: E402 import rlcard # noqa: E402 from host import Child # noqa: E402 SEATS = ['landlord', 'farmer (down)', 'farmer (up)'] def player(folder): bot = os.path.join(league.bot_dir(folder), 'agent.py') if not os.path.isfile(bot): sys.exit(f'No bot in {folder}: add agent.py with a class Agent (eval_step(state)), as RLCard agents are written') return Child([sys.executable, '-s', os.path.join(ROOT, 'referee', 'child.py'), bot], cwd=os.path.dirname(bot), limit=1.0) def points(trace, landlord_won): bombs = sum(1 for _, a in trace if a == 'BR' or (len(a) == 4 and len(set(a)) == 1)) played = [sum(1 for p, a in trace if p == s and a != 'pass') for s in range(3)] spring = (landlord_won and played[1] == played[2] == 0) or (not landlord_won and played[0] == 1) x = 2 ** bombs * (2 if spring else 1) return [2 * x if landlord_won else -2 * x, -x if landlord_won else x, -x if landlord_won else x], bombs, spring def game(env, seats, deal): env.seed(deal) np.random.seed(deal) random.seed(deal) state, pid = env.reset() hands = env.get_perfect_information()['hand_cards_with_suit'] bottom = env.game.round.seen_cards # the three bottom cards, read now: RLCard clears them as they are played while not env.is_over(): legal = state['raw_legal_actions'] move = seats[pid].ask({'raw_obs': state['raw_obs']}) if move not in legal: # a late, broken or illegal answer passes when it may, else plays the first legal move move = 'pass' if 'pass' in legal else legal[0] state, pid = env.step(move, raw_action=True) trace = [[p, a] for p, a in env.game.round.trace] won = env.get_payoffs()[0] > 0 pts, bombs, spring = points(trace, won) return pts, {'game': 'doudizhu', 'initHands': hands, 'landlordCards': bottom, 'moveHistory': [{'playerIdx': p, 'move': a} for p, a in trace], 'landlordWon': bool(won), 'points': pts, 'bombs': bombs, 'spring': spring} def main(mode, route): env = rlcard.make('doudizhu') name = league.route_name() reps = league.Replays() if mode == 'practice' else None subtasks = {} me = player(route) for opp in league.field(os.path.join(ROOT, 'field')): them = player(os.path.join(ROOT, 'field', opp)) got = [] for deal in range(1, 21 if mode == 'check' else 3): for mine in range(3): seats = [me if s == mine else them for s in range(3)] pts, replay = game(env, seats, deal) got.append(pts[mine]) if reps: who = [name if s == mine else opp for s in range(3)] replay['playerInfo'] = [{'index': s, 'role': 'landlord' if s == 0 else 'peasant', 'name': who[s]} for s in range(3)] 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 '') reps.add(f'{opp}-deal{deal}-{mine}.json', replay, f'Deal {deal}: {name} as {SEATS[mine]}, {opp} at the other seats', who, end) them.close() subtasks[opp] = sum(got) / len(got) me.close() league.write_metrics(sum(subtasks.values()) / len(subtasks), subtasks) if __name__ == '__main__': if sys.flags.hash_randomization: # python3 -I ignores PYTHONHASHSEED, and RLCard's order of moves hangs on it os.execve(sys.executable, [sys.executable, '-s', *sys.argv], {**os.environ, 'PYTHONHASHSEED': '0'}) main(sys.argv[1], sys.argv[2])