Move over old poker code

This commit is contained in:
2024-09-08 21:54:19 -05:00
parent 856bc306fe
commit b4e261d954
20 changed files with 1576 additions and 398 deletions

View File

@ -0,0 +1,15 @@
# Copyright (c) 2022 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT
# Sources
target_sources(${DAWN_TARGET_NAME}
PRIVATE
Card.cpp
PokerPot.cpp
PokerPlayer.cpp
PokerGame.cpp
PokerWinning.cpp
PokerTurn.cpp
)

View File

@ -0,0 +1,70 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "Card.hpp"
using namespace Dawn;
void Card::fillDeck(std::vector<struct Card> *deck) {
assertNotNull(deck);
for(uint8_t i = 0; i < CARD_DECK_SIZE; i++) {
deck->push_back(Card(i));
}
}
int32_t Card::contains(std::vector<struct Card> *deck, struct Card c) {
assertNotNull(deck);
auto it = deck->begin();
while(it != deck->end()) {
if(it->cardValue == c.cardValue) return (int32_t)(it - deck->begin());
++it;
}
return -1;
}
int32_t Card::containsNumber(
std::vector<struct Card> *deck,
enum CardValue number
) {
assertNotNull(deck);
assertTrue(number < CARD_COUNT_PER_SUIT);
auto it = deck->begin();
while(it != deck->end()) {
if(it->getValue() == number) return (int32_t)(it - deck->begin());
++it;
}
return -1;
}
std::vector<struct Card> Card::countPairs(
std::vector<struct Card> *deck,
enum CardValue val
) {
std::vector<struct Card> pairs;
assertNotNull(deck);
assertTrue(deck->size() > 0);
auto it = deck->begin();
while(it != deck->end()) {
if(it->getValue() == val) pairs.push_back(*it);
++it;
}
return pairs;
}
bool_t Card::cardSorter(struct Card left, struct Card right) {
return left.cardValue < right.cardValue;
}
void Card::sort(std::vector<struct Card> *deck) {
assertNotNull(deck);
assertTrue(deck->size() > 1);
std::sort(deck->begin(), deck->end(), &Card::cardSorter);
}

View File

@ -0,0 +1,137 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawnlibs.hpp"
#include "assert/assert.hpp"
namespace Dawn {
enum class CardSuit : uint8_t {
Clubs = 0,
Diamonds = 1,
Hearts = 2,
Spades = 3,
Invalid = 0xFF
};
enum class CardValue : uint8_t {
Two = 0,
Three = 1,
Four = 2,
Five = 3,
Six = 4,
Seven = 5,
Eight = 6,
Nine = 7,
Ten = 8,
Jack = 9,
Queen = 10,
King = 11,
Ace = 12,
Invalid = 0xFF
};
/** Count of cards in each suit */
#define CARD_COUNT_PER_SUIT 13
/** Count of suits */
#define CARD_SUIT_COUNT 4
/** Standard Card Deck Size */
#define CARD_DECK_SIZE CARD_COUNT_PER_SUIT*CARD_SUIT_COUNT
struct Card {
public:
uint8_t cardValue;
/**
* Shuffles a hand / deck
*
* @param deck Array of cards to shuffle.
*/
static void shuffle(std::vector<struct Card> *deck);
/**
* Fills a vector with all of the cards in a deck, in order.
*
* @param deck Deck to fill.
*/
static void fillDeck(std::vector<struct Card> *deck);
/**
* Check if an array of cards contains a specific card.
*
* @param deck Deck/Hand/Array of cards to check.
* @param card Card to look for
* @returns The index within the array that the card is. -1 if not found.
*/
static int32_t contains(std::vector<struct Card> *deck, struct Card card);
/**
* Check if the array of cards contains a specific number.
*
* @param deck Array of cards to check
* @param number The number to look for.
* @returns The index that the first card is. -1 if not found.
*/
static int32_t containsNumber(
std::vector<struct Card> *deck,
enum CardValue number
);
/**
* Counts the amount of times a card's number appears within the given
* hand.
*
* @param deck The hand to check
* @param val Value of pairs to find.
* @return Card pairs in the deck.
*/
static std::vector<struct Card> countPairs(
std::vector<struct Card> *deck,
enum CardValue val
);
static bool_t cardSorter(struct Card left, struct Card right);
/**
* Sort a hand of cards. Cards are ordered in descending weight, aces are
* high. Cards will be grouped by their suits, e.g. CARD_CLUBS_TWO will
* appear before CARD_DIAMONDS_KING.
*
* @param deck Hand of cards to sort.
*/
static void sort(std::vector<struct Card> *deck);
Card(CardSuit suit, CardValue num) :
cardValue(((uint8_t)suit * CARD_COUNT_PER_SUIT) + num)
{
if(suit == CardSuit::Invalid || num == CardValue::Invalid) {
this->cardValue = 0xFF;
}
}
Card(uint8_t cv) : cardValue(cv) {
// assertTrue(cv < CARD_DECK_SIZE);
}
/**
* Returns the number of a given card.
* @returns The card number.
*/
CardValue getValue() {
return (CardValue)(cardValue % CARD_COUNT_PER_SUIT);
}
/**
* Returns the suit of a given card.
* @returns The suit.
*/
CardSuit getSuit() {
return (CardSuit)(cardValue / CARD_COUNT_PER_SUIT);
}
};
}

View File

@ -0,0 +1,197 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "PokerGame.hpp"
using namespace Dawn;
PokerGame::PokerGame(SceneItem *item) : SceneItemComponent(item) {
}
void PokerGame::onStart() {
SceneItemComponent::onStart();
this->players = this->getScene()->findComponents<PokerPlayer>();
assertTrue(this->players.size() > 0);
this->newGame();
}
void PokerGame::newGame() {
this->newRound();
auto it = this->players.begin();
while(it != this->players.end()) {
auto player = *it;
player->setChips(POKER_PLAYER_CHIPS_DEFAULT);
player->isOut = false;
++it;
}
this->setDealer(0x00);
}
void PokerGame::newRound() {
this->deck.clear();
Card::fillDeck(&this->deck);
this->smallBlind = POKER_BLIND_SMALL_DEFAULT;
this->bigBlind = POKER_BLIND_BIG_DEFAULT;
this->grave.clear();
this->community.clear();
this->pots.clear();
this->pots.push_back(PokerPot());
auto it = this->players.begin();
while(it != this->players.end()) {
auto player = *it;
player->hand.clear();
player->currentBet = 0;
player->isFolded = false;
player->isShowingHand = false;
player->hasBetThisRound = false;
player->timesRaised = 0;
player->currentBet = 0;
++it;
}
this->setDealer(this->dealerIndex + 0x01);
}
void PokerGame::newBettingRound() {
auto it = this->players.begin();
while(it != this->players.end()) {
auto player = *it;
player->hasBetThisRound = false;
player->timesRaised = 0;
++it;
}
this->betterIndex = this->bigBlindIndex;
this->betterIndex = this->getNextBetterIndex();
}
uint8_t PokerGame::getNextBetterIndex() {
uint8_t j, i;
for(i = 0; i < this->players.size(); i++) {
j = (i + this->betterIndex) % this->players.size();
auto player = this->players[j];
if(player->needsToBetThisRound()) return j;
}
return 0xFF;
}
void PokerGame::takeBlinds() {
auto playerSmallBlind = this->players[this->smallBlindIndex];
auto playerBigBlind = this->players[this->bigBlindIndex];
playerSmallBlind->bet(this->smallBlind);
playerBigBlind->bet(this->bigBlind);
playerSmallBlind->hasBetThisRound = false;
playerBigBlind->hasBetThisRound = false;
}
void PokerGame::setDealer(uint8_t dealer) {
uint8_t i, k;
PokerPlayer *player;
bool_t foundDealer;
bool_t foundSmall;
foundDealer = false;
foundSmall = false;
this->dealerIndex = dealer;
for(i = 0; i < this->players.size(); i++) {
k = (dealer + i) % this->players.size();
player = this->players[k];
if(player->isOut) continue;
if(!foundDealer) {
this->dealerIndex = k;
foundDealer = true;
} else if(!foundSmall) {
this->smallBlindIndex = k;
foundSmall = true;
} else {
this->bigBlindIndex = k;
break;
}
}
}
uint8_t PokerGame::getRemainingBettersCount() {
uint8_t count = 0;
auto it = this->players.begin();
while(it != this->players.end()) {
if((*it)->needsToBetThisRound()) count++;
++it;
}
return count;
}
uint8_t PokerGame::getRemainingPlayersCount() {
uint8_t count = 0;
auto it = this->players.begin();
while(it != this->players.end()) {
auto player = *it;
if(!player->isFolded && !player->isOut) count++;
++it;
}
return count;
}
int32_t PokerGame::getCurrentCallValue() {
assertTrue(this->pots.size() > 0);
return this->pots.back().call;
}
void PokerGame::burnCard() {
assertTrue(this->deck.size() > 0);
auto card = this->deck.back();
this->deck.pop_back();
this->grave.push_back(card);
}
void PokerGame::dealCard(PokerPlayer *player) {
assertTrue(this->deck.size() > 0);
auto card = this->deck.back();
this->deck.pop_back();
player->hand.push_back(card);
}
void PokerGame::dealToEveryone(uint8_t count) {
for(uint8_t i = 0; i < count; i++) {
auto it = this->players.begin();
while(it != this->players.end()) {
this->dealCard(*it);
++it;
}
}
}
void PokerGame::turn(uint8_t count) {
assertTrue(this->deck.size() >= count);
for(uint8_t i = 0; i < count; i++) {
auto card = this->deck.back();
this->deck.pop_back();
this->community.push_back(card);
}
}
uint8_t PokerGame::getCountOfCardsToTurn() {
switch(this->community.size()) {
case 0x00:
return POKER_FLOP_CARD_COUNT;
case 0x03:
return POKER_TURN_CARD_COUNT;
case 0x04:
return POKER_RIVER_CARD_COUNT;
default:
return 0xFF;
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "PokerPlayer.hpp"
/** The default blind cost for the big blind. */
#define POKER_BLIND_BIG_DEFAULT 600
/** The default blind cost for the small blind. (Defaults half big blind) */
#define POKER_BLIND_SMALL_DEFAULT (POKER_BLIND_BIG_DEFAULT/2)
/** How many cards are dealt for the flop, turn and river */
#define POKER_FLOP_CARD_COUNT 3
#define POKER_TURN_CARD_COUNT 1
#define POKER_RIVER_CARD_COUNT 1
namespace Dawn {
class PokerGame : public SceneItemComponent {
protected:
std::vector<struct Card> deck;
std::vector<struct Card> grave;
std::vector<struct Card> community;
uint8_t dealerIndex;
uint8_t smallBlindIndex;
uint8_t bigBlindIndex;
int32_t smallBlind = POKER_BLIND_SMALL_DEFAULT;
int32_t bigBlind = POKER_BLIND_BIG_DEFAULT;
public:
std::vector<PokerPlayer*> players;
std::vector<struct PokerPot> pots;
uint8_t betterIndex;
PokerGame(SceneItem *item);
void onStart() override;
void newGame();
void newRound();
void newBettingRound();
void takeBlinds();
void setBlinds(int32_t small, int32_t big);
uint8_t getRemainingBettersCount();
int32_t getCurrentCallValue();
uint8_t getNextBetterIndex();
void setDealer(uint8_t dealer);
void newDealer();
void burnCard();
void dealCard(PokerPlayer *player);
void dealToEveryone(uint8_t count);
void turn(uint8_t count);
uint8_t getCountOfCardsToTurn();
uint8_t getRemainingPlayersCount();
friend class PokerPlayer;
};
}

View File

@ -0,0 +1,450 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "PokerPlayer.hpp"
#include "PokerGame.hpp"
using namespace Dawn;
PokerPlayer::PokerPlayer(SceneItem *item) : SceneItemComponent(item) {
}
void PokerPlayer::onStart() {
SceneItemComponent::onStart();
this->pokerGame = this->getScene()->findComponent<PokerGame>();
}
void PokerPlayer::addChips(int32_t chips) {
assertTrue(chips > 0);
this->chips += chips;
if(this->chips > 0) this->isOut = false;
eventChipsChanged.invoke();
}
void PokerPlayer::setChips(int32_t chips) {
this->chips = 0;
this->addChips(chips);
}
bool_t PokerPlayer::needsToBetThisRound() {
if(this->isFolded) return false;
if(this->chips <= 0) return false;
if(!this->hasBetThisRound) return true;
if(this->currentBet < this->pokerGame->getCurrentCallValue()) return true;
return false;
}
void PokerPlayer::bet(struct PokerPot *pot, int32_t chips) {
assertNotNull(pot);
assertTrue(chips >= 0);
assertTrue(!this->isFolded);
assertTrue(!this->isOut);
this->setChips(this->chips - chips);
this->currentBet += chips;
this->hasBetThisRound = true;
if(chips > 0) {
this->timesRaised++;
} else {
this->timesRaised = 0;
}
pot->chips += chips;
pot->call = mathMax<int32_t>(pot->call, this ->currentBet);
auto existing = std::find(pot->players.begin(), pot->players.end(), this);
if(existing == pot->players.end()) pot->players.push_back(this);
}
void PokerPlayer::bet(int32_t chips) {
assertTrue(this->pokerGame->pots.size() > 0);
this->bet(&this->pokerGame->pots.back(), chips);
}
void PokerPlayer::fold() {
this->isFolded = true;
this->hasBetThisRound = true;
this->timesRaised = 0;
}
bool_t PokerPlayer::canCheck() {
return this->pokerGame->getCurrentCallValue() <= this->currentBet;
}
struct PokerTurn PokerPlayer::getAITurn() {
struct PokerTurn turn;
float_t confidence;
int32_t callBet;
float_t potOdds;
// Can the player do anything?
if(this->isFolded || this->isOut) {
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(this->pokerGame->community.size() == 0) {
assertTrue(this->hand.size() == POKER_PLAYER_HAND_SIZE_MAX);
// Get the hand weight
auto cardNumber0 = this->hand[0].getValue();
auto suitNumber0 = this->hand[0].getSuit();
auto cardNumber1 = this->hand[1].getValue();
auto suitNumber1 = this->hand[1].getSuit();
// Get delta between cards
auto i = (uint8_t)mathAbs<int8_t>(
(int8_t)cardNumber0 - (int8_t)cardNumber1
);
// Get card weight
confidence = (float_t)cardNumber0 + (float_t)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;
// This may change in future, but I was finding the AI did not want to bet
// during the preflop enough, this curves the AI to want to preflop call
// often.
confidence = easeOutCubic(confidence);
} else {
// Simulate my hand being the winning hand, use that as the confidence
auto winning = this->getWinning();
confidence = PokerWinning::getWinningTypeConfidence(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 = this->getCallBet();
// Do they need chips to call, or is it possible to check?
if(callBet > 0) {
potOdds = (float_t)callBet / (
(float_t)callBet +
(float_t)this->getSumOfChips()
);
} else {
potOdds = 1.0f / (float_t)this->pokerGame->getRemainingBettersCount();
}
// Now determine the expected ROI
auto expectedGain = confidence / potOdds;
// Now get a random 0-100
auto random = randomGenerate<int32_t>() % 100;
// Determine the max bet that the AI is willing to make
auto maxBet = (int32_t)((float_t)this->chips / 1.75f) - (random / 2);
maxBet -= callBet;
// Determine what's a good bluff bet.
auto bluffBet = random * maxBet / 100 / 2;
// Now prep the output
auto isBluff = false;
auto 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.8f && confidence < 0.8f) {
if(random < 85) {
amount = 0;
} else {
amount = bluffBet;
isBluff = true;
}
} else if((expectedGain < 1.0f && confidence < 0.85f) || confidence < 0.1f) {
if(random < 80) {
amount = 0;
} else if(random < 5) {
amount = callBet;
isBluff = true;
} else {
amount = bluffBet;
isBluff = true;
}
} else if((expectedGain < 1.3f && confidence < 0.9f) || confidence < 0.5f) {
if(random < 60 || confidence < 0.5f) {
amount = callBet;
} else {
amount = maxBet;
}
} else if(confidence < 0.95f || this->pokerGame->community.size() < 4) {
if(random < 20) {
amount = callBet;
} else {
amount = maxBet;
}
} else {
amount = (this->chips - callBet) * 9 / 10;
}
// TODO: We can nicely round the amounts here to get us to a more "human"
// number.
// If this is the first round... make it a lot less likely I'll bet
if(this->pokerGame->community.size() == 0 && amount > callBet) {
if(random > 5) amount = callBet;
}
// Did we actually bet?
if(amount > 0) {
std::cout << "AI is betting " << amount << " chips, bluff:" << isBluff << std::endl;
// Let's not get caught in a raising loop with AI.
if(this->timesRaised >= POKER_PLAYER_MAX_RAISES) {
amount = callBet;
}
amount = mathMax<int32_t>(amount, callBet);
turn = PokerTurn::bet(this, amount);
turn.confidence = confidence;
} else if(this->canCheck()) {
turn = PokerTurn::bet(this, 0);
turn.confidence = 1;
} else {
turn = PokerTurn::fold(this);
turn.confidence = 1 - confidence;
}
return turn;
}
int32_t PokerPlayer::getCallBet() {
return this->pokerGame->getCurrentCallValue() - this->currentBet;
}
int32_t PokerPlayer::getSumOfChips() {
int32_t count = 0;
auto it = this->pokerGame->pots.begin();
while(it != this->pokerGame->pots.end()) {
if(std::find(it->players.begin(), it->players.end(), this) != it->players.end()) {
count += it->chips;
}
++it;
}
return count;
}
struct PokerWinning PokerPlayer::getWinning() {
struct PokerWinning winning;
struct Card card(0x00);
uint8_t i, j;
int32_t index;
enum CardValue number, look;
enum CardSuit suit;
std::vector<struct Card> pairs;
winning.player = this;
// Get the full poker hand (should be a 7 card hand, but MAY not be)
for(i = 0; i < this->pokerGame->community.size(); i++) {
winning.full.push_back(this->pokerGame->community[i]);
}
for(i = 0; i < this->hand.size(); i++) {
winning.full.push_back(this->hand[i]);
}
Card::sort(&winning.full);
//////////////////////// Now look for the winning set ////////////////////////
// Royal / Straight Flush
for(i = 0; i < winning.full.size(); i++) {
card = winning.full[i];
number = card.getValue();
if(number < CARD_FIVE) continue;
suit = card.getSuit();
winning.set.clear();
winning.set.push_back(card);
// Now look for the matching cards (Reverse order to order from A to 10)
for(j = 1; j <= 4; j++) {
// Ace low.
look = (
number == CARD_FIVE && j == 4 ?
(enum CardValue)CARD_ACE :
(enum CardValue)(number - j)
);
index = Card::contains(&winning.full, Card(suit, look));
if(index == -1) break;
winning.set.push_back(winning.full[index]);
}
// Check if has all necessary cards.
if(winning.set.size() < POKER_WINNING_SET_SIZE) continue;
// Add self to array
winning.type = (
number == CARD_ACE ? POKER_WINNING_TYPE_ROYAL_FLUSH :
POKER_WINNING_TYPE_STRAIGHT_FLUSH
);
winning.fillRemaining();
return winning;
}
// Four of a kind.
for(i = 0; i < winning.full.size(); i++) {
card = winning.full[i];
number = card.getValue();
pairs = Card::countPairs(&winning.full, number);
if(pairs.size() < CARD_SUIT_COUNT) continue;
winning.set = pairs;
winning.type = POKER_WINNING_TYPE_FOUR_OF_A_KIND;
winning.fillRemaining();
return winning;
}
// Full House
winning.set.clear();
for(i = 0; i < winning.full.size(); i++) {
// Check we haven't already added this card.
card = winning.full[i];
if(Card::contains(&winning.set, card) != -1) {
continue;
}
number = card.getValue();
pairs = Card::countPairs(&winning.full, number);
// Did we find either two pair or three pair?
if(pairs.size() != 2 && pairs.size() != 3) continue;
if(winning.set.size() == 3) {//Clamp to 5 max.
pairs.pop_back();
}
// Copy found pairs.
for(j = 0; j < pairs.size(); j++) {
winning.set.push_back(pairs[j]);
}
// Winned?
if(winning.set.size() != POKER_WINNING_SET_SIZE) continue;
winning.type = POKER_WINNING_TYPE_FULL_HOUSE;
winning.fillRemaining();
return winning;
}
// Flush (5 same suit)
for(i = 0; i < winning.full.size(); i++) {
card = winning.full[i];
suit = card.getSuit();
winning.set.clear();
winning.set.push_back(card);
for(j = i+1; j < winning.full.size(); j++) {
if(winning.full[j].getSuit() != suit) continue;
winning.set.push_back(winning.full[j]);
if(winning.set.size() == POKER_WINNING_SET_SIZE) break;
}
if(winning.set.size() < POKER_WINNING_SET_SIZE) continue;
winning.type = POKER_WINNING_TYPE_FLUSH;
winning.fillRemaining();
return winning;
}
// Straight (sequence any suit)
for(i = 0; i < winning.full.size(); i++) {
card = winning.full[i];
number = card.getValue();
if(number < CARD_FIVE) continue;
winning.set.clear();
winning.set.push_back(card);
for(j = 1; j <= 4; j++) {
// Ace low.
look = (
number == CARD_FIVE && j == 4 ?
(enum CardValue)CARD_ACE :
(enum CardValue)(number - j)
);
index = Card::containsNumber(&winning.full, look);
if(index == -1) break;
winning.set.push_back(winning.full[index]);
}
// Check if has all necessary cards.
if(winning.set.size() < POKER_WINNING_SET_SIZE) continue;
winning.type = POKER_WINNING_TYPE_STRAIGHT;
winning.fillRemaining();
return winning;
}
// Three of a kind
for(i = 0; i < winning.full.size(); i++) {
card = winning.full[i];
number = card.getValue();
pairs = Card::countPairs(&winning.full, number);
if(pairs.size() != 3) continue;
winning.set = pairs;
winning.type = POKER_WINNING_TYPE_THREE_OF_A_KIND;
winning.fillRemaining();
return winning;
}
// Two Pair
winning.set.clear();
for(i = 0; i < winning.full.size(); i++) {
card = winning.full[i];// Check we haven't already added this card.
if(
winning.set.size() > 0 &&
Card::contains(&winning.set, card) != -1
) {
continue;
}
number = card.getValue();
pairs = Card::countPairs(&winning.full, number);
if(pairs.size() != 2) continue;
for(j = 0; j < pairs.size(); j++) {
winning.set.push_back(pairs[j]);
}
if(winning.set.size() != 4) continue;
winning.type = POKER_WINNING_TYPE_TWO_PAIR;
winning.fillRemaining();
return winning;
}
// Pair
if(winning.set.size() == 2) {
winning.type = POKER_WINNING_TYPE_PAIR;
winning.fillRemaining();
return winning;
}
// High card
winning.set.clear();
winning.fillRemaining();
winning.type = POKER_WINNING_TYPE_HIGH_CARD;
return winning;
}

View File

@ -0,0 +1,132 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "scene/SceneItemComponent.hpp"
#include "Card.hpp"
#include "PokerPot.hpp"
#include "util/mathutils.hpp"
#include "display/animation/Easing.hpp"
#include "PokerWinning.hpp"
#include "PokerTurn.hpp"
#include "util/random.hpp"
#define POKER_PLAYER_CHIPS_DEFAULT 10000
/** Maximum cards a players' hand can hold */
#define POKER_PLAYER_HAND_SIZE_MAX 2
#define POKER_PLAYER_MAX_RAISES 0x02
namespace Dawn {
class PokerGame;
class PokerPlayer : public SceneItemComponent {
public:
PokerGame *pokerGame;
int32_t chips = 0;
int32_t currentBet = 0;
uint8_t timesRaised = 0;
bool_t isFolded = false;
bool_t isOut = false;
bool_t hasBetThisRound = false;
bool_t isShowingHand = false;
bool_t isHuman = false;
std::vector<struct Card> hand;
Event<> eventChipsChanged;
/**
* Creates a PokerPlayer instance.
*
* @param item Item that this poker player belongs to.
*/
PokerPlayer(SceneItem *item);
/** Override for scene item component event for init */
void onStart() override;
/**
* Adds chips to the player. This will also update the players' state.
*
* @param chips Count of chips to add.
*/
void addChips(int32_t chips);
/**
* Sets the chips a player has.
*
* @param chips Chips to set to the player.
*/
void setChips(int32_t chips);
/**
* Returns true if the player still needs to bet this betting round.
*
* @return True if betting is still required by this player.
*/
bool_t needsToBetThisRound();
/**
* Let a player bet chips into the pot.
*
* @param pot Poker pot to bet in to.
* @param amount The amount of chips the player is betting.
*/
void bet(struct PokerPot *pot, int32_t amount);
/**
* Let a player bet chips into the current pot.
*
* @param amount The amount of chips the player is betting.
*/
void bet(int32_t amount);
/**
* Player folds.
*/
void fold();
/**
* Returns the AI result for a turn done by a non human player.
*
* @return Some information about the move the player is trying to perform
*/
struct PokerTurn getAITurn();
/**
* Calculates and returns the winning state for a given player
*
* @return The winning state for this current players hand.
*/
struct PokerWinning getWinning();
/**
* Returns the sum of chips in the pot(s) that the specified player is in.
* This does not consider the pot, player or hand, just the pure sum of
* chips.
*
* @return The sum of chips from the pots the player is within.
*/
int32_t getSumOfChips();
/**
* Get the bet necessary for a specific player to make a call. This takes
* the players current bet and the bet necessary to call into the pot and
* will return the difference.
*
* @return The count of chips needed to call into the current active pot.
*/
int32_t getCallBet();
/**
* Returns whether or not the player can check, or if they need to either
* fold, call or bet.
*
* @return True if they can check.
*/
bool_t canCheck();
};
}

View File

@ -0,0 +1,111 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "PokerPot.hpp"
#include "PokerGame.hpp"
#include "PokerPlayer.hpp"
using namespace Dawn;
void PokerPotWinning::award() {
auto it = this->winners.begin();
while(it != this->winners.end()) {
if(it == this->winners.begin()) {
(*it)->addChips(this->chipsEach + this->chipsOverflow);
} else {
(*it)->addChips(this->chipsEach);
}
++it;
}
}
struct PokerPotWinning PokerPot::getWinners(PokerGame *game) {
struct PokerPotWinning winning;
winning.pot = this;
// Calculate the winnings first.
auto it = this->players.begin();
while(it != this->players.end()) {
auto player = *it;
if(player->isOut || player->isFolded) {
++it;
continue;
}
winning.participants.push_back(player);
winning.winnings[player] = player->getWinning();
++it;
}
// Compare participating players
auto it2 = winning.participants.begin();
while(it2 != winning.participants.end()) {
auto playerLeft = *it2;
auto winnerLeft = &winning.winnings[playerLeft];
bool_t isWinner = true;
enum CardValue highNumber = CARD_VALUE_INVALD;
enum CardValue number = CARD_VALUE_INVALD;
struct Card highCard(0xFF);
struct Card card(0xFF);
auto it3 = winning.participants.begin();
while(it3 != winning.participants.end()) {
if(it2 == it3) {
++it3;
continue;
}
auto playerRight = *it3;
auto winnerRight = &winning.winnings[playerRight];
// Am I the better hand / Is it the better hand?
if(winnerLeft->type < winnerRight->type) {
++it3;
continue;
}
if(winnerLeft->type > winnerRight->type) {
isWinner = false;
break;
}
// Equal, compare hands.
card = PokerWinning::compare(winnerLeft, winnerRight);
if(card.cardValue == 0xFF) {
isWinner = false;
break;
}
// Determine high card.
number = card.getValue();
if(
highNumber == CARD_VALUE_INVALD ||
number == CARD_ACE ||
number > highNumber
) {
highCard = card;
highNumber = number;
}
++it3;
}
if(!isWinner) {
++it2;
continue;
}
winnerLeft->kicker = highCard;
winning.winners.push_back(playerLeft);
++it2;
}
winning.chipsEach = this->chips / (int32_t)winning.winners.size();
winning.chipsOverflow = this->chips - (
winning.chipsEach * (int32_t)winning.winners.size()
);
return winning;
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "PokerWinning.hpp"
namespace Dawn {
class PokerPlayer;
class PokerGame;
struct PokerPot;
struct PokerPotWinning {
public:
std::map<PokerPlayer*,struct PokerWinning> winnings;
std::vector<PokerPlayer*> winners;
std::vector<PokerPlayer*> participants;
struct PokerPot *pot;
int32_t chipsEach;
int32_t chipsOverflow;
void award();
};
struct PokerPot {
public:
int32_t chips;
int32_t call;
std::vector<PokerPlayer*> players;
struct PokerPotWinning getWinners(PokerGame *game);
};
}

View File

@ -0,0 +1,72 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "PokerTurn.hpp"
#include "PokerPlayer.hpp"
#include "PokerGame.hpp"
using namespace Dawn;
struct PokerTurn PokerTurn::bet(PokerPlayer *player, int32_t chips) {
struct PokerTurn turn;
int32_t i;
assertNotNull(player);
assertTrue(chips >= 0);
turn.player = player;
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 = player->pokerGame->getCurrentCallValue();
if(chips == (i - player->currentBet)) {
turn.type = POKER_TURN_TYPE_CALL;
}
}
return turn;
}
struct PokerTurn PokerTurn::fold(PokerPlayer *player) {
struct PokerTurn turn;
turn.player = player;
turn.chips = 0;
turn.confidence = 1;
turn.type = POKER_TURN_TYPE_FOLD;
return turn;
}
void PokerTurn::action() {
assertNotNull(this->player);
switch(this->type) {
case POKER_TURN_TYPE_BET:
case POKER_TURN_TYPE_CALL:
case POKER_TURN_TYPE_ALL_IN:
this->player->bet(this->chips);
break;
case POKER_TURN_TYPE_CHECK:
player->bet(0);
break;
case POKER_TURN_TYPE_FOLD:
player->fold();
break;
default:
assertUnreachable();
break;
}
}

View File

@ -0,0 +1,53 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawnlibs.hpp"
namespace Dawn {
class PokerPlayer;
enum PokerTurnType {
POKER_TURN_TYPE_OUT,
POKER_TURN_TYPE_FOLD,
POKER_TURN_TYPE_BET,
POKER_TURN_TYPE_CALL,
POKER_TURN_TYPE_CHECK,
POKER_TURN_TYPE_ALL_IN
};
struct PokerTurn {
public:
/** What type of action the turn is */
enum PokerTurnType type;
/** How many chips they did in their turn (if applicable) */
int32_t chips;
/** How confident the AI is about their turn. 0 = none, 1 = full */
float_t confidence;
/** Player that this action belongs to */
PokerPlayer *player;
/**
* Generate a turn action for betting as a player.
*
* @param player Player index who is betting.
* @param chips Chips to raise by.
* @return A turn for a bet action.
*/
static struct PokerTurn bet(PokerPlayer *player, int32_t chips);
/**
* Return a turn action for the given player to fold.
*
* @return A turn for a fold action.
*/
static struct PokerTurn fold(PokerPlayer *player);
/**
* Actions / Performs this turn against the defined player.
*/
void action();
};
}

View File

@ -0,0 +1,159 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#include "PokerWinning.hpp"
#include "PokerPlayer.hpp"
using namespace Dawn;
float_t PokerWinning::getWinningTypeConfidence(enum PokerWinningType 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;
}
}
struct Card PokerWinning::compare(
struct PokerWinning *left,
struct PokerWinning *right
) {
assertNotNull(left);
assertNotNull(right);
uint8_t i;
enum CardValue number = CARD_VALUE_INVALD;
enum CardValue highNumberLeft = CARD_VALUE_INVALD;
enum CardValue highNumberRight = CARD_VALUE_INVALD;
struct Card card(0xFF), highCardLeft(0xFF), highCardRight(0xFF);
int32_t index;
uint8_t countCardsSame;
countCardsSame = 0;
for(i = 0; i < left->set.size(); i++) {
card = left->set[i];
number = card.getValue();
// Quick check
if(highNumberLeft != CARD_VALUE_INVALD && number < highNumberLeft) continue;
// Check if this number is within the other hand or not
index = Card::containsNumber(&right->set, 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 = Card::contains(&right->set, card);
// Exact card match
if(index != -1) {
countCardsSame++;
continue;
}
// Not exact card match.. ?
}
if(
highNumberLeft == CARD_VALUE_INVALD ||
number == CARD_ACE ||
highNumberLeft < number
) {
highNumberLeft = number;
highCardLeft = card;
}
}
for(i = 0; i < right->set.size(); i++) {
card = right->set[i];
number = card.getValue();
if(highNumberRight != CARD_VALUE_INVALD && number < highNumberRight) {
continue;
}
index = Card::containsNumber(&left->set, number);
if(index != -1) {
index = Card::contains(&left->set, card);
if(index != -1) continue;
}
if(
highNumberRight == CARD_VALUE_INVALD ||
number == CARD_ACE || highNumberRight < number
) {
highNumberRight = number;
highCardRight = card;
}
}
if(countCardsSame == left->set.size()) {
for(i = 0; i < left->set.size(); i++) {
card = left->set[i];
number = card.getValue();
if(
highNumberLeft == CARD_VALUE_INVALD ||
number == CARD_ACE ||
highNumberLeft < number
) {
highNumberLeft = number;
highCardLeft = card;
}
}
return highCardLeft;
}
if(highCardLeft.cardValue == CARD_VALUE_INVALD) return 0xFF;
if(highNumberLeft < highNumberRight) return 0xFF;
return highCardLeft;// Greater or Equal to.
}
void PokerWinning::fillRemaining() {
uint8_t i, highest, current;
struct Card highestCard(0x00);
struct Card currentCard(0x00);
// Set the kicker
this->kicker = 0xFF;
// Fill the remaining cards
while(this->set.size() < POKER_WINNING_SET_SIZE) {
highest = 0xFF;
for(i = 0; i < this->full.size(); i++) {
currentCard = this->full[i];
if(Card::contains(&this->set, currentCard) != -1) continue;
if(highest == 0xFF) {
highestCard = currentCard;
highest = highestCard.getValue();
} else {
current = currentCard.getValue();
if(current != CARD_ACE && current < highest) continue;
highestCard = currentCard;
highest = current;
}
}
if(highest == 0xFF) break;
this->set.push_back(highestCard);
}
Card::sort(&this->set);
}

View File

@ -0,0 +1,86 @@
// Copyright (c) 2022 Dominic Masters
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
#pragma once
#include "dawnlibs.hpp"
#include "Card.hpp"
#define POKER_WINNING_CONFIDENCE_ROYAL_FLUSH 1.0f
#define POKER_WINNING_CONFIDENCE_STRAIGHT_FLUSH 0.99f
#define POKER_WINNING_CONFIDENCE_FOUR_OF_A_KIND 0.9f
#define POKER_WINNING_CONFIDENCE_FULL_HOUSE 0.85f
#define POKER_WINNING_CONFIDENCE_FLUSH 0.8f
#define POKER_WINNING_CONFIDENCE_STRAIGHT 0.7f
#define POKER_WINNING_CONFIDENCE_THREE_OF_A_KIND 0.5f
#define POKER_WINNING_CONFIDENCE_TWO_PAIR 0.4f
#define POKER_WINNING_CONFIDENCE_PAIR 0.2f
#define POKER_WINNING_CONFIDENCE_HIGH_CARD 0.1f
/** How many cards in the winning set */
#define POKER_WINNING_SET_SIZE 5
namespace Dawn {
class PokerPlayer;
enum PokerWinningType {
POKER_WINNING_TYPE_NULL,
POKER_WINNING_TYPE_ROYAL_FLUSH,
POKER_WINNING_TYPE_STRAIGHT_FLUSH,
POKER_WINNING_TYPE_FOUR_OF_A_KIND,
POKER_WINNING_TYPE_FULL_HOUSE,
POKER_WINNING_TYPE_FLUSH,
POKER_WINNING_TYPE_STRAIGHT,
POKER_WINNING_TYPE_THREE_OF_A_KIND,
POKER_WINNING_TYPE_TWO_PAIR,
POKER_WINNING_TYPE_PAIR,
POKER_WINNING_TYPE_HIGH_CARD
};
struct PokerWinning {
public:
/**
* Get the confidence of the bet for a given winning type.
*
* @param type Winning type type.
* @return The confidence.
*/
static float_t getWinningTypeConfidence(enum PokerWinningType type);
/**
* Compares two winning sets. The returned card is the kicker if the LEFT
* side is the winner. If LEFT is not a winner then 0xFF will be returned.
*
* @param left Left winning set.
* @param right Right winning set.
* @return The kicker card from left's hand or 0xFF if not the winner.
*/
static struct Card compare(
struct PokerWinning *left,
struct PokerWinning *right
);
/** Winning Type */
enum PokerWinningType type;
/** The full set of both the dealer and player's hand */
std::vector<struct Card> full;
/** Holds the winning set */
std::vector<struct Card> set;
/** If there was a kicker card it will be here */
struct Card kicker;
/* The player this winning state belongs to */
PokerPlayer *player;
PokerWinning() : kicker(0xFF) {}
/**
* Fills the remaining cards for a given poker player winning hand.
* Essentially this will just take the highest cards and slot them into
* the array. This also sorts the cards.
*
* @param winning Pointer to the poker winning to fill out.
*/
void fillRemaining();
};
}

View File

@ -1,4 +1,4 @@
# Copyright (c) 2023 Dominic Masters
# Copyright (c) 2024 Dominic Masters
#
# This software is released under the MIT License.
# https://opensource.org/licenses/MIT