/** * Copyright (c) 2022 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "poker.h" pokerplayer_t POKER_PLAYERS[POKER_PLAYER_COUNT_MAX]; uint8_t POKER_DECK[CARD_DECK_SIZE]; uint8_t POKER_DECK_SIZE; uint8_t POKER_COMMUNITY[POKER_COMMUNITY_SIZE_MAX]; uint8_t POKER_COMMUNITY_SIZE; uint8_t POKER_PLAYER_DEALER; uint8_t POKER_PLAYER_SMALL_BLIND; uint8_t POKER_PLAYER_BIG_BLIND; uint8_t POKER_PLAYER_BETTER; uint16_t POKER_GAME_BLINDS_CURRENT; pokerpot_t POKER_POTS[POKER_POT_COUNT_MAX]; uint8_t POKER_POT_CURRENT; uint8_t POKER_POT_COUNT; pokerplayerwinning_t POKER_WINNERS[POKER_PLAYER_COUNT_MAX]; uint8_t POKER_WINNER_PLAYERS[POKER_PLAYER_COUNT_MAX]; uint8_t POKER_WINNER_PARTICIPANTS[POKER_PLAYER_COUNT_MAX]; uint8_t POKER_WINNER_COUNT; uint8_t POKER_WINNER_PARTICIPANT_COUNT; void pokerInit() { uint8_t i; // Set up players for(i = 0; i < POKER_PLAYER_COUNT_MAX; i++) { POKER_PLAYERS[i].chips = 10000; POKER_PLAYERS[i].state = 0; } // Set up the initial state. // TODO: Should this be randomized? POKER_PLAYER_DEALER = 0; POKER_GAME_BLINDS_CURRENT = 10; // Reset the round state (For the first round) pokerNewRound(); } void pokerNewRound() { uint8_t i, j, k; uint8_t found; // Reset round state POKER_COMMUNITY_SIZE = 0; POKER_POT_COUNT = 0; POKER_POT_CURRENT = 0; // Reset the pots. for(i = 0; i < POKER_POT_COUNT_MAX; i++) { POKER_POTS[i].chips = 0; POKER_POTS[i].call = 0; for(j = 0; j < POKER_PLAYER_COUNT_MAX; j++) { POKER_POTS[i].players[j] = 0; } } // Fill deck for(i = 0; i < CARD_DECK_SIZE; i++) POKER_DECK[i] = i; POKER_DECK_SIZE = CARD_DECK_SIZE; // Shuffle Deck for(i = CARD_DECK_SIZE-1; i > 0; i--) { k = POKER_DECK[i]; j = rand() % (i+1); POKER_DECK[i] = POKER_DECK[j]; POKER_DECK[j] = k; } // Reset Players and decide new blinds. found = 0; POKER_PLAYER_DEALER++; for(i = 0; i < POKER_PLAYER_COUNT_MAX; i++) { POKER_PLAYERS[i].state &= ~( POKER_PLAYER_STATE_FOLDED | POKER_PLAYER_STATE_HAS_BET_THIS_ROUND ); POKER_PLAYERS[i].timesRaised = 0; // Have we found the dealer, small blind, and big blind players? if(found != 3 && (POKER_PLAYERS[i].state & POKER_PLAYER_STATE_OUT) == 0){ k = (POKER_PLAYER_DEALER + i) % POKER_PLAYER_COUNT_MAX; if(found == 0) {// Have we found the dealer? POKER_PLAYER_DEALER = i; found++; } else if(found == 1) {// Have we found the small blind? POKER_PLAYER_SMALL_BLIND = i; found++; } else if(found == 2) {// Have we found the big blind? POKER_PLAYER_BIG_BLIND = i; found++; } } // Deal two cards to the player. for(j = 0; j < POKER_PLAYER_HAND_SIZE_MAX; j++) { POKER_PLAYERS[i].hand[j] = POKER_DECK[POKER_DECK_SIZE--]; } } // Take blinds // TODO: I need to make sure the blind players even have the chips to blind. pokerBet(POKER_PLAYER_SMALL_BLIND, POKER_GAME_BLINDS_CURRENT); pokerBet(POKER_PLAYER_BIG_BLIND, (POKER_GAME_BLINDS_CURRENT*2)); // Set the initial better, we set this to the BIG BLIND player because we will // cycle to the "next better" as soon as the game starts. POKER_PLAYER_BETTER = POKER_PLAYER_BIG_BLIND; } inline void pokerBet(uint8_t player, uint16_t amount) { // TODO: This may become a function because if a player doesn't have enough // chips to bet to the active pot, then the pot needs to autosplit, take those // who have bet into the pot up to the amount that the player betting can bet, // and push them into a new pot. // There also needs to be a limit on this, for example; // player 0 has $1200, and bets $1000, they can't bet more than that ever. // player 1 has $1000, and bets all of it. The remanin // player 2 has $700, and bets all o it. A new $300 sidepot auto creates // player 3 has $500 and bets all of it, Another sidepot with $200 is auto made. POKER_PLAYERS[player].state |= POKER_PLAYER_STATE_HAS_BET_THIS_ROUND; POKER_PLAYERS[player].chips -= amount; POKER_POTS[POKER_POT_CURRENT].chips += amount; POKER_POTS[POKER_POT_CURRENT].players[player] += amount; POKER_POTS[POKER_POT_CURRENT].call = MATH_MAX( amount, POKER_POTS[POKER_POT_CURRENT].players[player] ); } inline uint8_t pokerGetCallBet(uint8_t player) { return ( POKER_POTS[POKER_POT_CURRENT].call - POKER_POTS[POKER_POT_CURRENT].players[player] ); } void pokerAi(uint8_t player, pokerturn_t *turn) { uint8_t i, cardNumber0, cardNumber1, suitNumber0, suitNumber1; uint16_t callBet, maxBet, amount, bluffBet; uint8_t random;// TODO: Determine type. uint16_t confidence, expectedGain, potOdds; pokerplayerwinning_t winning; pokerplayer_t *plyr = POKER_PLAYERS + player; // 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 hand being the winning hand. // Is this preflop? if(POKER_COMMUNITY_SIZE == 0) { // Get the hand weight cardNumber0 = cardGetNumber(plyr->hand[0]); cardNumber1 = cardGetNumber(plyr->hand[1]); suitNumber0 = cardGetSuit(plyr->hand[0]); suitNumber1 = cardGetSuit(plyr->hand[1]); // Get delta between cards i = MATH_ABS(cardNumber0 - cardNumber1); // Get card weight confidence = cardNumber0 + 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; } // Confidence is now a value between 0-30 (inclusive). Multiply by 1000 confidence = (confidence * 1000) / 30; // Now do over 30 to get the value represented as 0-1000 } else { // Simulate my hand being the winning hand, use that as the confidence // TODO: Repurpose old code lmao. Just take it from Dawn-C BGB_printf("Holy shit you guys"); pokerWinnerGetForPlayer(player, &winning); BGB_printf("Winning type %u", winning.type); 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 = pokerGetCallBet(player); // Do they need chips to call, or is it possible to check? if(callBet > 0) { // Work out how many chips the player could possibly win. This is a number // between 0 and player count * initial chips, at the time of writing that // is around 50,000. expectedGain = 0; for(i = 0; i < POKER_POT_COUNT; i++) { if(POKER_POTS[i].players[player] == 0) break; expectedGain += POKER_POTS[i].chips; } // Now work out the pot odds, this is basically "how many times the callbet // could I win if I win this hand". e.g. Desirable hands will have an // expected gain much higher than the callbet. potOdds = plyr->chips / 1000; potOdds = MATH_MAX((callBet + expectedGain), 1) / MATH_MAX(potOdds, 1); } else { // If the call bet is zero then there's fairly equal odds, so let's just // take the chances out of the remainig player count. potOdds = 1000 / pokerGetRemainingBetterCount() * 2;// 0 - 1000 } // Now determine the expected ROI //TODO: I think these values are a bit odd. expectedGain = (confidence*100) / (potOdds / 10); // Now get a random number 0-100. random = rand() % 100; // Determine the max bet that the AI is willing to make. The max bet is // basically how high the AI is willing to bet. maxBet = plyr->chips;// TODO: Replace with below code and test, just running // With this for now. // maxBet = plyr->chips / MATH_MAX(random / 10, 1); // maxBet -= callBet; // BGB_printf("Rand %u, Max Bet %u", random, callBet); // Determine what's a good bluff bet. // TODO: not float bluffBet = maxBet; // bluffBet = ((random * 100) / maxBet) * plyr->chips; // Now prep the output turn->bluff = 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 < 800 && confidence < 800) { if(random < 95) { amount = 0; } else { amount = bluffBet; turn->bluff = true; } } else if ((expectedGain < 1000 && confidence < 850) || confidence < 100) { if (random < 80) { amount = 0; } else if(random < 5) { amount = callBet; turn->bluff = true; } else { amount = bluffBet; turn->bluff = true; } } else if ((expectedGain < 1300 && confidence < 900) || confidence < 500) { if (random < 60 || confidence < 500) { amount = callBet; } else { amount = maxBet; } } else if (confidence < 950 || POKER_COMMUNITY_SIZE < 0x04) { if(random < 20) { amount = callBet; } else { amount = maxBet; } } else { // TODO: check this amount = (plyr->chips - callBet) * 9 / 10; } // If this is the first round... make it a lot less likely I'll bet if(POKER_COMMUNITY_SIZE == 0x00 && amount > callBet) { if(random > 5) amount = callBet; } // Did we actually bet? if(amount > 0) { // Let's not get caught in a raising loop with AI. if(plyr->timesRaised >= POKER_TURN_MAX_RAISES) amount = callBet; amount = MATH_MAX(amount, callBet); amount = MATH_MIN(amount, plyr->chips); turn->chips = amount; turn->confidence = confidence; if(amount == plyr->chips) { turn->type = POKER_TURN_TYPE_ALL_IN; } else if(amount == callBet) { turn->type = POKER_TURN_TYPE_CALL; } else { turn->type = POKER_TURN_TYPE_BET; } } else if(pokerCanPlayerCheck(player)) { turn->type = POKER_TURN_TYPE_CHECK; turn->chips = 0; turn->confidence = 1000; } else { turn->type = POKER_TURN_TYPE_FOLD; turn->chips = 0; turn->confidence = 1000 - confidence; } } inline bool pokerCanPlayerCheck(uint8_t player) { return ( POKER_POTS[POKER_POT_CURRENT].players[player] == POKER_POTS[POKER_POT_CURRENT].call ); } inline bool pokerDoesPlayerNeedToBet(uint8_t playerIndex) { uint8_t i; pokerplayer_t *player = POKER_PLAYERS + playerIndex; // Can this player even participate? if( (player->state & (POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT)) != 0 || player->chips == 0 ) { return false; } // Has the player bet? If so are they in the current pot? if((player->state & POKER_PLAYER_STATE_HAS_BET_THIS_ROUND) == 0) { return true; } //TODO: Refer to pokerbet function, but basically I can't let the player //bet if they have bet more money than the second richest player. // Check each pot, if the pot is inactive or the player is CALLED/CHECKED for(i = 0; i < POKER_POT_COUNT_MAX; i++) { // Is this pot active? if(POKER_POTS[i].chips == 0) break; // Is the player called into this pot all the way? if(POKER_POTS[i].players[playerIndex] == POKER_POTS[i].call) continue; return true; } return false; } inline uint8_t pokerGetRemainingBetterCount() { uint8_t i, count; count = 0; for(i = 0 ; i < POKER_PLAYER_COUNT_MAX; i++) { if(pokerDoesPlayerNeedToBet(i)) count++; } return count; } void pokerWinnerFillRemaining(pokerplayerwinning_t *winning) { uint8_t i, highest, current, 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) != 0xFF) { 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(uint8_t playerIndex,pokerplayerwinning_t *winning){ uint8_t i, j, l, card, number, suit, pairCount; int16_t index; int16_t pairs[CARD_SUIT_COUNT]; pokerplayer_t *player; player = POKER_PLAYERS + playerIndex; // Get the full poker hand (should be a 7 card hand, but MAY not be) for(i = 0; i < POKER_COMMUNITY_SIZE; i++) { winning->full[i] = POKER_COMMUNITY[i]; } for(i = 0; i < POKER_PLAYER_HAND_SIZE_MAX; i++) { winning->full[i + POKER_COMMUNITY_SIZE] = player->hand[i]; } winning->fullSize = POKER_COMMUNITY_SIZE + POKER_PLAYER_HAND_SIZE_MAX; // TODO: Do I need to sort this? // 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 == 0xFF) 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) != 0xFF) 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 == 0xFF) 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) != 0xFF) { 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]]; } 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; } inline uint16_t pokerWinnerGetTypeConfidence(uint8_t type) { switch(type) { case POKER_WINNING_TYPE_ROYAL_FLUSH: return POKER_WINNING_CONFIDENCE_ROYAL_FLUSH; case POKER_WINNING_TYPE_STRAIGHT_FLUSH: return POKER_WINNING_CONFIDENCE_STRAIGHT_FLUSH; case POKER_WINNING_TYPE_FOUR_OF_A_KIND: return POKER_WINNING_CONFIDENCE_FOUR_OF_A_KIND; case POKER_WINNING_TYPE_FULL_HOUSE: return POKER_WINNING_CONFIDENCE_FULL_HOUSE; case POKER_WINNING_TYPE_FLUSH: return POKER_WINNING_CONFIDENCE_FLUSH; case POKER_WINNING_TYPE_STRAIGHT: return POKER_WINNING_CONFIDENCE_STRAIGHT; case POKER_WINNING_TYPE_THREE_OF_A_KIND: return POKER_WINNING_CONFIDENCE_THREE_OF_A_KIND; case POKER_WINNING_TYPE_TWO_PAIR: return POKER_WINNING_CONFIDENCE_TWO_PAIR; case POKER_WINNING_TYPE_PAIR: return POKER_WINNING_CONFIDENCE_PAIR; default: return POKER_WINNING_CONFIDENCE_HIGH_CARD; } } uint8_t pokerWinnerCompare( pokerplayerwinning_t *left, pokerplayerwinning_t *right ) { uint8_t i, number, card, countCardsSame, index, highCardLeft, highCardRight, 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 == 0xFF) { // 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 != 0xFF) { 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 != 0xFF) { index = cardContains(left->set, left->setSize, card); if(index != 0xFF) 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( pokerpot_t *pot, pokerplayerwinning_t *winners, uint8_t *winnerPlayers, uint8_t *winnerCount, uint8_t *participants, uint8_t *participantCount ) { uint8_t i, j, countPlayers, countWinners, number, highNumber, card, highCard; pokerplayerwinning_t *left, *right; pokerplayer_t *player; bool isWinner; countPlayers = 0; countWinners = 0; highCard = 0xFF; // Get participating players and their hands. for(i = 0; i < POKER_PLAYER_COUNT_MAX; i++) { if(pot->players[i] == 0) continue; player = POKER_PLAYERS + i; if(player->state & (POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT)) { continue; } participants[countPlayers] = i; pokerWinnerGetForPlayer(i, 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; }