/// <summary> /// Shows and hides the manual decision buttons. Making sure those buttons available are allowed. Also sets the default raise amount to the minimum allowable raise. /// </summary> /// <param name="buttonsVisible"></param> public void showHideManualButtons(bool buttonsVisible) { this.checkFoldButton.Visible = buttonsVisible; this.checkFoldButton.Enabled = true; this.callButton.Visible = buttonsVisible; this.callButton.Enabled = true; this.raiseAmount.Visible = buttonsVisible; this.raiseAmount.Enabled = true; this.raiseButton.Visible = buttonsVisible; this.raiseButton.Enabled = true; //If we are going to show the buttons we can decide at this point what the valid decisions are. if (buttonsVisible) { manualDecisionMade = false; long currentActivePlayerId = genericCache.getPlayerId(genericCache.getCurrentActiveTablePosition()); if (genericCache.getMinimumPlayAmount() - genericCache.getPlayerCurrentRoundBetAmount(currentActivePlayerId) == 0) { //If the game is post flop and no-one has yet bet this.checkFoldButton.Text = "Check"; this.callButton.Enabled = false; this.raiseAmount.Text = genericCache.BigBlind.ToString(); } else { decimal minCallAmount = genericCache.getMinimumPlayAmount(); decimal currentRoundBetAmount = genericCache.getPlayerCurrentRoundBetAmount(currentActivePlayerId); decimal lastAdditionalRaiseAmount = genericCache.getCurrentRoundLastRaiseAmount(); decimal minimumRaiseToAmount = (minCallAmount - lastAdditionalRaiseAmount) + (lastAdditionalRaiseAmount * 2); //Check is no longer the option. this.checkFoldButton.Text = "Fold"; //If raising is no longer an option disable that. if (minCallAmount - currentRoundBetAmount > genericCache.getPlayerStack(currentActivePlayerId)) { this.raiseButton.Enabled = false; this.raiseAmount.Enabled = false; } else { //Set the text box to the minimum allowable raise amount. this.raiseAmount.Text = minimumRaiseToAmount.ToString(); } } } }
/// <summary> /// Validates the AI action. /// Prevents raises when only calls are possible. /// Prevents raising more than allowed. /// Prevent raising less than allowed. /// </summary> /// <param name="aiAction">The AI action to be validated.</param> /// <returns>The validated decision.</returns> public static Play ValidatePlayerDecision(Play oldAiAction, databaseCache cache) { //PlayerId passed through because "cache.getCurrentActiveTablePosition()" is a slow method. //long playerId = cache.getPlayerId(cache.getCurrentActiveTablePosition()); long playerId = oldAiAction.PlayerId; decimal minCallAmount = cache.getMinimumPlayAmount(); decimal currentRoundBetAmount = cache.getPlayerCurrentRoundBetAmount(playerId); decimal playerRemaningStack = cache.getPlayerStack(playerId); //if (oldAiAction.PlayerId != playerId) // throw new Exception("Current active table position does not correspond with bot player position."); //Copy the action here so that we can still see the old one for debugging reasons. //Once we know this is stable we can get rid of this line. Play aiAction = new Play(oldAiAction.Serialise()); //We need to make sure a raise is possible byte[] activePositions = cache.getActivePositions(); byte numPlayersAllIn = (byte)cache.getAllInPositions().Length; //We need to ensure the bot is not calling dead, i.e. if the winPercentage is less than 5% we should not be calling after the river if (aiAction.Action == PokerAction.Fold) { //Check for the free check if (minCallAmount - currentRoundBetAmount == 0) { aiAction.Action = PokerAction.Check; } } else if (aiAction.Action == PokerAction.Call) { //We must call atleast the minimum amount if (minCallAmount - currentRoundBetAmount > aiAction.Amount) { aiAction.Amount = minCallAmount; } //We cannot call 0, it should be a check if (minCallAmount - currentRoundBetAmount == 0) { aiAction.Action = PokerAction.Check; aiAction.Amount = 0; } else if (minCallAmount - currentRoundBetAmount > playerRemaningStack) { //We cannot call more than the stack amount aiAction.Amount = playerRemaningStack; } //Never call a 0 amount!!! if (aiAction.Amount == 0 && aiAction.Action == PokerAction.Call) { aiAction.Action = PokerAction.Check; } } else if (aiAction.Action == PokerAction.Raise) { //If the raiseToAmount is less than the minimum allowable raise then raise the minimum. decimal lastAdditionalRaiseAmount = cache.getCurrentRoundLastRaiseAmount(); decimal minimumRaiseToAmount = (minCallAmount - lastAdditionalRaiseAmount) + (lastAdditionalRaiseAmount * 2); if (aiAction.Amount < minimumRaiseToAmount) { aiAction.Amount = minimumRaiseToAmount; } //if (aiAction.Amount > (cache.getCurrentHandDetails().potValue * 2)) // aiAction.Amount = cache.getCurrentHandDetails().potValue * 2; //If the raiseToAmount is more than we are able to raise then raise the maximum amount possible. if (aiAction.Amount > currentRoundBetAmount + playerRemaningStack) { aiAction.Amount = currentRoundBetAmount + playerRemaningStack; } if (minCallAmount - currentRoundBetAmount >= playerRemaningStack) { //If we are trying to raise but we cannot even meet the minimum call then call stack aiAction.Action = PokerAction.Call; aiAction.Amount = playerRemaningStack; } else if (numPlayersAllIn + 1 == activePositions.Length) { //If everyone else is all in then calling is our only option aiAction.Action = PokerAction.Call; aiAction.Amount = minCallAmount - currentRoundBetAmount; //If we already have the right amount in the pot we can only check if (aiAction.Amount == 0) { aiAction.Action = PokerAction.Check; } } } //If we are still trying to call a 0 amounts somthing else has gone very wrong if (aiAction.Amount == 0 && aiAction.Action == PokerAction.Call) { throw new Exception("Critical validation error - trying to call 0 amount."); } /* * if (oldAiAction.Amount != aiAction.Amount || oldAiAction.Action != aiAction.Action) * throw new Exception("Validation Error"); */ return(aiAction); }
public void UpdatePlayerModels(databaseCache cache) { //First get hand details var handDetails = cache.getCurrentHandDetails(); //If hand id has changed since last time then some reseting is needed if (cache.getCurrentHandId() != lastHandIdConsidered) { //reset variables lastHandIdConsidered = cache.getCurrentHandId(); wasRaisedPot = false; wasHandState = HandState.PreFlop; lastActionIdConsidered = -1; //get keys in players dictionary and players in cache who are sat in //long[] keys = playersOld.Keys.ToArray(); long[] satInPlayerIds = cache.getSatInPlayerIds(); lastNumberActivePlayers = cache.PlayerIdsStartedHand().Length; //Remove from players all items that don't correspond to sat in players //long[] toRemove = keys.Except(satInPlayerIds).ToArray(); //for (byte i = 0; i < toRemove.Length; i++) // playersOld.Remove(toRemove[i]); //Go through each seat //for (int i = 0; i < satInPlayerIds.Length; i++) //{ // //Get player id for person in seat // long pid = satInPlayerIds[i]; // //and the player is not in dictionary add them, otherwise reset their entry // if (!playersOld.ContainsKey(pid)) // playersOld.Add(pid, new PlayerModel(tableId, lastHandIdConsidered, pid, wrProv)); // else // playersOld[pid].ResetProbsToDefault(lastHandIdConsidered); // playersOld[pid].UpdateCardsWinPercentages(new Card[] { }, lastNumberActivePlayers); //} //get keys in players dictionary and players in cache who are sat in long[] keys = playersNew.Keys.ToArray(); //Remove from players all items that don't correspond to sat in players long[] toRemove = keys.Except(satInPlayerIds).ToArray(); for (byte i = 0; i < toRemove.Length; i++) { playersNew.Remove(toRemove[i]); } //Go through each seat for (int i = 0; i < satInPlayerIds.Length; i++) { //Get player id for person in seat long pid = satInPlayerIds[i]; //and the player is not in dictionary add them, otherwise reset their entry if (!playersNew.ContainsKey(pid)) { playersNew.Add(pid, new PlayerModelFixed(tableId, cache.BigBlind, lastHandIdConsidered, pid, wrProv)); } else { playersNew[pid].ResetProbsToDefault(lastHandIdConsidered); } playersNew[pid].UpdateCardsWinPercentages(new Card[] { }, lastNumberActivePlayers); } } //Setup cards array based on cards that were present at end of last update Card[] cards = new Card[0]; switch (wasHandState) { case HandState.Flop: cards = new Card[] { (Card)(handDetails.tableCard1), (Card)(handDetails.tableCard2), (Card)(handDetails.tableCard3) }; break; case HandState.Turn: cards = new Card[] { (Card)(handDetails.tableCard1), (Card)(handDetails.tableCard2), (Card)(handDetails.tableCard3), (Card)(handDetails.tableCard4) }; break; case HandState.River: cards = new Card[] { (Card)(handDetails.tableCard1), (Card)(handDetails.tableCard2), (Card)(handDetails.tableCard3), (Card)(handDetails.tableCard4), (Card)(handDetails.tableCard5) }; break; } //Get all dealer and betting actions since last update var handActions = cache.getHandActions(lastHandIdConsidered); handActions = (from a in handActions where a.localIndex > lastActionIdConsidered && (a.actionType == PokerAction.Check || a.actionType == PokerAction.Call || a.actionType == PokerAction.Raise || a.actionType == PokerAction.Fold || a.actionType == PokerAction.DealFlop || a.actionType == PokerAction.DealTurn || a.actionType == PokerAction.DealRiver) orderby a.localIndex select a).ToArray(); //Get a list of active players by the end of the hand actions above var activePlayers = cache.getActivePlayerIds(); //Now go through each new hand action foreach (var handAction in handActions) { //if the action is a dealer action then update hand state, change raised pot flag and for each active player update card probs change after deal if (handAction.actionType == PokerAction.DealFlop) { wasHandState = HandState.Flop; wasRaisedPot = false; cards = new Card[] { (Card)(handDetails.tableCard1), (Card)(handDetails.tableCard2), (Card)(handDetails.tableCard3) }; foreach (var player in activePlayers) { //playersOld[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard1)); //playersOld[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard2)); //playersOld[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard3)); playersNew[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard1)); playersNew[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard2)); playersNew[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard3)); } continue; } else if (handAction.actionType == PokerAction.DealTurn) { wasHandState = HandState.Turn; wasRaisedPot = false; cards = new Card[] { (Card)(handDetails.tableCard1), (Card)(handDetails.tableCard2), (Card)(handDetails.tableCard3), (Card)(handDetails.tableCard4) }; foreach (var player in activePlayers) { //playersOld[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard4)); playersNew[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard4)); } continue; } else if (handAction.actionType == PokerAction.DealRiver) { wasHandState = HandState.River; wasRaisedPot = false; cards = new Card[] { (Card)(handDetails.tableCard1), (Card)(handDetails.tableCard2), (Card)(handDetails.tableCard3), (Card)(handDetails.tableCard4), (Card)(handDetails.tableCard5) }; foreach (var player in activePlayers) { //playersOld[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard5)); playersNew[player].UpdateProbsAfterCardDealt((Card)(handDetails.tableCard5)); } continue; } else if (handAction.actionType == PokerAction.Fold) { //if a player has folded set all their probabilities of having cards to zero and reduce number of active players //if (playersOld.ContainsKey(handAction.playerId)) // playersOld[handAction.playerId].SetAllProbsToZeroOnFold(); if (playersNew.ContainsKey(handAction.playerId)) { playersNew[handAction.playerId].SetAllProbsToZeroOnFold(); } lastNumberActivePlayers--; continue; } //otherwise we're dealing with a betting action so they should be in active players if (activePlayers.Contains(handAction.playerId)) { //Update win percentages and indices in player model and update card probs based on action //playersOld[handAction.playerId].UpdateCardsWinPercentages(cards, lastNumberActivePlayers); playersNew[handAction.playerId].UpdateCardsWinPercentages(cards, lastNumberActivePlayers); decimal ra = 0, ca = 0, pa = 0; double dd; if (handAction.actionType == PokerAction.Raise) { ra = cache.getCurrentRoundLastRaiseAmount(handAction.localIndex + 1L); } if (wasRaisedPot) { ca = cache.getMinimumPlayAmount(handAction.localIndex) - cache.getPlayerCurrentRoundBetAmount(handAction.playerId, handAction.localIndex); } pa = cache.getPotUpToActionId(handAction.localIndex); dd = (cache.getActivePlayerDistanceToDealer(handAction.playerId, handAction.localIndex) - 1.0) / (cache.getActivePositions(handAction.localIndex).Length - 1.0); //playersOld[handAction.playerId].UpdateCardProbsBasedOnAction(handAction.actionType, wasHandState, ca, ra, pa, wasRaisedPot, dd < 0.5); playersNew[handAction.playerId].UpdateCardProbsBasedOnAction(handAction.actionType, wasHandState, ca, ra, pa, wasRaisedPot, dd < 0.5); } //Finally if the action was a raise we now have a raised pot if (handAction.actionType == PokerAction.Raise) { wasRaisedPot = true; } } //for (int i = 0; i < activePlayers.Length; i++) // playersOld[activePlayers[i]].UpdateCardsWinPercentages(cards, activePlayers.Length); for (int i = 0; i < activePlayers.Length; i++) { playersNew[activePlayers[i]].UpdateCardsWinPercentages(cards, activePlayers.Length); } //finally update last action considered and last number players if (handActions.Count() > 0) { lastActionIdConsidered = handActions.Last().localIndex; } lastNumberActivePlayers = activePlayers.Length; }
public void GetRaiseCallStealAmounts(databaseCache cache, long playerId, out decimal call, out decimal steel, out double probStealSuccess, out double probCallSuccess) { var playersInHand = cache.getActivePlayerIds(); call = decimal.MinValue; steel = decimal.MinValue; decimal callTemp, stealTemp; double stealProbTemp, callProbTemp; probStealSuccess = 0; probCallSuccess = 0; HandState stage; var details = cache.getCurrentHandDetails(); if (details.tableCard1 == 0) { stage = HandState.PreFlop; } else if (details.tableCard4 == 0) { stage = HandState.Flop; } else if (details.tableCard5 == 0) { stage = HandState.Turn; } else { stage = HandState.River; } var playerCards = cache.getPlayerHoleCards(playerId); decimal minExtraRaise = (cache.getCurrentRoundLastRaiseAmount() == 0 ? cache.BigBlind : cache.getCurrentRoundLastRaiseAmount()); decimal maxExtraRaise = cache.getPlayerStack(playerId) - cache.getMinimumPlayAmount() + cache.getPlayerCurrentRoundBetAmount(playerId); double numberOpponents = cache.getActivePositions().Length - cache.getAllInPositions().Length - 1; if (playersInHand.Length > cache.getAllInPositions().Length + 1 && maxExtraRaise > 0) { foreach (var player in playersNew) { if (player.Key != playerId && playersInHand.Contains(player.Key)) { if (cache.getAllInPositions().Contains(cache.getPlayerPosition(player.Key))) { continue; } var dd = (cache.getActivePlayerDistanceToDealer(player.Key) - 1.0) / (cache.getActivePositions().Length - 1.0); player.Value.GetRaiseCallSteal(stage, (Card)playerCards.holeCard1, (Card)playerCards.holeCard2, dd < 0.5, details.potValue, minExtraRaise, maxExtraRaise, Math.Pow(0.5, 1.0 / numberOpponents), Math.Pow(0.75, 1.0 / numberOpponents), out callTemp, out stealTemp, out stealProbTemp, out callProbTemp); if (callTemp > call) { call = callTemp; probCallSuccess = callProbTemp; } if (stealTemp > steel) { steel = stealTemp; probStealSuccess = stealProbTemp; } } } probStealSuccess = Math.Pow(probStealSuccess, numberOpponents); probCallSuccess = Math.Pow(probCallSuccess, numberOpponents); } else { call = minExtraRaise; steel = minExtraRaise; probStealSuccess = 0; probCallSuccess = 0; } decimal currentMinPlayAmount = cache.getMinimumPlayAmount(); decimal minRaise = minExtraRaise + currentMinPlayAmount; decimal maxRaise = maxExtraRaise + currentMinPlayAmount; call += currentMinPlayAmount; steel += currentMinPlayAmount; decimal callChange = call * 0.2m * (decimal)(wrProv.randomGen.NextDouble() - 0.5); decimal stealChange = steel * 0.2m * (decimal)(wrProv.randomGen.NextDouble() - 0.5); if (Math.Abs(callChange) < cache.LittleBlind) { callChange = cache.LittleBlind * callChange / Math.Abs(callChange); } if (Math.Abs(stealChange) < cache.LittleBlind) { stealChange = cache.LittleBlind * stealChange / Math.Abs(stealChange); } call += callChange; call = Math.Round(call / cache.LittleBlind, 0, MidpointRounding.AwayFromZero) * cache.LittleBlind; if (call < minRaise) { call = minRaise; } if (call > maxRaise) { call = maxRaise; } steel += stealChange; steel = Math.Round(steel / cache.LittleBlind, 0, MidpointRounding.AwayFromZero) * cache.LittleBlind; if (steel < minRaise) { steel = minRaise; } if (steel > maxRaise) { steel = maxRaise; } }