public void GetBetSizeBigPotBetTest() { int betSize = ActionAbstracter.GetBetSize(ActionBucket.LowBet, 0, 180); int expectedBetSize = 10; Assert.AreEqual(expectedBetSize, betSize); betSize = ActionAbstracter.GetBetSize(ActionBucket.MediumBet, 0, 180); expectedBetSize = 10; Assert.AreEqual(expectedBetSize, betSize); betSize = ActionAbstracter.GetBetSize(ActionBucket.HighBet, 0, 150); expectedBetSize = 25; Assert.AreEqual(expectedBetSize, betSize); }
public void GetBetSizeSmallPotRaiseTest() { int betSize = ActionAbstracter.GetBetSize(ActionBucket.LowBet, 10, 20); int expectedBetSize = 20; Assert.AreEqual(expectedBetSize, betSize); betSize = ActionAbstracter.GetBetSize(ActionBucket.MediumBet, 10, 20); expectedBetSize = 30; Assert.AreEqual(expectedBetSize, betSize); betSize = ActionAbstracter.GetBetSize(ActionBucket.HighBet, 10, 30); expectedBetSize = 90; Assert.AreEqual(expectedBetSize, betSize); }
public override Task <GameActionEntity> GetAction(List <ActionType> possibleActions, int amountToCall) { var infoSet = new InformationSet <ActionBucket>(); infoSet.ActionHistory = actionHistory; infoSet.CardBucket = handBucket; RegretGameNode <ActionBucket> gameNode; trainedTree.TryGetValue(infoSet.GetLongHashCode(), out gameNode); if (gameNode == null) { // this should never occur randomBot.ChipStack = this.ChipStack; return(randomBot.GetAction(possibleActions, amountToCall)); } else { var optimalStrategy = gameNode.calculateAverageStrategy(); var rand = new Random(); double randomValue = rand.NextDouble(); bool isCallActionEnabled = (optimalStrategy.Count == 5); int index = 0; double sumPercent = 0; ActionBucket selectedActionBucket = ActionBucket.None; foreach (ActionBucket action in Enum.GetValues(typeof(ActionBucket))) { if (action == ActionBucket.None) { continue; } if (action == ActionBucket.Call && !isCallActionEnabled) { continue; } double newSumPercent = sumPercent + optimalStrategy[index]; if (randomValue >= sumPercent && randomValue <= newSumPercent) { selectedActionBucket = action; break; } sumPercent = newSumPercent; index++; } ActionType selectedAction = ActionAbstracter.MapToAction(selectedActionBucket, amountToCall); int betSize = 0; switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: betSize = ActionAbstracter.GetBetSize(selectedActionBucket, amountToCall, currentGame.PotSize); break; case ActionType.Call: betSize = amountToCall; break; } if (!possibleActions.Contains(selectedAction)) { // in all-in scenarios actions from bot may differ from possible actions switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: case ActionType.Check: if (possibleActions.Contains(ActionType.Call)) { selectedAction = ActionType.Call; } break; default: throw new Exception("Selected action is illegal!"); } } if (ChipStack < betSize) { betSize = this.ChipStack; } return(Task.FromResult <GameActionEntity>(new GameActionEntity { ActionType = selectedAction, Amount = betSize, PlayerId = this.Id })); } }
public override Task <GameActionEntity> GetAction(List <ActionType> possibleActions, int amountToCall) { var possibleFeatureActions = new List <FeatureAction>(); foreach (var possibleAction in possibleActions) { var featureAction = FeatureActionAbstracter.FromActionType(possibleAction); if (!possibleFeatureActions.Contains(featureAction)) { possibleFeatureActions.Add(featureAction); } } var currentPhasehistory = actionHistoriesPerPhase.FirstOrDefault(x => x.Phase == currentGame.Phase); Positioning opponentPositioning = Positioning.None; Aggression opponentAggression = Aggression.None; if (currentGame.Phase > GamePhase.PreFlop) { opponentPositioning = getOpponentPositioning(currentPhasehistory.ActionHistory.Count); opponentAggression = getOpponentAggression(opponentPositioning); } var counterAction = opponent.GetCounterAction(possibleFeatureActions, currentPhasehistory.ActionHistory, currentGame.Phase, opponentAggression, opponentPositioning); ActionBucket selectedActionBucket = ActionBucket.None; if (counterAction == null) { if (currentGame.Phase == GamePhase.PreFlop) { switch (opponent.PlayStyle) { case PlayStyle.LooseAggressive: case PlayStyle.LoosePassive: if (this.IsBigBlind) { selectedActionBucket = ActionBucket.LowBet; } else { if (startHandBucket >= StartHandBucket.VeryBad) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } } break; case PlayStyle.TightAggressive: case PlayStyle.TightPassive: //play hyper aggressive selectedActionBucket = ActionBucket.LowBet; break; } } else { switch (opponent.PlayStyle) { case PlayStyle.LooseAggressive: //play TightAggressive if (handStrengthBucket >= HandStrengthBucket.HighCardAce) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } break; case PlayStyle.LoosePassive: selectedActionBucket = ActionBucket.LowBet; break; case PlayStyle.TightAggressive: //play LooseAggressive if (handStrengthBucket >= HandStrengthBucket.LowPair) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } break; case PlayStyle.TightPassive: //play LooseAggressive if (handStrengthBucket >= HandStrengthBucket.HighCardElse) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } break; } } } else { if (currentGame.Phase == GamePhase.PreFlop) { switch (counterAction) { case FeatureAction.Bet: if (startHandBucket > StartHandBucket.Worst) { selectedActionBucket = ActionBucket.LowBet; } break; case FeatureAction.Pass: if (startHandBucket < StartHandBucket.Bad) { selectedActionBucket = ActionBucket.Pass; } break; } } else { switch (counterAction) { case FeatureAction.Bet: selectedActionBucket = ActionBucket.LowBet; break; case FeatureAction.Pass: if (handStrengthBucket < HandStrengthBucket.LowPair) { selectedActionBucket = ActionBucket.Pass; } break; } } } switch (selectedActionBucket) { case ActionBucket.None: //no counter move has been found. Fall back to randomness randomBot.ChipStack = this.ChipStack; return(randomBot.GetAction(possibleActions, amountToCall)); default: ActionType selectedAction = ActionAbstracter.MapToAction(selectedActionBucket, amountToCall); int betSize = 0; switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: betSize = ActionAbstracter.GetBetSize(selectedActionBucket, amountToCall, currentGame.PotSize); break; case ActionType.Call: betSize = amountToCall; break; } if (!possibleActions.Contains(selectedAction)) { switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: case ActionType.Check: if (possibleActions.Contains(ActionType.Call)) { selectedAction = ActionType.Call; } break; default: throw new Exception("Selected action is illegal!"); } } if (ChipStack < betSize) { betSize = this.ChipStack; } return(Task.FromResult <GameActionEntity>( new GameActionEntity { ActionType = selectedAction, Amount = betSize, PlayerId = this.Id })); } }
public override Task <GameActionEntity> GetAction(List <ActionType> possibleActions, int amountToCall) { ActionBucket selectedActionBucket; switch (currentGame.Phase) { case GamePhase.PreFlop: double startHandStrengthRatio = (double)startHandBucket / (double)StartHandBucket.Best; if (TightRatio <= startHandStrengthRatio) { startHandStrengthRatio = 1 - startHandStrengthRatio; if (AggressiveRatio >= startHandStrengthRatio) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Call; } } else { selectedActionBucket = ActionBucket.Pass; } break; default: double handStrengthRatio = 1 - (double)handStrengthBucket / (double)HandStrengthBucket.TopHands; if (AggressiveRatio >= handStrengthRatio) { selectedActionBucket = ActionBucket.LowBet; } else { selectedActionBucket = ActionBucket.Pass; } break; } ActionType selectedAction = ActionAbstracter.MapToAction(selectedActionBucket, amountToCall); int betSize = 0; switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: betSize = ActionAbstracter.GetBetSize(selectedActionBucket, amountToCall, currentGame.PotSize); break; case ActionType.Call: betSize = amountToCall; break; } if (!possibleActions.Contains(selectedAction)) { switch (selectedAction) { case ActionType.Bet: case ActionType.Raise: case ActionType.Check: if (possibleActions.Contains(ActionType.Call)) { selectedAction = ActionType.Call; } break; case ActionType.Call: if (possibleActions.Contains(ActionType.Check)) { selectedAction = ActionType.Check; } break; default: throw new Exception("Selected action is illegal!"); } } if (ChipStack < betSize) { betSize = this.ChipStack; } return(Task.FromResult <GameActionEntity>( new GameActionEntity { ActionType = selectedAction, Amount = betSize, PlayerId = this.Id })); }
/// <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); }