"""A live room (docs/games.md §8): one deal on RLCard's own Dou Dizhu, with people, agents and bots at the table, speaking the room protocol through kit/room.py. RLCard has no bidding: seat 0 is always the landlord and takes the three bottom cards, shown to all; seat 1 plays after it (farmer, down) and seat 2 before it (farmer, up). Bots run as in the checks (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 time 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 end the deal is left in replays/ in the practice replays' format with a matches.json entry, so it lists as a match. SS_ROOM='{"seats": {"0": {"kind": "human", "name": "ada"}, "1": {"kind": "bot", "field": "rule"}}}' \ python3 -I referee/room.py [seed] Messages (cards are RLCard's suited cards 'S3', 'HT', 'BJ', 'RJ'; moves and legal are RLCard's moves '33344', 'pass'): event {type: deal, landlord, bottom, left, seats}, then to each seat {obs} with its hand request {to, version, obs: {seat, hand, landlord, bottom, played, left, last, lead, bombs, multiplier, trace}, legal} event {type: play, seat, move, cards, left, bombs, multiplier} or {type: pass, seat} event {type: over, winner, spring, bombs, multiplier, hands}, then end {result, scores} """ import os, random, sys ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) sys.path[:0] = [os.path.join(ROOT, 'kit'), os.path.join(ROOT, 'referee')] # kit's room.py, not this file import league # noqa: E402 import numpy as np # noqa: E402 import rlcard # noqa: E402 from host import load # noqa: E402 from play import player, points # noqa: E402 from room import Room, Stop # noqa: E402 def suited(cards): return [c.suit + c.rank for c in cards] def is_bomb(move): return move == 'BR' or (len(move) == 4 and len(set(move)) == 1) def main(seed): room = Room(player, field=os.path.join(ROOT, 'field')) env = rlcard.make('doudizhu') env.seed(seed) np.random.seed(seed) # the rule agent (auto) draws from numpy's own generator random.seed(seed) state, pid = env.reset() game = env.game hands = env.get_perfect_information()['hand_cards_with_suit'] bottom = suited(game.round.dealer.deck[-3:]) # the deck keeps them; RLCard's seen_cards empties as they are played bottom_ranks = game.round.seen_cards # as the practice replays write them, read before any are played rule = load(os.path.join(ROOT, 'field', 'strategic', 'agent.py'), 'Agent')() played, shown = [[], [], []], [[], [], []] names = [room.label(str(s)) for s in range(3)] def left(): return {str(s): len(game.players[s].current_hand) for s in range(3)} def bombs(): return sum(1 for _, a in game.round.trace if is_bomb(a)) def obs(s): g = game.round.greater_player lead = g is None or g.player_id == s 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, 'played': {str(i): played[i] for i in range(3)}, 'left': left(), 'last': None if lead else {'seat': str(g.player_id), 'move': g.played_cards, 'cards': shown[g.player_id]}, 'lead': lead, 'bombs': bombs(), 'multiplier': 2 ** bombs(), 'trace': [[str(p), a] for p, a in game.round.trace]} room.event({'type': 'deal', 'landlord': '0', 'bottom': bottom, 'left': left(), 'seats': {str(s): {'name': names[s], 'kind': room.kind(str(s))} for s in range(3)}}) for s in range(3): room.tell(str(s), obs(s)) stopped = False try: while not env.is_over(): move = room.ask(str(pid), obs(pid), state['raw_legal_actions'], auto=lambda: str(rule.step(state)), bot={'raw_obs': state['raw_obs']}) state, nxt = env.step(move, raw_action=True) if move == 'pass': room.event({'type': 'pass', 'seat': str(pid)}) else: shown[pid] = suited(game.players[pid]._recorded_played_cards[-1]) # the very cards RLCard took played[pid] += shown[pid] room.event({'type': 'play', 'seat': str(pid), 'move': move, 'cards': shown[pid], 'left': left()[str(pid)], 'bombs': bombs(), 'multiplier': 2 ** bombs()}) pid = nxt except Stop: stopped = True trace = [[p, a] for p, a in game.round.trace] if stopped: won, pts, n, spring = None, [0, 0, 0], bombs(), False result = 'stopped' else: won = bool(env.get_payoffs()[0] > 0) pts, n, spring = points(trace, won) result = f'{"landlord" if won else "farmers"} win' + (' · spring' if spring else '') + (f' · {n} bombs' if n else '') room.event({'type': 'over', 'winner': None if won is None else 'landlord' if won else 'farmers', 'spring': spring, 'bombs': n, 'multiplier': 2 ** n * (2 if spring else 1), 'hands': {str(s): suited(game.players[s].current_hand) for s in range(3)}}) replay = {'game': 'doudizhu', 'initHands': hands, 'landlordCards': bottom_ranks, 'bottom': bottom, 'moveHistory': [{'playerIdx': p, 'move': a} for p, a in trace], 'landlordWon': won, 'points': pts, 'bombs': n, 'spring': spring, 'stopped': stopped, 'playerInfo': [{'index': s, 'role': 'landlord' if s == 0 else 'peasant', 'name': names[s]} for s in range(3)]} league.Replays().add(f'room-{seed}.json', replay, f'Room: {names[0]} as landlord, {names[1]} and {names[2]} as farmers', names, result + ', ' + ', '.join(f'{names[s]} {pts[s]:+d}' for s in range(3))) room.end(result, {str(s): pts[s] for s in range(3)}) room.close() if __name__ == '__main__': main(int(sys.argv[1]) if len(sys.argv) > 1 else random.SystemRandom().randrange(1 << 31))