Moved poker code to main dawn code.
This commit is contained in:
@ -8,6 +8,7 @@ target_link_libraries(${DAWN_TARGET_NAME}
|
||||
PUBLIC
|
||||
glm
|
||||
archive_static
|
||||
freetype
|
||||
)
|
||||
|
||||
# Includes
|
||||
@ -29,6 +30,7 @@ add_subdirectory(game)
|
||||
add_subdirectory(locale)
|
||||
add_subdirectory(prefab)
|
||||
# add_subdirectory(physics)
|
||||
add_subdirectory(poker)
|
||||
add_subdirectory(save)
|
||||
add_subdirectory(scene)
|
||||
# add_subdirectory(state)
|
||||
|
9
src/dawn/display/font/CMakeLists.txt
Normal file
9
src/dawn/display/font/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
# Copyright (c) 2023 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
target_sources(${DAWN_TARGET_NAME}
|
||||
PRIVATE
|
||||
TrueTypeTexture.cpp
|
||||
)
|
16
src/dawn/display/font/TrueTypeCharacter.hpp
Normal file
16
src/dawn/display/font/TrueTypeCharacter.hpp
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "dawnlibs.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
struct TrueTypeCharacter {
|
||||
glm::vec2 advance;
|
||||
glm::vec2 size;
|
||||
glm::vec2 offset;
|
||||
glm::vec4 quad;
|
||||
};
|
||||
}
|
198
src/dawn/display/font/TrueTypeTexture.cpp
Normal file
198
src/dawn/display/font/TrueTypeTexture.cpp
Normal file
@ -0,0 +1,198 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "TrueTypeTexture.hpp"
|
||||
#include "assert/assert.hpp"
|
||||
#include "util/Math.hpp"
|
||||
#include "display/mesh/QuadMesh.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
TrueTypeTexture::TrueTypeTexture(const uint32_t fontSize) :
|
||||
fontSize(fontSize)
|
||||
{
|
||||
assertTrue(fontSize > 0, "Font size cannot be zero");
|
||||
texture = std::make_shared<Texture>();
|
||||
}
|
||||
|
||||
void TrueTypeTexture::setFace(const FT_Face face) {
|
||||
this->face = face;
|
||||
assertTrue(fontSize < 256, "Font size cannot be greater than 256");
|
||||
|
||||
// Set freetype font size prior to baking.
|
||||
auto ret = FT_Set_Pixel_Sizes(face, 0, fontSize);
|
||||
if(ret != 0) {
|
||||
assertUnreachable("Failed to set font size %i", ret);
|
||||
}
|
||||
|
||||
// Set the texture size
|
||||
texture->setSize(
|
||||
fontSize * 24,
|
||||
fontSize * 24,
|
||||
TextureFormat::R,
|
||||
TextureDataFormat::UNSIGNED_BYTE
|
||||
);
|
||||
|
||||
// Texture buffer
|
||||
uint8_t *buffer = new uint8_t[texture->getWidth() * texture->getHeight()];
|
||||
// Fill with zeros
|
||||
std::memset(buffer, 0, texture->getWidth() * texture->getHeight());
|
||||
|
||||
size_t offset = 0;
|
||||
struct TrueTypeCharacter info;
|
||||
int32_t textureX = 0, textureY = 0;
|
||||
int32_t rowHeight = 0;
|
||||
|
||||
// Character sets
|
||||
std::vector<wchar_t> characterBlocks;
|
||||
// Latin
|
||||
for(wchar_t c = 0x0020; c < 0x007F; c++) characterBlocks.push_back(c);
|
||||
// Latin-1 Supplement
|
||||
for(wchar_t c = 0x00A0; c < 0x00FF; c++) characterBlocks.push_back(c);
|
||||
// Latin Extended-A
|
||||
for(wchar_t c = 0x0100; c < 0x017F; c++) characterBlocks.push_back(c);
|
||||
// Latin Extended-B
|
||||
for(wchar_t c = 0x0180; c < 0x024F; c++) characterBlocks.push_back(c);
|
||||
// Hiragana
|
||||
for(wchar_t c = 0x3040; c < 0x309F; c++) characterBlocks.push_back(c);
|
||||
// Katakana
|
||||
for(wchar_t c = 0x30A0; c < 0x30FF; c++) characterBlocks.push_back(c);
|
||||
|
||||
// For each character in the character set
|
||||
for(wchar_t c : characterBlocks) {
|
||||
// Load the character
|
||||
if(FT_Load_Char(face, c, FT_LOAD_RENDER)) {
|
||||
assertUnreachable("Failed to load character (1)");
|
||||
}
|
||||
|
||||
// Store the character information
|
||||
info.advance.x = (float_t)(face->glyph->advance.x >> 6);
|
||||
info.advance.y = (float_t)(face->glyph->advance.y >> 6);
|
||||
info.size = glm::vec2(face->glyph->bitmap.width, face->glyph->bitmap.rows);
|
||||
|
||||
// Determine the texture position
|
||||
if(textureX + face->glyph->bitmap.width >= texture->getWidth()) {
|
||||
textureX = 0;
|
||||
textureY += rowHeight + 2;// Tiny gap between rows
|
||||
rowHeight = face->glyph->bitmap.rows;
|
||||
} else {
|
||||
rowHeight = Math::max<int32_t>(rowHeight, face->glyph->bitmap.rows);
|
||||
}
|
||||
|
||||
// Set the quad positions
|
||||
info.offset = glm::vec2(
|
||||
face->glyph->bitmap_left,
|
||||
-face->glyph->bitmap_top
|
||||
);
|
||||
info.quad = glm::vec4(
|
||||
textureX,
|
||||
textureY,
|
||||
textureX + face->glyph->bitmap.width,
|
||||
textureY + face->glyph->bitmap.rows
|
||||
) / glm::vec4(
|
||||
texture->getWidth(),
|
||||
texture->getHeight(),
|
||||
texture->getWidth(),
|
||||
texture->getHeight()
|
||||
);
|
||||
|
||||
// Store the cached character data.
|
||||
this->characterData[c] = info;
|
||||
|
||||
// Determine pixel offset.
|
||||
offset = textureX + (textureY * texture->getWidth());
|
||||
assertTrue(
|
||||
offset + (face->glyph->bitmap.rows * texture->getWidth()) <=
|
||||
texture->getWidth() * texture->getHeight(),
|
||||
"Font texture buffer overflow will occur."
|
||||
);
|
||||
|
||||
// Buffer pixels, we have to do this one row at a time due to the
|
||||
// differences in width between the glyph and the texture.
|
||||
const size_t countPerRow = face->glyph->bitmap.width;
|
||||
int32_t i = 0;
|
||||
while(i != face->glyph->bitmap.rows) {
|
||||
std::memcpy(
|
||||
buffer + offset + (i * texture->getWidth()),
|
||||
face->glyph->bitmap.buffer + (i * countPerRow),
|
||||
countPerRow
|
||||
);
|
||||
i++;
|
||||
}
|
||||
|
||||
// Increment textureX
|
||||
textureX += face->glyph->bitmap.width + 2;// I add a tiny gap between chars
|
||||
}
|
||||
|
||||
this->texture->buffer(buffer);
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
struct TrueTypeCharacter TrueTypeTexture::getCharacterData(wchar_t c) {
|
||||
return this->characterData[c];
|
||||
}
|
||||
|
||||
glm::vec2 TrueTypeTexture::bufferStringToMesh(
|
||||
std::shared_ptr<Mesh> mesh,
|
||||
const std::wstring text,
|
||||
glm::vec2 &position,
|
||||
bool_t flipY
|
||||
) {
|
||||
assertNotNull(mesh, "Mesh must be supplied and not null");
|
||||
assertTrue(text.size() > 0, "Text must be at least one character long.");
|
||||
|
||||
// Create mesh buffers
|
||||
mesh->createBuffers(
|
||||
text.length() * QUAD_VERTICE_COUNT,
|
||||
text.length() * QUAD_INDICE_COUNT
|
||||
);
|
||||
|
||||
// Foreach char
|
||||
size_t i = 0;
|
||||
glm::vec2 size = { 0, 0 };
|
||||
for(wchar_t c : text) {
|
||||
// Get the character data
|
||||
auto info = this->getCharacterData(c);
|
||||
|
||||
// Buffer the quad
|
||||
glm::vec4 quad = glm::vec4(
|
||||
position.x,
|
||||
position.y,
|
||||
position.x + info.size.x,
|
||||
position.y + info.size.y
|
||||
);
|
||||
if(flipY) {
|
||||
QuadMesh::buffer(
|
||||
mesh,
|
||||
quad,
|
||||
glm::vec4(
|
||||
info.quad.x,
|
||||
info.quad.w,
|
||||
info.quad.z,
|
||||
info.quad.y
|
||||
),
|
||||
i * QUAD_VERTICE_COUNT,
|
||||
i * QUAD_INDICE_COUNT
|
||||
);
|
||||
} else {
|
||||
QuadMesh::buffer(
|
||||
mesh,
|
||||
quad,
|
||||
info.quad,
|
||||
i * QUAD_VERTICE_COUNT,
|
||||
i * QUAD_INDICE_COUNT
|
||||
);
|
||||
}
|
||||
position += info.advance;
|
||||
size += info.advance;
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
TrueTypeTexture::~TrueTypeTexture() {
|
||||
}
|
66
src/dawn/display/font/TrueTypeTexture.hpp
Normal file
66
src/dawn/display/font/TrueTypeTexture.hpp
Normal file
@ -0,0 +1,66 @@
|
||||
// Copyright (c) 2023 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "display/Texture.hpp"
|
||||
#include "TrueTypeCharacter.hpp"
|
||||
#include "display/mesh/Mesh.hpp"
|
||||
#include <ft2build.h>
|
||||
#include FT_FREETYPE_H
|
||||
|
||||
namespace Dawn {
|
||||
class TrueTypeTexture final {
|
||||
private:
|
||||
FT_Face face;
|
||||
|
||||
public:
|
||||
uint32_t fontSize;
|
||||
std::shared_ptr<Texture> texture;
|
||||
std::unordered_map<wchar_t, struct TrueTypeCharacter> characterData;
|
||||
|
||||
/**
|
||||
* Construct a new New True Type Face Texture object
|
||||
*
|
||||
* @param fontSize Size of the font.
|
||||
*/
|
||||
TrueTypeTexture(const uint32_t fontSize);
|
||||
|
||||
/**
|
||||
* Sets the face for this texture.
|
||||
*
|
||||
* @param face Face to set.
|
||||
*/
|
||||
void setFace(const FT_Face face);
|
||||
|
||||
/**
|
||||
* Returns the character data for the given character.
|
||||
*
|
||||
* @param c Character to get data for.
|
||||
* @return The Character data for the given character.
|
||||
*/
|
||||
struct TrueTypeCharacter getCharacterData(wchar_t c);
|
||||
|
||||
/**
|
||||
* Buffers a string to the given mesh.
|
||||
*
|
||||
* @param mesh Mesh to buffer to.
|
||||
* @param text Text to buffer.
|
||||
* @param position Position to buffer to.
|
||||
* @param flipY Whether or not to flip the Y axis.
|
||||
* @return The size of the string.
|
||||
*/
|
||||
glm::vec2 bufferStringToMesh(
|
||||
std::shared_ptr<Mesh> mesh,
|
||||
const std::wstring text,
|
||||
glm::vec2 &position,
|
||||
bool_t flipY = false
|
||||
);
|
||||
|
||||
/**
|
||||
* Destroys this true type face texture.
|
||||
*/
|
||||
~TrueTypeTexture();
|
||||
};
|
||||
}
|
15
src/dawn/poker/CMakeLists.txt
Normal file
15
src/dawn/poker/CMakeLists.txt
Normal file
@ -0,0 +1,15 @@
|
||||
# Copyright (c) 2022 Dominic Masters
|
||||
#
|
||||
# This software is released under the MIT License.
|
||||
# https://opensource.org/licenses/MIT
|
||||
|
||||
# Sources
|
||||
target_sources(${DAWN_TARGET_NAME}
|
||||
PRIVATE
|
||||
Card.cpp
|
||||
PokerPot.cpp
|
||||
PokerPlayer.cpp
|
||||
PokerGame.cpp
|
||||
PokerWinning.cpp
|
||||
PokerTurn.cpp
|
||||
)
|
94
src/dawn/poker/Card.cpp
Normal file
94
src/dawn/poker/Card.cpp
Normal file
@ -0,0 +1,94 @@
|
||||
// Copyright (c) 2022 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "Card.hpp"
|
||||
#include "util/Random.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
void Card::shuffle(std::vector<struct Card> &deck) {
|
||||
for(uint8_t i = 0; i < deck.size(); i++) {
|
||||
uint8_t swap = Random::random<uint8_t>(0, deck.size() - 1);
|
||||
struct Card tmp = deck[i];
|
||||
deck[i] = deck[swap];
|
||||
deck[swap] = tmp;
|
||||
}
|
||||
}
|
||||
|
||||
void Card::fillDeck(std::vector<struct Card> &deck) {
|
||||
deck.clear();
|
||||
for(uint8_t i = 0; i < CARD_DECK_SIZE; i++) {
|
||||
deck.push_back(Card(i));
|
||||
}
|
||||
}
|
||||
|
||||
int32_t Card::contains(
|
||||
const std::vector<struct Card> &deck,
|
||||
const struct Card c
|
||||
) {
|
||||
if(deck.size() == 0) return -1;
|
||||
|
||||
return std::distance(
|
||||
deck.begin(),
|
||||
std::find_if(
|
||||
deck.begin(),
|
||||
deck.end(),
|
||||
[c](struct Card card) {
|
||||
return card.cardValue == c.cardValue;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
int32_t Card::containsValue(
|
||||
const std::vector<struct Card> &deck,
|
||||
const enum CardValue number
|
||||
) {
|
||||
if(deck.size() == 0) return -1;
|
||||
|
||||
return std::distance(
|
||||
deck.begin(),
|
||||
std::find_if(
|
||||
deck.begin(),
|
||||
deck.end(),
|
||||
[number](struct Card c) {
|
||||
return c.getValue() == number;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
std::vector<struct Card> Card::countPairs(
|
||||
const std::vector<struct Card> &deck,
|
||||
const enum CardValue val
|
||||
) {
|
||||
std::vector<struct Card> pairs;
|
||||
|
||||
std::for_each(
|
||||
deck.begin(),
|
||||
deck.end(),
|
||||
[&pairs, val](struct Card c) {
|
||||
if(c.getValue() == val) pairs.push_back(c);
|
||||
}
|
||||
);
|
||||
|
||||
return pairs;
|
||||
}
|
||||
|
||||
bool_t Card::cardSorter(struct Card left, struct Card right) {
|
||||
return left.cardValue < right.cardValue;
|
||||
}
|
||||
|
||||
void Card::sort(std::vector<struct Card> &deck) {
|
||||
std::sort(deck.begin(), deck.end(), &Card::cardSorter);
|
||||
}
|
||||
|
||||
enum CardValue Card::getValue() {
|
||||
return (enum CardValue)(cardValue % CARD_COUNT_PER_SUIT);
|
||||
}
|
||||
|
||||
enum CardSuit Card::getSuit() {
|
||||
return (enum CardSuit)(cardValue / CARD_COUNT_PER_SUIT);
|
||||
}
|
151
src/dawn/poker/Card.hpp
Normal file
151
src/dawn/poker/Card.hpp
Normal file
@ -0,0 +1,151 @@
|
||||
// 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,
|
||||
};
|
||||
|
||||
enum class CardValue : uint8_t {
|
||||
Two = 0,
|
||||
Three = 1,
|
||||
Four = 2,
|
||||
Five = 3,
|
||||
Six = 4,
|
||||
Seven = 5,
|
||||
Eight = 6,
|
||||
Nine = 7,
|
||||
Ten = 8,
|
||||
Jack = 9,
|
||||
Queen = 10,
|
||||
King = 11,
|
||||
Ace = 12,
|
||||
Invalid = 0xFF
|
||||
};
|
||||
|
||||
/** Count of cards in each suit */
|
||||
#define CARD_COUNT_PER_SUIT 13
|
||||
|
||||
/** Count of suits */
|
||||
#define CARD_SUIT_COUNT 4
|
||||
|
||||
/** Standard Card Deck Size */
|
||||
#define CARD_DECK_SIZE CARD_COUNT_PER_SUIT*CARD_SUIT_COUNT
|
||||
|
||||
struct Card {
|
||||
public:
|
||||
uint8_t cardValue;
|
||||
|
||||
/**
|
||||
* Shuffles a hand / deck
|
||||
*
|
||||
* @param deck Array of cards to shuffle.
|
||||
*/
|
||||
static void shuffle(std::vector<struct Card> &deck);
|
||||
|
||||
/**
|
||||
* Fills a vector with all of the cards in a deck, in order.
|
||||
*
|
||||
* @param deck Deck to fill.
|
||||
*/
|
||||
static void fillDeck(std::vector<struct Card> &deck);
|
||||
|
||||
/**
|
||||
* Check if an array of cards contains a specific card.
|
||||
*
|
||||
* @param deck Deck/Hand/Array of cards to check.
|
||||
* @param card Card to look for
|
||||
* @returns The index within the array that the card is. -1 if not found.
|
||||
*/
|
||||
static int32_t contains(
|
||||
const std::vector<struct Card> &deck,
|
||||
const 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 containsValue(
|
||||
const std::vector<struct Card> &deck,
|
||||
const enum CardValue number
|
||||
);
|
||||
|
||||
/**
|
||||
* Counts the amount of times a card's number appears within the given
|
||||
* hand.
|
||||
*
|
||||
* @param deck The hand to check
|
||||
* @param val Value of pairs to find.
|
||||
* @return Card pairs in the deck.
|
||||
*/
|
||||
static std::vector<struct Card> countPairs(
|
||||
const std::vector<struct Card> &deck,
|
||||
const enum CardValue val
|
||||
);
|
||||
|
||||
/**
|
||||
* Sorter for the cardSorter function.
|
||||
*
|
||||
* @param left Left card to compare.
|
||||
* @param right Right card to compare.
|
||||
* @returns True if left is less than right.
|
||||
*/
|
||||
static bool_t cardSorter(struct Card left, struct Card right);
|
||||
|
||||
/**
|
||||
* Sort a hand of cards. Cards are ordered in descending weight, aces are
|
||||
* high. Cards will be grouped by their suits, e.g. CARD_CLUBS_TWO will
|
||||
* appear before CARD_DIAMONDS_KING.
|
||||
*
|
||||
* @param deck Hand of cards to sort.
|
||||
*/
|
||||
static void sort(std::vector<struct Card> &deck);
|
||||
|
||||
/**
|
||||
* Constructor for the Card class.
|
||||
*
|
||||
* @param suit Suit of the card.
|
||||
* @param num Number of the card.
|
||||
*/
|
||||
Card(const CardSuit suit, const CardValue num) :
|
||||
Card(
|
||||
((uint8_t)suit * CARD_COUNT_PER_SUIT) + (uint8_t)num
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor for the Card class.
|
||||
*
|
||||
* @param cv Card value.
|
||||
*/
|
||||
Card(const uint8_t cv) : cardValue(cv) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of a given card.
|
||||
* @returns The card number.
|
||||
*/
|
||||
enum CardValue getValue();
|
||||
|
||||
/**
|
||||
* Returns the suit of a given card.
|
||||
* @returns The suit.
|
||||
*/
|
||||
enum CardSuit getSuit();
|
||||
};
|
||||
|
||||
}
|
197
src/dawn/poker/PokerGame.cpp
Normal file
197
src/dawn/poker/PokerGame.cpp
Normal file
@ -0,0 +1,197 @@
|
||||
// Copyright (c) 2022 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "PokerGame.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
std::shared_ptr<PokerPlayer> PokerGame::addNewPlayer() {
|
||||
auto player = std::make_shared<PokerPlayer>(
|
||||
weak_from_this(), this->players.size()
|
||||
);
|
||||
assertFalse(this->players.size() == PLAYER_COUNT_MAX, "Too many players.");
|
||||
this->players.push_back(player);
|
||||
return player;
|
||||
}
|
||||
|
||||
std::shared_ptr<PokerPlayer> PokerGame::getCurrentBetter() {
|
||||
auto nextIndex = this->getNextBetterIndex();
|
||||
if(nextIndex == 0xFF) return nullptr;
|
||||
return this->players[nextIndex];
|
||||
}
|
||||
|
||||
void PokerGame::newGame() {
|
||||
assertTrue(this->players.size() >= PLAYER_COUNT_MIN, "Not enough players.");
|
||||
this->newRound();
|
||||
this->smallBlind = POKER_BLIND_SMALL_DEFAULT;
|
||||
this->bigBlind = POKER_BLIND_BIG_DEFAULT;
|
||||
|
||||
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->grave.clear();
|
||||
this->community.clear();
|
||||
this->pots.clear();
|
||||
this->pots.push_back(PokerPot());
|
||||
|
||||
this->hasFlopped = false;
|
||||
this->hasTurned = false;
|
||||
this->hasRivered = false;
|
||||
|
||||
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(const uint8_t dealer) {
|
||||
uint8_t i, k;
|
||||
std::shared_ptr<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()) {
|
||||
if(!(*it)->isFolded && !(*it)->isOut) count++;
|
||||
++it;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
int32_t PokerGame::getCurrentCallValue() {
|
||||
assertTrue(this->pots.size() > 0, "No pots?");
|
||||
return this->pots.back().call;
|
||||
}
|
||||
|
||||
void PokerGame::burnCard() {
|
||||
assertTrue(this->deck.size() > 0, "No cards to burn.");
|
||||
auto card = this->deck.back();
|
||||
this->deck.pop_back();
|
||||
this->grave.push_back(card);
|
||||
}
|
||||
|
||||
void PokerGame::dealCard(PokerPlayer &player) {
|
||||
assertTrue(this->deck.size() > 0, "No cards to deal.");
|
||||
auto card = this->deck.back();
|
||||
this->deck.pop_back();
|
||||
player.hand.push_back(card);
|
||||
}
|
||||
|
||||
void PokerGame::dealToEveryone(const 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(const uint8_t count) {
|
||||
uint8_t c = count;
|
||||
if(c == 0xFF) c = this->getCountOfCardsToTurn();
|
||||
assertTrue(this->deck.size() >= c, "Not enough cards to turn.");
|
||||
for(uint8_t i = 0; i < c; i++) {
|
||||
auto card = this->deck.back();
|
||||
this->deck.pop_back();
|
||||
this->community.push_back(card);
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t PokerGame::getCountOfCardsToTurn() {
|
||||
if(!this->hasFlopped) return 3;
|
||||
if(!this->hasTurned) return 1;
|
||||
if(!this->hasRivered) return 1;
|
||||
assertUnreachable("No more cards to turn.");
|
||||
}
|
151
src/dawn/poker/PokerGame.hpp
Normal file
151
src/dawn/poker/PokerGame.hpp
Normal file
@ -0,0 +1,151 @@
|
||||
// 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
|
||||
|
||||
#define PLAYER_COUNT_MAX 8
|
||||
#define PLAYER_COUNT_MIN 2
|
||||
|
||||
namespace Dawn {
|
||||
class PokerGame : public std::enable_shared_from_this<PokerGame> {
|
||||
protected:
|
||||
std::vector<struct Card> deck;
|
||||
std::vector<struct Card> grave;
|
||||
uint8_t dealerIndex;
|
||||
uint8_t smallBlindIndex;
|
||||
uint8_t bigBlindIndex;
|
||||
int32_t smallBlind = POKER_BLIND_SMALL_DEFAULT;
|
||||
int32_t bigBlind = POKER_BLIND_BIG_DEFAULT;
|
||||
|
||||
bool_t hasFlopped = false;
|
||||
bool_t hasTurned = false;
|
||||
bool_t hasRivered = false;
|
||||
|
||||
public:
|
||||
std::vector<std::shared_ptr<PokerPlayer>> players;
|
||||
std::vector<struct PokerPot> pots;
|
||||
std::vector<struct Card> community;
|
||||
uint8_t betterIndex;
|
||||
|
||||
/**
|
||||
* Creates and adds a new player to the game.
|
||||
*/
|
||||
std::shared_ptr<PokerPlayer> addNewPlayer();
|
||||
|
||||
/**
|
||||
* Returns the player that is currently the better.
|
||||
*
|
||||
* @return The player that is currently the better.
|
||||
*/
|
||||
std::shared_ptr<PokerPlayer> getCurrentBetter();
|
||||
|
||||
/**
|
||||
* Starts a new game of poker.
|
||||
*/
|
||||
void newGame();
|
||||
|
||||
/**
|
||||
* Starts a new round of poker.
|
||||
*/
|
||||
void newRound();
|
||||
|
||||
/**
|
||||
* Starts a new betting round.
|
||||
*/
|
||||
void newBettingRound();
|
||||
|
||||
/**
|
||||
* Takes the blinds from the players.
|
||||
*/
|
||||
void takeBlinds();
|
||||
|
||||
/**
|
||||
* Sets the blinds for the game.
|
||||
*
|
||||
* @param small The cost of the small blind.
|
||||
* @param big The cost of the big blind.
|
||||
*/
|
||||
void setBlinds(const int32_t small, const int32_t big);
|
||||
|
||||
/**
|
||||
* Returns the count of players that still need to bet this round.
|
||||
*
|
||||
* @return The count of players that still need to bet this round.
|
||||
*/
|
||||
uint8_t getRemainingBettersCount();
|
||||
|
||||
/**
|
||||
* Returns the current call value for the game.
|
||||
*
|
||||
* @return The current call value for the game.
|
||||
*/
|
||||
int32_t getCurrentCallValue();
|
||||
|
||||
/**
|
||||
* Returns the next better index.
|
||||
*
|
||||
* @return The next better index.
|
||||
*/
|
||||
uint8_t getNextBetterIndex();
|
||||
|
||||
/**
|
||||
* Sets the dealer for the game.
|
||||
*
|
||||
* @param dealer The index of the dealer.
|
||||
*/
|
||||
void setDealer(const uint8_t dealer);
|
||||
|
||||
/**
|
||||
* Sends a card to the burn pile.
|
||||
*/
|
||||
void burnCard();
|
||||
|
||||
/**
|
||||
* Deals a card to a player.
|
||||
*
|
||||
* @param player The player to deal the card to.
|
||||
*/
|
||||
void dealCard(PokerPlayer &player);
|
||||
|
||||
/**
|
||||
* Deals a card to each player.
|
||||
*
|
||||
* @param count The count of cards to deal.
|
||||
*/
|
||||
void dealToEveryone(const uint8_t count);
|
||||
|
||||
/**
|
||||
* Deals a card to the community.
|
||||
*
|
||||
* @param count The count of cards to turn.
|
||||
*/
|
||||
void turn(const uint8_t count = 0xFF);
|
||||
|
||||
/**
|
||||
* Returns the count of cards that need to be turned.
|
||||
*
|
||||
* @return The count of cards that need to be turned.
|
||||
*/
|
||||
uint8_t getCountOfCardsToTurn();
|
||||
|
||||
/**
|
||||
* Returns the count of players that are still in the game.
|
||||
*
|
||||
* @return The count of players that are still in the game.
|
||||
*/
|
||||
uint8_t getRemainingPlayersCount();
|
||||
};
|
||||
}
|
481
src/dawn/poker/PokerPlayer.cpp
Normal file
481
src/dawn/poker/PokerPlayer.cpp
Normal file
@ -0,0 +1,481 @@
|
||||
// Copyright (c) 2022 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "PokerPlayer.hpp"
|
||||
#include "PokerGame.hpp"
|
||||
#include "util/Math.hpp"
|
||||
#include "util/Random.hpp"
|
||||
#include "util/Easing.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
PokerPlayer::PokerPlayer(
|
||||
const std::weak_ptr<PokerGame> pokerGame,
|
||||
const uint8_t playerIndex
|
||||
) {
|
||||
this->pokerGame = pokerGame;
|
||||
this->playerIndex = playerIndex;
|
||||
this->chips = POKER_PLAYER_CHIPS_DEFAULT;
|
||||
}
|
||||
|
||||
void PokerPlayer::addChips(const int32_t chips) {
|
||||
assertTrue(chips > 0, "Must add a positive amount of chips.");
|
||||
|
||||
this->chips += chips;
|
||||
if(this->chips > 0) this->isOut = false;
|
||||
|
||||
this->eventChipsChanged.emit();
|
||||
}
|
||||
|
||||
void PokerPlayer::setChips(const 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;
|
||||
auto pg = this->pokerGame.lock();
|
||||
assertNotNull(pg, "PokerGame has become invalid.");
|
||||
if(this->currentBet < pg->getCurrentCallValue()) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
void PokerPlayer::bet(
|
||||
struct PokerPot &pot,
|
||||
const int32_t chips
|
||||
) {
|
||||
assertTrue(chips >= 0, "Chips must be a positive value.");
|
||||
assertTrue(!this->isFolded, "Cannot bet if player is folded.");
|
||||
assertTrue(!this->isOut, "Cannot bet if player is out.");
|
||||
|
||||
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 = Math::max<int32_t>(pot.call, this ->currentBet);
|
||||
|
||||
auto existing = std::find(pot.players.begin(), pot.players.end(), shared_from_this());
|
||||
if(existing == pot.players.end()) pot.players.push_back(shared_from_this());
|
||||
}
|
||||
|
||||
void PokerPlayer::bet(const int32_t chips) {
|
||||
auto pg = this->pokerGame.lock();
|
||||
assertNotNull(pg, "PokerGame has become invalid.");
|
||||
assertTrue(pg->pots.size() > 0, "PokerGame has no pots?");
|
||||
this->bet(pg->pots.back(), chips);
|
||||
}
|
||||
|
||||
void PokerPlayer::fold() {
|
||||
this->isFolded = true;
|
||||
this->hasBetThisRound = true;
|
||||
this->timesRaised = 0;
|
||||
}
|
||||
|
||||
bool_t PokerPlayer::canCheck() {
|
||||
if(this->isFolded) return false;
|
||||
if(this->isOut) return false;
|
||||
|
||||
auto pg = this->pokerGame.lock();
|
||||
assertNotNull(pg, "PokerGame has become invalid.");
|
||||
return pg->getCurrentCallValue() == this->currentBet;
|
||||
}
|
||||
|
||||
struct PokerTurn PokerPlayer::getAITurn() {
|
||||
struct PokerTurn turn;
|
||||
float_t confidence;
|
||||
int32_t callBet;
|
||||
float_t potOdds;
|
||||
|
||||
auto pg = this->pokerGame.lock();
|
||||
assertNotNull(pg, "PokerGame has become invalid.");
|
||||
|
||||
// Can the player do anything?
|
||||
if(this->isFolded || this->isOut) {
|
||||
turn.type = PokerTurnType::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(pg->community.size() == 0) {
|
||||
assertTrue(
|
||||
this->hand.size() == POKER_PLAYER_HAND_SIZE_MAX,
|
||||
"Invalid hand size."
|
||||
);
|
||||
|
||||
// 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)Math::abs<int8_t>(
|
||||
(int8_t)cardNumber0 - (int8_t)cardNumber1
|
||||
);
|
||||
|
||||
// Get card weight
|
||||
confidence = (float_t)cardNumber0 + (float_t)cardNumber1;
|
||||
if(cardNumber0 == cardNumber1) {// Pairs
|
||||
confidence += 6;
|
||||
} else if(suitNumber0 == suitNumber1) {// Same suit
|
||||
confidence += 4;
|
||||
}
|
||||
|
||||
// Get difference from cards for guessing flush
|
||||
if(i > 4) {
|
||||
confidence -= 4;
|
||||
} else if(i > 2) {
|
||||
confidence -= i;
|
||||
}
|
||||
|
||||
// Get the confidence delta 0-1
|
||||
confidence = confidence / 30.0f;
|
||||
|
||||
// This may change in future, but I was finding the AI did not want to bet
|
||||
// during the preflop enough, this curves the AI to want to preflop call
|
||||
// often.
|
||||
confidence = Easing::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)pg->getRemainingBettersCount();
|
||||
}
|
||||
|
||||
// Now determine the expected ROI
|
||||
auto expectedGain = confidence / potOdds;
|
||||
|
||||
// Now get a random 0-100
|
||||
auto random = Random::random<int32_t>() % 100;
|
||||
|
||||
// Determine the max bet that the AI is willing to make
|
||||
auto maxBet = (int32_t)((float_t)this->chips / 1.75f) - (random / 2);
|
||||
maxBet -= callBet;
|
||||
|
||||
// Determine what's a good bluff bet.
|
||||
auto bluffBet = random * maxBet / 100 / 2;
|
||||
|
||||
// Now prep the output
|
||||
auto isBluff = false;
|
||||
auto amount = 0;
|
||||
|
||||
// Now the actual AI can happen. This is basically a weight to confidence
|
||||
// ratio. The higher the gains and the confidence then the more likely the AI
|
||||
// is to betting. There are also bluff chances within here.
|
||||
if(expectedGain < 0.8f && confidence < 0.8f) {
|
||||
if(random < 85) {
|
||||
amount = 0;
|
||||
} else {
|
||||
amount = bluffBet;
|
||||
isBluff = true;
|
||||
}
|
||||
} else if((expectedGain < 1.0f && confidence < 0.85f) || confidence < 0.1f) {
|
||||
if(random < 80) {
|
||||
amount = 0;
|
||||
} else if(random < 5) {
|
||||
amount = callBet;
|
||||
isBluff = true;
|
||||
} else {
|
||||
amount = bluffBet;
|
||||
isBluff = true;
|
||||
}
|
||||
} else if((expectedGain < 1.3f && confidence < 0.9f) || confidence < 0.5f) {
|
||||
if(random < 60 || confidence < 0.5f) {
|
||||
amount = callBet;
|
||||
} else {
|
||||
amount = maxBet;
|
||||
}
|
||||
} else if(confidence < 0.95f || pg->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(pg->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 = Math::max<int32_t>(amount, callBet);
|
||||
turn = PokerTurn::bet(shared_from_this(), amount);
|
||||
turn.confidence = confidence;
|
||||
} else if(this->canCheck()) {
|
||||
turn = PokerTurn::bet(shared_from_this(), 0);
|
||||
turn.confidence = 1;
|
||||
} else {
|
||||
turn = PokerTurn::fold(shared_from_this());
|
||||
turn.confidence = 1 - confidence;
|
||||
}
|
||||
|
||||
return turn;
|
||||
}
|
||||
|
||||
int32_t PokerPlayer::getCallBet() {
|
||||
auto pg = this->pokerGame.lock();
|
||||
assertNotNull(pg, "PokerGame has become invalid.");
|
||||
return pg->getCurrentCallValue() - this->currentBet;
|
||||
}
|
||||
|
||||
int32_t PokerPlayer::getSumOfChips() {
|
||||
auto pg = this->pokerGame.lock();
|
||||
assertNotNull(pg, "PokerGame has become invalid.");
|
||||
int32_t count = 0;
|
||||
auto it = pg->pots.begin();
|
||||
while(it != pg->pots.end()) {
|
||||
if(std::find(
|
||||
it->players.begin(), it->players.end(), shared_from_this()
|
||||
) != it->players.end()) {
|
||||
count += it->chips;
|
||||
}
|
||||
++it;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
struct PokerWinning PokerPlayer::getWinning() {
|
||||
struct PokerWinning winning;
|
||||
struct Card card(0x00);
|
||||
uint8_t i, j;
|
||||
int32_t index;
|
||||
enum CardValue number, look;
|
||||
enum CardSuit suit;
|
||||
std::vector<struct Card> pairs;
|
||||
|
||||
auto pg = this->pokerGame.lock();
|
||||
assertNotNull(pg, "PokerGame has become invalid.");
|
||||
|
||||
winning.player = shared_from_this();
|
||||
|
||||
// Get the full poker hand (should be a 7 card hand, but MAY not be)
|
||||
for(i = 0; i < pg->community.size(); i++) {
|
||||
winning.full.push_back(pg->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 < CardValue::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 == CardValue::Five && j == 4 ?
|
||||
(enum CardValue)CardValue::Ace :
|
||||
(enum CardValue)((uint8_t)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 == CardValue::Ace ? PokerWinningType::RoyalFlush :
|
||||
PokerWinningType::StraightFlush
|
||||
);
|
||||
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 = PokerWinningType::FourOfAKind;
|
||||
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 = PokerWinningType::FullHouse;
|
||||
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 = PokerWinningType::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 < CardValue::Five) continue;
|
||||
|
||||
winning.set.clear();
|
||||
winning.set.push_back(card);
|
||||
|
||||
for(j = 1; j <= 4; j++) {
|
||||
// Ace low.
|
||||
look = (
|
||||
number == CardValue::Five && j == 4 ?
|
||||
(enum CardValue)CardValue::Ace :
|
||||
(enum CardValue)((uint8_t)number - j)
|
||||
);
|
||||
index = Card::containsValue(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 = PokerWinningType::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 = PokerWinningType::ThreeOfAKind;
|
||||
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 = PokerWinningType::TwoPair;
|
||||
winning.fillRemaining();
|
||||
return winning;
|
||||
}
|
||||
|
||||
// Pair
|
||||
if(winning.set.size() == 2) {
|
||||
winning.type = PokerWinningType::Pair;
|
||||
winning.fillRemaining();
|
||||
return winning;
|
||||
}
|
||||
|
||||
// High card
|
||||
winning.set.clear();
|
||||
winning.fillRemaining();
|
||||
winning.type = PokerWinningType::HighCard;
|
||||
return winning;
|
||||
}
|
131
src/dawn/poker/PokerPlayer.hpp
Normal file
131
src/dawn/poker/PokerPlayer.hpp
Normal file
@ -0,0 +1,131 @@
|
||||
// Copyright (c) 2022 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "Card.hpp"
|
||||
#include "PokerPot.hpp"
|
||||
#include "PokerWinning.hpp"
|
||||
#include "PokerTurn.hpp"
|
||||
#include "event/Event.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 std::enable_shared_from_this<PokerPlayer> {
|
||||
public:
|
||||
std::weak_ptr<PokerGame> pokerGame;
|
||||
uint8_t playerIndex;
|
||||
int32_t chips = 0;
|
||||
int32_t currentBet = 0;
|
||||
uint8_t timesRaised = 0;
|
||||
bool_t isFolded = false;
|
||||
bool_t isOut = false;
|
||||
bool_t hasBetThisRound = false;
|
||||
bool_t isShowingHand = false;
|
||||
bool_t isHuman = false;
|
||||
std::vector<struct Card> hand;
|
||||
|
||||
Event<> eventChipsChanged;
|
||||
|
||||
/**
|
||||
* Constructor for the PokerPlayer class.
|
||||
*
|
||||
* @param pokerGame Poker game this player is a part of.
|
||||
* @param playerIndex Index of the player in the game.
|
||||
*/
|
||||
PokerPlayer(
|
||||
const std::weak_ptr<PokerGame> pokerGame,
|
||||
const uint8_t playerIndex
|
||||
);
|
||||
|
||||
/**
|
||||
* Adds chips to the player. This will also update the players' state.
|
||||
*
|
||||
* @param chips Count of chips to add.
|
||||
*/
|
||||
void addChips(const int32_t chips);
|
||||
|
||||
/**
|
||||
* Sets the chips a player has.
|
||||
*
|
||||
* @param chips Chips to set to the player.
|
||||
*/
|
||||
void setChips(const 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, const int32_t amount);
|
||||
|
||||
/**
|
||||
* Let a player bet chips into the current pot.
|
||||
*
|
||||
* @param amount The amount of chips the player is betting.
|
||||
*/
|
||||
void bet(const 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();
|
||||
};
|
||||
}
|
111
src/dawn/poker/PokerPot.cpp
Normal file
111
src/dawn/poker/PokerPot.cpp
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright (c) 2022 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#include "PokerPot.hpp"
|
||||
#include "PokerGame.hpp"
|
||||
#include "PokerPlayer.hpp"
|
||||
|
||||
using namespace Dawn;
|
||||
|
||||
void PokerPotWinning::award() {
|
||||
auto it = this->winners.begin();
|
||||
while(it != this->winners.end()) {
|
||||
if(it == this->winners.begin()) {
|
||||
(*it)->addChips(this->chipsEach + this->chipsOverflow);
|
||||
} else {
|
||||
(*it)->addChips(this->chipsEach);
|
||||
}
|
||||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
struct PokerPotWinning PokerPot::getWinners() {
|
||||
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 = CardValue::Invalid;
|
||||
enum CardValue number = CardValue::Invalid;
|
||||
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 == CardValue::Invalid ||
|
||||
number == CardValue::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;
|
||||
}
|
39
src/dawn/poker/PokerPot.hpp
Normal file
39
src/dawn/poker/PokerPot.hpp
Normal file
@ -0,0 +1,39 @@
|
||||
// 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<std::shared_ptr<PokerPlayer>, struct PokerWinning> winnings;
|
||||
std::vector<std::shared_ptr<PokerPlayer>> winners;
|
||||
std::vector<std::shared_ptr<PokerPlayer>> participants;
|
||||
struct PokerPot *pot;
|
||||
int32_t chipsEach;
|
||||
int32_t chipsOverflow;
|
||||
|
||||
void award();
|
||||
};
|
||||
|
||||
struct PokerPot {
|
||||
public:
|
||||
int32_t chips;
|
||||
int32_t call;
|
||||
std::vector<std::shared_ptr<PokerPlayer>> players;
|
||||
|
||||
/**
|
||||
* Get the winners of the pot.
|
||||
*
|
||||
* @return The winning state of the pot.
|
||||
*/
|
||||
struct PokerPotWinning getWinners();
|
||||
};
|
||||
}
|
77
src/dawn/poker/PokerTurn.cpp
Normal file
77
src/dawn/poker/PokerTurn.cpp
Normal file
@ -0,0 +1,77 @@
|
||||
// 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(
|
||||
std::shared_ptr<PokerPlayer> player,
|
||||
const int32_t chips
|
||||
) {
|
||||
struct PokerTurn turn;
|
||||
int32_t i;
|
||||
|
||||
assertNotNull(player, "Player cannot be null.");
|
||||
assertTrue(chips >= 0, "Chips must be greater than or equal to 0.");
|
||||
|
||||
turn.player = player;
|
||||
turn.confidence = 1;
|
||||
|
||||
if(chips == 0) {
|
||||
turn.type = PokerTurnType::Check;
|
||||
turn.chips = 0;
|
||||
} else if(player->chips <= chips) {
|
||||
turn.chips = player->chips;
|
||||
turn.type = PokerTurnType::AllIn;
|
||||
} else {
|
||||
turn.chips = chips;
|
||||
turn.type = PokerTurnType::Bet;
|
||||
|
||||
auto pg = player->pokerGame.lock();
|
||||
assertNotNull(pg, "Player must be in a game.");
|
||||
|
||||
i = pg->getCurrentCallValue();
|
||||
|
||||
if(chips == (i - player->currentBet)) turn.type = PokerTurnType::Call;
|
||||
}
|
||||
|
||||
return turn;
|
||||
}
|
||||
|
||||
struct PokerTurn PokerTurn::fold(std::shared_ptr<PokerPlayer> player) {
|
||||
struct PokerTurn turn;
|
||||
turn.player = player;
|
||||
turn.chips = 0;
|
||||
turn.confidence = 1;
|
||||
turn.type = PokerTurnType::Fold;
|
||||
return turn;
|
||||
}
|
||||
|
||||
void PokerTurn::action() {
|
||||
assertNotNull(this->player, "Player cannot be null.");
|
||||
|
||||
switch(this->type) {
|
||||
case PokerTurnType::Bet:
|
||||
case PokerTurnType::Call:
|
||||
case PokerTurnType::AllIn:
|
||||
this->player->bet(this->chips);
|
||||
break;
|
||||
|
||||
case PokerTurnType::Check:
|
||||
player->bet(0);
|
||||
break;
|
||||
|
||||
case PokerTurnType::Fold:
|
||||
player->fold();
|
||||
break;
|
||||
|
||||
default:
|
||||
assertUnreachable("Unknown turn type.");
|
||||
break;
|
||||
}
|
||||
}
|
55
src/dawn/poker/PokerTurn.hpp
Normal file
55
src/dawn/poker/PokerTurn.hpp
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 class PokerTurnType : uint8_t {
|
||||
Out = 0,
|
||||
Fold = 1,
|
||||
Bet = 2,
|
||||
Call = 3,
|
||||
Check = 4,
|
||||
AllIn = 5
|
||||
};
|
||||
|
||||
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 */
|
||||
std::shared_ptr<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(
|
||||
std::shared_ptr<PokerPlayer> player, const int32_t chips
|
||||
);
|
||||
|
||||
/**
|
||||
* Return a turn action for the given player to fold.
|
||||
*
|
||||
* @return A turn for a fold action.
|
||||
*/
|
||||
static struct PokerTurn fold(std::shared_ptr<PokerPlayer> player);
|
||||
|
||||
/**
|
||||
* Actions / Performs this turn against the defined player.
|
||||
*/
|
||||
void action();
|
||||
};
|
||||
}
|
157
src/dawn/poker/PokerWinning.cpp
Normal file
157
src/dawn/poker/PokerWinning.cpp
Normal file
@ -0,0 +1,157 @@
|
||||
// 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 PokerWinningType::RoyalFlush:
|
||||
return POKER_WINNING_CONFIDENCE_ROYAL_FLUSH;
|
||||
case PokerWinningType::StraightFlush:
|
||||
return POKER_WINNING_CONFIDENCE_STRAIGHT_FLUSH;
|
||||
case PokerWinningType::FourOfAKind:
|
||||
return POKER_WINNING_CONFIDENCE_FOUR_OF_A_KIND;
|
||||
case PokerWinningType::FullHouse:
|
||||
return POKER_WINNING_CONFIDENCE_FULL_HOUSE;
|
||||
case PokerWinningType::Flush:
|
||||
return POKER_WINNING_CONFIDENCE_FLUSH;
|
||||
case PokerWinningType::Straight:
|
||||
return POKER_WINNING_CONFIDENCE_STRAIGHT;
|
||||
case PokerWinningType::ThreeOfAKind:
|
||||
return POKER_WINNING_CONFIDENCE_THREE_OF_A_KIND;
|
||||
case PokerWinningType::TwoPair:
|
||||
return POKER_WINNING_CONFIDENCE_TWO_PAIR;
|
||||
case PokerWinningType::Pair:
|
||||
return POKER_WINNING_CONFIDENCE_PAIR;
|
||||
default:
|
||||
return POKER_WINNING_CONFIDENCE_HIGH_CARD;
|
||||
}
|
||||
}
|
||||
|
||||
struct Card PokerWinning::compare(
|
||||
const struct PokerWinning &left,
|
||||
const struct PokerWinning &right
|
||||
) {
|
||||
uint8_t i;
|
||||
enum CardValue number = CardValue::Invalid;
|
||||
enum CardValue highNumberLeft = CardValue::Invalid;
|
||||
enum CardValue highNumberRight = CardValue::Invalid;
|
||||
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 != CardValue::Invalid && number < highNumberLeft) continue;
|
||||
|
||||
// Check if this number is within the other hand or not
|
||||
index = Card::containsValue(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 == CardValue::Invalid ||
|
||||
number == CardValue::Ace ||
|
||||
highNumberLeft < number
|
||||
) {
|
||||
highNumberLeft = number;
|
||||
highCardLeft = card;
|
||||
}
|
||||
}
|
||||
|
||||
for(i = 0; i < right.set.size(); i++) {
|
||||
card = right.set[i];
|
||||
number = card.getValue();
|
||||
if(highNumberRight != CardValue::Invalid && number < highNumberRight) {
|
||||
continue;
|
||||
}
|
||||
|
||||
index = Card::containsValue(left.set, number);
|
||||
if(index != -1) {
|
||||
index = Card::contains(left.set, card);
|
||||
if(index != -1) continue;
|
||||
}
|
||||
|
||||
if(
|
||||
highNumberRight == CardValue::Invalid ||
|
||||
number == CardValue::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 == CardValue::Invalid ||
|
||||
number == CardValue::Ace ||
|
||||
highNumberLeft < number
|
||||
) {
|
||||
highNumberLeft = number;
|
||||
highCardLeft = card;
|
||||
}
|
||||
}
|
||||
return highCardLeft;
|
||||
}
|
||||
|
||||
if(highCardLeft.cardValue == 0xFF) return 0xFF;
|
||||
if(highNumberLeft < highNumberRight) return 0xFF;
|
||||
return highCardLeft;// Greater or Equal to.
|
||||
}
|
||||
|
||||
void PokerWinning::fillRemaining() {
|
||||
uint8_t i;
|
||||
CardValue 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 = CardValue::Invalid;
|
||||
|
||||
for(i = 0; i < this->full.size(); i++) {
|
||||
currentCard = this->full[i];
|
||||
if(Card::contains(this->set, currentCard) != -1) continue;
|
||||
|
||||
if(highest == CardValue::Invalid) {
|
||||
highestCard = currentCard;
|
||||
highest = highestCard.getValue();
|
||||
} else {
|
||||
current = currentCard.getValue();
|
||||
if(current != CardValue::Ace && current < highest) continue;
|
||||
highestCard = currentCard;
|
||||
highest = current;
|
||||
}
|
||||
}
|
||||
|
||||
if(highest == CardValue::Invalid) break;
|
||||
this->set.push_back(highestCard);
|
||||
}
|
||||
Card::sort(this->set);
|
||||
}
|
86
src/dawn/poker/PokerWinning.hpp
Normal file
86
src/dawn/poker/PokerWinning.hpp
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright (c) 2022 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "dawnlibs.hpp"
|
||||
#include "Card.hpp"
|
||||
|
||||
#define POKER_WINNING_CONFIDENCE_ROYAL_FLUSH 1.0f
|
||||
#define POKER_WINNING_CONFIDENCE_STRAIGHT_FLUSH 0.99f
|
||||
#define POKER_WINNING_CONFIDENCE_FOUR_OF_A_KIND 0.9f
|
||||
#define POKER_WINNING_CONFIDENCE_FULL_HOUSE 0.85f
|
||||
#define POKER_WINNING_CONFIDENCE_FLUSH 0.8f
|
||||
#define POKER_WINNING_CONFIDENCE_STRAIGHT 0.7f
|
||||
#define POKER_WINNING_CONFIDENCE_THREE_OF_A_KIND 0.5f
|
||||
#define POKER_WINNING_CONFIDENCE_TWO_PAIR 0.4f
|
||||
#define POKER_WINNING_CONFIDENCE_PAIR 0.2f
|
||||
#define POKER_WINNING_CONFIDENCE_HIGH_CARD 0.1f
|
||||
|
||||
/** How many cards in the winning set */
|
||||
#define POKER_WINNING_SET_SIZE 5
|
||||
|
||||
namespace Dawn {
|
||||
class PokerPlayer;
|
||||
|
||||
enum class PokerWinningType {
|
||||
Null,
|
||||
RoyalFlush,
|
||||
StraightFlush,
|
||||
FourOfAKind,
|
||||
FullHouse,
|
||||
Flush,
|
||||
Straight,
|
||||
ThreeOfAKind,
|
||||
TwoPair,
|
||||
Pair,
|
||||
HighCard
|
||||
};
|
||||
|
||||
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(
|
||||
const struct PokerWinning &left,
|
||||
const struct PokerWinning &right
|
||||
);
|
||||
|
||||
/** Winning Type */
|
||||
enum PokerWinningType type;
|
||||
/** The full set of both the dealer and player's hand */
|
||||
std::vector<struct Card> full;
|
||||
/** Holds the winning set */
|
||||
std::vector<struct Card> set;
|
||||
/** If there was a kicker card it will be here */
|
||||
struct Card kicker;
|
||||
/* The player this winning state belongs to */
|
||||
std::shared_ptr<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();
|
||||
};
|
||||
}
|
17
src/dawn/ui/elements/UILabel.hpp
Normal file
17
src/dawn/ui/elements/UILabel.hpp
Normal file
@ -0,0 +1,17 @@
|
||||
// Copyright (c) 2024 Dominic Masters
|
||||
//
|
||||
// This software is released under the MIT License.
|
||||
// https://opensource.org/licenses/MIT
|
||||
|
||||
#pragma once
|
||||
#include "ui/UIAlignableElement.hpp"
|
||||
|
||||
namespace Dawn {
|
||||
class UILabel final : public UIAlignableElement {
|
||||
protected:
|
||||
void getSelfQuads(UICanvas &ctx) override;
|
||||
|
||||
public:
|
||||
std::shared_ptr<TrueTypeTexture> font;
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user