text: # coding: utf-8
  """Deal poker hand.
  
  This class deals a hand and plays it.
  It asks the players their betting choice.
  
  All deals save history from every move of the trained player.
  It can resume the deal from a situation.
  """
  
  
  from .rules import Action, Combo, Deck
  from ..abstraction.bet import Bet
  from ..abstraction.information import Info
  from ..player.data import Player
  
  
  class Deal:
      """Represent a poker deal.
  
      It plays with 6 players, starting with 10000 chips.
      Blinds are 50, 100. No ante.
  
      Button is the first player on seat 0.
      """
  
      def __init__(self, player_seat):
          """Set deal attributes.
  
          During initialization, button is always seat 0, and
          a poker hand is dealt randomly from a deck of the class Deck.
  
          Seat 1 puts the small blind, seat 2 puts the big blind.
  
          7-card hands are constituted for each player, using hole cards
          and board cards. Each of these hands is evaluated and players
          get hand strengths.
  
          player_seat is the current trained player's seat number.
          """
  
          # State parameters.
          self.actions = []                                # Deal action history.
          self.alive_nbr = 6                       # Keep track of alive players.
          self.bettors_nbr = 6                 # Keep track of remaining bettors.
          self.call_value = 100                               # Limp : call = BB.
          self.current = 3                 # Current speaker, first after blinds.
          self.history = []                                       # Hand history.
          self.min_raise = 100      # Double the BB (raises do not include call).
          self.player_seat = player_seat                # Current trained player.
          self.players = [Player("Adrien"), Player("Benoît"), Player("Carlos"),
                          Player("Donald"), Player("Eugène"), Player("France")]
          self.pot = 150                                               # SB + BB.
          self.put_blinds()                            # Get blinds from players.
          self.stage = 2                  # Preflop (Flop: 3, Turn: 4, River: 5).
  
          # Deal the hand.
          self.deck = Deck()                                   # Create new deck.
          self.board = self.deck.deal_board()                  # Deal five cards.
          self.hole_cards = self.deck.deal_hole_cards()        # Deal hole cards.
          self.hand_evals = Combo(self.board, self.hole_cards).hand_evals
  
      def ask_bets(self, current_player):
          """Return avalaible bets for a player given a situation.
  
          Situation is defined by call value, previous round investment,
          remaining player's chips, min raise allowed and current pot.
  
          There are 14 bets returned.
          """
          return Bet(self.call_value - current_player.investment,
                     current_player.chips, self.min_raise,
                     self.pot).avalaible_bets()
  
      def betting_round(self):
          """Do a betting round.
  
          This method bets from players' choice and keeps track of them.
          It carries on the betting while there are at least two players alive
          and that the round of betting is not over (all call or check).
          """
          # Keep on as long as there are at least one bettor and two alive.
          while 0 < self.bettors_nbr and 1 < self.alive_nbr:
              current_player = self.players[self.current % 6]       # Get player.
              if current_player.alive:            # If player has not folded yet.
                  # Bet system.
                  avalaible_bets = self.ask_bets(current_player)     # Bet range.
                  # Choose bet.
                  self.network_input = self.get_network_input()
                  bet = current_player.bet(avalaible_bets, self.network_input)
                  action = self.get_action(bet, current_player)     # Get action.
                  self.actions.append(action)                   # Update actions.
                  if not self.current % 6 - self.player_seat:   # Trained player.
                      self.history_update()                     # Update history.
                  current_player.do_bet(bet)            # Update player's states.
                  self.process_bet(action)                  # Update hand states.
              self.current += 1                # Move current player to next one.
          # Remaining bettors are players alive and not allin.
          self.bettors_nbr = len([player for player in self.players
                                 if player.alive and not player.allin])
          self.call_value = 0                  # Reset call value for next round.
          self.current = 1                     # Reset first player to talk (SB).
          self.min_raise = 100                                 # Reset min raise.
          self.reset_investment()                      # Reset round investments.
          self.stage += 1          # Going through preflop, flop, turn and river.
  
      def change_strategies(self):
          for player_nbr in range(6):
              player = self.players[player_nbr]
              if player.alive and player_nbr - self.player_seat:
                  player.change_strategy()
  
      def deal_hand(self):
          """Deal the whole hand.
  
          This method deals the hand from the current state, which is initially
          the beginning of the hand, but can be set to another one via the
          set_state method of the class.
          """
          # While game not finished and at least two bettors.
          while 1 < self.alive_nbr and self.stage < 6:
              self.betting_round()                        # Do the betting round.
          self.distribute_pot()                  # Get the chips back to winners.
  
      def distribute_pot(self):
          """Distribute the pot between players.
  
          This is a simple version of pot distributing. It works only for
          all players start with the same amout of chips.
          Odd chips are not restituted as they are negligeable for CFR.
          """
          max_strength = max([self.hand_evals[player_nbr] for player_nbr
                              in range(6) if self.players[player_nbr].alive])
          # Get winning players.
          winners = [self.players[player_nbr] for player_nbr in range(6)
                     if self.players[player_nbr].alive
                     and self.hand_evals[player_nbr] == max_strength]
          for player in winners:                     # Share pot between winners.
              player.chips += self.pot // len(winners)    # Stay within integers.
  
      def get_action(self, bet, player):
          """Get last action made by current player.
  
          An action move can be a fold, a call, a check or a raise.
          Action class saves the bet, the move, the player id
          and the total value of the bet.
          """
          # Get action
          total_value = bet + player.investment
          if not bet:                                               # Check/Fold.
              if self.call_value - player.investment:                     # Fold.
                  move = "fold"
              else:                                                      # Check.
                  move = "check"
          elif self.call_value < total_value:                        # Bet/Raise.
              if not self.call_value:
                  move = "bet"                                             # Bet.
              else:
                  move = "raise"                                         # Raise.
          else:                                                           # Call.
              move = "call"
          action = Action(bet == player.chips, bet, move, player, total_value)
          return action
  
      def get_network_input(self):
          """Return abstract hand situation for network."""
          return Info(self.actions, self.board,
                      self.hole_cards[self.current % 6], self.player_seat,
                      self.stage).get_input()
  
      def get_result(self):
          """Return deal result for player training."""
          return self.players[self.player_seat].chips
  
      def history_update(self):
          """Update hand history."""
          players_states = [player.states() for player in self.players]
          states = {'actions': self.actions.copy(),
                    'alive_nbr': self.alive_nbr,
                    'bettors_nbr': self.bettors_nbr,
                    'call_value': self.call_value,
                    'current': self.current,
                    'min_raise': self.min_raise,
                    'network_input': self.network_input,
                    'players_states': players_states,
                    'pot': self.pot,
                    'stage': self.stage}
          self.history.append(states)
  
      def process_bet(self, action):
          """Set hand states after each bet."""
          # State updates.
          if action.move == "fold":
              action.player.alive = False
              self.alive_nbr -= 1
          elif action.move == "bet" or action.move == "raise":
              self.bettors_nbr = self.alive_nbr
              self.min_raise = action.total_value - self.call_value
              self.call_value = action.total_value
          self.bettors_nbr -= 1        # Each bet diminues the number of bettors.
          self.pot += action.bet                                    # Update pot.
  
      def put_blinds(self):
          """Put both blinds."""
          self.players[1].do_bet(50)
          self.players[2].do_bet(100)
  
      def reset_investment(self):
          """Reset round investments."""
          for player in self.players:
              player.investment = 0
  
      def set_states(self, **states):
          """Set states from situation.
  
          Erase history.
          """
          self.actions = states['actions'].copy()
          self.alive_nbr = states['alive_nbr']
          self.bettors_nbr = states['bettors_nbr']
          self.call_value = states['call_value']
          self.current = states['current']
          self.history = []                                 # Erase hand history.
          self.min_raise = states['min_raise']
          self.pot = states['pot']
          self.stage = states['stage']
          for player_nbr in range(6):
              self.players[player_nbr].set_states(**states['players_states']
                                                  [player_nbr])
  