diff --git a/src/dawn/dawnlibs.hpp b/src/dawn/dawnlibs.hpp index d11591cc..3524b1f7 100644 --- a/src/dawn/dawnlibs.hpp +++ b/src/dawn/dawnlibs.hpp @@ -1,47 +1,48 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once - -// Static Libs -extern "C" { - // Standard Libs - #include - #include - #include - #include - #include - #include - #include - #include - - typedef bool bool_t; - typedef float float_t; -} - -#include -#include -#include -#include -#include -#include -#include -// #include -// #include -// #include -// #include - -#include -#include -#include -#include -#include -#include -#include -#include -#include +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once + +// Static Libs +extern "C" { + // Standard Libs + #include + #include + #include + #include + #include + #include + #include + #include + + typedef bool bool_t; + typedef float float_t; +} + +#include +#include +#include +#include +#include +#include +#include +#include +// #include +// #include +// #include +// #include + +#include +#include +#include +#include +#include +#include +#include +#include +#include #include \ No newline at end of file diff --git a/src/dawn/display/animation/Easing.hpp b/src/dawn/display/animation/Easing.hpp new file mode 100644 index 00000000..2046274c --- /dev/null +++ b/src/dawn/display/animation/Easing.hpp @@ -0,0 +1,77 @@ +/** + * Copyright (c) 2021 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ +#pragma once +#include "dawnlibs.hpp" + +typedef float_t easefunction_t(float_t t); + +/** + * Returns the ease time for a given real time duration span. + * @param start At what point in time the animation started + * @param current The current point in time the animation is at. + * @param duration The total duration on the animation. + * @returns The easing time (0-1 time) that the animation is at. + */ +static inline float_t easeTimeToEase( + float_t start, + float_t current, + float_t duration +) { + return (current - start) / duration; +} + +static inline float_t easeLinear(float_t t) { + return t; +} + +static inline float_t easeInQuad(float_t t) { + return t * t; +} + +static inline float_t easeOutQuad(float_t t) { + return t * (2 - t); +} + +static inline float_t easeOutCubic(float_t t) { + return 1 - powf(1 - t, 3); +} + +static inline float_t easeInOutQuad(float_t t) { + return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t; +} + +static inline float_t easeInCubic(float_t t) { + return t * t * t; +} + +static inline float_t easeInOutCubic(float_t t) { + return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1; +} + +static inline float_t easeInQuart(float_t t) { + return t * t * t * t; +} + +static inline float_t easeOutQuart(float_t t) { + return 1 - (t-1)*(t-1)*(t-1)*(t-1); +} + +static inline float_t easeInOutQuart(float_t t) { + return t < .5 ? 8*t*t*t*t : 1-8*(t-1)*(t-1)*(t-1)*(t-1); +} + +static inline float_t easeInQuint(float_t t) { + return t*t*t*t*t; +} + +static inline float_t easeOutQuint(float_t t) { + return 1 + (t-1)*(t-1)*(t-1)*(t-1)*(t-1); +} + +static inline float_t easeInOutQuint(float_t t) { + return t<.5 ? 16*t*t*t*t*t : 1+16*(t-1)*(t-1)*(t-1)*(t-1)*(t-1); +} \ No newline at end of file diff --git a/src/dawn/display/font/TrueTypeFont.cpp b/src/dawn/display/font/TrueTypeFont.cpp index a56a96a3..94c9df27 100644 --- a/src/dawn/display/font/TrueTypeFont.cpp +++ b/src/dawn/display/font/TrueTypeFont.cpp @@ -37,7 +37,7 @@ float_t TrueTypeFont::getScale(float_t scale) { float_t TrueTypeFont::getSpaceSize(float_t fontSize) { assertTrue(fontSize > 0); - return mathRoundFloat(this->fontSize * 0.3f); + return mathRound(this->fontSize * 0.3f); } float_t TrueTypeFont::getInitialLineHeight(float_t fontSize) { diff --git a/src/dawn/poker/CMakeLists.txt b/src/dawn/poker/CMakeLists.txt index 82f6b520..dba2a129 100644 --- a/src/dawn/poker/CMakeLists.txt +++ b/src/dawn/poker/CMakeLists.txt @@ -6,5 +6,12 @@ # Sources target_sources(${DAWN_TARGET_NAME} PRIVATE + Card.cpp + PokerPlayer.cpp PokerGame.cpp -) \ No newline at end of file + PokerWinning.cpp + PokerTurn.cpp +) + +# Subdirs +add_subdirectory(visualnovel) \ No newline at end of file diff --git a/src/dawn/poker/Card.cpp b/src/dawn/poker/Card.cpp new file mode 100644 index 00000000..6622523e --- /dev/null +++ b/src/dawn/poker/Card.cpp @@ -0,0 +1,22 @@ +// 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 *deck) { + for(uint8_t i = 0; i < CARD_DECK_SIZE; i++) { + deck->push_back(Card(i)); + } +} + +void Card::sort(std::vector *deck) { + std::sort(deck->begin(), deck->end(), &cardSorter); +} + +bool_t cardSorter(struct Card left, struct Card right) { + return left.cardValue < right.cardValue; +} \ No newline at end of file diff --git a/src/dawn/poker/Card.hpp b/src/dawn/poker/Card.hpp index 40cd65fd..41933ed3 100644 --- a/src/dawn/poker/Card.hpp +++ b/src/dawn/poker/Card.hpp @@ -6,54 +6,71 @@ #pragma once #include "dawnlibs.hpp" -enum CardSuit { - CLUBS = 0, - DIAMONDS = 1, - HEARTS = 2, - SPADES = 3 -}; +namespace Dawn { + enum CardSuit { + CARD_CLUBS = 0, + CARD_DIAMONDS = 1, + CARD_HEARTS = 2, + CARD_SPADES = 3 + }; -enum CardValue { - 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 -}; + enum CardValue { + CARD_TWO = 0, + CARD_THREE = 1, + CARD_FOUR = 2, + CARD_FIVE = 3, + CARD_SIX = 4, + CARD_SEVEN = 5, + CARD_EIGHT = 6, + CARD_NINE = 7, + CARD_TEN = 8, + CARD_JACK = 9, + CARD_QUEEN = 10, + CARD_KING = 11, + CARD_ACE = 12 + }; -/** Count of cards in each suit */ -#define CARD_COUNT_PER_SUIT 13 + /** Count of cards in each suit */ + #define CARD_COUNT_PER_SUIT 13 -/** Count of suits */ -#define CARD_SUIT_COUNT 4 + /** Count of suits */ + #define CARD_SUIT_COUNT 4 -/** Standard Card Deck Size */ -#define CARD_DECK_SIZE CARD_COUNT_PER_SUIT*CARD_SUIT_COUNT + /** Standard Card Deck Size */ + #define CARD_DECK_SIZE CARD_COUNT_PER_SUIT*CARD_SUIT_COUNT -struct Card { - uint8_t cardValue; + struct Card { + public: + uint8_t cardValue; - Card(CardSuit suit, CardValue num) : - cardValue((suit * CARD_COUNT_PER_SUIT) + num) - { - } + static void fillDeck(std::vector *deck); + static void shuffle(std::vector *deck); - Card(uint8_t cv) : cardValue(cv) { - } + /** + * 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 *deck); - CardValue getValue() { - return (CardValue)(cardValue % CARD_COUNT_PER_SUIT); - } + Card(CardSuit suit, CardValue num) : + cardValue((suit * CARD_COUNT_PER_SUIT) + num) + { + } - CardSuit getSuit() { - return (CardSuit)(cardValue / CARD_COUNT_PER_SUIT); - } -}; \ No newline at end of file + Card(uint8_t cv) : cardValue(cv) { + } + + CardValue getValue() { + return (CardValue)(cardValue % CARD_COUNT_PER_SUIT); + } + + CardSuit getSuit() { + return (CardSuit)(cardValue / CARD_COUNT_PER_SUIT); + } + }; + + bool_t cardSorter(struct Card left, struct Card right); +} \ No newline at end of file diff --git a/src/dawn/poker/PokerGame.cpp b/src/dawn/poker/PokerGame.cpp index 637c5592..e29aebe4 100644 --- a/src/dawn/poker/PokerGame.cpp +++ b/src/dawn/poker/PokerGame.cpp @@ -7,6 +7,191 @@ using namespace Dawn; -PokerGame::PokerGame() { +PokerGame::PokerGame(SceneItem *item) : SceneItemComponent(item) { +} + +void PokerGame::onStart() { + SceneItemComponent::onStart(); + + this->players = this->getScene()->findComponents(); + 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->chips = 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) ; + 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; + } } \ No newline at end of file diff --git a/src/dawn/poker/PokerGame.hpp b/src/dawn/poker/PokerGame.hpp index 76510c61..b2281866 100644 --- a/src/dawn/poker/PokerGame.hpp +++ b/src/dawn/poker/PokerGame.hpp @@ -4,31 +4,25 @@ // https://opensource.org/licenses/MIT #pragma once -#include "scene/SceneItemComponent.hpp" -#include "Card.hpp" +#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 PokerPlayer { - public: - int32_t i; - }; - - class PokerPot { - public: - int32_t i; - }; - - class PokerGame { + class PokerGame : public SceneItemComponent { protected: - std::vector players; - std::vector cards; + std::vector deck; std::vector grave; std::vector community; + std::vector pots; uint8_t dealerIndex; uint8_t smallBlindIndex; uint8_t bigBlindIndex; @@ -37,6 +31,29 @@ namespace Dawn { int32_t bigBlind = POKER_BLIND_BIG_DEFAULT; public: - PokerGame(); + std::vector players; + + 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; }; } \ No newline at end of file diff --git a/src/dawn/poker/PokerPlayer.cpp b/src/dawn/poker/PokerPlayer.cpp new file mode 100644 index 00000000..176e2537 --- /dev/null +++ b/src/dawn/poker/PokerPlayer.cpp @@ -0,0 +1,432 @@ +// 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(); + assertNotNull(this->pokerGame); +} + +void PokerPlayer::addChips(int32_t chips) { + assertTrue(chips > 0); + + this->chips += chips; + if(this->chips > 0) this->isOut = false; +} + +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->chips -= chips; + this->currentBet += chips; + this->hasBetThisRound = true; + if(chips > 0) this->timesRaised++; + + pot->chips += chips; + pot->call = mathMax(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); +} + +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)cardNumber0 - (int8_t)cardNumber1 + ); + + // Get card weight + float_t 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() % 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(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(); + 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; + + // Get the full poker hand (should be a 7 card hand, but MAY not be) + auto itHand = this->hand.begin(); + while(itHand != this->hand.end()) { + winning.full.push_back(*itHand); + ++itHand; + } + + auto itCommunity = this->pokerGame->community.begin(); + while(itCommunity != this->pokerGame->community.end()) { + winning.full.push_back(*itCommunity); + ++itCommunity; + } + Card::sort(&winning.full); + + //////////////////////// Now look for the winning set //////////////////////// + + // Royal / Straight Flush + auto it = winning.full.begin(); + while(it != winning.full.end()) { + auto number = it->getValue(); + if(number < CARD_FIVE) { + ++it; + continue; + } + + auto suit = it->getSuit(); + 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(winning->setSize > 0 && (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( + winning->setSize > 0 && + 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]]; + } + 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; +} \ No newline at end of file diff --git a/src/dawn/poker/PokerPlayer.hpp b/src/dawn/poker/PokerPlayer.hpp new file mode 100644 index 00000000..eac233c8 --- /dev/null +++ b/src/dawn/poker/PokerPlayer.hpp @@ -0,0 +1,119 @@ +// 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 "scene/Scene.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 hand; + + /** + * 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); + + /** + * 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); + + /** + * 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(); + }; +} \ No newline at end of file diff --git a/src/dawn/poker/PokerPot.hpp b/src/dawn/poker/PokerPot.hpp new file mode 100644 index 00000000..07d7da69 --- /dev/null +++ b/src/dawn/poker/PokerPot.hpp @@ -0,0 +1,17 @@ +// 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; + + struct PokerPot { + int32_t chips; + int32_t call; + std::vector players; + }; +} \ No newline at end of file diff --git a/src/dawn/poker/PokerTurn.cpp b/src/dawn/poker/PokerTurn.cpp new file mode 100644 index 00000000..c7b1731a --- /dev/null +++ b/src/dawn/poker/PokerTurn.cpp @@ -0,0 +1,46 @@ +// 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.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() { + struct PokerTurn turn; + turn.chips = 0; + turn.confidence = 1; + turn.type = POKER_TURN_TYPE_FOLD; + return turn; +} \ No newline at end of file diff --git a/src/dawn/poker/PokerTurn.hpp b/src/dawn/poker/PokerTurn.hpp new file mode 100644 index 00000000..75d4129b --- /dev/null +++ b/src/dawn/poker/PokerTurn.hpp @@ -0,0 +1,46 @@ +// 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; + + /** + * 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(); + }; +} \ No newline at end of file diff --git a/src/dawn/poker/PokerWinning.cpp b/src/dawn/poker/PokerWinning.cpp new file mode 100644 index 00000000..35521a35 --- /dev/null +++ b/src/dawn/poker/PokerWinning.cpp @@ -0,0 +1,33 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "PokerWinning.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; + } +} \ No newline at end of file diff --git a/src/dawn/poker/PokerWinning.hpp b/src/dawn/poker/PokerWinning.hpp new file mode 100644 index 00000000..65904817 --- /dev/null +++ b/src/dawn/poker/PokerWinning.hpp @@ -0,0 +1,43 @@ +// 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 + +namespace Dawn { + 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: + enum PokerWinningType type; + std::vector full; + + static float_t getWinningTypeConfidence(enum PokerWinningType type); + }; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/CMakeLists.txt b/src/dawn/poker/visualnovel/CMakeLists.txt new file mode 100644 index 00000000..452b22f9 --- /dev/null +++ b/src/dawn/poker/visualnovel/CMakeLists.txt @@ -0,0 +1,10 @@ +# 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 + PokerDetermineBetterEvent.cpp +) \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerAIBetEvent.hpp b/src/dawn/poker/visualnovel/PokerAIBetEvent.hpp new file mode 100644 index 00000000..1f02023e --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerAIBetEvent.hpp @@ -0,0 +1,35 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "PokerGameEvent.hpp" + +#define POKER_DEAL_EVENT_CARD_COUNT 2 + +namespace Dawn { + class PokerDealEvent : public PokerGameEvent { + protected: + void onStart() override { + PokerGameEvent::onStart(); + + auto better = this->pokerGame->getNextBetterIndex(); + this->pokerGame->better = better; + auto player = this->pokerGame->players[better]; + } + + bool_t onUpdate() override { + return false; + } + + void onEnd() override { + } + + public: + uint8_t better; + + PokerDealEvent(VisualNovelManager *manager) : PokerGameEvent(manager) { + } + }; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerDealEvent.hpp b/src/dawn/poker/visualnovel/PokerDealEvent.hpp new file mode 100644 index 00000000..5c86374a --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerDealEvent.hpp @@ -0,0 +1,30 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "PokerGameEvent.hpp" + +#define POKER_DEAL_EVENT_CARD_COUNT 2 + +namespace Dawn { + class PokerDealEvent : public PokerGameEvent { + protected: + void onStart() override { + PokerGameEvent::onStart(); + this->pokerGame->dealToEveryone(POKER_DEAL_EVENT_CARD_COUNT); + } + + bool_t onUpdate() override { + return false; + } + + void onEnd() override { + } + + public: + PokerDealEvent(VisualNovelManager *manager) : PokerGameEvent(manager) { + } + }; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerDetermineBetterEvent.cpp b/src/dawn/poker/visualnovel/PokerDetermineBetterEvent.cpp new file mode 100644 index 00000000..88e01be0 --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerDetermineBetterEvent.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#include "PokerDetermineBetterEvent.hpp" + +using namespace Dawn; + +PokerDetermineBetterEvent::PokerDetermineBetterEvent(VisualNovelManager *man) : + PokerGameEvent(man) +{ + +} + +void PokerDetermineBetterEvent::onStart() { + PokerGameEvent::onStart(); + + if(this->pokerGame->getRemainingPlayersCount() <= 1) { + this->doNext = this->eventEveryoneFolded; + return; + } + + uint8_t nextBetterIndex = this->pokerGame->getNextBetterIndex(); + if(nextBetterIndex == 0xFF) { + if(this->pokerGame->getCountOfCardsToTurn() == 0xFF) { + this->doNext = this->eventBettingFinished; + } else { + this->doNext = this->eventTurn; + } + return; + } + + auto nextBetter = this->pokerGame->players[nextBetterIndex]; + if(nextBetter->isHuman) { + this->doNext = this->eventHumanBet; + } else { + this->doNext = this->eventAiBet; + } +} + +bool_t PokerDetermineBetterEvent::onUpdate() { + return false; +} + +void PokerDetermineBetterEvent::onEnd() { +} + +PokerDetermineBetterEvent::~PokerDetermineBetterEvent() { + if( + this->eventEveryoneFolded != nullptr && + this->eventEveryoneFolded != this->doNext + ) delete this->eventEveryoneFolded; + + if( + this->eventBettingFinished != nullptr && + this->eventBettingFinished != this->doNext + ) delete this->eventBettingFinished; + + if( + this->eventTurn != nullptr && + this->eventTurn != this->doNext + ) delete this->eventTurn; + + if( + this->eventAiBet != nullptr && + this->eventAiBet != this->doNext + ) delete this->eventAiBet; + + if( + this->eventHumanBet != nullptr && + this->eventHumanBet != this->doNext + ) delete this->eventHumanBet; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerDetermineBetterEvent.hpp b/src/dawn/poker/visualnovel/PokerDetermineBetterEvent.hpp new file mode 100644 index 00000000..6a3c1b77 --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerDetermineBetterEvent.hpp @@ -0,0 +1,106 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "PokerGameEvent.hpp" + +namespace Dawn { + class PokerDetermineBetterEvent : public PokerGameEvent { + protected: + IVisualNovelEvent * eventEveryoneFolded = nullptr; + IVisualNovelEvent * eventBettingFinished = nullptr; + IVisualNovelEvent * eventTurn = nullptr; + IVisualNovelEvent * eventAiBet = nullptr; + IVisualNovelEvent * eventHumanBet = nullptr; + + void onStart() override; + bool_t onUpdate() override; + void onEnd() override; + + public: + /** + * Construct a better determine event. + * + * @param manager VN manager that this event belongs to. + */ + PokerDetermineBetterEvent(VisualNovelManager *manager); + + /** + * Sets the event that is triggered when everyone is folded. + * + * @param event Event to trigger. + * @return The event that was passed in. + */ + template + T * whenEveryoneFolded(T *event) { + assertNotNull(event); + this->eventEveryoneFolded = event; + event->previous = this; + return event; + } + + /** + * Sets the event that is triggered after everyone has finished betting. + * + * @param event Event to trigger. + * @return The event that was passed in. + */ + template + T * whenBettingFinished(T *event) { + assertNotNull(event); + this->eventBettingFinished = event; + event->previous = this; + return event; + } + + /** + * Event to trigger when all betting is finished and its time to turn the + * next set of community cards. + * + * @param event Event to trigger. + * @return The event that was passed in. + */ + template + T * whenTurn(T *event) { + assertNotNull(event); + this->eventTurn = event; + event->previous = this; + return event; + } + + /** + * Sets the event to trigger when its an AI's turn to bet. + * + * @param event Event to trigger. + * @return The event that was passed in. + */ + template + T * whenAiBet(T *event) { + assertNotNull(event); + this->eventAiBet = event; + event->previous = this; + return event; + } + + /** + * Sets the event to trigger when its a human's turn to bet. + * + * @param event Event to trigger. + * @return The event that was passed in. + */ + template + T * whenHumanBet(T *event) { + assertNotNull(event); + this->eventHumanBet = event; + event->previous = this; + return event; + } + + /** + * Dispose override for the event. + */ + ~PokerDetermineBetterEvent(); + }; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerGameEvent.hpp b/src/dawn/poker/visualnovel/PokerGameEvent.hpp new file mode 100644 index 00000000..7823e1da --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerGameEvent.hpp @@ -0,0 +1,27 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "visualnovel/VisualNovelManager.hpp" +#include "poker/PokerGame.hpp" + +namespace Dawn { + class PokerGameEvent : public IVisualNovelEvent { + protected: + PokerGame *pokerGame; + + void onStart() override { + pokerGame = this->manager->getScene()->findComponent(); + assertNotNull(pokerGame); + } + + public: + PokerGameEvent(VisualNovelManager *manager) : + IVisualNovelEvent(manager) + { + + } + }; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerNewGameEvent.hpp b/src/dawn/poker/visualnovel/PokerNewGameEvent.hpp new file mode 100644 index 00000000..8a0aac25 --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerNewGameEvent.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "PokerGameEvent.hpp" + +namespace Dawn { + class PokerNewGameEvent : public PokerGameEvent { + protected: + void onStart() override { + PokerGameEvent::onStart(); + this->pokerGame->newGame(); + } + + bool_t onUpdate() override { + return false; + } + + void onEnd() override { + } + + public: + PokerNewGameEvent(VisualNovelManager *manager) : PokerGameEvent(manager) { + } + }; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerNewRoundEvent.hpp b/src/dawn/poker/visualnovel/PokerNewRoundEvent.hpp new file mode 100644 index 00000000..ee8128ec --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerNewRoundEvent.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "PokerGameEvent.hpp" + +namespace Dawn { + class PokerNewRoundEvent : public PokerGameEvent { + protected: + void onStart() override { + PokerGameEvent::onStart(); + this->pokerGame->newRound(); + } + + bool_t onUpdate() override { + return false; + } + + void onEnd() override { + } + + public: + PokerNewRoundEvent(VisualNovelManager *manager) : PokerGameEvent(manager) { + } + }; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerTakeBlindsEvent.hpp b/src/dawn/poker/visualnovel/PokerTakeBlindsEvent.hpp new file mode 100644 index 00000000..b0cc0c07 --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerTakeBlindsEvent.hpp @@ -0,0 +1,28 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "PokerGameEvent.hpp" + +namespace Dawn { + class PokerTakeBlindsEvent : public PokerGameEvent { + protected: + void onStart() override { + PokerGameEvent::onStart(); + this->pokerGame->takeBlinds(); + } + + bool_t onUpdate() override { + return false; + } + + void onEnd() override { + } + + public: + PokerTakeBlindsEvent(VisualNovelManager *manager) : PokerGameEvent(manager) { + } + }; +} \ No newline at end of file diff --git a/src/dawn/poker/visualnovel/PokerTurnEvent.hpp b/src/dawn/poker/visualnovel/PokerTurnEvent.hpp new file mode 100644 index 00000000..5009eb95 --- /dev/null +++ b/src/dawn/poker/visualnovel/PokerTurnEvent.hpp @@ -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 "PokerGameEvent.hpp" + +namespace Dawn { + class PokerTurnEvent : public PokerGameEvent { + protected: + void onStart() override { + PokerGameEvent::onStart(); + + this->cardsTurned = this->pokerGame->getCountOfCardsToTurn(); + assertTrue(this->cardsTurned != 0xFF); + + this->pokerGame->turn(this->cardsTurned); + } + + bool_t onUpdate() override { + return false; + } + + void onEnd() override { + } + + public: + uint8_t cardsTurned; + + PokerTurnEvent(VisualNovelManager *manager) : PokerGameEvent(manager) { + } + }; +} \ No newline at end of file diff --git a/src/dawn/util/mathutils.hpp b/src/dawn/util/mathutils.hpp index 639d1103..28bfe2e0 100644 --- a/src/dawn/util/mathutils.hpp +++ b/src/dawn/util/mathutils.hpp @@ -1,105 +1,117 @@ -/** - * Copyright (c) 2022 Dominic Masters - * - * This software is released under the MIT License. - * https://opensource.org/licenses/MIT - */ - -#pragma once -#include "dawnlibs.hpp" - -#define MATH_PI 3.1415926535897f - -/** - * Returns the largest of the two provided int32 numbers. - * - * @param left Left number to get the largest of. - * @param right Right number to get the largest of. - * @return The larger of the two numbers - */ -template -static inline T mathMax(T left, T right) { - return left < right ? right : left; -} - -/** - * Returns the smallest of two provided int32 numbers. - * - * @param left Left number to get the smallest of. - * @param right Right number to get the smallest of. - * @return Smaller of the two numbers. - */ -template -static inline T mathMin(T left, T right) { - return left < right ? left : right; -} - -/** - * Returns the input value, constrained between the min and max values, so that - * the value cannot underceed the min, and cannot exceed the max. - * - * @param val Value to get the clamp for. - * @param min Minimum clamping value. - * @param max Maximum clamping value. - * @return The value, or the closest clamped value. - */ -template -static inline T mathClamp(T val, T min, T max) { - return mathMin(mathMax(val, min), max); -} - -/** - * Returns the absolute value (the non-negative representation of) for the given - * int32 number.Abs values will be -value if value < 0. - * - * @param value Value to get the absolute value for. - * @return The absolute value (-value if value < 0) - */ -template -static inline T mathAbs(T value) { - return value < 0 ? -value : value; -} - -template -static inline int32_t mathMod(T value, T modulo) { - return ((value % modulo) + modulo) % modulo; -} - -// Float-specific - -/** - * Convert degrees to radians. - * - * @param n Degrees to convert. - * @returns The number in radians. - */ -static inline float_t mathDeg2Rad(float_t degrees) { - return degrees * (MATH_PI / 180.0f); -} - -/** - * Convert radians to degrees. - * @param n Radians to convert. - * @returns The number in degrees. - */ -static inline float_t mathRad2Deg(float_t n) { - return (n * 180.0f) / MATH_PI; -} - -/** - * Round a number to the nearest whole number. - * @param n Number to round. - * @return Rounded number. - */ -static inline float_t mathRoundFloat(float_t n) { - return roundf(n); -} - -/** - * Rounds the number down to the nearest whole number. - * @param n Number to round down. - * @return Rounded number. - */ -static inline float_t mathFloorFloat(float_t n) { - return floorf(n); +/** + * Copyright (c) 2022 Dominic Masters + * + * This software is released under the MIT License. + * https://opensource.org/licenses/MIT + */ + +#pragma once +#include "dawnlibs.hpp" + +#define MATH_PI 3.1415926535897f + +namespace Dawn { + /** + * Returns the largest of the two provided int32 numbers. + * + * @param left Left number to get the largest of. + * @param right Right number to get the largest of. + * @return The larger of the two numbers + */ + template + static T mathMax(T left, T right) { + return left < right ? right : left; + } + + /** + * Returns the smallest of two provided int32 numbers. + * + * @param left Left number to get the smallest of. + * @param right Right number to get the smallest of. + * @return Smaller of the two numbers. + */ + template + static T mathMin(T left, T right) { + return left < right ? left : right; + } + + /** + * Returns the input value, constrained between the min and max values, so that + * the value cannot underceed the min, and cannot exceed the max. + * + * @param val Value to get the clamp for. + * @param min Minimum clamping value. + * @param max Maximum clamping value. + * @return The value, or the closest clamped value. + */ + template + static T mathClamp(T val, T min, T max) { + return mathMin(mathMax(val, min), max); + } + + /** + * Returns the absolute value (the non-negative representation of) for the given + * int32 number.Abs values will be -value if value < 0. + * + * @param value Value to get the absolute value for. + * @return The absolute value (-value if value < 0) + */ + template + static T mathAbs(T value) { + return value < 0 ? -value : value; + } + + /** + * Returns the modulous a result for b. Works for floating point numbers. + * + * @param a Number to modulo against. (a % b) + * @param b Number to modulo with. (a % b) + * @returns The modulo result. + */ + static float_t mathMod(float_t value, float_t modulo) { + return (float_t)fmod(value, modulo); + } + + static int32_t mathMod(int32_t value, int32_t modulo) { + return ((value % modulo) + modulo) % modulo; + } + + /** + * Convert degrees to radians. + * + * @param n Degrees to convert. + * @returns The number in radians. + */ + static float_t mathDeg2Rad(float_t degrees) { + return degrees * (MATH_PI / 180.0f); + } + + /** + * Convert radians to degrees. + * @param n Radians to convert. + * @returns The number in degrees. + */ + static float_t mathRad2Deg(float_t n) { + return (n * 180.0f) / MATH_PI; + } + + /** + * Round a number to the nearest whole number. + * @param n Number to round. + * @return Rounded number. + */ + template + static T mathRound(float_t n) { + return (T)roundf(n); + } + + /** + * Rounds the number down to the nearest whole number. + * @param n Number to round down. + * @return Rounded number. + */ + template + static T mathFloor(float_t n) { + return (T)floorf(n); + } } \ No newline at end of file diff --git a/src/dawn/util/random.hpp b/src/dawn/util/random.hpp new file mode 100644 index 00000000..8f1c3ffc --- /dev/null +++ b/src/dawn/util/random.hpp @@ -0,0 +1,43 @@ +// Copyright (c) 2022 Dominic Masters +// +// This software is released under the MIT License. +// https://opensource.org/licenses/MIT + +#pragma once +#include "dawnlibs.hpp" +#include "mathutils.hpp" + +/** + * Seed the random number generator + * @param seed Seed to use for the seeded random number generator. + */ +static void randSeed(int32_t seed) { + srand(seed); +} + +static int32_t randomGeneratei32() { + return (int32_t)rand(); +} + +/** + * Generates a random number. + * @returns A random number. + */ +template +static T randomGenerate() { + return (T)((float_t)randomGeneratei32() * MATH_PI); +} + +//////////////////////////////////////////////////////////////////////////////// + +/** + * Clamps a random number generation. + * + * @param min Minimum value to generate from. (Inclusive) + * @param max Maximum value to generate to. (Exclusive) + * @return Random number between min and max. + */ +template +static inline T randRange(T min, T max) { + return mathMod(randomGenerate(), (max - min)) + min; +} \ No newline at end of file diff --git a/src/dawn/visualnovel/VisualNovelManager.cpp b/src/dawn/visualnovel/VisualNovelManager.cpp index 12cfdbe8..0cf56c7d 100644 --- a/src/dawn/visualnovel/VisualNovelManager.cpp +++ b/src/dawn/visualnovel/VisualNovelManager.cpp @@ -66,13 +66,12 @@ IVisualNovelEvent * IVisualNovelEvent::end() { if(this->doNext != nullptr) { auto next = this->doNext; - this->doNext = nullptr; return next; } } IVisualNovelEvent::~IVisualNovelEvent() { - if(this->doNext != nullptr) { + if(!this->hasStarted && this->doNext != nullptr) { delete this->doNext; } } \ No newline at end of file diff --git a/src/dawn/visualnovel/VisualNovelManager.hpp b/src/dawn/visualnovel/VisualNovelManager.hpp index 9e031840..74632d79 100644 --- a/src/dawn/visualnovel/VisualNovelManager.hpp +++ b/src/dawn/visualnovel/VisualNovelManager.hpp @@ -67,12 +67,15 @@ namespace Dawn { virtual void onEnd() = 0; public: + IVisualNovelEvent *previous = nullptr; + IVisualNovelEvent(VisualNovelManager *manager); template T * then(T *next) { assertNotNull(next); this->doNext = next; + next->previous = this; return next; } diff --git a/src/dawn/visualnovel/ui/VisualNovelTextbox.cpp b/src/dawn/visualnovel/ui/VisualNovelTextbox.cpp index aea30aff..a3d0a91a 100644 --- a/src/dawn/visualnovel/ui/VisualNovelTextbox.cpp +++ b/src/dawn/visualnovel/ui/VisualNovelTextbox.cpp @@ -43,7 +43,6 @@ void VisualNovelTextbox::show() { if(this->isVisible()) return; this->visible = true; this->addChild(&this->selfParent); - std::cout << "Showing" << std::endl; } void VisualNovelTextbox::hide() { @@ -114,13 +113,13 @@ void VisualNovelTextbox::textboxOnSceneUpdate() { return; } - auto lastTimeCharacter = (int32_t)mathFloorFloat(this->timeCharacter); + auto lastTimeCharacter = mathFloor(this->timeCharacter); if(game->inputManager.isDown(INPUT_BIND_ACCEPT)) { this->timeCharacter += game->timeManager.delta * VISUAL_NOVEL_TEXTBOX_SPEED_FASTER; } else { this->timeCharacter += game->timeManager.delta * VISUAL_NOVEL_TEXTBOX_SPEED; } - auto newTimeCharacter = (int32_t)mathFloorFloat(this->timeCharacter); + auto newTimeCharacter = mathFloor(this->timeCharacter); if(newTimeCharacter == lastTimeCharacter) return; this->label.quadCount = newTimeCharacter; @@ -187,7 +186,7 @@ bool_t VisualNovelTextbox::hasRevealedAllCurrentCharacters() { ) { quadsTotal += this->label.measure.getQuadsOnLine(i); } - return mathFloorFloat(this->timeCharacter) >= quadsTotal; + return mathFloor(this->timeCharacter) >= quadsTotal; } bool_t VisualNovelTextbox::hasRevealedAllCharacters() { diff --git a/src/dawnpokergame/scenes/TestScene.hpp b/src/dawnpokergame/scenes/TestScene.hpp index f83efef2..10d89242 100644 --- a/src/dawnpokergame/scenes/TestScene.hpp +++ b/src/dawnpokergame/scenes/TestScene.hpp @@ -10,6 +10,13 @@ #include "ui/PokerGameTextbox.hpp" #include "visualnovel/VisualNovelManager.hpp" #include "visualnovel/events/VisualNovelTextboxEvent.hpp" +#include "poker/PokerGame.hpp" +#include "poker/visualnovel/PokerNewGameEvent.hpp" +#include "poker/visualnovel/PokerNewRoundEvent.hpp" +#include "poker/visualnovel/PokerTakeBlindsEvent.hpp" +#include "poker/visualnovel/PokerDealEvent.hpp" +#include "poker/visualnovel/PokerTurnEvent.hpp" +#include "poker/visualnovel/PokerDetermineBetterEvent.hpp" namespace Dawn { class TestScene { @@ -26,12 +33,44 @@ namespace Dawn { auto textbox = PokerGameTextbox::create(canvas); // VN Manager - auto item = scene->createSceneItem(); - auto vnManager = item->addComponent(); + auto vnManagerItem = scene->createSceneItem(); + auto vnManager = vnManagerItem->addComponent(); - vnManager - ->setEvent(new VisualNovelTextboxEvent(vnManager, "Bruh event")) - ->then(new VisualNovelTextboxEvent(vnManager, "Bruh event 2")) + // Poker Test + auto pokerGameItem = scene->createSceneItem(); + auto pokerGame = pokerGameItem->addComponent(); + + for(int32_t i = 0; i < 4; i++) { + auto pokerPlayerInstance = scene->createSceneItem(); + auto pokerPlayer = pokerPlayerInstance->addComponent(); + } + + auto betting = vnManager + ->setEvent(new VisualNovelTextboxEvent(vnManager, "Starting Game")) + ->then(new PokerNewGameEvent(vnManager)) + ->then(new VisualNovelTextboxEvent(vnManager, "Game Started")) + ->then(new PokerNewRoundEvent(vnManager)) + ->then(new VisualNovelTextboxEvent(vnManager, "Round Started")) + ->then(new PokerTakeBlindsEvent(vnManager)) + ->then(new VisualNovelTextboxEvent(vnManager, "Blinds Taken")) + ->then(new PokerDealEvent(vnManager)) + ->then(new VisualNovelTextboxEvent(vnManager, "Cards Dealt")) + ->then(new PokerDetermineBetterEvent(vnManager)) + ; + betting + ->whenEveryoneFolded(new VisualNovelTextboxEvent(vnManager, "Everyone Folded")) + ; + betting + ->whenBettingFinished(new VisualNovelTextboxEvent(vnManager, "Betting Finished")) + ; + betting + ->whenTurn(new VisualNovelTextboxEvent(vnManager, "Turn Time")) + ; + betting + ->whenAiBet(new VisualNovelTextboxEvent(vnManager, "AI Bet")) + ; + betting + ->whenHumanBet(new VisualNovelTextboxEvent(vnManager, "Human Bet")) ; return scene;