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); }