Учитывая двухкарточную покерную руку и 5 карт на столе, я хочу оценить силу руки игрока, как указано в таблице. здесь.
Оценка покерной руки составила в среднем ~ 100 микросекунд. Существующие бесплатные калькуляторы капитала могут выполнить этот расчет менее чем за 1 микросекунду (например, Equilab). Я хотел бы знать, как повысить производительность моего алгоритма до этого уровня.
Функция, оценивающая руку, — это unique_ptr<ShowdownHand> eval_hand(const PokerHand& hand, const Board& board)
в showdown.h.
Вот полный код:
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));
}
}
1 ответ
Ваш код выглядит очень хорошо написанным, в соответствии со многими передовыми практиками C ++. Единственное странное, что я обнаружил, это то, что вы не используете auto
внутри for
-высказывания, например, я бы написал for (auto &c: cards)
вместо for (const Card &c: cards)
, и некоторые упущенные возможности использования std::make_unique
. Поэтому я сосредоточусь только на аспекте производительности вашего кода.
Каким должен быть результат оценки силы рук?
В ShowdownHand
класс довольно тяжелый. Он содержит два std::vector
s, по одной на все карты, по одной за лучшую руку. И чтобы получить результат, нужно пройти через virtual
функция, которая сравнивает его с рукой оппонента. Нужна ли вся эта информация? И если это так, нужно ли хранить копию в ShowdownHand
если эта информация все еще должна быть в PokerHand
а также Board
которые использовались в качестве входных данных для eval_hand()
?
Сведите к минимуму информацию о результате и избегайте ненужных копий.
Используйте массивы вместо векторов и карт фиксированного размера
У тебя много std::vectors
фиксированного небольшого размера. Это означает, что из-за выделения кучи и косвенного обращения к указателям возникает много накладных расходов. Использовать std::array
s вместо этого в этих случаях.
Я также вижу std::map
s, который когда-либо будет содержать только несколько элементов. Рассмотрим, например:
std::map<CardSuit, int> suits;
for (const Card& card : all_cards) {
...
++suits[card.get_suit()];
}
Возможных мастей всего пять, поэтому было бы гораздо эффективнее написать:
std::array<int, 5> suits{};
for (const Card& card : all_cards) {
...
++suits[card.get_suit_index()];
}
Где get_suit_index()
вернет значение от 0 до 5. Последнее было бы тривиально, если бы вы только что сделали enum class CardSuit
имеют значения от 0 до 5. Что подводит меня к:
Избегайте ненужных преобразований
В CardRank
класс очень странный. Он имеет 14 статических членов собственного типа, функции для преобразования в / из этих статических членов, repr
член, который бесполезен для самой логики оценки рук. Кроме того, почему val
а также repr
нет const
? В operator++
а также operator--
функции возвращают указатель на CardRank
, но класс достаточно мал, поэтому было бы лучше вернуть CardRank
по стоимости.
Хотя абстракция — это хорошо, вы должны делать ее так, чтобы она была эффективной. А CardRank
должно быть просто целым числом, не более того. Не должно быть static
экземпляры CardRank
с. Единственным медленным делом должно быть преобразование в / из символьного представления, и это преобразование должно выполняться только во время операций ввода / вывода, таких как чтение в состоянии доски и руки или их распечатка.
Сортировочные руки
Вам нужно отсортировать карты в нескольких местах вашего кода. Стандартная библиотека предоставляет std::sort
который оптимизирован для общего использования. Однако здесь у нас есть особый случай, когда у нас есть только несколько элементов для сортировки, и иногда мы точно знаем, сколько элементов у нас есть. В таком случае, std::sort
может быть не оптимальным, а функция сортировки, написанная от руки, может быть быстрее.
Вы также можете выяснить, нужна ли сортировка во всех случаях. Есть ли смысл сортировать all_cards
в начале eval_hand()
? Если да, то зачем вам снова сортировать в calc_best_hand()
, когда кажется, что вы можете пропустить это и просто перебрать это all_cards
наоборот, или залейте вектор best_hand
сзади.
Также иногда кажется, что std::partial_sort()
это все, что тебе нужно.
Спасибо за подробный обзор, я внесу эти изменения и посмотрю, как они влияют на производительность. Помимо того, что было упомянуто здесь, я также заметил, что мой алгоритм для стрейтов можно сделать более эффективным, и большая часть производительности была потеряна при выделении и удалении памяти после каждой оценки, вместо того, чтобы выделять один раз и повторно использовать память.
— Я. Громанн