Skip to main content
Tweeted twitter.com/StackCodeReview/status/1387557282712539136
deleted 38 characters in body
Source Link
vnp
  • 57.3k
  • 4
  • 51
  • 140

Given a two-card poker hand and a board of 5 cards, I want to evaluate the player's hand strength as listed here: https://en.wikipedia.org/wiki/List_of_poker_handshere.

Given a two-card poker hand and a board of 5 cards, I want to evaluate the player's hand strength as listed here: https://en.wikipedia.org/wiki/List_of_poker_hands

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

Source Link

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: https://en.wikipedia.org/wiki/List_of_poker_hands

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',
        SPADE = 's',
        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,
        QUADS = 7,
        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;
    };


    // Quads : ShowdownHand

    class Quads : public ShowdownHand {
    public:
        Quads(CardRank quads_rank, const std::vector<Card> cards)
            : ShowdownHand(HandType::QUADS, cards), quads_rank{ quads_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 quads_rank;
        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 if (counter == 4) return top_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;
        CardRank quads = CardRank::PLACEHOLDER;
        process_matches(rank_matches, pairs, trips, quads);
        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());
        

        // Quads
        if (quads != CardRank::PLACEHOLDER) {
            return std::unique_ptr<ShowdownHand>(new Quads(quads, all_cards));
        }

        // 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::QUADS: return "Quads";
        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;
    }


    // Quads : ShowdownHand

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

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

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

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

        return Winner::SPLIT;
    }

    std::vector<Card> Quads::calc_best_hand() const
    {
        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;
    }

    void Quads::cache_kicker() const {
        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));
    }
}