/** * Copyright (c) 2021 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "poker.h" void pokerInit(poker_t *poker) { poker->playerCount = 0; poker->playerDealer = 0; poker->playerSmallBlind = 0; poker->playerBigBlind = 0; pokerResetRound(poker); } void pokerResetRound(poker_t *poker) { uint8_t i; pokerplayer_t *player; poker->deckSize = cardWriteDeck(poker->deck); poker->graveSize = 0; poker->communitySize = 0; poker->potCount = 0; pokerPotAdd(poker); pokerResetBettingRound(poker); for(i = 0; i < poker->playerCount; i++) { player = poker->players + i; player->cardCount = 0; player->currentBet = 0; player->state &= ~( POKER_PLAYER_STATE_FOLDED | POKER_PLAYER_STATE_HAS_BET_THIS_ROUND | POKER_PLAYER_STATE_SHOWING ); } } void pokerResetBettingRound(poker_t *poker) { uint8_t i; pokerplayer_t *player; // Reset the round betting state. for(i = 0; i < poker->playerCount; i++) { player = poker->players + i; player->state =flagOff(player->state,POKER_PLAYER_STATE_HAS_BET_THIS_ROUND); } // Now reset to the first player that needs to bet. if(poker->playerCount > 0) { poker->better = (poker->playerBigBlind + 1) % poker->playerCount; } else { poker->better = 0x00; } // Then we check who's remaining. We do this because the default better may // have folded already. poker->better = pokerPlayerGetRemainingBetter(poker); } void pokerNewDealer(poker_t *poker) { uint8_t i, j, k; pokerplayer_t *player; bool foundDealer; bool foundSmall; foundDealer = false; foundSmall = false; j = poker->playerDealer + 1; for(i = 0; i < poker->playerCount; i++) { k = (j + i) % poker->playerCount; player = poker->players + k; if(player->state & POKER_PLAYER_STATE_OUT) continue; if(!foundDealer) { poker->playerDealer = k; foundDealer = true; } else if(!foundSmall) { poker->playerSmallBlind = k; foundSmall = true; } else { poker->playerBigBlind = k; break; } } } void pokerTakeBlinds(poker_t *poker, int32_t small, int32_t big) { pokerPlayerBet(poker, poker->playerSmallBlind, small); pokerPlayerBet(poker, poker->playerBigBlind, big); } int32_t pokerGetCallValue(poker_t *poker) { uint8_t i; int32_t call; call = 0; for(i = 0; i < poker->playerCount; i++) { call = mathMax(call, poker->players[i].currentBet); } return call; } // Pot functions uint8_t pokerPotAdd(poker_t *poker) { pokerpot_t *pot; uint8_t i = poker->potCount++; pot = poker->pots + i; pot->chips = 0; pot->playerCount = 0; return i; } void pokerPotAddPlayer(pokerpot_t *pot, uint8_t playerIndex) { if(arrayContains( sizeof(uint8_t), pot->players, pot->playerCount, &playerIndex )) return; pot->players[pot->playerCount++] = playerIndex; } // Dealer Functions void pokerTurn(poker_t *poker, uint8_t count) { uint8_t i; for(i = 0; i < count; i++) { cardDeal( poker->deck, &poker->deckSize, poker->community, &poker->communitySize ); } } void pokerBurn(poker_t *poker, uint8_t count) { uint8_t i; for(i = 0; i < count; i++) { cardDeal(poker->deck, &poker->deckSize, poker->grave, &poker->graveSize); } } // Player Functions uint8_t pokerPlayerAdd(poker_t *poker) { pokerplayer_t *player; uint8_t i = poker->playerCount++; player = poker->players + i; player->cardCount = 0; player->chips = 0; player->currentBet = 0; player->state = POKER_PLAYER_STATE_OUT; return i; } void pokerPlayerDeal(poker_t *poker, pokerplayer_t *player, uint8_t count) { uint8_t i; for(i = 0; i < count; i++) { cardDeal(poker->deck, &poker->deckSize, player->cards, &player->cardCount); } } void pokerPlayerChipsAdd(pokerplayer_t *player, int32_t chips) { player->chips += chips; if(player->chips > 0) { player->state = flagOff(player->state, POKER_PLAYER_STATE_OUT); } } void pokerPlayerDealAll(poker_t *poker, uint8_t count) { uint8_t i, j; pokerplayer_t *player; for(j = 0; j < count; j++) { for(i = 0; i < poker->playerCount; i++) { player = poker->players + i; // Can't deal to a player who is folded or out if(player->state & (POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT)) { continue; } pokerPlayerDeal(poker, player, 1); } } } bool pokerPlayerDoesNeedToBetThisRound(poker_t *poker, uint8_t playerIndex) { pokerplayer_t *player; player = poker->players + playerIndex; if(player->state & POKER_PLAYER_STATE_FOLDED) return false; if(player->chips <= 0) return false; if(!(player->state & POKER_PLAYER_STATE_HAS_BET_THIS_ROUND)) return true; if(player->currentBet < pokerGetCallValue(poker)) return true; return false; } uint8_t pokerPlayerGetRemainingBetter(poker_t *poker) { uint8_t i, j; for(i = 0; i < poker->playerCount; i++) { j = (i + poker->playerBigBlind + 1) % poker->playerCount; if(pokerPlayerDoesNeedToBetThisRound(poker, j)) return j; } return 0xFF; } uint8_t pokerPlayerGetNextBetter(poker_t *poker, uint8_t current) { uint8_t i, j; for(i = 0; i < poker->playerCount; i++) { j = (i + current + 1) % poker->playerCount; if(pokerPlayerDoesNeedToBetThisRound(poker, j)) return j; } return 0xFF; } int32_t pokerPlayerGetCallBet(poker_t *poker, pokerplayer_t *player) { return pokerGetCallValue(poker) - player->currentBet; } uint8_t pokerInRoundGetCount(poker_t *poker) { uint8_t i, count; pokerplayer_t *player; count = 0; for(i = 0; i < poker->playerCount; i++) { player = poker->players + i; if(player->state & (POKER_PLAYER_STATE_FOLDED | POKER_PLAYER_STATE_OUT)) { continue; } count++; } return count; } uint8_t pokerPlayerGetRemainingBetterCount(poker_t *poker) { uint8_t i, count; count = 0; for(i = 0; i < poker->playerCount; i++) { if(!pokerPlayerDoesNeedToBetThisRound(poker, i)) continue; count++; } return count; } // Betting void pokerPlayerBetPot( poker_t *poker, pokerpot_t *pot, uint8_t playerIndex, int32_t chips ) { pokerplayer_t *player; player = poker->players + playerIndex; player->chips -= chips; player->currentBet += chips; pot->chips += chips; player->state |= POKER_PLAYER_STATE_HAS_BET_THIS_ROUND; pokerPotAddPlayer(pot, playerIndex); } void pokerPlayerBet(poker_t *poker, uint8_t playerIndex, int32_t chips) { pokerPlayerBetPot( poker, poker->pots + (poker->potCount - 1), playerIndex, chips ); } bool pokerPlayerCanCheck(poker_t *poker, pokerplayer_t *player) { return pokerGetCallValue(poker) <= player->currentBet; } pokerturn_t pokerTurnFold(poker_t *poker, uint8_t player) { return (pokerturn_t){ .chips = 0, .confidence = 1, .type = POKER_TURN_TYPE_FOLD }; } pokerturn_t pokerTurnBet(poker_t *poker, uint8_t playerIndex, int32_t chips) { pokerturn_t turn; pokerplayer_t *player; int32_t i; player = poker->players + playerIndex; turn.confidence = 1; if(chips == 0) { turn.type = POKER_TURN_TYPE_CHECK; turn.chips = 0; } else if(player->chips <= chips) { turn.chips = player->chips; turn.type = POKER_TURN_TYPE_ALL_IN; } else { turn.chips = chips; turn.type = POKER_TURN_TYPE_BET; i = pokerGetCallValue(poker); if(chips == (i - player->currentBet)) { turn.type = POKER_TURN_TYPE_CALL; } } return turn; } pokerturn_t pokerTurnGetForPlayer(poker_t *poker, uint8_t playerIndex) { int32_t random, maxBet, bluffBet, callBet; float confidence, expectedGain, potOdds; bool isBluff; int32_t amount; pokerplayer_t *player; pokerplayerwinning_t winning; uint8_t i, cardNumber0, cardNumber1, suitNumber0, suitNumber1; pokerturn_t turn; player = poker->players + playerIndex; // Can the player do anything? if(player->state & (POKER_PLAYER_STATE_FOLDED | POKER_PLAYER_STATE_OUT)) { turn.type = POKER_TURN_TYPE_OUT; return turn; } // The following logic is heavily inspired by; // https://github.com/gorel/C-Poker-AI/blob/master/src/common/pokerai.c // But with some changes and smarts added by me. The original source code will // essentially just run a crap tun of simulated games and get the times that // they are expected to win from those games, but I'm just going to use the // odds of the winning hand. // Is this preflop? if(poker->communitySize == 0 && player->cardCount >= 2) { // Get the hand weight cardNumber0 = cardGetNumber(player->cards[0]); cardNumber1 = cardGetNumber(player->cards[1]); suitNumber0 = cardGetSuit(player->cards[0]); suitNumber1 = cardGetSuit(player->cards[1]); // Get delta between cards i = mathAbs(cardNumber0 - cardNumber1); // Get card weight confidence = (float)cardNumber0 + (float)cardNumber1; if(cardNumber0 == cardNumber1) {// Pairs confidence += 6; } else if(suitNumber0 == suitNumber1) {// Same suit confidence += 4; } // Get difference from cards for guessing flush if(i > 4) { confidence -= 4; } else if(i > 2) { confidence -= i; } // Get the confidence delta 0-1 confidence = confidence / 30.0f; } else { // Simulate my hand being the winning hand, use that as the confidence // pokerWinnerPlayerGet(&poker->community, player, &winning); // confidence = pokerWinnerGetTypeConfidence(winning.type); } // Now we know how confident the AI is, let's put a chip value to that weight // How many chips to call? callBet = pokerPlayerGetCallBet(poker, player); // Do they need chips to call, or is it possible to check? // if(callBet > 0) { // potOdds = (float)callBet / ((float)callBet + (float)poker->bet.pot); // } else { // potOdds = ( // 1.0f / (float)pokerBetGetRemainingPlayerCount(&poker->bet, poker->players) // ); // } // Now determine the expected ROI expectedGain = confidence / potOdds; // Now get a random 0-100 random = randInt32() % 100; // Determine the max bet that the AI is willing to make maxBet = (int32_t)((float)player->chips / 1.75f) - (random / 2); maxBet -= callBet; // Determine what's a good bluff bet. bluffBet = random * maxBet / 100 / 2; // Now prep the output isBluff = false; amount = 0; // Now the actual AI can happen. This is basically a weight to confidence // ratio. The higher the gains and the confidence then the more likely the AI // is to betting. There are also bluff chances within here. if(expectedGain < 0.8 && confidence < 0.8) { if(random < 95) { amount = 0; } else { amount = bluffBet; isBluff = true; } } else if ((expectedGain < 1.0 && confidence < 0.85) || confidence < 0.1) { if (random < 80) { amount = 0; } else if(random < 5) { amount = callBet; isBluff = true; } else { amount = bluffBet; isBluff = true; } } else if ((expectedGain < 1.3 && confidence < 0.9) || confidence < 0.5) { if (random < 60 || confidence < 0.5) { amount = callBet; } else { amount = maxBet; } } else if (confidence < 0.95 || poker->communitySize < 0x04) { if(random < 20) { amount = callBet; } else { amount = maxBet; } } else { amount = (player->chips - callBet) * 9 / 10; } // If this is the first round... make it a lot less likely I'll bet if(poker->communitySize == 0x00 && amount > callBet) { if(random > 5) amount = callBet; } // Did we actually bet? // if(amount > 0) { // printf("AI is betting %i chips, bluff: %i\n", amount, isBluff); // // Let's not get caught in a raising loop with AI. // if(player->timesRaised >= POKER_TURN_MAX_RAISES) { // amount = callBet; // } // amount = mathMax(amount, callBet); // turn = pokerTurnRaise(poker, playerIndex, amount); // turn.confidence = confidence; // } else if(pokerTurnCanPlayerCheck(poker, playerIndex)) { // turn = pokerTurnCheck(poker, playerIndex); // turn.confidence = 1; // } else { // turn = pokerTurnFold(poker, playerIndex); // turn.confidence = 1 - confidence; // } return turn; } // Winning void pokerHandGetFull( poker_t *poker, pokerplayer_t *player, card_t cards[POKER_WINNING_FULL_SIZE] ) { uint8_t i; // Add the dealer hand for(i = 0; i < poker->communitySize; i++) { cards[i] = poker->community[i]; } // Add the player hand for(i = 0; i < player->cardCount; i++) { cards[i+poker->communitySize] = player->cards[i]; } } void pokerWinnerFillRemaining(pokerplayerwinning_t *winning) { uint8_t i, highest, current; card_t highestCard, currentCard; // Set the kicker winning->kicker = 0xFF; // Fill the remaining cards while(winning->setSize < POKER_WINNING_SET_SIZE) { highest = 0xFF; for(i = 0; i < winning->fullSize; i++) { currentCard = winning->full[i]; if(cardContains(winning->set, winning->setSize, currentCard) != -1) { continue; } if(highest == 0xFF) { highestCard = currentCard; highest = cardGetNumber(highestCard); } else { current = cardGetNumber(currentCard); if(current != CARD_ACE && current < highest) continue; highestCard = currentCard; highest = current; } } if(highest == 0xFF) break; winning->set[winning->setSize++] = highestCard; } cardHandSort(winning->set, winning->setSize); } void pokerWinnerGetForPlayer( poker_t *poker, pokerplayer_t *player, pokerplayerwinning_t *winning ) { uint8_t i, j, l; int32_t index; card_t card; uint8_t number, suit, pairCount; int32_t pairs[CARD_SUIT_COUNT]; // Get the full poker hand (should be a 7 card hand, but MAY not be) winning->fullSize = poker->communitySize + player->cardCount; pokerHandGetFull(poker, player, winning->full); cardHandSort(winning->full, winning->fullSize); // Reset the winning status. winning->setSize = 0; //////////////////////// Now look for the winning set //////////////////////// // Royal / Straight Flush for(i = 0; i < winning->fullSize; i++) { card = winning->full[i]; number = cardGetNumber(card); if(number < CARD_FIVE) continue; suit = cardGetSuit(card); winning->setSize = 1; // Now look for the matching cards (Reverse order to order from A to 10) for(j = 1; j <= 4; j++) { l = number == CARD_FIVE && j == 4 ? CARD_ACE : number - j;//Ace low. index = cardContains(winning->full, winning->fullSize, cardGet(l, suit)); if(index == -1) break; winning->set[j] = winning->full[index]; winning->setSize++; } // Check if has all necessary cards. if(winning->setSize < POKER_WINNING_SET_SIZE) continue; // Add self to array winning->set[0] = winning->full[i]; winning->type = ( number == CARD_ACE ? POKER_WINNING_TYPE_ROYAL_FLUSH : POKER_WINNING_TYPE_STRAIGHT_FLUSH ); pokerWinnerFillRemaining(winning); return; } // Four of a kind. winning->setSize = 0; for(i = 0; i < winning->fullSize; i++) { card = winning->full[i]; number = cardGetNumber(card); pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); if(pairCount < CARD_SUIT_COUNT) continue; winning->setSize = pairCount; for(j = 0; j < pairCount; j++) winning->set[j] = winning->full[pairs[j]]; winning->type = POKER_WINNING_TYPE_FOUR_OF_A_KIND; pokerWinnerFillRemaining(winning); return; } // Full House winning->setSize = 0; for(i = 0; i < winning->fullSize; i++) { // Check we haven't already added this card. card = winning->full[i]; if(cardContains(winning->set, winning->setSize, card) != -1) continue; number = cardGetNumber(card); pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); // Did we find either two pair or three pair? if(pairCount != 2 && pairCount != 3) continue; if(winning->setSize == 3) pairCount = 2;//Clamp to 5 max. // Copy found pairs. for(j = 0; j < pairCount; j++) { winning->set[winning->setSize + j] = winning->full[pairs[j]]; } winning->setSize += pairCount; // Winned? if(winning->setSize != POKER_WINNING_SET_SIZE) continue; winning->type = POKER_WINNING_TYPE_FULL_HOUSE; pokerWinnerFillRemaining(winning); return; } // Flush (5 same suit) for(i = 0; i < winning->fullSize; i++) { card = winning->full[i]; suit = cardGetSuit(card); winning->setSize = 1; for(j = i+1; j < winning->fullSize; j++) { if(cardGetSuit(winning->full[j]) != suit) continue; winning->set[winning->setSize++] = winning->full[j]; if(winning->setSize == POKER_WINNING_SET_SIZE) break; } if(winning->setSize < POKER_WINNING_SET_SIZE) continue; winning->set[0] = winning->full[i]; winning->type = POKER_WINNING_TYPE_FLUSH; pokerWinnerFillRemaining(winning); return; } // Straight (sequence any suit) winning->setSize = 0; for(i = 0; i < winning->fullSize; i++) { card = winning->full[i]; number = cardGetNumber(card); if(number < CARD_FIVE) continue; winning->setSize = 1; for(j = 1; j <= 4; j++) { l = number == CARD_FIVE && j == 4 ? CARD_ACE : number - j;//Ace low. index = cardContainsNumber(winning->full, winning->fullSize, l); if(index == -1) break; winning->set[j] = winning->full[index]; winning->setSize++; } // Check if has all necessary cards. if(winning->setSize < POKER_WINNING_SET_SIZE) continue; winning->set[0] = winning->full[i]; winning->type = POKER_WINNING_TYPE_STRAIGHT; pokerWinnerFillRemaining(winning); return; } // Three of a kind winning->setSize = 0; for(i = 0; i < winning->fullSize; i++) { card = winning->full[i]; number = cardGetNumber(card); pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); if(pairCount != 3) continue; winning->setSize = pairCount; for(j = 0; j < pairCount; j++) winning->set[j] = winning->full[pairs[j]]; winning->type = POKER_WINNING_TYPE_THREE_OF_A_KIND; pokerWinnerFillRemaining(winning); return; } // Two Pair winning->setSize = 0; for(i = 0; i < winning->fullSize; i++) { card = winning->full[i];// Check we haven't already added this card. if(cardContains(winning->set, winning->setSize, card) != -1) { continue; } number = cardGetNumber(card); pairCount = cardCountPairs(winning->full, winning->fullSize, number, pairs); if(pairCount != 2) continue; for(j = 0; j < pairCount; j++) { winning->set[winning->setSize+j] = winning->full[pairs[j]]; } // arrayCopy(sizeof(int32_t), pairs, pairCount, winning->set+winning->setSize); winning->setSize += pairCount; if(winning->setSize != 4) continue; winning->type = POKER_WINNING_TYPE_TWO_PAIR; pokerWinnerFillRemaining(winning); return; } // Pair if(winning->setSize == 2) { winning->type = POKER_WINNING_TYPE_PAIR; pokerWinnerFillRemaining(winning); return; } // High card winning->setSize = 0; pokerWinnerFillRemaining(winning); winning->type = POKER_WINNING_TYPE_HIGH_CARD; return; } card_t pokerWinnerCompare(pokerplayerwinning_t *left, pokerplayerwinning_t *right) { uint8_t i, number; card_t card; int32_t index; uint8_t countCardsSame; card_t highCardLeft, highCardRight; uint8_t highNumberLeft, highNumberRight; highNumberLeft = 0xFF; highNumberRight = 0xFF; countCardsSame = 0; for(i = 0; i < left->setSize; i++) { card = left->set[i]; number = cardGetNumber(card); if(highNumberLeft != 0xFF && number < highNumberLeft) continue;// Quick check // Check if this number is within the other hand or not index = cardContainsNumber(right->set, right->setSize, number); if(index != -1) { // This number IS within the other hand, let's check that the EXACT card // is a match/isn't a match. index = cardContains(right->set, right->setSize, card); // Exact card match if(index != -1) { countCardsSame++; continue; } // Not exact card match.. ? } if(highNumberLeft == 0xFF||number == CARD_ACE||highNumberLeft < number) { highNumberLeft = number; highCardLeft = card; } } for(i = 0; i < right->setSize; i++) { card = right->set[i]; number = cardGetNumber(card); if(highNumberRight != 0xFF && number < highNumberRight) continue; index = cardContainsNumber(left->set, left->setSize, number); if(index != -1) { index = cardContains(left->set, left->setSize, card); if(index != -1) continue; } if(highNumberRight == 0xFF||number == CARD_ACE||highNumberRight < number) { highNumberRight = number; highCardRight = card; } } if(countCardsSame == left->setSize) { for(i = 0; i < left->setSize; i++) { card = left->set[i]; number = cardGetNumber(card); if(highNumberLeft == 0xFF||number == CARD_ACE||highNumberLeft < number) { highNumberLeft = number; highCardLeft = card; } } return highCardLeft; } if(highCardLeft == 0xFF) return 0xFF; if(highNumberLeft < highNumberRight) return 0xFF; return highCardLeft;//Greater or Equal to. } void pokerWinnerDetermineForPot( poker_t *poker, pokerpot_t *pot, pokerplayerwinning_t winners[POKER_PLAYER_COUNT_MAX], uint8_t winnerPlayers[POKER_PLAYER_COUNT_MAX], uint8_t *winnerCount, uint8_t participants[POKER_PLAYER_COUNT_MAX], uint8_t *participantCount ) { uint8_t i, j, countPlayers, countWinners, number, highNumber; pokerplayerwinning_t *left, *right; pokerplayer_t *player; card_t card, highCard; bool isWinner; countPlayers = 0; countWinners = 0; highCard = 0xFF; // Get participating players and their hands. for(i = 0; i < pot->playerCount; i++) { player = poker->players + pot->players[i]; if(player->state & (POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT)) { continue; } participants[countPlayers] = pot->players[i]; pokerWinnerGetForPlayer(poker, player, winners + countPlayers++); } // Compare participating players for(i = 0; i < countPlayers; i++) { left = winners + i; isWinner = true; highNumber = 0xFF; for(j = 0; j < countPlayers; j++) { if(i == j) continue; right = winners + j; // Am I the better hand / Is it the better hand? if(left->type < right->type) continue; if(left->type > right->type) { isWinner = false; break; } // Equal, compare hands. card = pokerWinnerCompare(left, right); if(card == 0xFF) { isWinner = false; break; } // Determine high card. number = cardGetNumber(card); if(highNumber == 0xFF || number == CARD_ACE || number > highNumber) { highCard = card; highNumber = number; } } if(!isWinner) continue; left->kicker = highCard; winnerPlayers[countWinners++] = participants[i]; } *participantCount = countPlayers; *winnerCount = countWinners; }