/** * Copyright (c) 2021 Dominic Masters * * This software is released under the MIT License. * https://opensource.org/licenses/MIT */ #include "turn.h" pokerturn_t pokerTurnGet(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)) { return pokerTurnOut(poker, playerIndex); } // 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->dealer.cardsFacing == 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->dealer, 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 = poker->bet.currentBet - player->currentBet; // 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->dealer.cardsFacing < 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->dealer.cardsFacing == 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; } void pokerTurnAction(poker_t *poker, pokerplayer_t *player, pokerturn_t *turn) { switch(turn->type) { case POKER_TURN_TYPE_BET: case POKER_TURN_TYPE_CALL: case POKER_TURN_TYPE_ALL_IN: pokerBetPlayer(&poker->bet, player, turn->chips); player->timesRaised++; break; case POKER_TURN_TYPE_CHECK: pokerBetPlayer(&poker->bet, player, 0); player->timesRaised = 0; break; case POKER_TURN_TYPE_FOLD: player->state |= POKER_PLAYER_STATE_FOLDED; player->timesRaised = 0; break; } player->state |= POKER_PLAYER_STATE_ROUND_MOVE; } pokerturn_t pokerTurnOut(poker_t *poker, uint8_t player) { pokerturn_t turn; turn.type = POKER_TURN_TYPE_OUT; return turn; } pokerturn_t pokerTurnFold(poker_t *poker, uint8_t player) { pokerturn_t turn; turn.type = POKER_TURN_TYPE_FOLD; return turn; } pokerturn_t pokerTurnCheck(poker_t *poker, uint8_t player) { return pokerTurnRaise(poker, player, 0); } pokerturn_t pokerTurnCall(poker_t *poker, uint8_t playerIndex) { pokerturn_t turn; pokerplayer_t *player; player = poker->players + playerIndex; turn = pokerTurnRaise( poker, playerIndex, poker->bet.currentBet - player->currentBet ); return turn; } pokerturn_t pokerTurnRaise(poker_t *poker, uint8_t playerIndex, int32_t chips) { pokerturn_t turn; pokerplayer_t *player; player = poker->players + playerIndex; 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; if(chips == (poker->bet.currentBet - player->currentBet)) { turn.type = POKER_TURN_TYPE_CALL; } } return turn; } bool pokerTurnCanPlayerCheck(poker_t *poker, uint8_t playerIndex) { return (poker->players + playerIndex)->currentBet >= poker->bet.currentBet; }