Beispiel #1
0
 private void Game_ChangedPhase(List <Card> board, GamePhase phase)
 {
     handBucket = (byte)HandStrengthAbstracter.MapToBucket(board, HoleCards);
 }
        /// <summary>
        /// Traverses the sub tree of the passed hand buckets recursively. Calculates and then backpropagates the counter factual regret for each node.
        /// </summary>
        /// <param name="gameState"></param>
        /// <param name="handBuckets"></param>
        /// <param name="actions"></param>
        /// <param name="probabilityPlayer1">probability of player 1 to reach this node</param>
        /// <param name="probabilityPlayer2">probability of player 2 to reach this node</param>
        /// <returns></returns>
        private float CalculateCounterFactualRegret(HeadsUpGameState gameState, byte[] handBuckets, List <ActionBucket> actions, float probabilityPlayer1, float probabilityPlayer2)
        {
            int plays       = actions.Count;
            int playerIndex = plays % 2;

            var          newState   = gameState.GetCopy();
            ActionBucket lastAction = ActionBucket.None;

            if (actions.Count > 0)
            {
                lastAction = actions[plays - 1];
            }
            ActionBucket secondLastAction = ActionBucket.None;

            if (actions.Count > 1)
            {
                secondLastAction = actions[plays - 2];
            }

            bool nextActionCallEnabled = true;
            bool phaseChanged          = false;

            //the last actions determine whether the next phase has to be set or whether the game (i.e. the recursion) ends
            switch (lastAction)
            {
            case ActionBucket.Pass:
                if (secondLastAction == ActionBucket.LowBet ||
                    secondLastAction == ActionBucket.HighBet ||
                    secondLastAction == ActionBucket.MediumBet)
                {
                    int payoff = (newState.PotSize - newState.AmountToCall) / 2;
                    return((playerIndex == 0) ? payoff : -payoff);
                }

                switch (secondLastAction)
                {
                case ActionBucket.None:
                    return((playerIndex == 0) ? HeadsupGame.SmallBlindSize : -HeadsupGame.SmallBlindSize);

                case ActionBucket.Call:
                    newState.SetNextPhase(newState.Phase);
                    phaseChanged = true;
                    break;

                case ActionBucket.Pass:
                    //check if last pass count is dividable by 2 (then it's a new phase)
                    int lastActionPassCount = 0;
                    for (int i = actions.Count - 1; i >= 0; i--)
                    {
                        if (actions[i] == ActionBucket.Pass)
                        {
                            lastActionPassCount++;
                        }
                        else
                        {
                            //special case: if it's first round, call pass results in ending the round
                            if ((actions[i] == ActionBucket.Call && i == 0) &&
                                actions.Count > 2 && actions[i + 1] == ActionBucket.Pass)
                            {
                                lastActionPassCount--;
                            }
                            break;
                        }
                    }

                    if (lastActionPassCount % 2 == 0)
                    {
                        newState.SetNextPhase(newState.Phase);
                        phaseChanged = true;
                    }
                    break;
                }
                nextActionCallEnabled = false;
                break;

            case ActionBucket.Call:
                newState.PotSize     += newState.AmountToCall;
                newState.AmountToCall = 0;
                //special case for first round: big blind needs to check or bet
                if (actions.Count == 1)
                {
                    nextActionCallEnabled = false;
                }
                else
                {
                    newState.SetNextPhase(newState.Phase);
                    phaseChanged = true;
                }
                break;

            case ActionBucket.HighBet:
            case ActionBucket.MediumBet:
            case ActionBucket.LowBet:
                int betSize = 0;
                if (newState.AmountToCall > 0 && newState.AmountToCall == HeadsupGame.SmallBlindSize)
                {
                    //exception: first round
                    newState.PotSize += HeadsupGame.SmallBlindSize;
                }

                int lastActionLowBetCount = 0;
                for (int i = actions.Count - 1; i >= 0; i--)
                {
                    if (actions[i] == ActionBucket.LowBet)
                    {
                        lastActionLowBetCount++;
                    }
                    else
                    {
                        break;
                    }
                }

                betSize = ActionAbstracter.GetBetSize(lastAction, newState.AmountToCall, newState.PotSize);
                if (betSize > 0)
                {
                    newState.AmountToCall = betSize;
                    newState.PotSize     += betSize;
                    if (newState.PotSize >= HeadsupGame.StackSize * 2)
                    {
                        phaseChanged   = true;
                        newState.Phase = GamePhase.Showdown;
                    }
                }
                else
                {
                    phaseChanged   = true;
                    newState.Phase = GamePhase.Showdown;
                }
                break;
            }

            //if the phase has changed, the next event has to occur (e.g. adding a card to the current board)
            if (phaseChanged)
            {
                if (newState.Phase == GamePhase.Showdown)
                {
                    int payoff         = newState.PotSize / 2;
                    var handComparison = HandComparer.Compare(newState.Player1HoleCards, newState.Player2HoleCards, newState.Board);
                    switch (handComparison)
                    {
                    case HandComparison.None:
                        return(0);

                    case HandComparison.Player1Won:
                        return((playerIndex == 0) ? payoff : -payoff);

                    case HandComparison.Player2Won:
                        return((playerIndex == 0) ? -payoff : payoff);
                    }
                }
                else
                {
                    nextActionCallEnabled = false;

                    List <Card> currentBoard = null;
                    switch (newState.Phase)
                    {
                    case GamePhase.Flop:
                        currentBoard = gameState.Board.Take(3).ToList();
                        break;

                    case GamePhase.Turn:
                        currentBoard = gameState.Board.Take(4).ToList();
                        break;

                    case GamePhase.River:
                        currentBoard = gameState.Board;
                        break;
                    }

                    //evaluate new hand buckets
                    byte bucket1 = (byte)HandStrengthAbstracter.MapToBucket(currentBoard, newState.Player1HoleCards);
                    byte bucket2 = (byte)HandStrengthAbstracter.MapToBucket(currentBoard, newState.Player2HoleCards);
                    handBuckets = new byte[] { bucket1, bucket2 };
                }
            }

            var infoSet = new InformationSet <ActionBucket>()
            {
                CardBucket    = handBuckets[playerIndex],
                ActionHistory = actions
            };

            int numberOfActions = Settings.NumberOfActions;

            if (!nextActionCallEnabled)
            {
                numberOfActions = Settings.NumberOfActions - 1;
            }

            RegretGameNode <ActionBucket> node = null;
            long hash = infoSet.GetLongHashCode();

            //checks if the current information set already exists in O(1)
            if (!GameNodes.TryGetValue(hash, out node))
            {
                node         = new RegretGameNode <ActionBucket>(numberOfActions);
                node.InfoSet = infoSet;
                GameNodes.Add(hash, node);
            }

            //gets the strategy of the current player
            var strategy = node.calculateStrategy(playerIndex == 0 ? probabilityPlayer1 : probabilityPlayer2);

            // initialise utilities  with zeros
            var utilities = new List <float>(numberOfActions);

            for (int i = 0; i < numberOfActions; i++)
            {
                utilities.Add(0);
            }

            float nodeUtility = 0;
            int   index       = 0;

            // traverse the tree further down with a breadth first search
            foreach (ActionBucket nextAction in Enum.GetValues(typeof(ActionBucket)))
            {
                //skip illegal actions
                if (nextAction == ActionBucket.None)
                {
                    continue;
                }
                if (nextAction == ActionBucket.Call && !nextActionCallEnabled)
                {
                    continue;
                }

                var nextHistory = new List <ActionBucket>();
                nextHistory.AddRange(actions.ToArray());
                nextHistory.Add(nextAction);

                utilities[index] = playerIndex == 0
                    ? -CalculateCounterFactualRegret(newState, handBuckets, nextHistory, probabilityPlayer1 * strategy[index], probabilityPlayer2)
                   : -CalculateCounterFactualRegret(newState, handBuckets, nextHistory, probabilityPlayer1, probabilityPlayer2 * strategy[index]);

                //accumulate the utility of the sub branches
                nodeUtility += strategy[index] * utilities[index];
                index++;
            }

            for (int i = 0; i < numberOfActions; i++)
            {
                //calculate the regret
                float regret = utilities[i] - nodeUtility;
                //calculate the regret sum based on the current player
                node.RegretSum[i] += (playerIndex == 0 ? probabilityPlayer2 : probabilityPlayer1) * regret;
            }

            return(nodeUtility);
        }