# Poker Hand Evaluation (speed optimization)

Given a two-card poker hand and a board of 5 cards, I want to evaluate the player's hand strength as listed here.

The evaluation of a poker hand was measured to be ~100 microseconds on average. Existing free equity calculators are able to perform this calculation in under 1 microsecond (for example, Equilab). I would like to know how to increase the performance of my algorithm to that level.

The function that evaluates the hand is unique_ptr<ShowdownHand> eval_hand(const PokerHand& hand, const Board& board) in showdown.h.

Here is the complete code:

poker_game.h

#pragma once

#include <string>
#include <vector>
#include <memory>

namespace Poker {
// CardSuit

enum class CardSuit : char {
CLUB = 'c',
HEART = 'h',
DIAMOND = 'd',
PLACEHOLDER = 'x'
};

// CardRank

class CardRank {
public:
const static CardRank C_2;
const static CardRank C_3;
const static CardRank C_4;
const static CardRank C_5;
const static CardRank C_6;
const static CardRank C_7;
const static CardRank C_8;
const static CardRank C_9;
const static CardRank C_T;
const static CardRank C_J;
const static CardRank C_Q;
const static CardRank C_K;
const static CardRank C_A;
const static CardRank PLACEHOLDER;

static CardRank from_val(int val);
static CardRank from_repr(char repr);

int get_val() const { return val; };
char get_repr() const { return repr; };

const CardRank& operator++();
const CardRank& operator--();

private:
constexpr CardRank(int val, char repr) : val{ val }, repr{ repr } {};

int val;
char repr;
};

inline bool operator<(CardRank hero, CardRank vill) { return hero.get_val() < vill.get_val(); }
inline bool operator>(CardRank hero, CardRank vill) { return vill < hero; }
inline bool operator==(CardRank hero, CardRank vill) { return !(hero < vill) && !(hero > vill); }
inline bool operator!=(CardRank hero, CardRank vill) { return !(hero == vill); }
inline bool operator>=(CardRank hero, CardRank vill) { return hero > vill || hero == vill; }
inline bool operator<=(CardRank hero, CardRank vill) { return hero < vill || hero == vill; }

// Card

class Card {
public:
Card() : rank{ CardRank::PLACEHOLDER }, suit{ CardSuit::PLACEHOLDER } {}
Card(CardRank rank, CardSuit suit) : rank{ rank }, suit{ suit } {}
Card(std::string card_str);

CardRank get_rank() const { return rank; }
CardSuit get_suit() const { return suit; }
std::string repr() const { return std::string{} + rank.get_repr() + static_cast<char>(suit); }

const Card& operator++() { ++rank; }
const Card& operator--() { --rank; }

private:
CardRank rank;
CardSuit suit;
};

inline bool operator<(Card hero, Card vill) { return hero.get_rank() < vill.get_rank(); }
inline bool operator>(Card hero, Card vill) { return vill < hero; }
inline bool operator==(Card hero, Card vill) { return !(hero < vill) && !(hero > vill); }
inline bool operator!=(Card hero, Card vill) { return !(hero == vill); }
inline bool operator>=(Card hero, Card vill) { return hero > vill || hero == vill; }
inline bool operator<=(Card hero, Card vill) { return hero < vill || hero == vill; }

// PokerHand

class PokerHand {
public:
PokerHand(Card card1, Card card2);
PokerHand(CardRank rank1, CardSuit suit1, CardRank rank2, CardSuit suit2);
PokerHand(std::string hand_str);

const Card& get_primary() const { return primary; }
const Card& get_secondary() const { return secondary; }
bool is_suited() const { return primary.get_suit() == secondary.get_suit(); }
bool is_pp() const { return primary.get_rank() == secondary.get_rank(); }
std::string repr() const { return primary.repr() + secondary.repr(); }

private:
Card primary;
Card secondary;
};

// Street

enum class Street {
PREFLOP = 0, FLOP = 3, TURN = 4, RIVER = 5
};

// Board

class Board {
public:
Board() { cards.reserve(5); }
Board(const std::vector<Card>& cards) : cards{ cards } {}
Board(const std::vector<Card>&& cards) : cards{ cards } {}
Board(std::string board_str);

void add_card(const Card& card) { cards.push_back(card); }
void pop_card() { cards.pop_back(); }
Street street() const { return static_cast<Street>(cards.size()); }
int count(CardRank rank) const;
int count(CardSuit rank) const;

const Card& operator[](int i) const { return cards[i]; }
Card& operator[](int i) { return cards[i]; }

std::vector<Card>::iterator begin() { return cards.begin(); }
std::vector<Card>::iterator end() { return cards.end(); }
std::vector<Card>::const_iterator begin() const { return cards.cbegin(); }
std::vector<Card>::const_iterator end() const { return cards.cend(); }
std::vector<Card>::reverse_iterator rbegin() { return cards.rbegin(); }
std::vector<Card>::reverse_iterator rend() { return cards.rend(); }

private:
std::vector<Card> cards;
};
}


poker_game.cpp

#include "poker_game.h"
#include "random.h"

#include <stdexcept>
#include <list>
#include <iostream>
#include <algorithm>
#include <functional>

using std::cout;
using std::endl;

namespace Poker {
// CardRank

const CardRank CardRank::C_2{ 0, '2' };
const CardRank CardRank::C_3{ 1, '3' };
const CardRank CardRank::C_4{ 2, '4' };
const CardRank CardRank::C_5{ 3, '5' };
const CardRank CardRank::C_6{ 4, '6' };
const CardRank CardRank::C_7{ 5, '7' };
const CardRank CardRank::C_8{ 6, '8' };
const CardRank CardRank::C_9{ 7, '9' };
const CardRank CardRank::C_T{ 8, 'T' };
const CardRank CardRank::C_J{ 9, 'J' };
const CardRank CardRank::C_Q{ 10, 'Q' };
const CardRank CardRank::C_K{ 11, 'K' };
const CardRank CardRank::C_A{ 12, 'A' };
const CardRank CardRank::PLACEHOLDER{ -1, 'X' };

CardRank CardRank::from_val(int val)
{
// TODO: Add stacktrace to exception

switch (val) {
case 0: return CardRank::C_2; break;
case 1: return CardRank::C_3; break;
case 2: return CardRank::C_4; break;
case 3: return CardRank::C_5; break;
case 4: return CardRank::C_6; break;
case 5: return CardRank::C_7; break;
case 6: return CardRank::C_8; break;
case 7: return CardRank::C_9; break;
case 8: return CardRank::C_T; break;
case 9: return CardRank::C_J; break;
case 10: return CardRank::C_Q; break;
case 11: return CardRank::C_K; break;
case 12: return CardRank::C_A; break;
default: throw std::invalid_argument("CardRank::from_val - Invalid Argument: val =" + val);
}
}

CardRank CardRank::from_repr(char repr)
{
// TODO: Add stacktrace to exception

switch (repr) {
case '2': return CardRank::C_2; break;
case '3': return CardRank::C_3; break;
case '4': return CardRank::C_4; break;
case '5': return CardRank::C_5; break;
case '6': return CardRank::C_6; break;
case '7': return CardRank::C_7; break;
case '8': return CardRank::C_8; break;
case '9': return CardRank::C_9; break;
case 'T': return CardRank::C_T; break;
case 'J': return CardRank::C_J; break;
case 'Q': return CardRank::C_Q; break;
case 'K': return CardRank::C_K; break;
case 'A': return CardRank::C_A; break;
default: throw std::invalid_argument("CardRank::from_repr - Invalid Argument: repr = " + repr);
}
}

const CardRank& CardRank::operator++()
{
++val;
if (val == 13) val = 0;
repr = from_val(val).repr;

return *this;
}

const CardRank& CardRank::operator--()
{
--val;
if (val == -1) val = 12;
repr = from_val(val).repr;

return *this;
}

// Card

Card::Card(std::string card_str) :
rank{ CardRank::from_repr(card_str[0]) },
suit{ static_cast<CardSuit>(card_str[1]) } {}

// PokerHand

PokerHand::PokerHand(Card card1, Card card2) : primary{ card1 }, secondary{ card2 }
{
if (card1 < card2) {
primary = card2;
secondary = card1;
}
}

PokerHand::PokerHand(CardRank rank1, CardSuit suit1, CardRank rank2, CardSuit suit2) :
PokerHand(Card(rank1, suit1), Card(rank2, suit2)) {}

PokerHand::PokerHand(std::string hand_str) :
PokerHand(Card(hand_str.substr(0, 2)), Card(hand_str.substr(2, 2))) {}

// Board

Board::Board(std::string board_str)
{
cards.reserve(5);

for (unsigned int i = 0; i < board_str.size(); i += 2) {
cards.push_back(Card(board_str.substr(i, 2)));
}
}

int Board::count(CardRank rank) const
{
int count = 0;
for (const Card& c : cards) {
if (c.get_rank() == rank) {
count++;
}
}

return count;
}

int Board::count(CardSuit suit) const
{
int count = 0;
for (const Card& c : cards) {
if (c.get_suit() == suit) {
count++;
}
}

return count;
}
}


showdown.h

#pragma once

#include "poker_game.h"

#include <string>
#include <vector>
#include <memory>

namespace Poker {
// HandType

enum class HandType : char {
HIGH_CARD = 0,
PAIR = 1,
TWO_PAIR = 2,
TRIPS = 3,
STRAIGHT = 4,
FLUSH = 5,
FULL_HOUSE = 6,
STRAIGHT_FLUSH = 8,
ROYAL_FLUSH = 9
};

inline bool operator<(HandType hero, HandType vill) { return static_cast<int>(hero) < static_cast<int>(vill); }
inline bool operator>(HandType hero, HandType vill) { return vill < hero; }
inline bool operator==(HandType hero, HandType vill) { return !(hero < vill) && !(hero > vill); }
inline bool operator!=(HandType hero, HandType vill) { return !(hero == vill); }
inline bool operator>=(HandType hero, HandType vill) { return hero > vill || hero == vill; }
inline bool operator<=(HandType hero, HandType vill) { return hero < vill || hero == vill; }

// ShowdownHand

enum class Winner {
HERO, VILL, SPLIT
};

class ShowdownHand {
public:
virtual ~ShowdownHand() = default;

HandType get_hand_type() const { return hand_type; }
Winner get_winner(const ShowdownHand& vill) const;
std::string repr() const;

protected:
ShowdownHand(HandType hand_type, std::vector<Card> cards)
: hand_type{ hand_type }, cards{ cards } {}

const std::vector<Card>& get_cards() const { return cards; }
const std::vector<Card>& get_best_hand() const { return best_hand; }
void set_best_hand() const { best_hand = calc_best_hand(); }

private:
virtual Winner v_get_winner(const ShowdownHand& vill) const = 0;
virtual std::vector<Card> calc_best_hand() const = 0;

const HandType hand_type;
const std::vector<Card> cards;
mutable std::vector<Card> best_hand;
};

std::unique_ptr<ShowdownHand> eval_hand(const PokerHand& hand, const Board& board);

// HighCard : ShowdownHand

class HighCard : public ShowdownHand {
public:
HighCard(const std::vector<Card> cards) : ShowdownHand(HandType::HIGH_CARD, cards) {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;
};

// Pair : ShowdownHand

class Pair : public ShowdownHand {
public:
Pair(CardRank pair_rank, const std::vector<Card> cards)
: ShowdownHand(HandType::PAIR, cards), pair_rank{ pair_rank } {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;
void cache_kickers() const;

const CardRank pair_rank;
mutable std::vector<Card> kickers;
};

// TwoPair : ShowdownHand

class TwoPair : public ShowdownHand {
public:
TwoPair(CardRank top_rank, CardRank bot_rank, const std::vector<Card> cards)
: ShowdownHand(HandType::TWO_PAIR, cards), top_rank{ top_rank }, bot_rank{ bot_rank } {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;
void cache_kicker() const;

const CardRank top_rank;
const CardRank bot_rank;
mutable Card kicker;
};

// Trips : ShowdownHand

class Trips : public ShowdownHand {
public:
Trips(CardRank set_rank, const std::vector<Card> cards)
: ShowdownHand(HandType::TRIPS, cards), trips_rank{ set_rank } {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;
void cache_kickers() const;

const CardRank trips_rank;
mutable std::vector<Card> kickers;
};

// Straight : ShowdownHand

class Straight : public ShowdownHand {
public:
Straight(CardRank top_rank, const std::vector<Card> cards)
: ShowdownHand(HandType::STRAIGHT, cards), top_rank{ top_rank } {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;

const CardRank top_rank;
};

// Flush : ShowdownHand

class Flush : public ShowdownHand {
public:
Flush(CardSuit flush_suit, const std::vector<Card> cards)
: ShowdownHand(HandType::FLUSH, cards), flush_suit{ flush_suit } {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;

const CardSuit flush_suit;
};

// FullHouse : ShowdownHand

class FullHouse : public ShowdownHand {
public:
FullHouse(CardRank trips_rank, CardRank pair_rank, const std::vector<Card> cards)
: ShowdownHand(HandType::FULL_HOUSE, cards), trips_rank{ trips_rank }, pair_rank{ pair_rank } {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;

const CardRank trips_rank;
const CardRank pair_rank;
};

class Quads : public ShowdownHand {
public:

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;
void cache_kicker() const;

mutable Card kicker;
};

// StraightFlush : ShowdownHand

class StraightFlush : public ShowdownHand {
public:
StraightFlush(CardRank top_rank, CardSuit flush_suit, const std::vector<Card> cards)
: ShowdownHand(HandType::STRAIGHT_FLUSH, cards), top_rank{ top_rank }, flush_suit{ flush_suit } {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;

const CardRank top_rank;
const CardSuit flush_suit;
};

class RoyalFlush : public ShowdownHand {
public:
RoyalFlush(CardSuit flush_suit, const std::vector<Card> cards)
: ShowdownHand(HandType::ROYAL_FLUSH, cards), flush_suit{ flush_suit } {}

private:
Winner v_get_winner(const ShowdownHand& inp_vill) const override;
std::vector<Card> calc_best_hand() const override;

const CardSuit flush_suit;
};
Winner showdown(const PokerHand& hero, const PokerHand& vill, const Board& board);
}


showdown.cpp

#include "showdown.h"

#include <stdexcept>
#include <list>
#include <iostream>
#include <algorithm>
#include <functional>
#include <map>

namespace Poker {

std::vector<Card> get_all_cards(const PokerHand& hand, const Board& board)
{
std::vector<Card> all_cards;

all_cards.reserve(7);
all_cards.push_back(hand.get_primary());
all_cards.push_back(hand.get_secondary());
all_cards.insert(all_cards.end(), board.begin(), board.end());

return all_cards;
}

std::vector<Card> filter_cards(
const std::vector<Card>& cards, const std::function <bool(const Card&)> filter)
{
std::vector<Card> filtered;
filtered.reserve(7);

for (const Card& card : cards) {
if (filter(card)) {
filtered.push_back(card);
}
}

return filtered;
}

// eval_hand

CardSuit check_flush(const std::map<CardSuit, int>& suits)
{
for (std::pair<CardSuit, int> e : suits) {
if (e.second >= 5) {
return e.first;
}
}

return CardSuit::PLACEHOLDER;
}

CardRank check_straight(const std::vector<Card>& all_cards)
{
int counter = 0;
int next_val;
CardRank top_rank = CardRank::PLACEHOLDER;

for (unsigned int i = 0; i < all_cards.size() - 1; i++) {
if (all_cards[i].get_rank() == all_cards[i + 1].get_rank()) {
continue;
}

next_val = all_cards[i].get_rank().get_val() - 1;

if (all_cards[i + 1].get_rank().get_val() == next_val) {
++counter;
if (counter == 1) top_rank = all_cards[i].get_rank();
}
else {
counter = 0;
}
}

if (counter == 3 &&
all_cards.back().get_rank() == CardRank::C_2 &&
all_cards.front().get_rank() == CardRank::C_A)
{
return CardRank::C_5;
}

return CardRank::PLACEHOLDER;
}

void process_matches(const std::map<CardRank, int>& rank_matches,
std::vector<CardRank>& pairs, std::vector<CardRank>& trips, CardRank& quads)
{
for (std::pair<CardRank, int> match : rank_matches) {
if (match.second == 2) pairs.push_back(match.first);
else if (match.second == 3) trips.push_back(match.first);
else if (match.second == 4) quads = match.first;
}
}

ShowdownHand* make_royal_flush(CardSuit flush_suit, const std::vector<Card>& all_cards)
{
int suit_counter = 0;

for (unsigned int i = 0; i < all_cards.size() - 1; i++) {
if (all_cards[i].get_rank() == all_cards[i + 1].get_rank()) {
continue;
}

if (all_cards[i].get_suit() == flush_suit) {
++suit_counter;
if (suit_counter == 5) {
break;
}
}
else {
return nullptr;
}
}

return new RoyalFlush(flush_suit, all_cards);
}

ShowdownHand* make_straight_flush(CardRank top_rank, CardSuit flush_suit, const std::vector<Card>& all_cards)
{
int suit_counter = 0;
int card_count = all_cards.size();

for (int i = 0; i < card_count; i++) {
if (all_cards[i].get_rank() > top_rank) {
continue;
}
if (i != card_count - 1 &&
all_cards[i].get_rank() == all_cards[i + 1].get_rank() &&
all_cards[i].get_suit() != flush_suit)
{
continue;
}

if (i != 0 &&
all_cards[i - 1].get_rank() == all_cards[i].get_rank() &&
all_cards[i].get_suit() != flush_suit)
{
continue;
}
if (suit_counter == 0 && all_cards[i].get_suit() != flush_suit) {
--top_rank;
continue;
}

if (all_cards[i].get_suit() == flush_suit) {
++suit_counter;
if (suit_counter == 5) {
break;
}
}
else {
suit_counter = 0;
}
}

return new StraightFlush(top_rank, flush_suit, all_cards);
}

std::unique_ptr<ShowdownHand> eval_hand(const PokerHand& hand, const Board& board)
{
std::vector<Card> all_cards = get_all_cards(hand, board);
std::sort(all_cards.rbegin(), all_cards.rend());

std::map<CardRank, int> rank_matches;
std::map<CardSuit, int> suits;
for (const Card& card : all_cards) {
++rank_matches[card.get_rank()];
++suits[card.get_suit()];
}

CardSuit flush_suit = check_flush(suits);
CardRank straight_rank = check_straight(all_cards);
bool is_flush = flush_suit != CardSuit::PLACEHOLDER;
bool is_straight = straight_rank != CardRank::PLACEHOLDER;

ShowdownHand* hand_ptr = nullptr;

// Royal Flush, Straight Flush
if (is_flush && is_straight) {
if (straight_rank == CardRank::C_A) {
hand_ptr = make_royal_flush(flush_suit, all_cards);
if (hand_ptr) return std::unique_ptr<ShowdownHand>(hand_ptr);
}
else {
hand_ptr = make_straight_flush(straight_rank, flush_suit, all_cards);
if (hand_ptr) return std::unique_ptr<ShowdownHand>(hand_ptr);
}
}

std::vector<CardRank> pairs;
std::vector<CardRank> trips;
bool is_trips = trips.size() >= 1;
bool is_pair = pairs.size() >= 1;
if(is_pair) std::sort(pairs.rbegin(), pairs.rend());
if(is_trips) std::sort(trips.rbegin(), trips.rend());

}

// Full house
if (is_trips) {
if (is_pair) {
return std::unique_ptr<ShowdownHand>(new FullHouse(trips[0], pairs[0], all_cards));
}
else if (trips.size() == 2) {
return std::unique_ptr<ShowdownHand>(new FullHouse(trips[0], trips[1], all_cards));
}
}

// Flush
if (is_flush) {
return std::unique_ptr<ShowdownHand>(new Flush(flush_suit, all_cards));
}

// Straight
if (is_straight) {
return std::unique_ptr<ShowdownHand>(new Straight(straight_rank, all_cards));
}

// Trips
if (is_trips) {
return std::unique_ptr<ShowdownHand>(new Trips(trips[0], all_cards));
}

// Two Pair
if (pairs.size() >= 2) {
return std::unique_ptr<ShowdownHand>(new TwoPair(pairs[0], pairs[1], all_cards));
}

// Pair
if (is_pair) {
return std::unique_ptr<ShowdownHand>(new Pair(pairs[0], all_cards));
}

// High Card
return std::unique_ptr<ShowdownHand>(new HighCard(all_cards));
}

// ShowdownHand

std::string hand_type_to_str(HandType hand_type)
{
switch (hand_type) {
case HandType::HIGH_CARD: return "High Card";
case HandType::PAIR: return "Pair";
case HandType::TWO_PAIR: return "Two Pair";
case HandType::TRIPS:   return "Trips";
case HandType::STRAIGHT: return "Straight";
case HandType::FLUSH: return "Flush";
case HandType::FULL_HOUSE: return "Full House";
case HandType::STRAIGHT_FLUSH: return "Straight Flush";
case HandType::ROYAL_FLUSH: return "Royal Flush";
default: throw std::invalid_argument("hand_type_to_str - Invalid Argument!");
}
}

Winner ShowdownHand::get_winner(const ShowdownHand& vill) const
{
if (hand_type != vill.hand_type) {
return hand_type > vill.hand_type ? Winner::HERO : Winner::VILL;
}

return v_get_winner(vill);
}

std::string ShowdownHand::repr() const
{
if (best_hand.size() != 5) {
set_best_hand();
}

std::string hand_repr = hand_type_to_str(hand_type);
hand_repr += " [";
for (const Card& card : best_hand) {
hand_repr += card.repr() + " ";
}
hand_repr.pop_back();
hand_repr += "]";

return hand_repr;
}

Winner compare_kickers(const std::vector<Card>& kickers_h, const std::vector<Card>& kickers_v) {
for (unsigned int i = 0; i < kickers_h.size(); i++) {
if (kickers_h[i] > kickers_v[i]) {
return Winner::HERO;
}
else if (kickers_h[i] < kickers_v[i]) {
return Winner::VILL;
}
}

return Winner::SPLIT;
}

// HighCard : ShowdownHand

Winner HighCard::v_get_winner(const ShowdownHand& inp_vill) const
{
const HighCard& vill = static_cast<const HighCard&>(inp_vill);
const std::vector<Card>& kickers_h = get_best_hand();
const std::vector<Card>& kickers_v = vill.get_best_hand();

if (kickers_h.size() != 5) {
set_best_hand();
}

if (kickers_v.size() != 5) {
vill.set_best_hand();
}

return compare_kickers(kickers_h, kickers_v);
}

std::vector<Card> HighCard::calc_best_hand() const
{
const std::vector<Card>& cards = get_cards();
std::vector<Card> best_hand(cards.begin(), cards.begin() + 5);

return best_hand;
}

// Pair : ShowdownHand

Winner Pair::v_get_winner(const ShowdownHand& inp_vill) const
{
const Pair& vill = static_cast<const Pair&>(inp_vill);

if (pair_rank > vill.pair_rank) return Winner::HERO;
if (pair_rank < vill.pair_rank) return Winner::VILL;

if (kickers.size() != 3) cache_kickers();
if (vill.kickers.size() != 3) vill.cache_kickers();

return compare_kickers(kickers, vill.kickers);
}

std::vector<Card> Pair::calc_best_hand() const
{
if (kickers.size() != 3) {
cache_kickers();
}

std::vector<Card> best_hand;

best_hand.reserve(5);
for (const Card& card : get_cards()) {
if (card.get_rank() == pair_rank) {
best_hand.push_back(card);
}
}
best_hand.insert(best_hand.end(), kickers.begin(), kickers.end());

return best_hand;
}

void Pair::cache_kickers() const
{
kickers = filter_cards(get_cards(), [this](const Card& card) { return card.get_rank() != pair_rank; });
std::sort(kickers.rbegin(), kickers.rend());
kickers.erase(kickers.begin() + 3, kickers.end());
}

// TwoPair : ShowdownHand

Winner TwoPair::v_get_winner(const ShowdownHand& inp_vill) const
{
const TwoPair& vill = static_cast<const TwoPair&>(inp_vill);

if (top_rank > vill.top_rank) return Winner::HERO;
if (top_rank < vill.top_rank) return Winner::VILL;

if (bot_rank > vill.bot_rank) return Winner::HERO;
if (bot_rank < vill.bot_rank) return Winner::VILL;

if (kicker.get_rank() == CardRank::PLACEHOLDER) cache_kicker();
if (vill.kicker.get_rank() == CardRank::PLACEHOLDER) vill.cache_kicker();

if (kicker > vill.kicker) return Winner::HERO;
if (kicker < vill.kicker) return Winner::VILL;

return Winner::SPLIT;
}

std::vector<Card> TwoPair::calc_best_hand() const
{
if (kicker.get_rank() == CardRank::PLACEHOLDER) {
cache_kicker();
}

const std::vector<Card>& cards = get_cards();
std::vector<Card> best_hand;

best_hand.reserve(5);
for (const Card& card : cards) {
if (card.get_rank() == top_rank) {
best_hand.push_back(card);
}
}
for (const Card& card : cards) {
if (card.get_rank() == bot_rank) {
best_hand.push_back(card);
}
}
best_hand.push_back(kicker);

return best_hand;
}

void TwoPair::cache_kicker() const
{
std::vector<Card> cards = filter_cards(
get_cards(),
[this](const Card& card) {
CardRank rank = card.get_rank();
return rank != top_rank && rank != bot_rank;
});

kicker = *std::max_element(cards.begin(), cards.end());
}

// Trips : ShowdownHand

Winner Trips::v_get_winner(const ShowdownHand& inp_vill) const
{
const Trips& vill = static_cast<const Trips&>(inp_vill);

if (trips_rank > vill.trips_rank) return Winner::HERO;
if (trips_rank < vill.trips_rank) return Winner::VILL;

if (kickers.size() != 2) cache_kickers();
if (vill.kickers.size() != 2) vill.cache_kickers();

return compare_kickers(kickers, vill.kickers);
}

std::vector<Card> Trips::calc_best_hand() const
{
if (kickers.size() != 2) {
cache_kickers();
}

std::vector<Card> best_hand;
best_hand.reserve(5);

for (const Card& card : get_cards()) {
if (card.get_rank() == trips_rank) {
best_hand.push_back(card);
}
}
best_hand.insert(best_hand.end(), kickers.begin(), kickers.end());

return best_hand;
}

void Trips::cache_kickers() const
{
kickers = filter_cards(get_cards(), [this](const Card& card) { return card.get_rank() != trips_rank; });
std::sort(kickers.rbegin(), kickers.rend());
kickers.erase(kickers.begin() + 2, kickers.end());
}

// Straight : ShowdownHand

Winner Straight::v_get_winner(const ShowdownHand& inp_vill) const
{
const Straight& vill = static_cast<const Straight&>(inp_vill);

if (top_rank > vill.top_rank) return Winner::HERO;
if (top_rank < vill.top_rank) return Winner::VILL;
return Winner::SPLIT;
}

std::vector<Card> Straight::calc_best_hand() const
{
const std::vector<Card>& cards = get_cards();
std::vector<Card> best_hand;
best_hand.reserve(5);

CardRank rank = top_rank;
for (const Card& card : cards) {
if (card.get_rank() == rank) {
best_hand.push_back(card);
--rank;

if (best_hand.size() == 5) break;
}
}

if (best_hand.size() == 4 && cards[0].get_rank() == CardRank::C_A) {
best_hand.push_back(cards[0]);
}

return best_hand;
}

// Flush : ShowdownHand

Winner Flush::v_get_winner(const ShowdownHand& inp_vill) const
{
const Flush& vill = static_cast<const Flush&>(inp_vill);
const std::vector<Card>& kickers_h = get_best_hand();
const std::vector<Card>& kickers_v = vill.get_best_hand();

if (kickers_h.size() != 5) {
set_best_hand();
}

if (kickers_v.size() != 5) {
vill.set_best_hand();
}

return compare_kickers(kickers_h, kickers_v);
}

std::vector<Card> Flush::calc_best_hand() const
{
std::vector<Card> best_hand = filter_cards(
get_cards(), [this](const Card& card) { return card.get_suit() == flush_suit; });
std::sort(best_hand.rbegin(), best_hand.rend());

if (best_hand.size() > 5) {
best_hand.erase(best_hand.begin() + 5, best_hand.end());
}

return best_hand;
}

// FullHouse : ShowdownHand

Winner FullHouse::v_get_winner(const ShowdownHand& inp_vill) const
{
const FullHouse& vill = static_cast<const FullHouse&>(inp_vill);

if (trips_rank > vill.trips_rank) return Winner::HERO;
if (trips_rank < vill.trips_rank) return Winner::VILL;

if (pair_rank > vill.pair_rank) return Winner::HERO;
if (pair_rank < vill.pair_rank) return Winner::VILL;

return Winner::SPLIT;
}

std::vector<Card> FullHouse::calc_best_hand() const
{
const std::vector<Card>& cards = get_cards();
std::vector<Card> best_hand;
best_hand.reserve(5);

for (const Card& card : cards) {
if (card.get_rank() == trips_rank) {
best_hand.push_back(card);
}
}
for (const Card& card : cards) {
if (card.get_rank() == pair_rank) {
best_hand.push_back(card);
}
}

return best_hand;
}

{

if (kicker.get_rank() == CardRank::PLACEHOLDER) cache_kicker();
if (vill.kicker.get_rank() == CardRank::PLACEHOLDER) vill.cache_kicker();

if (kicker > vill.kicker) return Winner::HERO;
if (kicker < vill.kicker) return Winner::VILL;

return Winner::SPLIT;
}

{
std::vector<Card> best_hand;
best_hand.reserve(5);
best_hand = filter_cards(
get_cards(), [this](const Card& card) { return card.get_rank() == quads_rank; });

if (kicker.get_rank() == CardRank::PLACEHOLDER) {
cache_kicker();
}
best_hand.push_back(kicker);

return best_hand;
}

std::vector<Card> all_cards = filter_cards(
get_cards(), [this](const Card& card) { return card.get_rank() != quads_rank; });

kicker = *std::max_element(all_cards.begin(), all_cards.end());
}

// StraightFlush : ShowdownHand

Winner StraightFlush::v_get_winner(const ShowdownHand& inp_vill) const
{
const StraightFlush& vill = static_cast<const StraightFlush&>(inp_vill);

if (top_rank > vill.top_rank) return Winner::HERO;
if (top_rank < vill.top_rank) return Winner::VILL;
return Winner::SPLIT;
}

std::vector<Card> StraightFlush::calc_best_hand() const
{
std::vector<Card> all_cards = filter_cards(
get_cards(), [this](const Card& card) { return card.get_suit() == flush_suit; });
std::sort(all_cards.rbegin(), all_cards.rend());

std::vector<Card> best_hand;
best_hand.reserve(5);

CardRank rank = top_rank;
for (const Card& card : all_cards) {
if (card.get_rank() == rank) {
best_hand.push_back(card);
--rank;

if (best_hand.size() == 5) break;
}
}

return best_hand;
}

// RoyalFlush : ShowdownHand

Winner RoyalFlush::v_get_winner(const ShowdownHand& inp_vill) const
{
throw std::logic_error("RoyalFlush::v_get_winner - Cannot compare two Royal Flushes!");
}

std::vector<Card> RoyalFlush::calc_best_hand() const
{
std::vector<Card> best_hand = filter_cards(
get_cards(), [this](const Card& card) { return card.get_suit() == flush_suit; });
std::sort(best_hand.rbegin(), best_hand.rend());

if (best_hand.size() > 5) {
best_hand.erase(best_hand.begin() + 5, best_hand.end());
}

return best_hand;
}

Winner showdown(const PokerHand& hero, const PokerHand& vill, const Board& board) {
return eval_hand(hero, board)->get_winner(*eval_hand(vill, board));
}
}

• Hi @JGrohmann - the Card operator< and other operators are not "stable" ... i.e. given two cards with the same rank but different suit they don't make for a comparison that is logically the same ie. a<b is -b<a
– Mr R
Commented Apr 25, 2021 at 2:41
• @MrR, I see what you mean, thanks for catching that. Commented Apr 25, 2021 at 19:32
• A quick fix is to use array lookup instead of switch. Commented Apr 26, 2021 at 0:47

Your code looks very well written, following many C++ best practices. The only strange things I found is that you don't use auto inside for-statements, for example I would write for (auto &c: cards) instead of for (const Card &c: cards), and some missed opportunities for using std::make_unique. So I'll focus only on the performance aspect of your code.

# What should the result of hand strength evaluation be?

The ShowdownHand class is quite heavy. It contains two std::vectors, one for all cards, one for the best hand. And to get the result, you have to go via a virtual function that compares it against an opponent's hand. Is all this information necessary? And if it is, does a copy need to be stored in ShowdownHand if this information should still be in the PokerHand and Board that were used as input for eval_hand()?

Keep the information kept for the result to a minimum, and avoid making unnecessary copies.

# Use arrays instead of fixed-size vectors and maps

You have a lot of std::vectors of a fixed, small size. This means there is a lot of overhead from heap allocations and pointer indirections. Use std::arrays instead in these cases.

I also see std::maps being used that will only ever contain just a few elements. Consider for example:

std::map<CardSuit, int> suits;
for (const Card& card : all_cards) {
...
++suits[card.get_suit()];
}


There are only five possible suits, so it would be much more efficient to write:

std::array<int, 5> suits{};
for (const Card& card : all_cards) {
...
++suits[card.get_suit_index()];
}


Where get_suit_index() would return a value between 0 and 5. The latter would be trivial if you just made enum class CardSuit have values from 0 to 5. Which brings me to:

# Avoid unnecessary conversions

The CardRank class is very weird. It has 14 static members of its own type, functions to convert to/from those static members, a repr member that is not useful for the hand evaluation logic itself. Also, why are val and repr not const? The operator++ and operator-- functions return a pointer to a CardRank, but the class is small enough that it would be better to return a CardRank by value.

While abstraction is good, you should do it in such a way that it is efficient. A CardRank should just be an integer, nothing more. There should be no static instances of CardRanks. The only slow thing should be converting to/from a character representation, and that conversion should only be done during input/output operations, like reading in the board and hand state, or printing those out.

# Sorting hands

You have to sort cards in several places in your code. The standard library provides std::sort which is optimized for general usage. However, here we have a special case where we have only a few items to sort, and sometimes we know exactly how many items we have. In this case, std::sort might not be optimal, and a hand-written sorting function might be faster.

You might also investigate whether sorting is necessary in all cases. Does it make sense to sort all_cards at the start of eval_hand()? If so, why do you need to sort again in calc_best_hand(), when it seems like you could skip that and just iterate over that all_cards in reverse, or fill the vector best_hand from the back.

It also looks like sometimes a std::partial_sort() is all you need.

• Thanks for the detailed review, I'll implement these changes and see how they change performance. Aside from what was mentioned here, I also noticed my algorithm for straights can be made more efficient, and a lot of performance was lost allocating and deleting memory after each evaluation, rather than allocating once and reusing the memory. Commented Apr 25, 2021 at 19:49