class Table:

  def __init__(self, cheat_deck=None):
    self.dealer_btn = 0
    self._blind_pos = None
    self.seats = Seats()
    self.deck = cheat_deck if cheat_deck else Deck()
    self._community_card = []

  def set_blind_pos(self, sb_pos, bb_pos):
    self._blind_pos = [sb_pos, bb_pos]

  def sb_pos(self):
    if self._blind_pos is None: raise Exception("blind position is not yet set")
    return self._blind_pos[0]

  def bb_pos(self):
    if self._blind_pos is None: raise Exception("blind position is not yet set")
    return self._blind_pos[1]

  def get_community_card(self):
    return self._community_card[::]

  def add_community_card(self, card):
    if len(self._community_card) == 5:
      raise ValueError(self.__exceed_card_size_msg)
    self._community_card.append(card)

  def reset(self):
    self.deck.restore()
    self._community_card = []
    for player in self.seats.players:
      player.clear_holecard()
      player.clear_action_histories()
      player.clear_pay_info()

  def shift_dealer_btn(self):
    self.dealer_btn = self.next_active_player_pos(self.dealer_btn)

  def next_active_player_pos(self, start_pos):
    return self.__find_entitled_player_pos(start_pos, lambda player: player.is_active() and player.stack != 0)

  def next_ask_waiting_player_pos(self, start_pos):
    return self.__find_entitled_player_pos(start_pos, lambda player: player.is_waiting_ask())

  def serialize(self):
    community_card = [card.to_id() for card in self._community_card]
    return [
        self.dealer_btn, Seats.serialize(self.seats),
        Deck.serialize(self.deck), community_card, self._blind_pos
    ]

  @classmethod
  def deserialize(self, serial):
    deck = Deck.deserialize(serial[2])
    community_card = [Card.from_id(cid) for cid in serial[3]]
    table = self(cheat_deck=deck)
    table.dealer_btn = serial[0]
    table.seats = Seats.deserialize(serial[1])
    table._community_card = community_card
    table._blind_pos = serial[4]
    return table

  def __find_entitled_player_pos(self, start_pos, check_method):
    players = self.seats.players
    search_targets = players + players
    search_targets = search_targets[start_pos+1:start_pos+len(players)+1]
    assert(len(search_targets) == len(players))
    match_player = next((player for player in search_targets if check_method(player)), -1)
    return self._player_not_found if match_player == -1 else players.index(match_player)

  _player_not_found = "not_found"

  __exceed_card_size_msg = "Community card is already full" class Seats:

  def __init__(self):
    self.players = []

  def sitdown(self, player):
    self.players.append(player)

  def size(self):
    return len(self.players)

  def count_active_players(self):
    return len([p for p in self.players if p.is_active()])

  def count_ask_wait_players(self):
    return len([p for p in self.players if p.is_waiting_ask()])

  def serialize(self):
    return [player.serialize() for player in self.players]

  @classmethod
  def deserialize(self, serial):
    seats = self()
    seats.players = [Player.deserialize(s) for s in serial]
    return seats class Card:

  CLUB = 2
  DIAMOND = 4
  HEART = 8
  SPADE = 16

  SUIT_MAP = {
      2  : 'C',
      4  : 'D',
      8  : 'H',
      16 : 'S'
  }

  RANK_MAP = {
      2  :  '2',
      3  :  '3',
      4  :  '4',
      5  :  '5',
      6  :  '6',
      7  :  '7',
      8  :  '8',
      9  :  '9',
      10 : 'T',
      11 : 'J',
      12 : 'Q',
      13 : 'K',
      14 : 'A'
  }


  def __init__(self, suit, rank):
    self.suit = suit
    self.rank = 14 if rank == 1 else rank

  def __eq__(self, other):
    return self.suit == other.suit and self.rank == other.rank

  def __str__(self):
    suit = self.SUIT_MAP[self.suit]
    rank = self.RANK_MAP[self.rank]
    return "{0}{1}".format(suit, rank)

  def to_id(self):
    rank = 1 if self.rank == 14 else self.rank
    num = 0
    tmp = self.suit >> 1
    while tmp&1 != 1:
      num += 1
      tmp >>= 1

    return rank + 13 * num

  @classmethod
  def from_id(cls, card_id):
    suit, rank = 2, card_id
    while rank > 13:
      suit <<= 1
      rank -= 13

    return cls(suit, rank)

  @classmethod
  def from_str(cls, str_card):
    assert(len(str_card)==2)
    inverse = lambda hsh: {v:k for k,v in hsh.items()}
    suit = inverse(cls.SUIT_MAP)[str_card[0].upper()]
    rank = inverse(cls.RANK_MAP)[str_card[1]]
    return cls(suit, rank) class Deck:

  def __init__(self, deck_ids=None, cheat=False, cheat_card_ids=[]):
    self.cheat = cheat
    self.cheat_card_ids = cheat_card_ids
    self.deck = [Card.from_id(cid) for cid in deck_ids] if deck_ids else self.__setup()

  def draw_card(self):
    return self.deck.pop()

  def draw_cards(self, num):
    return reduce(lambda acc, _: acc + [self.draw_card()], range(num), [])

  def size(self):
    return len(self.deck)

  def restore(self):
    self.deck = self.__setup()

  def shuffle(self):
    if not self.cheat:
      random.shuffle(self.deck)

  # serialize format : [cheat_flg, chat_card_ids, deck_card_ids]
  def serialize(self):
    return [self.cheat, self.cheat_card_ids, [card.to_id() for card in self.deck]]

  @classmethod
  def deserialize(self, serial):
    cheat, cheat_card_ids, deck_ids = serial
    return self(deck_ids=deck_ids, cheat=cheat, cheat_card_ids=cheat_card_ids)

  def __setup(self):
    return self.__setup_cheat_deck() if self.cheat else self.__setup_52_cards()

  def __setup_52_cards(self):
    return [Card.from_id(cid) for cid in range(1,53)]

  def __setup_cheat_deck(self):
    cards = [Card.from_id(cid) for cid in self.cheat_card_ids]
    return cards[::-1] class Player:

  ACTION_FOLD_STR = "FOLD"
  ACTION_CALL_STR = "CALL"
  ACTION_RAISE_STR = "RAISE"
  ACTION_SMALL_BLIND = "SMALLBLIND"
  ACTION_BIG_BLIND = "BIGBLIND"
  ACTION_ANTE = "ANTE"

  def __init__(self, uuid, initial_stack, name="No Name"):
    self.name = name
    self.uuid = uuid
    self.hole_card = []
    self.stack = initial_stack
    self.round_action_histories = self.__init_round_action_histories()
    self.action_histories = []
    self.pay_info = PayInfo()

  def add_holecard(self, cards):
    if len(self.hole_card) != 0:
      raise ValueError(self.__dup_hole_msg)
    if len(cards) != 2:
      raise ValueError(self.__wrong_num_hole_msg % (len(cards)))
    if not all([isinstance(card, Card) for card in cards]):
      raise ValueError(self.__wrong_type_hole_msg)
    self.hole_card = cards

  def clear_holecard(self):
    self.hole_card = []

  def append_chip(self, amount):
    self.stack += amount

  def collect_bet(self, amount):
    if self.stack < amount:
      raise ValueError(self.__collect_err_msg % (amount, self.stack))
    self.stack -= amount

  def is_active(self):
    return self.pay_info.status != PayInfo.FOLDED

  def is_waiting_ask(self):
    return self.pay_info.status == PayInfo.PAY_TILL_END

  def add_action_history(self, kind, chip_amount=None, add_amount=None, sb_amount=None):
    history = None
    if kind == Const.Action.FOLD:
      history = self.__fold_history()
    elif kind == Const.Action.CALL:
      history = self.__call_history(chip_amount)
    elif kind == Const.Action.RAISE:
      history = self.__raise_history(chip_amount, add_amount)
    elif kind == Const.Action.SMALL_BLIND:
      history = self.__blind_history(True, sb_amount)
    elif kind == Const.Action.BIG_BLIND:
      history = self.__blind_history(False, sb_amount)
    elif kind == Const.Action.ANTE:
      history = self.__ante_history(chip_amount)
    else:
      raise "UnKnown action history is added (kind = %s)" % kind
    history = self.__add_uuid_on_history(history)
    self.action_histories.append(history)

  def save_street_action_histories(self, street_flg):
    self.round_action_histories[street_flg] = self.action_histories
    self.action_histories = []

  def clear_action_histories(self):
    self.round_action_histories = self.__init_round_action_histories()
    self.action_histories = []

  def clear_pay_info(self):
    self.pay_info = PayInfo()

  def paid_sum(self):
    pay_history = [h for h in self.action_histories if h["action"] not in ["FOLD", "ANTE"]]
    last_pay_history = pay_history[-1] if len(pay_history)!=0 else None
    return last_pay_history["amount"] if last_pay_history else 0

  def serialize(self):
    hole = [card.to_id() for card in self.hole_card]
    return [
        self.name, self.uuid, self.stack, hole,\
            self.action_histories[::], self.pay_info.serialize(), self.round_action_histories[::]
    ]

  @classmethod
  def deserialize(self, serial):
    hole = [Card.from_id(cid) for cid in serial[3]]
    player = self(serial[1], serial[2], serial[0])
    if len(hole)!=0: player.add_holecard(hole)
    player.action_histories = serial[4]
    player.pay_info = PayInfo.deserialize(serial[5])
    player.round_action_histories = serial[6]
    return player

  """ private """

  __dup_hole_msg = "Hole card is already set"
  __wrong_num_hole_msg = "You passed  %d hole cards"
  __wrong_type_hole_msg = "You passed not Card object as hole card"
  __collect_err_msg = "Failed to collect %d chips. Because he has only %d chips"

  def __init_round_action_histories(self):
    return [None for _ in range(4)]  # 4 == len(["preflop", "flop", "turn", "river"])

  def __fold_history(self):
    return { "action" : self.ACTION_FOLD_STR }

  def __call_history(self, bet_amount):
    return {
        "action" : self.ACTION_CALL_STR,
        "amount" : bet_amount,
        "paid" : bet_amount - self.paid_sum()
    }

  def __raise_history(self, bet_amount, add_amount):
    return {
        "action" : self.ACTION_RAISE_STR,
        "amount" : bet_amount,
        "paid" : bet_amount - self.paid_sum(),
        "add_amount" : add_amount
        }

  def __blind_history(self, small_blind, sb_amount):
    assert(sb_amount is not None)
    action = self.ACTION_SMALL_BLIND if small_blind else self.ACTION_BIG_BLIND
    amount = sb_amount if small_blind else sb_amount*2
    add_amount = sb_amount
    return {
        "action" : action,
        "amount" : amount,
        "add_amount" : add_amount
        }

  def __ante_history(self, pay_amount):
    assert(pay_amount > 0)
    return {
        "action" : self.ACTION_ANTE,
        "amount" : pay_amount
        }

  def __add_uuid_on_history(self, history):
    history["uuid"] = self.uuid
    return history class PayInfo:

  PAY_TILL_END = 0
  ALLIN  = 1
  FOLDED = 2

  def __init__(self, amount=0, status=0):
    self.amount = amount
    self.status = status

  def update_by_pay(self, amount):
    self.amount += amount

  def update_to_fold(self):
    self.status = self.FOLDED

  def update_to_allin(self):
    self.status = self.ALLIN

  # serialize format : [amount, status]
  def serialize(self):
    return [self.amount, self.status]

  @classmethod
  def deserialize(self, serial):
    return self(amount=serial[0], status=serial[1]) class DataEncoder:

  PAY_INFO_PAY_TILL_END_STR = "participating"
  PAY_INFO_ALLIN_STR = "allin"
  PAY_INFO_FOLDED_STR = "folded"

  @classmethod
  def encode_player(self, player, holecard=False):
    hash_ = {
        "name": player.name,
        "uuid": player.uuid,
        "stack": player.stack,
        "state": self.__payinfo_to_str(player.pay_info.status)
        }
    if holecard:
      hole_hash = {"hole_card": [str(card) for card in player.hole_card]}
      hash_.update(hole_hash)
    return hash_

  @classmethod
  def encode_seats(self, seats):
    return {
        "seats": [self.encode_player(player) for player in seats.players]
        }

  @classmethod
  def encode_pot(self, players):
    pots = GameEvaluator.create_pot(players)
    main = { "amount": pots[0]["amount"] }
    gen_hsh = lambda sidepot: \
            { "amount": sidepot["amount"], "eligibles": [p.uuid for p in sidepot["eligibles"]] }
    side = [ gen_hsh(sidepot) for sidepot in pots[1:] ]
    return { "main": main, "side": side }

  @classmethod
  def encode_game_information(self, config, seats):
    hsh = {
        "player_num" : len(seats.players),
        "rule": {
          "initial_stack": config["initial_stack"],
          "max_round": config["max_round"],
          "small_blind_amount": config["small_blind_amount"],
          "ante": config["ante"],
          "blind_structure": config["blind_structure"]
        }
    }
    hsh.update(self.encode_seats(seats))
    return hsh

  @classmethod
  def encode_valid_actions(self, call_amount, min_bet_amount, max_bet_amount):
    return {
        "valid_actions": [
          { "action": "fold", "amount": 0 },
          { "action": "call", "amount": call_amount },
          { "action": "raise", "amount": { "min": min_bet_amount, "max": max_bet_amount } }
        ]
    }

  @classmethod
  def encode_action(self, player, action, amount):
    return {
        "player_uuid": player.uuid,
        "action": action,
        "amount": amount
        }

  @classmethod
  def encode_street(self, street):
    return {
        "street": self.__street_to_str(street)
        }

  @classmethod
  def encode_action_histories(self, table):
    all_street_histories = [[player.round_action_histories[street] for player in table.seats.players] for street in range(4)]
    past_street_histories = [histories for histories in all_street_histories if any([e is not None for e in histories])]
    current_street_histories = [player.action_histories for player in table.seats.players]
    street_histories = past_street_histories + [current_street_histories]
    street_histories = [self.__order_histories(table.sb_pos(), histories) for histories in street_histories]
    street_name = ["preflop", "flop", "turn", "river"]
    action_histories = { name:histories for name, histories in zip(street_name, street_histories) }
    return { "action_histories": action_histories }

  @classmethod
  def encode_winners(self, winners):
    return { "winners": self.__encode_players(winners) }

  @classmethod
  def encode_round_state(self, state):
    hsh = {
        "street": self.__street_to_str(state["street"]),
        "pot": self.encode_pot(state["table"].seats.players),
        "community_card": [str(card) for card in state["table"].get_community_card()],
        "dealer_btn": state["table"].dealer_btn,
        "next_player": state["next_player"],
        "small_blind_pos": state["table"].sb_pos(),
        "big_blind_pos": state["table"].bb_pos(),
        "round_count": state["round_count"],
        "small_blind_amount": state["small_blind_amount"]
    }
    hsh.update(self.encode_seats(state["table"].seats))
    hsh.update(self.encode_action_histories(state["table"]))
    return hsh


  @classmethod
  def __payinfo_to_str(self, status):
    if status == PayInfo.PAY_TILL_END:
      return self.PAY_INFO_PAY_TILL_END_STR
    if status == PayInfo.ALLIN:
      return self.PAY_INFO_ALLIN_STR
    if status == PayInfo.FOLDED:
      return self.PAY_INFO_FOLDED_STR

  @classmethod
  def __street_to_str(self, street):
    if street == Const.Street.PREFLOP:
      return "preflop"
    if street == Const.Street.FLOP:
      return "flop"
    if street == Const.Street.TURN:
      return "turn"
    if street == Const.Street.RIVER:
      return "river"
    if street == Const.Street.SHOWDOWN:
      return "showdown"

  @classmethod
  def __encode_players(self, players):
    return [self.encode_player(player) for player in players]

  @classmethod
  def __order_histories(self, start_pos, player_histories):
    ordered_player_histories = [player_histories[(start_pos+i)%len(player_histories)] for i in range(len(player_histories))]
    all_player_histories = [histories[::] for histories in ordered_player_histories]
    max_len = max([len(h) for h in all_player_histories])
    unified_histories = [self.__unify_length(max_len, l) for l in all_player_histories]
    ordered_histories = reduce(lambda acc, zp: acc + list(zp), zip(*unified_histories), [])
    return [history for history in ordered_histories if not history is None]

  @classmethod
  def __unify_length(self, max_len, lst):
    for _ in range(max_len-len(lst)):
      lst.append(None)
    return lst class PokerConstants:

  class Action:
    FOLD  = 0
    CALL  = 1
    RAISE = 2
    SMALL_BLIND = 3
    BIG_BLIND = 4
    ANTE = 5

  class Street:
    PREFLOP = 0
    FLOP = 1
    TURN = 2
    RIVER = 3
    SHOWDOWN = 4
    FINISHED = 5 class RoundManager:

  @classmethod
  def start_new_round(self, round_count, small_blind_amount, ante_amount, table):
    _state = self.__gen_initial_state(round_count, small_blind_amount, table)
    state = self.__deep_copy_state(_state)
    table = state["table"]

    table.deck.shuffle()
    self.__correct_ante(ante_amount, table.seats.players)
    self.__correct_blind(small_blind_amount, table)
    self.__deal_holecard(table.deck, table.seats.players)
    start_msg = self.__round_start_message(round_count, table)
    state, street_msgs = self.__start_street(state)
    return state, start_msg + street_msgs

  @classmethod
  def apply_action(self, original_state, action, bet_amount):
    state = self.__deep_copy_state(original_state)
    state = self.__update_state_by_action(state, action, bet_amount)
    update_msg = self.__update_message(state, action, bet_amount)
    if self.__is_everyone_agreed(state):
      [player.save_street_action_histories(state["street"]) for player in state["table"].seats.players]
      state["street"] += 1
      state, street_msgs = self.__start_street(state)
      return state, [update_msg] + street_msgs
    else:
      state["next_player"] = state["table"].next_ask_waiting_player_pos(state["next_player"])
      next_player_pos = state["next_player"]
      next_player = state["table"].seats.players[next_player_pos]
      ask_message = (next_player.uuid, MessageBuilder.build_ask_message(next_player_pos, state))
      return state, [update_msg, ask_message]

  @classmethod
  def __correct_ante(self, ante_amount, players):
    if ante_amount == 0: return
    active_players = [player for player in players if player.is_active()]
    for player in active_players:
      player.collect_bet(ante_amount)
      player.pay_info.update_by_pay(ante_amount)
      player.add_action_history(Const.Action.ANTE, ante_amount)

  @classmethod
  def __correct_blind(self, sb_amount, table):
    self.__blind_transaction(table.seats.players[table.sb_pos()], True, sb_amount)
    self.__blind_transaction(table.seats.players[table.bb_pos()], False, sb_amount)

  @classmethod
  def __blind_transaction(self, player, small_blind, sb_amount):
    action = Const.Action.SMALL_BLIND if small_blind else Const.Action.BIG_BLIND
    blind_amount = sb_amount if small_blind else sb_amount*2
    player.collect_bet(blind_amount)
    player.add_action_history(action, sb_amount=sb_amount)
    player.pay_info.update_by_pay(blind_amount)

  @classmethod
  def __deal_holecard(self, deck, players):
    for player in players:
      player.add_holecard(deck.draw_cards(2))

  @classmethod
  def __start_street(self, state):
    next_player_pos = state["table"].next_ask_waiting_player_pos(state["table"].sb_pos()-1)
    state["next_player"] = next_player_pos
    street = state["street"]
    if street == Const.Street.PREFLOP:
      return self.__preflop(state)
    elif street == Const.Street.FLOP:
      return self.__flop(state)
    elif street == Const.Street.TURN:
      return self.__turn(state)
    elif street == Const.Street.RIVER:
      return self.__river(state)
    elif street == Const.Street.SHOWDOWN:
      return self.__showdown(state)
    else:
      raise ValueError("Street is already finished [street = %d]" % street)

  @classmethod
  def __preflop(self, state):
    for i in range(2):
      state["next_player"] = state["table"].next_ask_waiting_player_pos(state["next_player"])
    return self.__forward_street(state)

  @classmethod
  def __flop(self, state):
    for card in state["table"].deck.draw_cards(3):
      state["table"].add_community_card(card)
    return self.__forward_street(state)

  @classmethod
  def __turn(self, state):
    state["table"].add_community_card(state["table"].deck.draw_card())
    return self.__forward_street(state)

  @classmethod
  def __river(self, state):
    state["table"].add_community_card(state["table"].deck.draw_card())
    return self.__forward_street(state)

  @classmethod
  def __showdown(self, state):
    winners, hand_info, prize_map = GameEvaluator.judge(state["table"])
    self.__prize_to_winners(state["table"].seats.players, prize_map)
    result_message = MessageBuilder.build_round_result_message(state["round_count"], winners, hand_info, state)
    state["table"].reset()
    state["street"] += 1
    return state, [(-1, result_message)]

  @classmethod
  def __prize_to_winners(self, players, prize_map):
    for idx, prize in prize_map.items():
      players[idx].append_chip(prize)

  @classmethod
  def __round_start_message(self, round_count, table):
    players = table.seats.players
    gen_msg = lambda idx: (players[idx].uuid, MessageBuilder.build_round_start_message(round_count, idx, table.seats))
    return reduce(lambda acc, idx: acc + [gen_msg(idx)], range(len(players)), [])

  @classmethod
  def __forward_street(self, state):
    table = state["table"]
    street_start_msg = [(-1, MessageBuilder.build_street_start_message(state))]
    if table.seats.count_active_players() == 1: street_start_msg = []
    if table.seats.count_ask_wait_players() <= 1:
      state["street"] += 1
      state, messages = self.__start_street(state)
      return state, street_start_msg + messages
    else:
      next_player_pos = state["next_player"]
      next_player = table.seats.players[next_player_pos]
      ask_message = [(next_player.uuid, MessageBuilder.build_ask_message(next_player_pos, state))]
      return state, street_start_msg + ask_message

  @classmethod
  def __update_state_by_action(self, state, action, bet_amount):
    table = state["table"]
    action, bet_amount = ActionChecker.correct_action(\
        table.seats.players, state["next_player"], state["small_blind_amount"], action, bet_amount)
    next_player = table.seats.players[state["next_player"]]
    if ActionChecker.is_allin(next_player, action, bet_amount):
      next_player.pay_info.update_to_allin()
    return self.__accept_action(state, action, bet_amount)

  @classmethod
  def __accept_action(self, state, action, bet_amount):
    player = state["table"].seats.players[state["next_player"]]
    if action == 'call':
      self.__chip_transaction(player, bet_amount)
      player.add_action_history(Const.Action.CALL, bet_amount)
    elif action == 'raise':
      self.__chip_transaction(player, bet_amount)
      add_amount = bet_amount - ActionChecker.agree_amount(state["table"].seats.players)
      player.add_action_history(Const.Action.RAISE, bet_amount, add_amount)
    elif action == 'fold':
      player.add_action_history(Const.Action.FOLD)
      player.pay_info.update_to_fold()
    else:
      raise ValueError("Unexpected action %s received" % action)
    return state

  @classmethod
  def __chip_transaction(self, player, bet_amount):
    need_amount = ActionChecker.need_amount_for_action(player, bet_amount)
    player.collect_bet(need_amount)
    player.pay_info.update_by_pay(need_amount)

  @classmethod
  def __update_message(self, state, action, bet_amount):
    return (-1, MessageBuilder.build_game_update_message(
      state["next_player"], action, bet_amount, state))

  @classmethod
  def __is_everyone_agreed(self, state):
    self.__agree_logic_bug_catch(state)
    players = state["table"].seats.players
    next_player_pos = state["table"].next_ask_waiting_player_pos(state["next_player"])
    next_player = players[next_player_pos] if next_player_pos != "not_found" else None
    max_pay = max([p.paid_sum() for p in players])
    everyone_agreed = len(players) == len([p for p in players if self.__is_agreed(max_pay, p)])
    lonely_player = state["table"].seats.count_active_players() == 1
    no_need_to_ask = state["table"].seats.count_ask_wait_players() == 1 and\
            next_player and next_player.is_waiting_ask() and next_player.paid_sum() == max_pay
    return everyone_agreed or lonely_player or no_need_to_ask

  @classmethod
  def __agree_logic_bug_catch(self, state):
    if state["table"].seats.count_active_players() == 0:
      raise "[__is_everyone_agreed] no-active-players!!"

  @classmethod
  def __is_agreed(self, max_pay, player):
    # BigBlind should be asked action at least once
    is_preflop = player.round_action_histories[0] == None
    bb_ask_once = len(player.action_histories)==1 \
            and player.action_histories[0]["action"] == Player.ACTION_BIG_BLIND
    bb_ask_check = not is_preflop or not bb_ask_once
    return (bb_ask_check and player.paid_sum() == max_pay and len(player.action_histories) != 0)\
        or player.pay_info.status in [PayInfo.FOLDED, PayInfo.ALLIN]

  @classmethod
  def __gen_initial_state(self, round_count, small_blind_amount, table):
    return {
        "round_count": round_count,
        "small_blind_amount": small_blind_amount,
        "street": Const.Street.PREFLOP,
        "next_player": table.next_ask_waiting_player_pos(table.bb_pos()),
        "table": table
    }

  @classmethod
  def __deep_copy_state(self, state):
    table_deepcopy = Table.deserialize(state["table"].serialize())
    return {
        "round_count": state["round_count"],
        "small_blind_amount": state["small_blind_amount"],
        "street": state["street"],
        "next_player": state["next_player"],
        "table": table_deepcopy
        } class ActionChecker:

  @classmethod
  def correct_action(self, players, player_pos, sb_amount, action, amount=None):
    if self.is_allin(players[player_pos], action, amount):
      amount = players[player_pos].stack + players[player_pos].paid_sum()
    elif self.__is_illegal(players, player_pos, sb_amount, action, amount):
      action, amount = "fold", 0
    return action, amount


  @classmethod
  def is_allin(self, player, action, bet_amount):
    if action == 'call':
      return bet_amount >= player.stack + player.paid_sum()
    elif action == 'raise':
      return bet_amount == player.stack + player.paid_sum()
    else:
      return False


  @classmethod
  def need_amount_for_action(self, player, amount):
    return amount - player.paid_sum()


  @classmethod
  def agree_amount(self, players):
    last_raise = self.__fetch_last_raise(players)
    return last_raise["amount"] if last_raise else 0


  @classmethod
  def legal_actions(self, players, player_pos, sb_amount):
    min_raise = self.__min_raise_amount(players, sb_amount)
    max_raise = players[player_pos].stack + players[player_pos].paid_sum()
    if max_raise < min_raise:
      min_raise = max_raise = -1
    return [
        { "action" : "fold" , "amount" : 0 },
        { "action" : "call" , "amount" : self.agree_amount(players) },
        { "action" : "raise", "amount" : { "min": min_raise, "max": max_raise } }
    ]

  @classmethod
  def _is_legal(self, players, player_pos, sb_amount, action, amount=None):
    return not self.__is_illegal(players, player_pos, sb_amount, action, amount)

  @classmethod
  def __is_illegal(self, players, player_pos, sb_amount, action, amount=None):
    if action == 'fold':
      return False
    elif action == 'call':
      return self.__is_short_of_money(players[player_pos], amount)\
          or self.__is_illegal_call(players, amount)
    elif action == 'raise':
      return self.__is_short_of_money(players[player_pos], amount) \
          or self.__is_illegal_raise(players, amount, sb_amount)

  @classmethod
  def __is_illegal_call(self, players, amount):
    return amount != self.agree_amount(players)

  @classmethod
  def __is_illegal_raise(self, players, amount, sb_amount):
    return self.__min_raise_amount(players, sb_amount) > amount

  @classmethod
  def __min_raise_amount(self, players, sb_amount):
    raise_ = self.__fetch_last_raise(players)
    return raise_["amount"] + raise_["add_amount"] if raise_ else sb_amount*2

  @classmethod
  def __is_short_of_money(self, player, amount):
    return player.stack < amount - player.paid_sum()

  @classmethod
  def __fetch_last_raise(self, players):
    all_histories = [p.action_histories for p in players]
    all_histories = reduce(lambda acc, e: acc + e, all_histories)  # flatten
    raise_histories = [h for h in all_histories if h["action"] in ["RAISE", "SMALLBLIND", "BIGBLIND"]]
    if len(raise_histories) == 0:
      return None
    else:
      return max(raise_histories, key=lambda h: h["amount"])  # maxby class MessageBuilder:

  GAME_START_MESSAGE = "game_start_message"
  ROUND_START_MESSAGE = "round_start_message"
  STREET_START_MESSAGE = "street_start_message"
  ASK_MESSAGE = "ask_message"
  GAME_UPDATE_MESSAGE = "game_update_message"
  ROUND_RESULT_MESSAGE = "round_result_message"
  GAME_RESULT_MESSAGE = "game_result_message"

  @classmethod
  def build_game_start_message(self, config, seats):
    message = {
        "message_type": self.GAME_START_MESSAGE,
        "game_information": DataEncoder.encode_game_information(config, seats)
    }
    return self.__build_notification_message(message)

  @classmethod
  def build_round_start_message(self, round_count, player_pos, seats):
    player = seats.players[player_pos]
    hole_card = DataEncoder.encode_player(player, holecard=True)["hole_card"]
    message = {
        "message_type": self.ROUND_START_MESSAGE,
        "round_count": round_count,
        "hole_card": hole_card
    }
    message.update(DataEncoder.encode_seats(seats))
    return self.__build_notification_message(message)

  @classmethod
  def build_street_start_message(self, state):
    message = {
        "message_type": self.STREET_START_MESSAGE,
        "round_state": DataEncoder.encode_round_state(state)
        }
    message.update(DataEncoder.encode_street(state["street"]))
    return self.__build_notification_message(message)

  @classmethod
  def build_ask_message(self, player_pos, state):
    players = state["table"].seats.players
    player = players[player_pos]
    hole_card = DataEncoder.encode_player(player, holecard=True)["hole_card"]
    valid_actions = ActionChecker.legal_actions(players, player_pos, state["small_blind_amount"])
    message = {
        "message_type" : self.ASK_MESSAGE,
        "hole_card": hole_card,
        "valid_actions": valid_actions,
        "round_state": DataEncoder.encode_round_state(state),
        "action_histories": DataEncoder.encode_action_histories(state["table"])
    }
    return self.__build_ask_message(message)

  @classmethod
  def build_game_update_message(self, player_pos, action, amount, state):
    player = state["table"].seats.players[player_pos]
    message = {
        "message_type": self.GAME_UPDATE_MESSAGE,
        "action": DataEncoder.encode_action(player, action, amount),
        "round_state": DataEncoder.encode_round_state(state),
        "action_histories": DataEncoder.encode_action_histories(state["table"])
    }
    return self.__build_notification_message(message)

  @classmethod
  def build_round_result_message(self, round_count, winners, hand_info, state):
    message = {
        "message_type": self.ROUND_RESULT_MESSAGE,
        "round_count": round_count,
        "hand_info"  : hand_info,
        "round_state": DataEncoder.encode_round_state(state)
    }
    message.update(DataEncoder.encode_winners(winners))
    return self.__build_notification_message(message)

  @classmethod
  def build_game_result_message(self, config, seats):
    message = {
      "message_type": self.GAME_RESULT_MESSAGE,
      "game_information": DataEncoder.encode_game_information(config, seats)
    }
    return self.__build_notification_message(message)


  @classmethod
  def __build_ask_message(self, message):
    return {
        "type": "ask",
        "message": message
    }

  @classmethod
  def __build_notification_message(self, message):
    return {
        "type": "notification",
        "message": message
    } class BasePokerPlayer(object):
  """Base Poker client implementation

  To create poker client, you need to override this class and
  implement following 7 methods.

  - declare_action
  - receive_game_start_message
  - receive_round_start_message
  - receive_street_start_message
  - receive_game_update_message
  - receive_round_result_message
  """

  def __init__(self):
    pass

  def declare_action(self, valid_actions, hole_card, round_state):
    err_msg = self.__build_err_msg("declare_action")
    raise NotImplementedError(err_msg)

  def receive_game_start_message(self, game_info):
    err_msg = self.__build_err_msg("receive_game_start_message")
    raise NotImplementedError(err_msg)

  def receive_round_start_message(self, round_count, hole_card, seats):
    err_msg = self.__build_err_msg("receive_round_start_message")
    raise NotImplementedError(err_msg)

  def receive_street_start_message(self, street, round_state):
    err_msg = self.__build_err_msg("receive_street_start_message")
    raise NotImplementedError(err_msg)

  def receive_game_update_message(self, new_action, round_state):
    err_msg = self.__build_err_msg("receive_game_update_message")
    raise NotImplementedError(err_msg)

  def receive_round_result_message(self, winners, hand_info, round_state):
    err_msg = self.__build_err_msg("receive_round_result_message")
    raise NotImplementedError(err_msg)

  def set_uuid(self, uuid):
    self.uuid = uuid

  def respond_to_ask(self, message):
    """Called from Dealer when ask message received from RoundManager"""
    valid_actions, hole_card, round_state = self.__parse_ask_message(message)
    return self.declare_action(valid_actions, hole_card, round_state)

  def receive_notification(self, message):
    """Called from Dealer when notification received from RoundManager"""
    msg_type = message["message_type"]

    if msg_type == "game_start_message":
      info = self.__parse_game_start_message(message)
      self.receive_game_start_message(info)

    elif msg_type == "round_start_message":
      round_count, hole, seats = self.__parse_round_start_message(message)
      self.receive_round_start_message(round_count, hole, seats)

    elif msg_type == "street_start_message":
      street, state = self.__parse_street_start_message(message)
      self.receive_street_start_message(street, state)

    elif msg_type == "game_update_message":
      new_action, round_state = self.__parse_game_update_message(message)
      self.receive_game_update_message(new_action, round_state)

    elif msg_type == "round_result_message":
      winners, hand_info, state = self.__parse_round_result_message(message)
      self.receive_round_result_message(winners, hand_info, state)


  def __build_err_msg(self, msg):
    return "Your client does not implement [ {0} ] method".format(msg)

  def __parse_ask_message(self, message):
    hole_card = message["hole_card"]
    valid_actions = message["valid_actions"]
    round_state = message["round_state"]
    return valid_actions, hole_card, round_state

  def __parse_game_start_message(self, message):
    game_info = message["game_information"]
    return game_info

  def __parse_round_start_message(self, message):
    round_count = message["round_count"]
    seats = message["seats"]
    hole_card = message["hole_card"]
    return round_count, hole_card, seats

  def __parse_street_start_message(self, message):
    street = message["street"]
    round_state = message["round_state"]
    return street, round_state

  def __parse_game_update_message(self, message):
    new_action = message["action"]
    round_state = message["round_state"]
    return new_action, round_state

  def __parse_round_result_message(self, message):
    winners = message["winners"]
    hand_info = message["hand_info"]
    round_state = message["round_state"]
    return winners, hand_info, round_state def deepcopy_game_state(game_state):
    tabledeepcopy = Table.deserialize(game_state["table"].serialize())
    return {
            "round_count": game_state["round_count"],
            "small_blind_amount": game_state["small_blind_amount"],
            "street": game_state["street"],
            "next_player": game_state["next_player"],
            "table": tabledeepcopy
            }
from pypokerengine.engine.table import Table
from pypokerengine.engine.seats import Seats
from pypokerengine.engine.card import Card
from pypokerengine.engine.deck import Deck
from pypokerengine.engine.player import Player
from pypokerengine.engine.pay_info import PayInfo
from pypokerengine.engine.data_encoder import DataEncoder
from pypokerengine.engine.poker_constants import PokerConstants as Const
from pypokerengine.engine.round_manager import RoundManager
from pypokerengine.engine.action_checker import ActionChecker
from pypokerengine.engine.message_builder import MessageBuilder
from pypokerengine.players import BasePokerPlayer
from pypokerengine.utils.game_state_utils import deepcopy_game_state

class Emulator(object):

    def __init__(self):
        self.game_rule = {}
        self.blind_structure = {}
        self.players_holder = {}

    def set_game_rule(self, player_num, max_round, small_blind_amount, ante_amount):
        self.game_rule["player_num"] = player_num
        self.game_rule["max_round"] = max_round
        self.game_rule["sb_amount"] = small_blind_amount
        self.game_rule["ante"] = ante_amount

    def set_blind_structure(self, blind_structure):
        self.blind_structure = blind_structure

    def register_player(self, uuid, player):
        if not isinstance(player, BasePokerPlayer):
            raise TypeError("player must inherit %s class." % BasePokerPlayer)
        self.players_holder[uuid] = player

    def fetch_player(self, uuid):
        return self.players_holder[uuid]

    def generate_initial_game_state(self, players_info):
        table = Table()
        for uuid, info in players_info.items():
            table.seats.sitdown(Player(uuid, info["stack"], info["name"]))

        table.dealer_btn = len(table.seats.players)-1
        return {
            "round_count": 0,
            "small_blind_amount": self.game_rule["sb_amount"],
            "street": Const.Street.PREFLOP,
            "next_player": None,
            "table": table
        }

    def generate_possible_actions(self, game_state):
        players = game_state["table"].seats.players
        player_pos = game_state["next_player"]
        sb_amount = game_state["small_blind_amount"]
        return ActionChecker.legal_actions(players, player_pos, sb_amount)

    def apply_action(self, game_state, action, bet_amount=0):
        if game_state["street"] == Const.Street.FINISHED:
            game_state, events = self._start_next_round(game_state)
        updated_state, messages = RoundManager.apply_action(game_state, action, bet_amount)
        events = [self.create_event(message[1]["message"]) for message in messages]
        events = [e for e in events if e]
        if self._is_last_round(updated_state, self.game_rule):
            events += self._generate_game_result_event(updated_state)
        return updated_state, events

    def _start_next_round(self, game_state):
        game_finished = game_state["round_count"] == self.game_rule["max_round"]
        game_state, events = self.start_new_round(game_state)
        if Event.GAME_FINISH == events[-1]["type"] or game_finished:
            raise Exception("Failed to apply action. Because game is already finished.")
        return game_state, events

    def run_until_round_finish(self, game_state):
        mailbox = []
        while game_state["street"] != Const.Street.FINISHED:
            next_player_pos = game_state["next_player"]
            next_player_uuid = game_state["table"].seats.players[next_player_pos].uuid
            next_player_algorithm = self.fetch_player(next_player_uuid)
            msg = MessageBuilder.build_ask_message(next_player_pos, game_state)["message"]
            action, amount = next_player_algorithm.declare_action(\
                    msg["valid_actions"], msg["hole_card"], msg["round_state"])
            game_state, messages = RoundManager.apply_action(game_state, action, amount)
            mailbox += messages
        events = [self.create_event(message[1]["message"]) for message in mailbox]
        events = [e for e in events if e]
        if self._is_last_round(game_state, self.game_rule):
            events += self._generate_game_result_event(game_state)
        return game_state, events

    def run_until_game_finish(self, game_state):
        mailbox = []
        event_box= []
        if game_state["street"] != Const.Street.FINISHED:
            game_state, events = self.run_until_round_finish(game_state)
            event_box += events
        while True:
            game_state, events = self.start_new_round(game_state)
            event_box += events
            if Event.GAME_FINISH == events[-1]["type"]: break
            game_state, events = self.run_until_round_finish(game_state)
            event_box += events
            if Event.GAME_FINISH == events[-1]["type"]: break
        event_box = [e for e in event_box if e]
        return game_state, event_box


    def start_new_round(self, game_state):
        round_count = game_state["round_count"] + 1
        ante, sb_amount = self.game_rule["ante"], self.game_rule["sb_amount"]

        deepcopy = deepcopy_game_state(game_state)