From b4e261d954437b6b7195da3a023e998acc4fb9fc Mon Sep 17 00:00:00 2001 From: Dominic Masters Date: Sun, 8 Sep 2024 21:54:19 -0500 Subject: [PATCH] Move over old poker code --- ci/build-tools.sh | 5 - ci/install-libraries.sh | 2 - ci/install-linux-toolchain.sh | 2 - ci/install-vita-toolchain.sh | 38 --- ci/quick-vita-build.sh | 10 - docs/COMPILING.md | 340 -------------------- src/dawnpoker/poker/CMakeLists.txt | 15 + src/dawnpoker/poker/Card.cpp | 70 +++++ src/dawnpoker/poker/Card.hpp | 137 ++++++++ src/dawnpoker/poker/PokerGame.cpp | 197 ++++++++++++ src/dawnpoker/poker/PokerGame.hpp | 59 ++++ src/dawnpoker/poker/PokerPlayer.cpp | 450 +++++++++++++++++++++++++++ src/dawnpoker/poker/PokerPlayer.hpp | 132 ++++++++ src/dawnpoker/poker/PokerPot.cpp | 111 +++++++ src/dawnpoker/poker/PokerPot.hpp | 34 ++ src/dawnpoker/poker/PokerTurn.cpp | 72 +++++ src/dawnpoker/poker/PokerTurn.hpp | 53 ++++ src/dawnpoker/poker/PokerWinning.cpp | 159 ++++++++++ src/dawnpoker/poker/PokerWinning.hpp | 86 +++++ src/dawnpoker/scenes/CMakeLists.txt | 2 +- 20 files changed, 1576 insertions(+), 398 deletions(-) delete mode 100755 ci/build-tools.sh delete mode 100755 ci/install-libraries.sh delete mode 100755 ci/install-linux-toolchain.sh delete mode 100755 ci/install-vita-toolchain.sh delete mode 100755 ci/quick-vita-build.sh delete mode 100644 docs/COMPILING.md create mode 100644 src/dawnpoker/poker/CMakeLists.txt create mode 100644 src/dawnpoker/poker/Card.cpp create mode 100644 src/dawnpoker/poker/Card.hpp create mode 100644 src/dawnpoker/poker/PokerGame.cpp create mode 100644 src/dawnpoker/poker/PokerGame.hpp create mode 100644 src/dawnpoker/poker/PokerPlayer.cpp create mode 100644 src/dawnpoker/poker/PokerPlayer.hpp create mode 100644 src/dawnpoker/poker/PokerPot.cpp create mode 100644 src/dawnpoker/poker/PokerPot.hpp create mode 100644 src/dawnpoker/poker/PokerTurn.cpp create mode 100644 src/dawnpoker/poker/PokerTurn.hpp create mode 100644 src/dawnpoker/poker/PokerWinning.cpp create mode 100644 src/dawnpoker/poker/PokerWinning.hpp diff --git a/ci/build-tools.sh b/ci/build-tools.sh deleted file mode 100755 index 1e2f793d..00000000 --- a/ci/build-tools.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/bash -mkdir tools -cd tools -cmake .. -DDAWN_BUILD_TARGET=target-tools -make \ No newline at end of file diff --git a/ci/install-libraries.sh b/ci/install-libraries.sh deleted file mode 100755 index 1b0c74c9..00000000 --- a/ci/install-libraries.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -git submodule update --init --recursive \ No newline at end of file diff --git a/ci/install-linux-toolchain.sh b/ci/install-linux-toolchain.sh deleted file mode 100755 index 0485dc71..00000000 --- a/ci/install-linux-toolchain.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/bin/bash -sudo apt install build-essential \ No newline at end of file diff --git a/ci/install-vita-toolchain.sh b/ci/install-vita-toolchain.sh deleted file mode 100755 index a77d03eb..00000000 --- a/ci/install-vita-toolchain.sh +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -sudo apt-get install cmake libarchive-tools -git clone https://github.com/vitasdk/vdpm ~/vdpm -cd ~/vdpm -./bootstrap-vitasdk.sh -export PATH=$VITASDK/bin:$PATH - -git clone https://github.com/vitasdk/packages.git ~/vitapackages -cd ~/vitapackages - -dir_array=( - zlib - bzip2 - henkaku - taihen - kubridge - openal-soft - openssl - curl - curlpp - expat - opus - opusfile - glm - kuio - vitaShaRK - libmathneon - vitaGL - SceShaccCgExt -) - -curdir=$(pwd) -for d in "${dir_array[@]}";do - echo "${curdir}${d}" - cd "${curdir}/${d}" - vita-makepkg - vdpm *-arm.tar.xz -done \ No newline at end of file diff --git a/ci/quick-vita-build.sh b/ci/quick-vita-build.sh deleted file mode 100755 index 6b3cbce6..00000000 --- a/ci/quick-vita-build.sh +++ /dev/null @@ -1,10 +0,0 @@ -#!/bin/bash -mkdir -p vita/build -cd vita/build -if [ ! -d "src" ] -then - cmake ../.. -DDAWN_BUILD_TARGET=target-helloworld-vita -DCMAKE_BUILD_TYPE=Debug -fi -make -cp ./src/dawnvita/*.vpk ../ -cd ../.. \ No newline at end of file diff --git a/docs/COMPILING.md b/docs/COMPILING.md deleted file mode 100644 index a2bb689b..00000000 --- a/docs/COMPILING.md +++ /dev/null @@ -1,340 +0,0 @@ -# Compiling, Debugging and Running -This document's purpose is to explain how to compile, debug and run the project -on your machine. It is assumed that you have a basic understanding of the -fundamentals of your operating system and how to use a terminal. - -## Preamble -The Dawn project is written almost entirely in C and C++. This includes all of -the tooling used to generate the project and assets. The only non-C/C++ code is -used by CMake to generate the output compilation files. This provides the Dawn -project an extremely high level of portability not typically seen in other -projects. - -## TLDR; Version -This document is going to go over the installation and configuration of the -following items. If you are already familiar with these tools, you can skip to -the "Downloading the Source Code" section. -- C/C++ Compiler -- CMake -- Git -- IDE -You may also need *python*, since we depend on third-party libraries that may use -python scripts to generate their own build files. This is not required for the -Dawn project itself. - -## Pre-Configuration -In order to compile the Dawn project, you are required to have the following -tools installed on your machine. - -### 1. A C/C++ Compiler -The exact tool(s) will depend on your specific scenario. The compiler is used to -take the .cpp/.hpp files and generating binaries that execute on the target -machine. Typically you will use your own C/C++ compiler for the machine that you -are currently running, e.g. if you are running Windows, you will use the Visual -Studio compiler. If you are running Linux, you will use GCC or Clang. If you are -running macOS, you will use Clang, and so-on. - -If you are intending to compile on a different machine than the one you are -currently running, you will need to use a cross-compiler that is specific for -your use-case. You will also need to refer to the documentation for creating a -new Dawn engine target. - -Please follow the instructions for your specific operating system to install the -appropriate C/C++ compiler. - -**Windows** -You will need to download and install [Visual Studio](https://visualstudio.microsoft.com/downloads/). -Visual Studio (not to be confused with Visual Studio Code) is a full IDE that -bundles the official Microsoft C/C++ compiler. It is the recommended compiler -for building the Dawn project on Windows. - -Advanced used can also use [MinGW](http://www.mingw.org/) or another compiler if -they wish, however this is not officially supported. - -After installing Visual Studio, you will need to install the C++ development -tools. This can be done by opening the Visual Studio Installer and selecting -the "Desktop development with C++" workload. - -**Linux** -You will need to install the GCC and Clang compilers. The compilers are usually -installed either by default or by installing the necessary packages for your -Linux distribution. - -For example, on Ubuntu, you can install the GCC and Clang compilers by running -the following command: -```bash -sudo apt install build-essential clang -``` - -On Arch Linux, you can install the GCC and Clang compilers by running the -following command: -```bash -sudo pacman -S base-devel clang -``` - -And on Fedora, you can install the GCC and Clang compilers by running the -following command: -```bash -sudo dnf install @development-tools clang -``` - -For other distributions, please refer to your distribution's documentation. - -**macOS** -You will need to install the Xcode command line tools. This can be done by done -by running the following command in your terminal: -```bash -xcode-select --install -``` - -Afterwards you will need to install and enable [Brew](https://brew.sh/). This -can be done by running the following command in your terminal: -```bash -/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" -``` - - -### 2. CMake -CMake is a tool that is used to generate the compilation files for the Dawn -project. In short this is used to create versions of the Dawn project that can -be compiled on all different sets of compilers, so if you are compiling on, for -example, Windows, you can use CMake to generate the compilation files for the -Visual Studio compiler, or if you are compiling on Linux, you can use CMake to -generate the compilation files for the GCC or Clang compilers, and so-on. - -To install CMake, please follow the instructions for your specific operating -system. All other operating systems can be found on the [CMake downloads page](https://cmake.org/download/). - -**Windows** -You will need to download and install [CMake](https://cmake.org/download/). The -installer will guide you through the installation process and will install the -CMake executable to your system. It is recommended that you add CMake to your -system PATH if requested by the installer. - -**Linux** -Like installing the C/C++ compilers, you will need to install CMake using your -specific Linux distribution's package manager, there is also a chance that CMake -can be installed using flatpak or snap. Please refer to your distribution's -documentation for more information. - -For Ubuntu and other Debian-based distributions, you can install CMake by using -the following command: -```bash -sudo apt install cmake -``` - -For Arch Linux, you can install CMake by using the following command: -```bash -sudo pacman -S cmake -``` - -And for Fedora, you can install CMake by using the following command: -```bash -sudo dnf install cmake -``` - -**macOS** -You will install CMake using brew. We detailed how to install brew in the C/C++ -compiler section. To install CMake, run the following command in your terminal: -```bash -brew install cmake -``` - -### 3. Git -Git is a version control system that is used to manage the Dawn project's source -code. It is used to download the source code, and to update the source code to -the latest version. It is also used to manage the project's dependencies. Git is -a standard tool in the programming industry and is used to manage complex -projects, especially those that are worked on by multiple people. - -Git, like all of the above tools, is installed slightly differently depending on -your operating system. Please follow the instructions for your specific -operating system. - -**Windows** -You will need to download and install [Git](https://git-scm.com/downloads). The -installer will guide you through the installation process and will install the -Git executable to your system. It is recommended that you add Git to your system -PATH if requested by the installer. You do not need to add the Context-menu -items to your system. - -**Linux** -Like installing the C/C++ compilers, you will need to install Git using your -specific Linux distribution's package manager. It is also likely that git would -have been installed by default. Please refer to your distribution's docs for -more information. - -For Ubuntu and other Debian-based distributions, you can install Git by using -the following command: -```bash -sudo apt install git -``` - -For Arch Linux, you can install Git by using the following command: -```bash -sudo pacman -S git -``` - -And for Fedora, you can install Git by using the following command: -```bash -sudo dnf install git -``` - -**macOS** -You will install Git using brew. We detailed how to install brew in the C/C++ -compiler section. To install Git, run the following command in your terminal: -```bash -brew install git -``` - -### 4. An IDE -An IDE (Integrated Development Environment) is a tool that is used to view and -edit code of projects. While it is not required to use an IDE, it is recommended -since it can make the process of editing and running the project much easier. - -There are many different IDEs available, and often people chose an IDE that will -suit their preferences and needs, however if you are unsure of which IDE you -should be using, the Dawn project recommends using [Visual Studio Code](https://code.visualstudio.com/), -not to be confused with Visual Studio. - -Visual Studio Code is a free and open-source IDE that is widely used in the -industry for its simple, modern and configurable interface. For example you can -configure VSCode to work on Web Projects, Game Projects, and more. It acts like -a text editor with a bunch of extra tools. - -In addition to installing the VSCode IDE, we will add several recommended -plugins that will make editing the Dawn project easier and more seamless. - -To install VSCode follow the instructions for your specific operating system on -the [VSCode downloads page](https://code.visualstudio.com/download), as it can -vary a lot between operating systems. - -After installing VSCode, you will need to install the following plugins, by -clicking on the "Extensions" icon on the left-hand side of the VSCode window, -and searching for the following plugins. You may also be able to click on the -following links to install the plugins directly, but it may not work. -- [C/C++](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) -- [CMake](https://marketplace.visualstudio.com/items?itemName=twxs.cmake) -- [CMake Tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cmake-tools) - -## Downloading the Source Code -Now that you have all of the necessary tools installed, you can download the -source code of the project and using it to build. The source code contains all -of the code of the project, as well as the assets and the build scripts. This is -confidential information and should not be shared with anyone. - -### 1. Cloning the Repository -We previously installed the git tool, which is used to download the source code -of the project, and some third-party libraries. To download the source code, you -will need to open a terminal and navigate to the directory where you want to -download the source code to. - -Afterwards, you will need to run the following command: -```bash -git clone https://git.wish.moe/YourWishes/Dawn.git -``` -This will download the source code of the project to a new subdirectory called -"Dawn" and put all of the projects' source code within there. - -### 2. Installing the Dependencies / Libraries. -I try to keep dependencies on third-party libraries to a minimum, however there -are a few libraries that are required to build the Dawn project. These libraries -are not included in the source code, and must be downloaded separately. This is -done using the git tool. - -After you have cloned the above repository, you will need to open a terminal and -navigate to the directory where you downloaded the source code to. Afterwards, -you will need to run the following command: -```bash -git submodule update --init --recursive -``` -This will fetch all of the third-party libraries that are required to build the -Dawn project. This may take a while depending on your internet connection. - -### 3. Loading the Project -This step is semi-optional. We are aiming to build the project using CMake, and -the easiest way to do this is to use the CMake Tools plugin for VSCode that we -installed earlier. This plugin will automatically detect the CMake files in the -project and will allow us to build the project using the VSCode interface. - -If you opted out of using VSCode, you will need to set up your CMake environment -to suit your IDE or needs. This is outside of the scope of this document, and -you will need to refer to your IDE's documentation for more information. - -To load the project, you will need to open VSCode and open the Dawn project -directory. Most operating systems will allow you to do this by dragging the -Dawn project directory onto the VSCode window. If this does not work, you can -open VSCode and click on the "File" menu, and click on "Open Folder". You will -then need to navigate to the Dawn project directory and click "Open". - -Afterwards you will likely be prompted autometically to configure the CMake -Tools plugin. If you are not, you can click on the "CMake" icon on the left-hand -side of the VSCode window, and click on "Configure". This will configure the -CMake Tools plugin to use the CMake files in the Dawn project directory. - -You may also be asked to select a compiler. If you are using Windows, you will -need to select the Visual Studio compiler. If you are using Linux, you will need -to select the GCC or Clang compiler. If you are using macOS, you will need to -select the Clang compiler. If you are using a different compiler, you will need -to select the appropriate compiler. - -If prompted to select a build type, select "Debug". - -## Compiling the Project -Now that we have the project loaded into our IDE, we can compile the project. -This is done using the CMake Tools plugin for VSCode. If you are not using -VSCode you will need to refer to your IDE's documentation for more information. - -Firstly, you will need to create a settings file to configure the project. In -VSCode you can create a new folder called `.vscode` (Including the leading `.`) -in the Dawn project root directory. Afterwards, you will need to create a new -file called `settings.json` in the `.vscode` directory. You will then need to -paste the following into the file: -```json -{ - "cmake.configureArgs": [ - "-DDAWN_BUILD_TOOLS=true", - "-DDAWN_BUILD_TARGET=target-liminal-win32-glfw", - "-DDAWN_DEBUG_BUILD=true" - ] -} -``` -And save the file. You may want to alter the values to suit your needs. For -example, if you are compiling on Linux, you will need to change -`target-liminal-win32-glfw` to `target-liminal-linux-glfw`. If you are compiling -on macOS, you will need to change it to `target-liminal-osx-glfw`. The specific -configure arguments are outside of the scope of this document, and you will need -to refer to the specific target documentation for more information. - -After you have created the settings file, you will need to configure and build -the project. To perform the build you need to click "Build" button the bottom of -the VSCode window. This will compile the project and will output the resulting -binaries in to a "build" directory within the Dawn project directory. - -Upon clicking the "Build" button, you will see an output panel appear at the -bottom of the VSCode window. This will show the process of the build, and will -show any errors that may occur. This is used to debug and fix any issues that -may occur during the build process. - -If the build was successful, you will see a "Build finished" message in the -output panel, typically read as; -```bash -[build] Build finished with exit code 0 -``` -If you do not see this message, or if the exit code is not 0, you will need to -debug the issue. The output panel will show the error message which is helpful -so we can debug the issue. If you are unable to debug the issue, you can ask for -help in the Discord server. - -## Running the built project -After the build process succeeds you will be able to run the project. This is -done by clicking on the Run icon, which looks like a play button, on the bottom -of the VSCode window. This will run the project in production mode and will not -show any debug information. - -If the program hangs, crashes or does not run, you can click the Debug icon, -which looks like a ladybug, on the bottom of the VSCode window. This will run -the project in debug mode and will show debug information and HALT the program -if there is an error detected. If program HALTS in debug mode you can use this -information to debug the issue. If you are unable to debug the issue, you can -ask for help in the Discord server. \ No newline at end of file diff --git a/src/dawnpoker/poker/CMakeLists.txt b/src/dawnpoker/poker/CMakeLists.txt new file mode 100644 index 00000000..bf81e955 --- /dev/null +++ b/src/dawnpoker/poker/CMakeLists.txt @@ -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 +) \ No newline at end of file diff --git a/src/dawnpoker/poker/Card.cpp b/src/dawnpoker/poker/Card.cpp new file mode 100644 index 00000000..e0e83aef --- /dev/null +++ b/src/dawnpoker/poker/Card.cpp @@ -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 *deck) { + assertNotNull(deck); + + for(uint8_t i = 0; i < CARD_DECK_SIZE; i++) { + deck->push_back(Card(i)); + } +} + +int32_t Card::contains(std::vector *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 *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 Card::countPairs( + std::vector *deck, + enum CardValue val +) { + std::vector 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 *deck) { + assertNotNull(deck); + assertTrue(deck->size() > 1); + std::sort(deck->begin(), deck->end(), &Card::cardSorter); +} diff --git a/src/dawnpoker/poker/Card.hpp b/src/dawnpoker/poker/Card.hpp new file mode 100644 index 00000000..5113a02d --- /dev/null +++ b/src/dawnpoker/poker/Card.hpp @@ -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 *deck); + + /** + * Fills a vector with all of the cards in a deck, in order. + * + * @param deck Deck to fill. + */ + static void fillDeck(std::vector *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 *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 *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 countPairs( + std::vector *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 *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); + } + }; + +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerGame.cpp b/src/dawnpoker/poker/PokerGame.cpp new file mode 100644 index 00000000..522891b1 --- /dev/null +++ b/src/dawnpoker/poker/PokerGame.cpp @@ -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(); + 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; + } +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerGame.hpp b/src/dawnpoker/poker/PokerGame.hpp new file mode 100644 index 00000000..9311cba2 --- /dev/null +++ b/src/dawnpoker/poker/PokerGame.hpp @@ -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 deck; + std::vector grave; + std::vector 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 players; + std::vector 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; + }; +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerPlayer.cpp b/src/dawnpoker/poker/PokerPlayer.cpp new file mode 100644 index 00000000..46ef133c --- /dev/null +++ b/src/dawnpoker/poker/PokerPlayer.cpp @@ -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(); +} + +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(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)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() % 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(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 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; +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerPlayer.hpp b/src/dawnpoker/poker/PokerPlayer.hpp new file mode 100644 index 00000000..b29e7c4e --- /dev/null +++ b/src/dawnpoker/poker/PokerPlayer.hpp @@ -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 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(); + }; +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerPot.cpp b/src/dawnpoker/poker/PokerPot.cpp new file mode 100644 index 00000000..1dbfdea5 --- /dev/null +++ b/src/dawnpoker/poker/PokerPot.cpp @@ -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; +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerPot.hpp b/src/dawnpoker/poker/PokerPot.hpp new file mode 100644 index 00000000..881660e8 --- /dev/null +++ b/src/dawnpoker/poker/PokerPot.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 "PokerWinning.hpp" + +namespace Dawn { + class PokerPlayer; + class PokerGame; + struct PokerPot; + + struct PokerPotWinning { + public: + std::map winnings; + std::vector winners; + std::vector participants; + struct PokerPot *pot; + int32_t chipsEach; + int32_t chipsOverflow; + + void award(); + }; + + struct PokerPot { + public: + int32_t chips; + int32_t call; + std::vector players; + + struct PokerPotWinning getWinners(PokerGame *game); + }; +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerTurn.cpp b/src/dawnpoker/poker/PokerTurn.cpp new file mode 100644 index 00000000..4598aa6e --- /dev/null +++ b/src/dawnpoker/poker/PokerTurn.cpp @@ -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; + } +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerTurn.hpp b/src/dawnpoker/poker/PokerTurn.hpp new file mode 100644 index 00000000..42e9aa95 --- /dev/null +++ b/src/dawnpoker/poker/PokerTurn.hpp @@ -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(); + }; +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerWinning.cpp b/src/dawnpoker/poker/PokerWinning.cpp new file mode 100644 index 00000000..527b356c --- /dev/null +++ b/src/dawnpoker/poker/PokerWinning.cpp @@ -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); +} \ No newline at end of file diff --git a/src/dawnpoker/poker/PokerWinning.hpp b/src/dawnpoker/poker/PokerWinning.hpp new file mode 100644 index 00000000..be71c3db --- /dev/null +++ b/src/dawnpoker/poker/PokerWinning.hpp @@ -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 full; + /** Holds the winning set */ + std::vector 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(); + }; +} \ No newline at end of file diff --git a/src/dawnpoker/scenes/CMakeLists.txt b/src/dawnpoker/scenes/CMakeLists.txt index d2eb416c..ba19fa9e 100644 --- a/src/dawnpoker/scenes/CMakeLists.txt +++ b/src/dawnpoker/scenes/CMakeLists.txt @@ -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