From 31a6271f271ee2fd04b3012519571ae4825b49e7 Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Thu, 13 Jan 2022 23:07:00 -0800 Subject: [PATCH] Brought over unfinished ai code. --- src/conversation/queue.c | 47 ++--------- src/poker/card.c | 16 ++++ src/poker/card.h | 5 +- src/poker/player.h | 6 +- src/poker/poker.c | 168 ++++++++++++++++++++++++++++++++++++++- src/poker/poker.h | 10 ++- src/poker/turn.h | 20 +++++ 7 files changed, 219 insertions(+), 53 deletions(-) create mode 100644 src/poker/card.c create mode 100644 src/poker/turn.h diff --git a/src/conversation/queue.c b/src/conversation/queue.c index 9c4c5e2..1da1722 100644 --- a/src/conversation/queue.c +++ b/src/conversation/queue.c @@ -58,41 +58,7 @@ void conversationQueueNextBetter() { // Next better is the better to the right of the current better. j = (POKER_PLAYER_BETTER + i) % POKER_PLAYER_COUNT_MAX; - - - // Can this player even participate? - if( - ( - POKER_PLAYERS[j].state & ( - POKER_PLAYER_STATE_FOLDED|POKER_PLAYER_STATE_OUT - ) - ) != 0 || POKER_PLAYERS[j].chips == 0 - ) continue; - - // Has the player bet? If so are they in the current pot? - if(( - POKER_PLAYERS[j].state & - POKER_PLAYER_STATE_HAS_BET_THIS_ROUND - ) != 0) { - //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(k = 0; k < POKER_POT_COUNT_MAX; k++) { - // Is this pot active? - if(POKER_POTS[k].chips == 0) { - k = POKER_POT_COUNT_MAX; - break; - } - - // Is the player called into this pot all the way? - if(POKER_POTS[k].players[j] == POKER_POTS[k].call) continue; - break; - } - - // Then skip to next player. - if(k == POKER_POT_COUNT_MAX) continue; - } + if(!pokerDoesPlayerNeedToBet(j)) continue; // They haven't bet yet, make them the "next better" POKER_PLAYER_BETTER = j; @@ -113,11 +79,6 @@ void conversationQueueNextBetter() { return; } - // Reset the players betting state so that they may bet next round. - for(i = 0; i < POKER_PLAYER_COUNT_MAX; i++) { - POKER_PLAYERS[i].state &= ~POKER_PLAYER_STATE_HAS_BET_THIS_ROUND; - } - QUEUE_ITEM = QUEUE_FLOP; conversationQueueNext(); } @@ -144,6 +105,12 @@ void conversationQueueFlopTurnRiver() { break; } + // Reset each players required to bet state + for(i = 0; i < POKER_PLAYER_COUNT_MAX; i++) { + POKER_PLAYERS[i].state &= ~POKER_PLAYER_STATE_HAS_BET_THIS_ROUND; + POKER_PLAYERS[i].timesRaised = 0; + } + // In reality we'd burn the top card but that would waste some CPU I need. // Deal the top cards. for(i = 0; i < count; i++) { diff --git a/src/poker/card.c b/src/poker/card.c new file mode 100644 index 0000000..bf355ee --- /dev/null +++ b/src/poker/card.c @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#include "card.h" + +inline uint8_t cardGetNumber(uint8_t card) { + return card % CARD_COUNT_PER_SUIT; +} + +inline uint8_t cardGetSuit(uint8_t card) { + return card / CARD_SUIT_COUNT; +} \ No newline at end of file diff --git a/src/poker/card.h b/src/poker/card.h index 7bfdf05..17ccf60 100644 --- a/src/poker/card.h +++ b/src/poker/card.h @@ -104,4 +104,7 @@ #define CARD_SUIT_COUNT 4 /** Standard Card Deck Size */ -#define CARD_DECK_SIZE 52 \ No newline at end of file +#define CARD_DECK_SIZE 52 + +inline uint8_t cardGetNumber(uint8_t card); +inline uint8_t cardGetSuit(uint8_t card); \ No newline at end of file diff --git a/src/poker/player.h b/src/poker/player.h index 2da0f9a..32c61a6 100644 --- a/src/poker/player.h +++ b/src/poker/player.h @@ -9,7 +9,7 @@ #include "../libs.h" #include "card.h" -#define POKER_PLAYER_COUNT_MAX 4 +#define POKER_PLAYER_COUNT_MAX 5 #define POKER_PLAYER_HAND_SIZE_MAX 2 #define POKER_PLAYER_STATE_FOLDED 1 << 0 @@ -21,9 +21,7 @@ typedef struct { uint16_t chips; uint8_t hand[POKER_PLAYER_HAND_SIZE_MAX]; uint8_t state; - - // int32_t currentBet; - // uint8_t timesRaised; + uint8_t timesRaised; } pokerplayer_t; extern pokerplayer_t POKER_PLAYERS[]; \ No newline at end of file diff --git a/src/poker/poker.c b/src/poker/poker.c index d848f93..5d61db4 100644 --- a/src/poker/poker.c +++ b/src/poker/poker.c @@ -82,6 +82,7 @@ void pokerNewRound() { 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){ @@ -137,14 +138,16 @@ inline void pokerBet(uint8_t player, uint16_t amount) { inline uint8_t pokerGetCallBet(uint8_t player) { return ( POKER_POTS[POKER_POT_CURRENT].call - - POKER_POTS[POKER_POT_CURRENT].players[POKER_PLAYER_BETTER] + POKER_POTS[POKER_POT_CURRENT].players[player] ); } -void pokerAi(uint8_t player) { +void pokerAi(uint8_t player, pokerturn_t *turn) { uint8_t i, cardNumber0, cardNumber1, suitNumber0, suitNumber1; - uint16_t callBet; - uint8_t confidence;// TODO: Determine type. + uint16_t callBet, maxBet, amount, bluffBet; + uint8_t random;// TODO: Determine type. + bool isBluff; + uint16_t confidence, expectedGain, potOdds; pokerplayer_t *plyr = POKER_PLAYERS + player; // The following logic is heavily inspired by; @@ -180,12 +183,169 @@ void pokerAi(uint8_t player) { } 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 + confidence = 0; } // 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. + //TODO: not float + potOdds = callBet / (callBet + expectedGain); + } 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(); + } + + // Now determine the expected ROI + // TODO: not float + expectedGain = confidence / potOdds; + + // Now get a random number 0-100. + random = rand() % 100; + + // Determine the max bet that the AI is willing to make. This is valued as + // "3/4 chips remaining - random(0-50)" and subtract the callbet. I will + // probably chance this since the random 0-50 is.. random + // TODO: Rewrite this + maxBet = plyr->chips - (random / 2); + maxBet -= callBet; + + // Determine what's a good bluff bet. + // TODO: not float + 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 < 800 && confidence < 800) { + if(random < 95) { + amount = 0; + } else { + amount = bluffBet; + isBluff = true; + } + } else if ((expectedGain < 1000 && confidence < 850) || confidence < 100) { + if (random < 80) { + amount = 0; + } else if(random < 5) { + amount = callBet; + isBluff = true; + } else { + amount = bluffBet; + isBluff = 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 { + 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); + // turn = pokerTurnBet(poker, playerIndex, amount); + turn->confidence = confidence; + } else if(pokerCanPlayerCheck(player)) { + // turn = pokerTurnBet(poker, playerIndex, 0); + turn->confidence = 1000; + } else { + // turn = pokerTurnFold(poker, playerIndex); + 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; } \ No newline at end of file diff --git a/src/poker/poker.h b/src/poker/poker.h index fc58860..9349260 100644 --- a/src/poker/poker.h +++ b/src/poker/poker.h @@ -11,6 +11,7 @@ #include "card.h" #include "player.h" #include "pot.h" +#include "turn.h" #define POKER_COMMUNITY_SIZE_MAX 5 #define POKER_HUMAN_INDEX 0x00 @@ -32,9 +33,10 @@ extern uint8_t POKER_PLAYER_BETTER; extern uint16_t POKER_GAME_BLINDS_CURRENT; void pokerInit(); - void pokerNewRound(); - inline void pokerBet(uint8_t player, uint16_t amount); - -inline uint8_t pokerGetCallBet(uint8_t player); \ No newline at end of file +inline uint8_t pokerGetCallBet(uint8_t player); +inline bool pokerCanPlayerCheck(uint8_t player); +inline bool pokerDoesPlayerNeedToBet(uint8_t playerIndex); +inline uint8_t pokerGetRemainingBetterCount(); +void pokerAi(uint8_t player, pokerturn_t *turn); \ No newline at end of file diff --git a/src/poker/turn.h b/src/poker/turn.h new file mode 100644 index 0000000..7881d42 --- /dev/null +++ b/src/poker/turn.h @@ -0,0 +1,20 @@ +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "../libs.h" + +#define POKER_TURN_MAX_RAISES 0x02 + +typedef struct { + /** What type of action the turn is */ + uint8_t type; + /** How many chips they did in their turn (if applicable) */ + uint16_t chips; + /** How confident the AI is about their turn. 0 = none, 1 = full */ + uint16_t confidence; +} pokerturn_t; \ No newline at end of file