diff --git a/pokerbotic/README.md b/pokerbotic/README.md new file mode 100644 index 00000000..fffc3dd8 --- /dev/null +++ b/pokerbotic/README.md @@ -0,0 +1,66 @@ +# Pokerbotic + +## This repository will be changing very soon to incorporate feedback from my CPPCon 2019 presentation, please check back in a couple of weeks + +**Pokerbotic** is a poker engine. It has been developed by a professional software engineer and a semi-professional poker player with professional knowledge of stochastic processes little by little. + +Currently, we have the hand evaluator framework, that achieves in normally available machines a rate of 100 million evaluations per second, that is, it classifies more than 100 million poker hands into what "four of a kind", etc. they are. + +**The code today assumes the AMD64 architecture**, and support of the [BMI2 instructions](https://en.wikipedia.org/wiki/Bit_Manipulation_Instruction_Sets#BMI2_.28Bit_Manipulation_Instruction_Set_2.29). AMD64/Intel is not essential to this code, just that the necessary adaptations have not been made. You are welcome to help with this. + +Currently, the code is a header-only framework with some use cases programmed in C++ 14. + +This code beats other poker engines, including the popular open source framework "PokerStove" both on ease of use and performance due to the application of Generic Programming. + +Generic Programming allows hoisting what otherwise would be run-time computation to compilation time, this is illustrated in the non-trivial `static_assert` in the code itself. + +The documentation for the advanced programming techniques, including the Floyd sampling algorithm, the SWAR techniques is being written. + +## How to build it + +### Prerequisites: + +1. GCC compatible compiler. We recommend Clang 3.9 or 4.0 specifically. Benchmarks indicate Clang gives noticeably faster code. The code uses GCC extensions in the way of builtins. +2. C++ 14. In GCC or Clang, do not forget the option `-std=c++14` +3. Support for BMI2 instructions, activated with `-march=native` (preferred way) or specifically with `-mbmi2` +4. Test cases require the ["Catch" testing framework](https://github.com/philsquared/Catch). +5. Currently the code does not require a Unix/POSIX operating system (this code should be compilable in Windows64 through either gcc or clang), however, **we reserve the option to make the code incompatible with any operating system**. + +### There are several test programs available: + +#### Unit tests at [src/main.cpp](https://github.com/thecppzoo/pokerbotic/blob/master/src/main.cpp) + +Several unit tests. This program illustrates how to use the engine framework. To build it, at the project root, you may do this: + +`clang++ -std=c++14 -Iinc -DTESTS -O3 -march=native -I../Catch/include src/main.cpp -o main` + +Notice you have to define TESTS and indicate the path to the "Catch" testing framework. + +#### [src/benchmarks.cpp](https://github.com/thecppzoo/pokerbotic/blob/master/src/benchmarks.cpp) + +A program that measures the execution speed of several internal mechanisms. To build it, at the project root, you may do this: + +`clang++ -std=c++14 -Iinc -DBENCHMARKS -O3 -march=native src/benchmarks.cpp -o benchmarks` + +This program can be run without arguments. It will generate all 7-card hands and time the execution of all evaluations. + +#### [src/comparisonBenchmark.cpp](https://github.com/thecppzoo/pokerbotic/blob/master/src/comparisonBenchmark.cpp) + +This program generates as in Texas Hold'em Poker, all possible 5-community cards, and proceeds to iterate over all two-player 2-card "pocket cards". + +Because of the size of this search space, this program emits a current tally of execution every 100 million cases. + +To build, for example: + +`clang++ -std=c++14 -Iinc -DHAND_COMPARISON -O3 -march=native -o cb src/comparisonBenchmark.cpp` + +Can be run without arguments. + +## Next feature to be implemented + +Currently, multithreaded partitioning of evaluations is being implemented. + +## Documentation/User manual + +Not yet written. Most of the code available under the folder `ep/` is fully operational. + diff --git a/pokerbotic/SWAR CPPCon 2019 - reduced.key b/pokerbotic/SWAR CPPCon 2019 - reduced.key new file mode 100644 index 00000000..f402cd95 Binary files /dev/null and b/pokerbotic/SWAR CPPCon 2019 - reduced.key differ diff --git a/pokerbotic/design/Fastest-Floyd-Sampling.md b/pokerbotic/design/Fastest-Floyd-Sampling.md new file mode 100644 index 00000000..c422ec93 --- /dev/null +++ b/pokerbotic/design/Fastest-Floyd-Sampling.md @@ -0,0 +1,35 @@ +The Floyd sampling algorithm --you can see an excellent exposition [here](http://www.nowherenearithaca.com/2013/05/robert-floyds-tiny-and-beautiful.html)-- is very convenient for use cases such as getting a hand of cards from a deck. + +The fastest way to represent sets of finite and small domains (such as a deck of cards) seems to be as bits in bitfields. + +For an example of a deck of 52 cards, we may want, for example, to generate all of the 7-card hands. I wrote an straightforward implementation [here](https://github.com/thecppzoo/pokerbotic/blob/master/inc/ep/Floyd.h). Its interface is this: + +```c++ +template +inline uint64_t floydSample(Rng &&g); +``` + +With speed in mind, the size of the set and subset are template parameters. Compilers such as Clang, GCC routinely generate optimal code for the given sizes, as can be seen in the compiler explorer, which means they take advantage of those parameters being template parameters. The return value is the subset expressed as the bits set in the least significant N bits of the resulting integer. + +However, what if the use case is to generate a sample (subset) of the *remaining* members of the set? for example, to generate a random 2-card *after* five cards have been selected? + +That has been implemented too, in a function with this signature: + +```c++ +template +inline uint64_t floydSample(Rng &&g, uint64_t preselected) +``` + +Here, `preselected` represents the cards already selected. If what is desired is to get two cards from the cards remaining after selecting `fiveAlreadySelected` cards, the call `ep::floydSample<47, 2>(randomGenerator, fiveAlreadySelected)` will suffice. Notice the template argument for `N` is now 47, reflecting the fact that the remaining set of cards has 47 cards. Unfortunately, it is difficult to guarantee at compilation time that the argument `fiveAlreadySelected` indeed has exactly five elements, because operations such as intersection or union result in sets with cardinalities that are fundamentally run-time values. + +This overload for `ep::floydSample` requires calling a "deposit" operation. This is an interesting operation hard to implement without direct support from the processor: Given a mask, the bits of the input will be "deposited" one at a time into the bit positions indicated as bits set in the mask. In the AMD64/Intel architecture EM64T this is supported in the instruction set "BMI2" as the instruction [`PDEP`](https://chessprogramming.wikispaces.com/BMI2). The implementation of the adaptation of the Floyd algorithm for a known number of preselected elements is then straightforward: discount from the total the number of bits set, call normal floydSample, and "deposit" the result in the inverse of the preselection. + +What are the costs of these implementations? + +1. The programmer needs to indicate at compilation time the number of elements in the set. If this number is a runtime value, a `switch` will be needed to convert runtime to compile time numbers, that transforms into an indexed jump at the assembler level. +2. All of the operations in the normal Floyd sampling algorithm are negligible in terms of execution costs compared to calling the random number generator, which is essential in each iteration. +3. The adaptation to account for preselections only requires two assembler instructions more: inverting the preselection and depositing it. `PDEP` has been measured to be an instruction with a throughput of one per clock, which is excellent compared to implementing it in software; however, in current processors it can only be executed in a particular pipeline. In Pokerbotic we don't think we are oversubscribing this pipeline, so we suspect we get a 1-per-clock throughput for this use case. +4. However, the adaptation to account for preselections also require the programmer to accurately indicate the cardinality of the preselection. This can add the same cost as number 1 here, plus the population count, another single-pipeline, 1-per-clock throughput instruction. + +We are interested in any way to implement a faster subset sample selection. This use case is at the heart of many operations in Pokerbotic. + diff --git a/pokerbotic/design/Hand-Ranking.md b/pokerbotic/design/Hand-Ranking.md new file mode 100644 index 00000000..0a9282c8 --- /dev/null +++ b/pokerbotic/design/Hand-Ranking.md @@ -0,0 +1,63 @@ +# Design of the hand classification mechanism in Pokerbotic + +## Detection of N-of-a-kind + +Detection of N-of-a-kind, two pairs and full house is described [here](https://github.com/thecppzoo/pokerbotic/blob/master/design/What-is-noak-or-how-to-determine-pairs.md). + +## Detection of flush + +Flush detection happens at [Poker.h:78](https://github.com/thecppzoo/pokerbotic/blob/master/inc/ep/Poker.h#L78) the hand is filtered per each suit, and the built in for population count on the filtered set is called. This code assumes a hand can only have one suit in flush (or that the hand has up to 9 cards). + +## Detection of straights + +The straightforward way to detect straights, if the ranks would be consecutive bits (which we will call "packed representation") is this: + +```c++ +unsigned straights(unsigned cards) { + auto shifted1 = cards << 1; + auto shifted2 = cards << 2; + auto shifted3 = cards << 3; + auto shifted4 = cards << 4; + return cards & shifted1 & shifted2 & shifted3 & shifted4 +} +``` + +By shifting and doing a conjuction at the end, the only bits in the result set to one are those that are succeeded by four consecutive bits set to one. Before accounting for the aces to work as "ace or one", there are possible improvements to be discussed: + +### Checking for the presence of 5 or 10 + +In a deck of 13 ranks starting with 2, all straights must have either the rank 5 or the rank ten. This has a probability of nearly a third; however, testing for this explicitly is performance disadvantageous. It seems the branch is fundamentaly not predictable by the processor, so, the penalty of misprediction overcompensates the benefit of early exit. In the code above, there are 8 binary operators and 4 compile-time constants, there is little budget for branch misprediction. Older versions of the code had this check until it was benchmarked to be a disadvantage. + +### Checking for partial conjunctions + +For the same reason, testing if any of the conjunctions is zero to return 0 early is not performance advantageous, confirmed through benchmarking. + +### Addition chain + +There is one improvement that benchmarks confirm: + +```c++ +unsigned straights(unsigned cards) { + // assume the point of view from the bit position for tens. + auto shift1 = cards >> 1; + // in shift1 the bit for the rank ten now contains the bit for jacks + auto tj = cards & shift1; + auto shift2 = tj >> 2; + // in shift2, the position for the rank ten now contains the conjunction of originals queen and king + auto tjqk = tj & shift2; + return tjqk & (cards >> 4); +} +``` + +This implementation (which does not take into account the ace duality) requires 6 binary operations and 3 constants and accomplishes the same thing as the straightforward implementation. Benchmarks confirm this taking roughly 3/4 of the time than the straightforward implementation. + +The key insight here is to view the detection of the straight as adding up to 5 starting with 1. The straightforward implementation does the equivalent of `1 + 1 + 1 + 1 + 1`, this new implementation does `auto two = 1 + 1; return two + two + 1`. This technique is to build an *addition chain*. This technique was inspired by the second chapter of the book ["From Mathematics To Generic Programming"](https://www.amazon.com/Mathematics-Generic-Programming-Alexander-Stepanov/dp/0321942043) + +Taking into account the dual rank of aces is simply to turn on the 'ones' if there is the ace, but this requires left shift to make room for it. This can be done at the beginning of the straight check, and its cost can be amortized by the compiler doing a conditional move early, meaning the result will be ready by the time it is used. + +There is one further complication in the code, which is that the engine uses the rank-array representation. Provided that the shifts are for 4, 8, 12, 16 bits instead of 1, 2, 3, 4 there isn't yet a difference. There are two needs for straights: + +1. Normal straights, in which the suit of the rank does not matter. This is accomplished by making the 13 rank counts as described in how to detect pairs, etc., and using the SWAR operation `greaterEqual<1>` prior to the straight code. Naturally, the straights don't incurr in an extra cost of doing popcounts because they are amortized in the necessary part of detection of pairs, three of a kind, etc., the `greaterEqual(arg)` operation requires two constants and two or three assembler operations, depending on how the result is used, thus, for practical purposes have negligible cost compared to a packed rank representation. +2. Straights to detect straight flush: Since the bits for + +We suspect our detection of straight code is maximal in terms of performance. diff --git a/pokerbotic/design/What-is-noak-or-how-to-determine-pairs.md b/pokerbotic/design/What-is-noak-or-how-to-determine-pairs.md new file mode 100644 index 00000000..c072593a --- /dev/null +++ b/pokerbotic/design/What-is-noak-or-how-to-determine-pairs.md @@ -0,0 +1,60 @@ +# What the hell does "noak" mean in Pokerbotic? or how did you implement detection of pairs, three of a kind, four of a kind? + +"Noak" in Pokerbotic code means N-Of-A-Kind, where N is 1, 2, 3, or 4. + +In Pokerbotic, more than a full third of the execution time of classification of hands is spent in determining whether the hand is a four-of-a-kind, a full-house, three-of-a-kind, a pair, or just a high-card. The other roughly two thirds are spent at about a similar execution time cost by the detection of straights and detection of flushes. + +Compared to other poker engines, such as Poker Stove, our implementation is easier to use and as fast (around 260 million evaluations per second in popular hardware today). + +Pokerbotic represents the cards as bits in an integer, in which the four least significant bits are, from least significant, the 2 of spades, `s2`, hearts, `h2`, diamonds and clubs; then the 3 of spades, and so on up to the ace of clubs, `cA`. Nominally, each nibble represents the four different suits of the number. This is implicit in the definition of the enumerations at [`ep::abbreviations::Cards`](https://github.com/thecppzoo/pokerbotic/blob/master/inc/ep/PokerTypes.h#L64) at [`PokerTypes.h`](https://github.com/thecppzoo/pokerbotic/blob/master/inc/ep/PokerTypes.h) + +## Mechanisms details + +Determining the number of repetitions of a number/rank in a hand then corresponds to determining the number of bits set to one in each of the positions of the array. Counting the number of bits set to one is called "population count" or "hamming weight". The AMD64/EM64T architecture has the `POPCNT` instruction since SSE4.2, however, we were not able to figure out an advantageous way to use `POPCNT` to find the counts for each of the 13 numbers in a normal deck of cards. Instead I came up with a [SWAR mechanism](https://en.wikipedia.org/wiki/SWAR) to accomplish these things: + +1. Getting all 13 population counts +2. Determining all the rank-array positions that repeat more than N times, where N is a compilation time value +3. Determining the highest rank that satisfies a condition + +To get the 13 population counts the code follows the first stages of the most popular way to find the population count when there is no provided built-in: + +```c++ +int popcntNibbles(int v) { + constexpr auto mask0 = 0x5...5; + v -= (v >> 1) & mask0; + constexpr auto mask1 = 0x3...3; + v = (v & mask1) + ((v >> 2) & mask1); + return v; +} +``` + +Our code in [`SWAR.h`](https://github.com/thecppzoo/pokerbotic/blob/master/inc/ep/core/SWAR.h) does not look like that at all, but that is what it does. The implementation is split into several useful components: + +1. A bit mask generator +2. A template that based off the SWAR size determines whether to use the assembler instruction `POPCNT` or the logic, or potentially table lookups. +3. The SWAR register code + +These components hurt readability, but are essential to fine tune the most performance sensitive operations in Pokerbotic. As a matter of fact, the current configuration, in which the population count of a SWAR of 16 bit or larger elements occurs calling the `POPCNT` instruction four times, and for a SWAR of 8 bit or smaller elements is through the logic, was determined by benchmarking. The benchmarking also proved table lookups (not present in the code) to not be advantageous compared to the best between the assembler instruction or the logic at any SWAR size. This configuration has been proven optimal for current AMD64/EM64T architecture processors, but of course, other processors may have different optimal configurations. To come up with the optimal configuration, we have: + +1. Very fine grained choices on the parameters +2. The code to test things together + +## SWAR register user interface + +The template [`ep::core::SWAR`](https://github.com/thecppzoo/pokerbotic/blob/master/inc/ep/core/SWAR.h#L112) takes two parameters: the size of each individual data element and the containing register, for a rank-array representation of a normal deck of cards, 4 and `uint64_t`. + +Its public interface member functions are straightforward, perhaps with the exception of `top()` and `fastIndex()`. They both return an index to a non-null data element. For example, if a SWAR operation determines the elements whose values are above 2, like for example, a SWAR of rank-counts when determining three-of-a-kinds, the code is interested in finding the highest rank repeated more than 2 times. This is accomplished calling `top()`. + +Unfortunately, the counting of leading zeroes must be converted to a bit index by substraction from 63, thus in general it is less performing than finding the lowest array element. That is the reason for `fastIndex()`, which returns the lowest non-null index. This is useful whenever the code cares about getting the index of any non-null data element. Furthermore, in AMD64/EM64T there are two ways in which the GCC builtin gets translated to, to `BSR` or `LZCNT` that behave differently and have subtle performance differences depending on how they are called. + +When implementing the deck representation we found that higher-rank is higher-bit-index overperforms the representation of higher-rank-is-lower-bit-index, because even if all of the rank comparison operations are interested in the highest rank and in SWAR registers they are less performing as higher-rank-more-significant-bit, straights, flushes, and sets of cards in general can be compared much, much faster if the highest rank is at the most significant bits using plain unsigned integer comparisons. + +There is an important free standing template function, [`greaterEqualSWAR`](https://github.com/thecppzoo/pokerbotic/blob/master/inc/ep/core/SWAR.h#L161), that given a SWAR and an integer template parameter N will return a boolean SWAR with the data elements whose values are greater than or equal to the given N. + +The key insight in this function is to leverage substraction. For example, assume an element size of four bits and a comparison against three (as if detecting three of a kind). We can set the most significant bit, giving a value of 8, and add the comparison constant, in this case 3, resulting in 11. We can build a SWAR in which each element is 11, and subtract the comparand. In the result from this subtraction, all the elements that have the bit for 8 set were greater equal to 4. + +## Comparisons to other choices + +Poker Stove uses table lookups for almost all of its combinatorial logic. We are very surprised that the logic approach we followed leads to the same performance with the benchmark granularity we've tested, because they are very different. + +We still strongly prefer the logic-based approach, because it is branch-less code with no dependencies on the input data, in terms of performance it will behave the same way all the time. However, the table-lookup based approach looks better in benchmark code than in real life, because it requires the caches to have the tables in cache memory; while it is complicated to generate realistic benchmarks in which on purpose the table-lookup mechanism is starved of cache memory for the tables, this may happen in real life. Especially with random data that leads to less predictable memory access patterns, and in hyperthreading in which several threads compete for the caches within the same core. diff --git a/pokerbotic/design/communities.md b/pokerbotic/design/communities.md new file mode 100644 index 00000000..3143bb7b --- /dev/null +++ b/pokerbotic/design/communities.md @@ -0,0 +1,28 @@ +Communities, from the perspective of suits: + +### Note: whenever mentioning specific suits, the reader must multiply the different cases in which different suits can be selected for the same roles. For example, four spades and one hears means is an abbreviation for all the ways in which a suit can be selected for four cards, and another for one card. + +1. All of the same suit: `Ns*(Nr choose 5): 4*(13 choose 5)` +2. Four spades, one heart: `Ns*(Ns - 1)*(Nr choose 4)*Nr: 4*3*(13 choose 4)*13` +3. Three spades, two hearts: `Ns*(Ns - 1)*(Nr choose 3)*(Nr choose 2): 4*3*(13 choose 3)*(13 choose 2)` +4. Three spades, one heart, one diamond: `Ns*((Ns - 1) choose 2)*(Nr choose 3)*Nr*Nr: 4*3*(13 choose 3)*13*13` +5. Two spades, two hearts, one diamond: `(Ns choose 2)*(Ns - 2)(Nr choose 2)^2*Nr: (4 choose 2)*2*(13 choose 2)^2*13` +6. Two spades, one heart, one diamond, one club: `Ns*((Ns - 1) choose 3)*(Nr choose 2)*Nr^3: 4*(13 choose 2)*13^3` + +``` +0005148 : 00.2% +0111540 : 04.3% +0267696 : 10.3% +0580008 : 22.3% +0949104 : 36.5% +0685464 : 26.4% +====== +0000030 +000023 +00017 +0027 +027 +23 +====== +2598960 +``` diff --git a/pokerbotic/inc/detail/ep/mechanisms.h b/pokerbotic/inc/detail/ep/mechanisms.h new file mode 100644 index 00000000..cfc64f97 --- /dev/null +++ b/pokerbotic/inc/detail/ep/mechanisms.h @@ -0,0 +1,59 @@ +#pragma once + +#include "obsolete/Poker.h" +#include "ep/Poker_io.h" +#include "ep/Floyd.h" + +namespace ep { namespace naive { + +template +RankCounts noaks(RankCounts rc) { return RankCounts(RankCounts::doNotConvert, rc.greaterEqual()); } + +inline HandRank handRank(CSet hand) { + auto rankCounts = hand.rankCounts(); + auto suitCounts = hand.suitCounts(); + auto ranks = hand.rankSet(); + auto toaks = noaks<3>(rankCounts); + HandRank rv; + RARE(toaks) { + auto foaks = noaks<4>(rankCounts); + RARE(foaks) { + static_assert(TotalHand < 8, "There can be four-of-a-kind and straight-flush"); + return { FOUR_OF_A_KIND, 0, 0 }; + } + auto toak = toaks.best(); + auto without = rankCounts.clearAt(toak); + auto pairs = noaks<2>(without); + RARE(pairs) { + static_assert(TotalHand < 8, "There can be full-house and straight-flush"); + return { FULL_HOUSE, 0, 0 }; + } + rv = HandRank(THREE_OF_A_KIND, 0, 0); + } + auto flushes = suitCounts.greaterEqual<5>(); + RARE(flushes) { + static_assert(TotalHand < 2*5, "No two flushes"); + auto suit = flushes.top(); + auto royal = hand.m_bySuit.at(suit); + auto isIt = straights(royal); + RARE(isIt) { + return { STRAIGHT_FLUSH, 0, 0 }; + } + return { FLUSH, 0, 0 }; + } + auto str = straights(ranks); + RARE(str) { return { STRAIGHT, 0, 0 }; } + RARE(rv.isSet()) { return rv; } + auto pairs = rankCounts.greaterEqual<2>(); + if(pairs) { + auto top = pairs.top(); + auto without = pairs.clear(top); + if(without) { + return { TWO_PAIRS, 0, 0 }; + } + return { PAIR, 0, 0 }; + } + return { HIGH_CARDS, 0, 0 }; +} + +}} diff --git a/pokerbotic/inc/ep/CascadeComparisons.h b/pokerbotic/inc/ep/CascadeComparisons.h new file mode 100644 index 00000000..d42ecd7e --- /dev/null +++ b/pokerbotic/inc/ep/CascadeComparisons.h @@ -0,0 +1,399 @@ +#pragma once + +#include "obsolete/Poker.h" + +namespace ep { + +template +RankCounts noaks(RankCounts rc) { return RankCounts(RankCounts::doNotConvert, rc.greaterEqual()); } + +inline int bestFlush(unsigned p1, unsigned p2) { + return positiveIndex1Better(p1, p2); +} + +inline int bestStraight(unsigned s1, unsigned s2) { + return positiveIndex1Better(s1, s2); +} + +inline int bestKickers( + RankCounts p1s, RankCounts p2s +) { + return + positiveIndex1Better( + p1s.greaterEqual<1>().value(), + p2s.greaterEqual<1>().value() + ); +} + +inline int bestKicker(RankCounts p1s, RankCounts p2s) { + return bestKickers(p1s, p2s); +} + +inline int bestFourOfAKind( + RankCounts p1s, RankCounts p2s, + RankCounts p1foaks, RankCounts p2foaks +) { + auto p1BestNdx = p1foaks.best(); + auto p2BestNdx = p2foaks.best(); + auto diff = positiveIndex1Better(p1BestNdx, p2BestNdx); + if(diff) { return diff; } + return bestKicker(p1s.clearAt(p1BestNdx), p2s.clearAt(p2BestNdx)); +} + +inline unsigned straightFlush(CSet cards, SuitCounts ss) { + static_assert(TotalHand < 2*5, "Assumes only one flush"); + auto ndx = ss.best(); + return straights(cards.m_bySuit.at(ndx)); +} + +struct ComparisonResult { + int64_t ifDecided; + bool decided; +}; + +struct FullHouseResult { + bool isFullHouse; + int bestThreeOfAKind, bestPair; +}; + +inline FullHouseResult isFullHouse(RankCounts counts) { + auto toaks = counts.greaterEqual<3>(); + RARE(toaks) { + auto bestTOAK = toaks.best(); + auto withoutBestTOAK = counts.clearAt(bestTOAK); + auto fullPairs = withoutBestTOAK.greaterEqual<2>(); + RARE(fullPairs) { return { true, bestTOAK, fullPairs.best() }; } + } + return { false, 0, 0 }; +} + +unsigned straightsDoingChecks(unsigned rankSet) { + constexpr auto NumberOfHandCards = 7; + auto rv = rankSet; + // 2 1 0 9 8 7 6 5 4 3 2 1 0 bit index + // ========================= + // 2 3 4 5 6 7 8 9 T J Q K A + // - 2 3 4 5 6 7 8 9 T J Q K & + // - - 2 3 4 6 5 6 7 8 9 T J & + // - - - 2 3 4 5 6 7 8 9 T J & + // - - - A - - - - - - - - - | + // - - - A 2 3 4 5 6 7 8 9 T & + + + rv &= (rv >> 1); // two + if(!rv) { return rv; } + rv &= (rv >> 1); // three in sequence + if(!rv) { return rv; } + rv &= (rv >> 1); // four in sequence + if(!rv) { return rv; } + RARE(1 & rankSet) { + // the argument had an ace, insert it as if for card number 1 + rv |= (1 << (NRanks - 4)); + } + rv &= (rv >> 1); + return rv; +} + +inline unsigned straightFrontCheck(unsigned rankSet) { + constexpr auto mask = (1 << 9) | (1 << 4); + if(rankSet & mask) { return straights(rankSet); } + /*auto notStraight = straights(rankSet); + if(notStraight) { + static auto count = 5; + if(5 == count) { pRanks(std::cerr, mask) << '\n' << std::endl; } + if(--count < 0) { return 0; } + pRanks(std::cerr, rankSet) << " | "; + pRanks(std::cerr, notStraight) << std::endl; + }*/ + return 0; +} + +inline ComparisonResult winnerPotentialFullHouseGivenThreeOfAKind( + RankCounts p1s, RankCounts p2s, + RanksPresent p1toaks, RanksPresent p2toaks +) { + auto p1BestThreeOfAKindIndex = p1toaks.best(); + auto p1WithoutBestThreeOfAKind = p1s.clearAt(p1BestThreeOfAKindIndex); + auto p1FullPairs = p1WithoutBestThreeOfAKind.greaterEqual<2>(); + RARE(p1FullPairs) { // p1 is full house + RARE(p2toaks) { + auto p2BestThreeOfAKindIndex = p2toaks.best(); + auto bestDominantIndex = + positiveIndex1Better( + p1BestThreeOfAKindIndex, p2BestThreeOfAKindIndex + ); + if(0 < bestDominantIndex) { + // p2's best three of a kind is not as good as p1's, + // even if a full house itself will lose + return { 1, true }; + } + auto p2WithoutBestThreeOfAKind = + p2s.clearAt(p2BestThreeOfAKindIndex); + auto p2FullPairs = p2WithoutBestThreeOfAKind.greaterEqual<2>(); + RARE(p2FullPairs) { + if(bestDominantIndex) { return { bestDominantIndex, true }; } + return { + positiveIndex1Better( + p1FullPairs.best(), p2FullPairs.best() + ), + true + }; + } + } + return { 1, true }; + } + // p1 is not a full-house + auto p2BestThreeOfAKindIndex = p2toaks.best(); + auto p2WithoutBestThreeOfAKind = p2s.clearAt(p2BestThreeOfAKindIndex); + auto p2FullPairs = p2WithoutBestThreeOfAKind.greaterEqual<2>(); + RARE(p2FullPairs) { return { -1, true }; } + // neither is a full house + return { 0, false }; +} + +inline ComparisonResult winnerPotentialFullHouseOrFourOfAKind( + RankCounts p1s, RankCounts p2s +) { + // xoptx How can any be full house or four of a kind? + // xoptx What is the optimal sequence of checks? + auto p1ThreeOfAKinds = p1s.greaterEqual<3>(); + auto p2ThreeOfAKinds = p2s.greaterEqual<3>(); + RARE(p1ThreeOfAKinds) { // p1 has a three of a kind + RARE(p2ThreeOfAKinds) { // p2 also has a three of a kind + auto p1FourOfAKinds = noaks<4>(p1s); + auto p2FourOfAKinds = noaks<4>(p2s); + RARE(p1FourOfAKinds) { + RARE(p2FourOfAKinds) { + return { + bestFourOfAKind(p1s, p2s, p1FourOfAKinds, p2FourOfAKinds), + true + }; + } + return { 1, true }; + } else RARE(p2FourOfAKinds) { return { -1, true }; } + // No four of a kind + return + winnerPotentialFullHouseGivenThreeOfAKind( + p1s, p2s, p1ThreeOfAKinds, p2ThreeOfAKinds + ); + } + // p2 lacks three of a kind, can not be full-house nor four-of-a-kind + auto p1BestThreeOfAKindIndex = p1ThreeOfAKinds.best(); + auto p1WithoutBestThreeOfAKind = p1s.clearAt(p1BestThreeOfAKindIndex); + auto p1FullPairs = p1WithoutBestThreeOfAKind.greaterEqual<2>(); + RARE(p1FullPairs) { return { 1, true }; } + // not a full house, but perhaps four of a kind? + RARE(p1s.greaterEqual<4>()) { return { 1, true }; } + // neither is full house or better, undecided + } // p1 lacks three of a kind, can not be full-house nor four-of-a-kind + else RARE(p2ThreeOfAKinds) { + auto p2BestThreeOfAKindIndex = p2ThreeOfAKinds.best(); + auto p2WithoutBestThreeOfAKind = p2s.clearAt(p2BestThreeOfAKindIndex); + auto p2FullPairs = p2s.greaterEqual<2>(); + RARE(p2FullPairs) { return { -1, true }; } + // p2 is not a full house, but perhaps a four of a kind? + RARE(p2s.greaterEqual<4>()) { return { -1, true }; } + } + return { 0, false }; +} + + +/// \todo Handle two pairs and pairs +/// \todo optimize this. Look for comments with the word xoptx +int winnerCascade(CSet community, CSet p1, CSet p2) { + // There are three independent criteria + // n-of-a-kind, straights and flushes + // since flushes dominate straigts, and have in texas holdem about the + // same probability than straights, the first inconditional check is + // for flushes. + // xoptx all the comparison operations should not be unary but binary + // in the player cards and community, this would allow calculating the + // community cards only once. + p1 = p1 | community; + auto p1Suits = p1.suitCounts(); + auto p1Flushes = flushes(p1Suits); + p2 = p2 | community ; + auto p2Suits = p2.suitCounts(); + auto p2Flushes = flushes(p2Suits); + // xoptx in the case of flushes, three of a kind, etc., there + // should be a constexpr template predicate that indicates whether + // current classification allows for flush, straight, four of a kind, etc + // xoptx also the community cards determine whether flush, full house can + // happen + RARE(p1Flushes) { + RARE(p2Flushes) { + // p2 also has a flush, there is the need to check for either being + // a straight flush + auto p1FlushSuit = p1Flushes.best(); + auto p1Flush = p1.m_bySuit.at(p1FlushSuit); + auto p1Straights = straights(p1Flush); + auto p2FlushSuit = p2Flushes.best(); + auto p2Flush = p2.m_bySuit.at(p2FlushSuit); + auto p2Straights = straights(p2Flush); + RARE(p1Straights) { // p1 is straight flush + // note: never needed to calculate number repetitions! + RARE(p2Straights) { // both are + return bestStraight(p1Straights, p2Straights); + } + return 1; + } + RARE(p2Straights) { // p2 is straight flush + // note: never needed to calculate numbers! + return -1; + } + // Both are flush but none straight flush + // xoptx How can any be full house or four of a kind? + // xoptx What is the optimal sequence of checks? + auto p1NumberCounts = p1.rankCounts(); + auto p2NumberCounts = p2.rankCounts(); + auto fullHouseOrFourOfAKind = + winnerPotentialFullHouseOrFourOfAKind( + p1NumberCounts, p2NumberCounts + ); + RARE(fullHouseOrFourOfAKind.decided) { + return fullHouseOrFourOfAKind.ifDecided; + } + // flushes, but not straight-flush, nor full-house nor four of a kind + return bestFlush(p1Flush, p2Flush); + } + // p2 is not a flush and p1 is a flush + // xoptx this check seems premature, only if p2 is a full house is justified + auto p1s = p1.rankCounts(); + auto p2s = p2.rankCounts(); + auto fullHouseOrFourOfAKind = + winnerPotentialFullHouseOrFourOfAKind(p1s, p2s); + RARE(fullHouseOrFourOfAKind.decided) { + return fullHouseOrFourOfAKind.ifDecided; + } + // p2 is not a full house nor a four of a kind + return 1; + } + // p1 is no flush + auto p1s = p1.rankCounts(); + auto p2s = p2.rankCounts(); + RARE(p2Flushes) { + auto p2FlushSuit = p2Flushes.best(); + auto p2Flush = p2.m_bySuit.at(p2FlushSuit); + auto p2Straights = straights(p2Flush); + RARE(p2Straights) { return -1; } // p2 is straight flush + auto fullHouseOrFourOfAKind = + winnerPotentialFullHouseOrFourOfAKind(p1s, p2s); + RARE(fullHouseOrFourOfAKind.decided) { + return fullHouseOrFourOfAKind.ifDecided; + } + return -1; + } + // No flushes + auto p1Set = p1.rankSet(); + auto p1Straights = straights(p1Set); + auto p2Set = p2.rankSet(); + auto p2Straights = straights(p2Set); + RARE(p1Straights | p2Straights) { + auto fhofoak = winnerPotentialFullHouseOrFourOfAKind(p1s, p2s); + RARE(fhofoak.decided) { return fhofoak.ifDecided; } + if(p1Straights) { + if(p2Straights) { + return positiveIndex1Better(p1Straights, p2Straights); + } + return 1; + } + return -1; + } + auto p1Pairs = p1s.greaterEqual<2>(); + auto p2Pairs = p2s.greaterEqual<2>(); + RARE(!p1Pairs) { + RARE(!p2Pairs) { + return bestFlush(p1.rankSet(), p2.rankSet()); + } + return -1; + } + RARE(!p2Pairs) { return 1; } + auto p1toaks = p1s.greaterEqual<3>(); + auto p2toaks = p2s.greaterEqual<3>(); + RARE(p1toaks) { + RARE(p2toaks) { + auto potentialFull = + winnerPotentialFullHouseGivenThreeOfAKind( + p1s, p2s, p1toaks, p2toaks + ); + RARE(potentialFull.decided) { return potentialFull.ifDecided; } + // no full house or better + auto + p1Toak = p1toaks.best(), + p2Toak = p2toaks.best(); + auto diff = positiveIndex1Better(p1Toak, p2Toak); + return diff; + } + return 1; + } + // double pairs and pairs + return 0; +} + +inline int bestFullHouse(FullHouseResult p1, FullHouseResult p2) { + return + positiveIndex1Better( + p1.bestPair + (p1.bestThreeOfAKind << 4), + p2.bestPair + (p2.bestThreeOfAKind << 4) + ); +} + +inline int winner(CSet community, CSet p1, CSet p2) { + // There are three independent criteria + // n-of-a-kind, straights and flushes + // since flushes dominate straigts, and have in texas holdem about the + // same probability than straights, the first inconditional check is + // for flushes. + // xoptx all the comparison operations should not be unary but binary + // in the player cards and community, this would allow calculating the + // community cards only once. + p1 = p1 | community; + auto p1Suits = p1.suitCounts(); + auto p1Flushes = flushes(p1Suits); + p2 = p2 | community ; + auto p2Suits = p2.suitCounts(); + auto p2Flushes = flushes(p2Suits); + + auto p1ranks = p1.rankCounts(); + auto p2ranks = p2.rankCounts(); + + auto p1toaks = noaks<3>(p1ranks); + auto p2toaks = noaks<3>(p2ranks); + + //auto p1str = straights(p1.rankSet()); + //auto p2str = straights(p2.rankSet()); + + RARE(p1toaks) { + RARE(p2toaks) { + auto p1foaks = noaks<4>(p1ranks); + auto p2foaks = noaks<4>(p2ranks); + RARE(p1foaks) { + static_assert(TotalHand - 3 < 5, "Four of a kind does not imply there is no straight"); + RARE(p2foaks) { + return bestFourOfAKind(p1ranks, p2ranks, p1foaks, p2foaks); + } + //RARE(straightFlush(p2, p2Flushes)) { return -1; } + return 1; + } + RARE(p2foaks) { + //RARE(straightFlush(p1, p2Flushes)) { return 1; } + return -1; + } + // no four of a kind + auto p1fh = isFullHouse(p1ranks); + RARE(p1fh.isFullHouse) { + static_assert(TotalHand < 8, "There can be full house and straight flush"); + auto p2fh = isFullHouse(p2ranks); + RARE(p2fh.isFullHouse) { return bestFullHouse(p1fh, p2fh); } + //RARE(straightFlush(p2, p2Flushes)) { return -1; } + return 1; + } + // no four of a kind, no full house either hand + RARE(p1Flushes) { + } + } + } + return 0; +} + +} diff --git a/pokerbotic/inc/ep/Cases.h b/pokerbotic/inc/ep/Cases.h new file mode 100644 index 00000000..5e164e30 --- /dev/null +++ b/pokerbotic/inc/ep/Cases.h @@ -0,0 +1,90 @@ +#pragma once + +#include "ep/Poker.h" +#include "ep/core/deposit.h" +#include "ep/nextSubset.h" +#include "PokerTypes.h" +#include "ep/metaBinomial.h" + +namespace ep { + +struct SuitedPocket { + using U = uint64_t; + + constexpr static auto equivalents = NSuits; + constexpr static auto count = Choose::value; + // Exclude everything not spades + constexpr static auto preselected = core::makeBitmask(0xE); + // 2 and 3 of spades + constexpr static uint64_t starting = 3; + constexpr static auto end = uint64_t(1) << NRanks; + constexpr static SWARSuit suits = SWARSuit(2); + + uint64_t m_current = starting; + + auto next() { + auto rv = core::deposit(preselected, m_current); + m_current = ep::nextSubset(m_current); + return rv; + } + + constexpr operator bool() const { return m_current < end; } +}; + +struct PocketPair { + constexpr static auto equivalents = Choose::value; + constexpr static auto count = NRanks; + + constexpr static auto preselected = core::makeBitmask(0xE); + constexpr static uint64_t starting = 1; + constexpr static auto end = uint64_t(1) << NRanks*RankSize; + constexpr static SWARSuit suits = SWARSuit((1 << SuitSize) | 1); + + uint64_t m_current = starting; + + auto next() { + auto heart = m_current << 1; + auto rv = m_current | heart; + m_current <<= RankSize; + return rv; + } + + constexpr operator bool() const { + return m_current < end; + } +}; + +struct UnsuitedPocket { + constexpr static auto equivalents = Choose::value; + constexpr static auto count = Choose::value; + constexpr static auto preselected = core::makeBitmask(0xE); + // 2 and 3 of spades + constexpr static uint64_t starting = 3; + constexpr static auto end = uint64_t(1) << NRanks; + constexpr static SWARSuit suits = SWARSuit((1 << SuitSize) | 1); + + uint64_t m_current = starting; + + auto next() { + auto asRanks = core::deposit(preselected, m_current); + auto makeLowRankHeart = (asRanks | (asRanks - 1)) + 1; + m_current = nextSubset(m_current); + return makeLowRankHeart; + } + + constexpr operator bool() const { + return m_current < end; + } +}; + +template +struct Communities; + +template<> +struct Communities { + using predecessor = UnsuitedPocket; +}; + +// Unsuited pocket continuations: +// +} diff --git a/pokerbotic/inc/ep/Classifications.h b/pokerbotic/inc/ep/Classifications.h new file mode 100644 index 00000000..7e5f8f84 --- /dev/null +++ b/pokerbotic/inc/ep/Classifications.h @@ -0,0 +1,45 @@ +#pragma once + +#include "ep/metaBinomial.h" + +namespace ep { + +struct MonotoneFlop { + constexpr static auto equivalents = NSuits; + constexpr static auto element_count = Choose::value; + constexpr static auto max_repetitions = 0; +}; + +/// \todo There are several subcategories here: +/// The card with the non-repeated suit is highest, pairs high, middle, pairs low, lowest +struct TwoToneFlop { + constexpr static auto equivalents = PartialPermutations::value; + constexpr static auto element_count = + NRanks * Choose::value; + constexpr static auto max_repetitions = 1; +}; + +struct RainbowFlop { + constexpr static auto equivalents = Choose::value; + constexpr static auto element_count = NRanks*NRanks*NRanks; + constexpr static auto max_repetitions = 2; +}; + +template struct Count { + constexpr static uint64_t value = 0; +}; +template +struct Count { + constexpr static uint64_t value = + H::equivalents*H::element_count + Count::value; +}; + +static_assert(4 * Choose<13, 3>::value == Count::value, ""); +static_assert(12*13*Choose::value == Count::value, ""); +static_assert(4*13*13*13 == Count::value, ""); +static_assert( + Choose<52, 3>::value == Count::value, + "" +); + +} diff --git a/pokerbotic/inc/ep/Compare.h b/pokerbotic/inc/ep/Compare.h new file mode 100644 index 00000000..ea7f0f10 --- /dev/null +++ b/pokerbotic/inc/ep/Compare.h @@ -0,0 +1,99 @@ +#pragma once + +#include "ep/Poker.h" +#include "PokerTypes.h" + +#include +#include + +namespace ep { + +struct Unclassified {}; + +template +int compare(Community c, CardSet comm, CardSet p1, CardSet p2) { + return compare(comm, p1, p2); +} + +struct Classification { + uint64_t mask; + enum type { + SPADES, FOUR_SPADES, THREE_SPADES_TWO_HEARTS, + THREE_SPADES_HEART_DIAMOND, TWO_SPADES_TWO_HEARTS, TWO_SPADES + }; +}; + +inline int classify(uint64_t cards) { + //static_assert(NCommunity + NPlayer < 10, "Two flushes possible"); + constexpr auto suitMask = core::makeBitmask<4, uint64_t>(1); + auto maskCopy = suitMask; + for(auto suit = 0; suit < 4; ++suit) { + auto filtered = cards & maskCopy; + auto counts = __builtin_popcountll(filtered); + if(3 <= counts) { return suit; } + cards >>= 1; + } + return 4; +} + +inline uint64_t flush(Colored *c, uint64_t cards) { + auto shifted = cards >> c->shift; + constexpr auto suitMask = core::makeBitmask<4, uint64_t>(1); + auto filtered = shifted & suitMask; + auto count = __builtin_popcountll(filtered); + if(5 <= count) { return filtered; } + return 0; +} + +inline uint64_t flush(ColorBlind *, uint64_t) { return 0; } + +template< + template class Continuation, + typename... Args +> +inline int classifyCommunity(CardSet comm, Args &&... arguments) { + auto ccards = comm.cards(); + constexpr auto suitMask = core::makeBitmask<4, uint64_t>(1); + auto maskCopy = suitMask; + for(auto suit = 0; suit < 4; ++suit) { + auto filtered = ccards & maskCopy; + auto counts = __builtin_popcountll(filtered); + if(3 <= counts) { + Colored co = { suit }; + return Continuation::execute(&co, comm, std::forward(arguments)...); + } + ccards >>= 1; + } + ColorBlind cb; + return Continuation::execute(&cb, comm, std::forward(arguments)...); +} + template struct Continuation { + static int execute(C *c, CardSet co, CardSet p1, CardSet p2) { + return handRank(c, co | p1).code - handRank(c, co | p2).code; + } + }; +inline int compareByCommunity(CardSet comm, CardSet p1, CardSet p2) { + + return classifyCommunity(comm, p1, p2); + auto ccards = comm.cards(); + constexpr auto suitMask = core::makeBitmask<4, uint64_t>(1); + auto maskCopy = suitMask; + for(auto suit = 0; suit < 4; ++suit) { + auto filtered = ccards & maskCopy; + auto counts = __builtin_popcountll(filtered); + if(3 <= counts) { + Colored c = { suit }; + return handRank(&c, comm | p1).code - handRank(&c, comm | p2).code; + } + ccards >>= 1; + } + ColorBlind cb; + return handRank(&cb, comm | p1).code - handRank(&cb, comm | p2).code; +} + +template +inline int compareByCommunity(Context c, CardSet community, CardSet p1, CardSet p2) { + return handRank(c, community | p1).code - handRank(c, community | p2).code; +} + +} \ No newline at end of file diff --git a/pokerbotic/inc/ep/Floyd.h b/pokerbotic/inc/ep/Floyd.h new file mode 100644 index 00000000..7a5d199a --- /dev/null +++ b/pokerbotic/inc/ep/Floyd.h @@ -0,0 +1,32 @@ +#pragma once + +#include "ep/core/deposit.h" + +#include + +namespace ep { + +template +inline uint64_t floydSample(Rng &&g) { + std::uniform_int_distribution<> dist; + using Bounds = std::uniform_int_distribution<>::param_type; + uint64_t rv = 0; + for(auto v = N - K; v < N; ++v) { + auto draw = dist(g, Bounds(0, v)); + auto bit = uint64_t(1) << draw; + if(bit & rv) { + draw = v; + bit = uint64_t(1) << draw; + } + rv |= bit; + } + return rv; +} + +template +inline uint64_t floydSample(Rng &&g, uint64_t preselected) { + auto normal = floydSample(std::forward(g)); + return core::deposit(preselected, normal); +} + +} diff --git a/pokerbotic/inc/ep/Poker.h b/pokerbotic/inc/ep/Poker.h new file mode 100644 index 00000000..77bb2ebf --- /dev/null +++ b/pokerbotic/inc/ep/Poker.h @@ -0,0 +1,249 @@ +#pragma once + +#include "core/SWAR.h" + + +#include "ep/PokerTypes.h" + +namespace ep { + + +constexpr int64_t positiveIndex1Better(uint64_t index1, uint64_t index2) { + return index1 - index2; +} + +template +struct Counted_impl { + using NoConversion = int Counted_impl::*; + constexpr static NoConversion doNotConvert = nullptr; + + Counted_impl() = default; + constexpr explicit Counted_impl(core::SWAR bits): + m_counts( + core::popcount(bits.value()) + ) + {} + constexpr Counted_impl(NoConversion, core::SWAR bits): + m_counts(bits) {} + + constexpr Counted_impl clearAt(int index) { + return Counted_impl(doNotConvert, m_counts.clear(index)); + } + + constexpr core::SWAR counts() { return m_counts; } + + template + constexpr core::BooleanSWAR greaterEqual() { + return core::greaterEqualSWAR(m_counts); + } + + constexpr operator bool() const { return m_counts.value(); } + + constexpr int best() { return m_counts.top(); } + + protected: + core::SWAR m_counts; +}; + +using RankCounts = Counted_impl; +using SuitCounts = Counted_impl; +using RanksPresent = core::BooleanSWAR; + +struct CardSet { + SWARRank m_ranks; + + CardSet() = default; + constexpr CardSet(uint64_t cards): m_ranks(cards) {} + + constexpr RankCounts counts() { return RankCounts(m_ranks); } + + static unsigned ranks(uint64_t arg);/* { + constexpr auto selector = ep::core::makeBitmask<4>(uint64_t(1)); + return __builtin_ia32_pext_di(arg, selector); + }*/ + + static unsigned ranks(RankCounts rc);/* { + constexpr auto selector = ep::core::makeBitmask<4>(uint64_t(8)); + auto present = rc.greaterEqual<1>(); + return __builtin_ia32_pext_di(present.value(), selector); + }*/ + + constexpr uint64_t cards() { return m_ranks.value(); } +}; + +inline CardSet operator|(CardSet l, CardSet r) { + return l.m_ranks.value() | r.m_ranks.value(); +} + +inline uint64_t flush(uint64_t arg) { + constexpr auto flushMask = ep::core::makeBitmask<4, uint64_t>(1); + for(auto n = 4; n--; ) { + auto filtered = arg & flushMask; + auto count = __builtin_popcountll(filtered); + if(5 <= count) { return filtered; } + arg >>= 1; + } + return 0; +} + +#define RARE(v) if(__builtin_expect(bool(v), false)) +#define LIKELY_NOT(v) if(__builtin_expect(!bool(v), true)) + +inline SWARRank straights_rankRepresentation(RanksPresent present) { + auto acep = + present.at(ep::abbreviations::rA) ? + uint64_t(0xF) << ep::abbreviations::rA : + uint64_t(0); + auto ranks = present.value(); + auto sk = ranks << 4; + auto rak = ranks & sk; + auto rt = acep | (ranks << 16); + auto rqj = rak << 8; + auto rakqj = rak & rqj; + return SWARRank(rakqj & rt); +} + +enum BestHand { + HIGH_CARDS = 0, PAIR, TWO_PAIRS, THREE_OF_A_KIND, STRAIGHT, FLUSH, + FULL_HOUSE, FOUR_OF_A_KIND, STRAIGHT_FLUSH +}; + +union HandRank { + int32_t code; + struct { + unsigned + low: 14, + high: 14, + hand: 4; + }; + + constexpr HandRank(): code(0) {} + + HandRank(BestHand h, int high, int low): + hand(h), high(high), low(low) + {} + + constexpr bool isSet() { return 0 != code; } + + constexpr bool operator==(HandRank hr) { + return code == hr.code; } +}; + +inline unsigned toRanks(uint64_t ranks) { + constexpr auto selector = ep::core::makeBitmask<4>(uint64_t(1)); + return __builtin_ia32_pext_di(ranks, selector); +} + +inline unsigned toRanks(RanksPresent rp) { + constexpr auto selector = ep::core::makeBitmask<4>(uint64_t(8)); + return __builtin_ia32_pext_di(rp.value(), selector); +} + +inline uint64_t flush(void *, uint64_t cards) { return flush(cards); } + +namespace { + struct Colored { int shift; }; + struct ColorBlind {}; +}; + +inline uint64_t flush(Colored *c, uint64_t cards); +inline uint64_t flush(ColorBlind *c, uint64_t cards); + +/// \note benchmarks seem to indicate this is a 0.5% time pessimization! +template +inline bool mayStraight(T *p, CardSet hand) { + static_assert(13 == NRanks, ""); + constexpr auto fivesOrTens = uint64_t(0xF0000F000); + return hand.cards() & fivesOrTens; +} + +template +inline HandRank handRank(T *p, CardSet hand) { + auto mayS = mayStraight(p, hand); + RankCounts rankCounts{SWARRank(hand.cards())}; + auto toaks = rankCounts.greaterEqual<3>(); + HandRank rv; + RARE(toaks) { + auto foaks = rankCounts.greaterEqual<4>(); + RARE(foaks) { + static_assert(TotalHand < 8, "There can be four-of-a-kind and straight-flush"); + auto foak = foaks.top(); + auto without = rankCounts.clearAt(foak); + return { FOUR_OF_A_KIND, 1 << foak, 1 << without.best() }; + } + auto toak = toaks.top(); + auto without = rankCounts.clearAt(toak); + auto pairs = without.greaterEqual<2>(); + RARE(pairs) { + static_assert(TotalHand < 8, "There can be full-house and straight-flush"); + auto pair = pairs.top(); + return { FULL_HOUSE, (1 << toak), (1 << pair) }; + } + auto high = 1 << toak; + auto kicker1 = without.best(); + auto without2 = without.clearAt(kicker1); + auto kicker2 = without2.best(); + auto low = (1 << kicker1) | (1 << kicker2); + rv = HandRank(THREE_OF_A_KIND, high, low); + } + static_assert(TotalHand < 2*5, "No two flushes"); + auto flushCards = flush(p, hand.cards()); + RARE(flushCards) { + auto tmp = RanksPresent(flushCards); + auto straightFlush = + mayS ? + straights_rankRepresentation(tmp) : + SWARRank(0); + RARE(straightFlush) { + return { STRAIGHT_FLUSH, 0, 1 << straightFlush.top() }; + } + auto cards = flushCards; + for(auto count = __builtin_popcountll(cards); 5 < count--; ) { + cards &= (cards - 1); // turns off lsb + } + auto ranks = toRanks(cards); + return { FLUSH, 0, int(ranks) }; + } + auto ranks = rankCounts.greaterEqual<1>(); + auto str = + mayS ? + straights_rankRepresentation(ranks) : + SWARRank(0); + RARE(str) { return { STRAIGHT, 0, 1 << str.top() }; } + RARE(rv.isSet()) { return rv; } + auto pairs = rankCounts.greaterEqual<2>(); + if(pairs) { + auto top = pairs.top(); + auto high = (1 << top); + auto bottomPairs = pairs.clear(top); + auto without = ranks.clear(top); + if(bottomPairs) { + auto bottom = bottomPairs.top(); + auto without2 = without.clear(bottom); + return { TWO_PAIRS, high | (1 << bottom), 1 << without2.top() }; + } + auto valueRanks = toRanks(without); + // Objective: leave in valueRanks three kickers + // Given: TotalHand - 2 ranks + // TotalHand - 1 - 1 ranks - count = 3 <=> count = TotalHand - 5; + for(auto count = TotalHand - 5; count--; ) { + valueRanks &= valueRanks - 1; + } + return { PAIR, high, int(valueRanks) }; + } + auto valueRanks = toRanks(ranks); + for(auto count = TotalHand - 5; count--; ) { + valueRanks &= valueRanks - 1; + } + return { HIGH_CARDS, 0, int(valueRanks) }; +} + +inline HandRank handRank(CardSet hand) { return handRank(nullptr, hand); } + +inline int compare(CardSet community, CardSet p1, CardSet p2) { + auto rank1 = handRank(community | p1); + auto rank2 = handRank(community | p2); + return rank1.code - rank2.code; +} + +} diff --git a/pokerbotic/inc/ep/PokerTypes.h b/pokerbotic/inc/ep/PokerTypes.h new file mode 100644 index 00000000..f237770d --- /dev/null +++ b/pokerbotic/inc/ep/PokerTypes.h @@ -0,0 +1,121 @@ +#pragma once + +#include "core/SWAR.h" + +namespace ep { + +/// Number of numbers in the card set +constexpr auto NRanks = 13; +/// Number of suits +constexpr auto NSuits = 4; + +namespace canonical { + +constexpr auto NRanks = 13; +constexpr auto NSuits = 4; + +}; + +constexpr auto SuitSize = 1 << core::metaLogCeiling(NRanks); +constexpr auto RankSize = 1 << core::metaLogCeiling(NSuits); + +using SWARSuit = core::SWAR; +using SWARRank = core::SWAR; + +inline SWARSuit convert(SWARRank r) { + uint64_t rv = 0; + auto input = r.value(); + while(input) { + auto low = __builtin_ctzll(input); + auto suit = (low & 0x3) << 4; + auto rank = low >> 2; + rv |= uint64_t(1) << (rank + suit); + input &= (input - 1); + } + return SWARSuit(rv); +} + +constexpr auto NCommunity = 5; +constexpr auto NPlayer = 2; +constexpr auto TotalHand = NCommunity + NPlayer; + +namespace abbreviations { + +enum Ranks { + r2 = 0, r3, r4, r5, r6, r7, r8, r9, rT, rJ, rQ, rK, rA +}; + +inline int operator|(Ranks r1, Ranks r2) { + return (1 << r1) | (1 << r2); +} + +inline int operator|(int rv, Ranks rank) { + return rv | (1 << rank); +} + +inline int operator|=(int &rv, Ranks rank) { + return rv |= (1 << rank); +} + +enum Suits { + sS = 0, sH, sD, sC +}; + +enum Cards: uint64_t { + s2 = uint64_t(1) << 0, + h2 = uint64_t(1) << 1, + d2 = uint64_t(1) << 2, + c2 = uint64_t(1) << 3, + s3 = uint64_t(1) << 4, + h3 = uint64_t(1) << 5, + d3 = uint64_t(1) << 6, + c3 = uint64_t(1) << 7, + s4 = uint64_t(1) << 8, + h4 = uint64_t(1) << 9, + d4 = uint64_t(1) << 10, + c4 = uint64_t(1) << 11, + s5 = uint64_t(1) << 12, + h5 = uint64_t(1) << 13, + d5 = uint64_t(1) << 14, + c5 = uint64_t(1) << 15, + s6 = uint64_t(1) << 16, + h6 = uint64_t(1) << 17, + d6 = uint64_t(1) << 18, + c6 = uint64_t(1) << 19, + s7 = uint64_t(1) << 20, + h7 = uint64_t(1) << 21, + d7 = uint64_t(1) << 22, + c7 = uint64_t(1) << 23, + s8 = uint64_t(1) << 24, + h8 = uint64_t(1) << 25, + d8 = uint64_t(1) << 26, + c8 = uint64_t(1) << 27, + s9 = uint64_t(1) << 28, + h9 = uint64_t(1) << 29, + d9 = uint64_t(1) << 30, + c9 = uint64_t(1) << 31, + sT = uint64_t(1) << 32, + hT = uint64_t(1) << 33, + dT = uint64_t(1) << 34, + cT = uint64_t(1) << 35, + sJ = uint64_t(1) << 36, + hJ = uint64_t(1) << 37, + dJ = uint64_t(1) << 38, + cJ = uint64_t(1) << 39, + sQ = uint64_t(1) << 40, + hQ = uint64_t(1) << 41, + dQ = uint64_t(1) << 42, + cQ = uint64_t(1) << 43, + sK = uint64_t(1) << 44, + hK = uint64_t(1) << 45, + dK = uint64_t(1) << 46, + cK = uint64_t(1) << 47, + sA = uint64_t(1) << 48, + hA = uint64_t(1) << 49, + dA = uint64_t(1) << 50, + cA = uint64_t(1) << 51, +}; + +} + +} diff --git a/pokerbotic/inc/ep/Poker_io.h b/pokerbotic/inc/ep/Poker_io.h new file mode 100644 index 00000000..9b7ab85b --- /dev/null +++ b/pokerbotic/inc/ep/Poker_io.h @@ -0,0 +1,28 @@ +#pragma once + +#include "PokerTypes.h" + +#include + +namespace ep { namespace core { + +inline std::ostream &operator<<(std::ostream &out, SWARRank ranks) { + static auto g_rankLetters = "23456789TJQKA0#%"; + static auto g_suitLetters = "shdc"; + if(!ranks.value()) { return out << '-'; } + for(;;) { + auto rank = ranks.top(); + for(auto suits = ranks.at(rank); ; ) { + auto suit = core::msb(suits); + out << g_suitLetters[suit] << g_rankLetters[rank]; + suits &= ~(1 << suit); + if(!suits) { break; } + out << '.'; + } + ranks = ranks.clear(rank); + if(!ranks.value()) { return out; } + out << '_'; + } +} + +}} diff --git a/pokerbotic/inc/ep/core/SWAR.h b/pokerbotic/inc/ep/core/SWAR.h new file mode 100644 index 00000000..5154dc1d --- /dev/null +++ b/pokerbotic/inc/ep/core/SWAR.h @@ -0,0 +1,179 @@ +#pragma once + +/// \file Swar.h SWAR operations + +#include "metaLog.h" +#include + +namespace ep { namespace core { + +/// Repeats the given pattern in the whole of the argument +/// \tparam T the desired integral type +/// \tparam Progression how big the pattern is +/// \tparam Remaining how many more times to copy the pattern +template struct BitmaskMaker { + constexpr static T repeat(T v) { + return + BitmaskMaker::repeat(v | (v << Progression)); + } +}; +template struct BitmaskMaker { + constexpr static T repeat(T v) { return v; } +}; + +/// Front end to \c BitmaskMaker with the repeating count set to the whole size +template +constexpr T makeBitmask(T v) { + return BitmaskMaker::repeat(v); +} + +/// Core implementation details +namespace detail { + template + constexpr auto popcountMask = + makeBitmask<1 << (level + 1)>( + BitmaskMaker::repeat(1) + ); + + static_assert(makeBitmask<2>(1ull) == popcountMask<0>, ""); +} + +template struct UInteger_impl; +template<> struct UInteger_impl<8> { using type = uint8_t; }; +template<> struct UInteger_impl<16> { using type = uint16_t; }; +template<> struct UInteger_impl<32> { using type = uint32_t; }; +template<> struct UInteger_impl<64> { using type = uint64_t; }; + +template using UInteger = typename UInteger_impl::type; + +template +constexpr uint64_t popcount_logic(uint64_t arg) { + auto v = popcount_logic(arg); + constexpr auto shifter = 1 << Level; + return + ((v >> shifter) & detail::popcountMask) + + (v & detail::popcountMask); +} +/// Hamming weight of each bit pair +template<> +constexpr uint64_t popcount_logic<0>(uint64_t v) { + // 00: 00; 00 + // 01: 01; 01 + // 10: 01; 01 + // 11: 10; 10 + return v - ((v >> 1) & detail::popcountMask<0>); +} + +template +constexpr uint64_t popcount_builtin(uint64_t v) { + using UI = UInteger<1 << (Level + 3)>; + constexpr auto times = 8*sizeof(v); + uint64_t rv = 0; + for(auto n = times; n; ) { + n -= 8*sizeof(UI); + UI tmp = v >> n; + tmp = __builtin_popcountll(tmp); + rv |= uint64_t(tmp) << n; + } + return rv; +} + +namespace detail { + + template struct Selector_impl { + template + constexpr static uint64_t execute(uint64_t v) { + return popcount_logic(v); + } + }; + template<> struct Selector_impl { + template + constexpr static uint64_t execute(uint64_t v) { + return popcount_builtin(v); + } + }; + +} + +template +constexpr uint64_t popcount(uint64_t a) { + return detail::Selector_impl<2 < Level>::template execute(a); +} + +static_assert(0x210 == popcount<0>(0x320), ""); +static_assert(0x4321 == popcount<1>(0xF754), ""); +static_assert(0x50004 == popcount<3>(0x3E001122), ""); + + +template constexpr typename std::make_unsigned::type msb(T v) { + return 8*sizeof(T) - 1 - __builtin_clzll(v); +} + +template struct SWAR { + SWAR() = default; + constexpr explicit SWAR(T v): m_v(v) {} + + constexpr T value() const noexcept { return m_v; } + + constexpr SWAR operator|(SWAR o) { return SWAR(m_v | o.m_v); } + constexpr SWAR operator&(SWAR o) { return SWAR(m_v & o.m_v); } + constexpr SWAR operator^(SWAR o) { return SWAR(m_v ^ o.m_v); } + + constexpr bool operator==(SWAR o) { return m_v == o.m_v; } + + constexpr T at(int position) { + constexpr auto filter = (T(1) << Size) - 1; + return filter & (m_v >> (Size * position)); + } + + constexpr SWAR clear(int position) { + constexpr auto filter = (T(1) << Size) - 1; + auto invertedMask = filter << (Size * position); + auto mask = ~invertedMask; + return SWAR(m_v & mask); + } + + constexpr int top() { return msb(m_v) / Size; } + constexpr int fastIndex() { return __builtin_ctzll(m_v) / Size; } + + constexpr SWAR set(int index, int bit) { + return SWAR(m_v | (T(1) << (index * Size + bit))); + } + + constexpr operator bool() const { return m_v; } +protected: + T m_v; +}; + +template +struct BooleanSWAR: SWAR { + constexpr BooleanSWAR(T v): SWAR(v) {} + + constexpr BooleanSWAR clear(int bit) { + constexpr auto Bit = T(1) << (Size - 1); + return this->m_v ^ (Bit << (Size * bit)); } + constexpr int best() { return this->top(); } + + constexpr operator bool() const { return this->m_v; } +}; + +template +constexpr BooleanSWAR greaterEqualSWAR(SWAR v) { + static_assert(1 < Size, "Degenerated SWAR"); + static_assert(metaLogCeiling(N) < Size, "N is too big for this technique"); + constexpr auto msbPos = Size - 1; + constexpr auto msb = T(1) << msbPos; + constexpr auto msbMask = makeBitmask(msb); + constexpr auto subtraend = makeBitmask(N); + auto adjusted = v.value() | msbMask; + auto rv = adjusted - subtraend; + rv &= msbMask; + return rv; +} + +static_assert( + 0x80880008 == greaterEqualSWAR<3>(SWAR<4, uint32_t>(0x32451027)).value(), + "" +); + +}} diff --git a/pokerbotic/inc/ep/core/deposit.h b/pokerbotic/inc/ep/core/deposit.h new file mode 100644 index 00000000..736c2ae1 --- /dev/null +++ b/pokerbotic/inc/ep/core/deposit.h @@ -0,0 +1,13 @@ +#pragma once + +#include + +namespace ep { namespace core { + +inline uint64_t deposit(uint64_t preselected, uint64_t extra) { + auto depositSelector = ~preselected; + auto deposited = __builtin_ia32_pdep_di(extra, depositSelector); + return deposited; +} + +}} diff --git a/pokerbotic/inc/ep/core/metaLog.h b/pokerbotic/inc/ep/core/metaLog.h new file mode 100644 index 00000000..3bc42817 --- /dev/null +++ b/pokerbotic/inc/ep/core/metaLog.h @@ -0,0 +1,16 @@ +#pragma once + +#include + +namespace ep { namespace core { + +constexpr int metaLogFloor(uint64_t arg) { + return 63 - __builtin_clzll(arg); +} + +constexpr int metaLogCeiling(uint64_t arg) { + auto floorLog = metaLogFloor(arg); + return floorLog + ((arg ^ (1ull << floorLog)) ? 1 : 0); +} + +}} diff --git a/pokerbotic/inc/ep/metaBinomial.h b/pokerbotic/inc/ep/metaBinomial.h new file mode 100644 index 00000000..c8704db2 --- /dev/null +++ b/pokerbotic/inc/ep/metaBinomial.h @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace ep { + +/// K-permutations of N elements +template struct PartialPermutations { + constexpr static auto value = N * PartialPermutations::value; +}; +template struct PartialPermutations { + constexpr static auto value = 1; +}; + +/// Compilation time unit test +static_assert(12 == PartialPermutations<4, 2>::value, ""); + +/// Implementation of binomial coefficients +template struct Choose_impl { + constexpr static uint64_t value = 0; +}; +/// Uses the recurrence (n choose k) = (n - 1 choose k - 1) + (n - 1 choose k) +template struct Choose_impl { + constexpr static auto value = + Choose_impl::value + + Choose_impl::value; +}; +template struct Choose_impl { + constexpr static uint64_t value = 1; +}; + +/// Binomial coefficients +template +using Choose = + Choose_impl; + +/// \section ChooseCTUT Compile time unit tests of binomial coefficient +static_assert(2 == Choose<2, 1>::value, ""); +static_assert(6 == Choose<4, 2>::value, ""); +static_assert(286 == Choose<13, 3>::value, ""); +static_assert(1326 == Choose<52, 2>::value, ""); + +} diff --git a/pokerbotic/inc/ep/nextSubset.h b/pokerbotic/inc/ep/nextSubset.h new file mode 100644 index 00000000..4f7806e7 --- /dev/null +++ b/pokerbotic/inc/ep/nextSubset.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +namespace ep { + +// 1100 +// 1010 +// 0110 +// 1001 +// 0101 +// 0011 +constexpr uint64_t nextSubset(uint64_t v) { + constexpr auto allSet = ~uint64_t(0); + constexpr auto one = uint64_t(1); + + // find first available element to include + auto trailingZero = __builtin_ctzll(v); + auto withoutTrailingZero = (v >> trailingZero); + auto inverted = ~withoutTrailingZero; + auto onesCount = __builtin_ctzll(inverted); + auto firstAvailable = trailingZero + onesCount; + // leave onesCount - 1 in the least significant parts of the result + // and one bit set at firstAvailable, the rest is the same + auto leastOnes = ~(allSet << (onesCount - 1)); + leastOnes |= (one << firstAvailable); + auto mask = allSet << firstAvailable; + return (v & mask) | leastOnes; +} + +static_assert(2 == nextSubset(1), ""); +static_assert(8 == nextSubset(4), ""); +static_assert(9 == nextSubset(6), ""); +// 0001. 1010 == 0x58 +// 1000. 0110 == 0x61 +static_assert(0x61 == nextSubset(0x58), ""); + +} \ No newline at end of file diff --git a/pokerbotic/inc/ep/timing/benchmark.h b/pokerbotic/inc/ep/timing/benchmark.h new file mode 100644 index 00000000..08c92263 --- /dev/null +++ b/pokerbotic/inc/ep/timing/benchmark.h @@ -0,0 +1,28 @@ +#pragma once + +#include +#include + +namespace ep { namespace timing { + +template +inline long benchmark(Callable &&call, Args &&... arguments) { + auto now = std::chrono::high_resolution_clock::now(); + call(std::forward(arguments)...); + auto end = std::chrono::high_resolution_clock::now(); + auto diff = std::chrono::duration_cast(end - now); + return diff.count(); +} + +template +inline long countedBenchmark(Callable &&call, long count, Args &&... arguments) { + auto now = std::chrono::high_resolution_clock::now(); + while(count--) { + call(std::forward(arguments)...); + } + auto end = std::chrono::high_resolution_clock::now(); + auto diff = std::chrono::duration_cast(end - now); + return diff.count(); +} + +}} diff --git a/pokerbotic/inc/obsolete/Poker.h b/pokerbotic/inc/obsolete/Poker.h new file mode 100644 index 00000000..860773b4 --- /dev/null +++ b/pokerbotic/inc/obsolete/Poker.h @@ -0,0 +1,153 @@ +#pragma once + +#include "ep/Poker.h" + +namespace ep { + +struct CSet { + SWARSuit m_bySuit; + SWARRank m_byRank; + + constexpr CSet operator|(CSet o) { + return { m_bySuit | o.m_bySuit, m_byRank | m_byRank }; + } + + constexpr CSet operator&(CSet o) { + return { m_bySuit & o.m_bySuit, m_byRank & m_byRank }; + } + + constexpr CSet operator^(CSet o) { + return { m_bySuit ^ o.m_bySuit, m_byRank ^ m_byRank }; + } + + constexpr SuitCounts suitCounts() { return SuitCounts(m_bySuit); } + constexpr RankCounts rankCounts() { return RankCounts(m_byRank); } + + constexpr static unsigned rankSet(uint64_t orig) { + auto rv = orig; + for(auto ndx = NSuits; --ndx;) { + orig = orig >> SuitSize; + rv |= orig; + } + constexpr auto isolateMask = (uint64_t(1) << NRanks) - 1; + return rv & isolateMask; + } + + constexpr unsigned rankSet() { return rankSet(m_bySuit.value()); } + + constexpr CSet include(int rank, int suit) { + return { m_bySuit.set(suit, rank), m_byRank.set(rank, suit) }; + } +}; + +constexpr core::BooleanSWAR<16, uint64_t> flushes(SuitCounts ss) { + return ss.greaterEqual<5>(); +} + +// \note Again the Egyptian multiplication algorithm, +// including Ace-as-1 7 operations instead of the 10 in the naive method +inline unsigned straights(unsigned ranks) { + // xoptx is it better hasAce = (1 & rv) << (NRanks - 4) ? + // xoptx what about rv |= ... ? + constexpr auto acep = 1 << (NRanks - 1); + auto hasAce = (acep & ranks) ? (1 << 3) : 0; + // 0 1 2 3 4 5 6 7 8 9 0 1 2 + // ========================= + // 2 3 4 5 6 7 8 9 T J Q K A + // ------------------------- + // - 2 3 4 5 6 7 8 9 T J Q K sK = ranks << 1 + // ------------------------- + // 2 3 4 5 6 7 8 9 T J Q K A + // - 2 3 4 5 6 7 8 9 T J Q K rAK = ranks & sK + // ------------------------- + // - - - A 2 3 4 5 6 7 8 9 T rT = hasAce | (ranks << 4) + // ------------------------- + // - - - 3 4 5 6 7 8 9 T J Q + // - - - 2 3 4 5 6 7 8 9 T J rQJ = rAK << 2 + // ------------------------- + // 2 3 4 5 6 7 8 9 T J Q K A + // - 2 3 4 5 6 7 8 9 T J Q K + // - - - 3 4 5 6 7 8 9 T J Q + // - - - 2 3 4 5 6 7 8 9 T J rAKQJ = rAK & rQJ + // ------------------------- + auto sk = ranks << 1; + auto rak = ranks & sk; + auto rt = hasAce | (ranks << 4); + auto rqj = rak << 2; + auto rakqj = rak & rqj; + return rakqj & rt; +} + +inline HandRank handRank(CSet hand) { + auto rankCounts = hand.rankCounts(); + auto suitCounts = hand.suitCounts(); + auto ranks = hand.rankSet(); + auto toaks = rankCounts.greaterEqual<3>(); + HandRank rv; + RARE(toaks) { + auto foaks = rankCounts.greaterEqual<4>(); + RARE(foaks) { + static_assert(TotalHand < 8, "There can be four-of-a-kind and straight-flush"); + auto foak = foaks.top(); + auto without = rankCounts.clearAt(foak); + return { FOUR_OF_A_KIND, 1 << foak, 1 << without.best() }; + } + auto toak = toaks.top(); + auto without = rankCounts.clearAt(toak); + auto pairs = without.greaterEqual<2>(); + RARE(pairs) { + static_assert(TotalHand < 8, "There can be full-house and straight-flush"); + auto pair = pairs.top(); + return { FULL_HOUSE, (1 << toak), (1 << pair) }; + } + auto high = 1 << toak; + ranks ^= high; + auto next = core::msb(ranks); + auto lowTwo = 1 << next; + ranks ^= lowTwo; + lowTwo |= 1 << core::msb(ranks); + rv = HandRank(THREE_OF_A_KIND, high, lowTwo); + } + auto flushes = suitCounts.greaterEqual<5>(); + RARE(flushes) { + static_assert(TotalHand < 2*5, "No two flushes"); + auto suit = flushes.top(); + auto royal = hand.m_bySuit.at(suit); + auto isIt = straights(royal); + RARE(isIt) { + return { STRAIGHT_FLUSH, 1 << core::msb(isIt), 0 }; + } + for(auto count = suitCounts.counts().at(suit) - 5; count--; ) { + royal &= (royal - 1); // turns off lsb + } + return { FLUSH, int(royal), 0 }; + } + auto str = straights(ranks); + RARE(str) { return { STRAIGHT, 1 << core::msb(str), 0 }; } + RARE(rv.isSet()) { return rv; } + auto pairs = rankCounts.greaterEqual<2>(); + if(pairs) { + auto top = pairs.top(); + auto without = pairs.clear(top); + if(without) { + auto bottom = without.top(); + auto high = (1 << top) | (1 << bottom); + ranks ^= high; + return { TWO_PAIRS, high, 1 << core::msb(ranks) }; + } + auto high = 1 << top; + ranks ^= high; + auto next1 = 1 << core::msb(ranks); + ranks ^= next1; + auto next2 = 1 << core::msb(ranks); + ranks ^= next2; + auto next3 = 1 << core::msb(ranks); + return { PAIR, high, next1 | next2 | next3 }; + } + for(auto count = TotalHand - 5; count--; ) { + ranks &= ranks - 1; + } + return { HIGH_CARDS, int(ranks), 0 }; +} + +} diff --git a/pokerbotic/src/benchmarks.cpp b/pokerbotic/src/benchmarks.cpp new file mode 100644 index 00000000..2e9bed60 --- /dev/null +++ b/pokerbotic/src/benchmarks.cpp @@ -0,0 +1,326 @@ +#include "detail/ep/mechanisms.h" +#include "ep/nextSubset.h" +#include "ep/Compare.h" +#include "ep/timing/benchmark.h" + +#include + +struct ProgressiveGenerator { + uint64_t m_state = 1; + + constexpr ProgressiveGenerator() = default; + constexpr ProgressiveGenerator(int v): m_state(v) {} + + constexpr uint64_t next() { + auto rv = m_state; + m_state = ep::nextSubset(rv); + return rv; + } + + constexpr uint64_t operator()() { return next(); } +}; + +int isFOAK(uint64_t cards) { + ep::SWARRank ranks(cards); + ep::RankCounts counts(ranks); + auto foaks = ep::core::greaterEqualSWAR<4>(counts.counts()); + return foaks; +} + +ep::SWARRank straights(ep::RankCounts rc) { + auto present = ep::core::greaterEqualSWAR<1>(rc.counts()); + auto acep = + present.at(ep::abbreviations::rA) ? + uint64_t(0xF) << ep::abbreviations::rA : + uint64_t(0); + auto ranks = present.value(); + auto sk = ranks << 4; + auto rak = ranks & sk; + auto rt = acep | (ranks << 16); + auto rqj = rak << 8; + auto rakqj = rak & rqj; + return ep::SWARRank(rakqj & rt); +} + +uint64_t flush(uint64_t ranks) { + auto ranksMask = ep::core::makeBitmask<4, uint64_t>(1); + for(auto n = 4; n--; ) { + auto masked = ranks & ranksMask; + auto count = __builtin_popcountll(masked); + if(5 <= count) { return masked; } + ranksMask <<= 1; + } + return 0; +} + +/// \return true if the cards have a three of a kind and a pair +int isTOAKplusPAIRv1(uint64_t cards) { + ep::SWARRank ranks(cards); + ep::RankCounts counts(ranks); + auto toaks = ep::core::greaterEqualSWAR<3>(counts.counts()); + if(!toaks) { return false; } + auto toakIndex = toaks.top(); + auto cleared = counts.clearAt(toakIndex); + auto pairs = ep::core::greaterEqualSWAR<2>(cleared.counts()); + return pairs; +} + +int colorBlindRank(uint64_t cards) { + ep::SWARRank ranks(cards); + ep::RankCounts counts(ranks); + auto toaks = ep::core::greaterEqualSWAR<3>(counts.counts()); + if(toaks) { + auto foaks = ep::core::greaterEqualSWAR<4>(counts.counts()); + if(foaks) { + return ep::FOUR_OF_A_KIND; + } + auto bestToak = counts.best(); + auto cleared = counts.clearAt(bestToak); + auto pairs = cleared.greaterEqual<2>(); + if(pairs) { return ep::FULL_HOUSE; } + auto s = straights(counts); + if(s) { return ep::STRAIGHT; } + return ep::THREE_OF_A_KIND; + } + auto s = straights(counts); + if(s) { return ep::STRAIGHT; } + auto pairs = counts.greaterEqual<2>(); + if(pairs) { + auto bestPair = pairs.top(); + auto cleared = counts.clearAt(bestPair); + auto secondPair = cleared.greaterEqual<2>(); + if(secondPair) { return ep::TWO_PAIRS; } + return ep::PAIR; + } + return ep::HIGH_CARDS; +} + +inline int encode(int hand, int h, int l) { + return (hand << 28) | (h << 14) | l; +} + +//#include + +int rankFlushG(uint64_t cards) { + auto s = straights(ep::RankCounts(ep::SWARRank(cards))); + if(s) { + return encode(ep::STRAIGHT_FLUSH, 0, 1 << s.top()); + } + for(auto count = __builtin_popcountll(cards); __builtin_expect(5 < count--, 0); ) { + cards &= cards - 1; + } + auto high = ep::toRanks(cards); + return encode(ep::FLUSH, 0, high); +} + +int rankFlushCheat(uint64_t cards) { + if(straights(ep::RankCounts(ep::SWARRank(cards)))) { return ep::STRAIGHT_FLUSH; } + return ep::FLUSH; +} + +int whole(uint64_t cards) { + ep::SWARRank ranks(cards); + ep::RankCounts counts(ranks); + auto toaks = ep::core::greaterEqualSWAR<3>(counts.counts()); + if(toaks) { + auto foaks = ep::core::greaterEqualSWAR<4>(counts.counts()); + if(foaks) { + auto high = foaks.top(); + auto cleared = ranks.clear(high); + auto kicker = cleared.top(); + return encode(ep::FOUR_OF_A_KIND, high, 1 << kicker); + } + auto bestToak = counts.best(); + auto cleared = counts.clearAt(bestToak); + auto pairs = cleared.greaterEqual<2>(); + if(pairs) { + return encode(ep::FULL_HOUSE, 1 << bestToak, 1 << pairs.top()); + } + + auto flushCards = flush(cards); + if(flushCards) { return rankFlushG(flushCards); } + auto s = straights(counts); + if(s) { return encode(ep::STRAIGHT, 0, 1 << s.top()); } + auto best = cleared.best(); + auto low = 1 << best; + auto recleared = cleared.clearAt(best); + auto worst = recleared.best(); + low |= 1 << worst; + return encode(ep::THREE_OF_A_KIND, 0, low); + } + + auto flushCards = flush(cards); + if(flushCards) { return rankFlushG(flushCards); } + auto s = straights(counts); + if(s) { return encode(ep::STRAIGHT, 0, 1 << s.top()); } + + auto pairs = counts.greaterEqual<2>(); + if(pairs) { + auto bestPair = pairs.best(); + auto cleared = counts.clearAt(bestPair); + auto secondPairs = cleared.greaterEqual<2>(); + if(secondPairs) { + auto worsePair = secondPairs.best(); + auto recleared = cleared.clearAt(worsePair); + auto high = (1 << bestPair) | (1 << worsePair); + auto tricleared = recleared.clearAt(worsePair); + return encode(ep::TWO_PAIRS, high, 1 << tricleared.best()); + } + auto top = cleared.best(); + auto recleared = cleared.clearAt(top); + auto middle = recleared.best(); + auto tricleared = recleared.clearAt(middle); + auto bottom = tricleared.best(); + return encode(ep::PAIR, 1 << bestPair, (1 << top) | (1 << middle) | (1 << bottom)); + } + cards &= cards - 1; + cards &= cards - 1; + return encode(ep::HIGH_CARDS, 0, ep::toRanks(cards)); +} + +int wholeCheat(uint64_t cards) { + ep::SWARRank ranks(cards); + ep::RankCounts counts(ranks); + auto toaks = counts.greaterEqual<3>(); + if(toaks) { + auto foaks = counts.greaterEqual<4>(); + if(foaks) { + return ep::FOUR_OF_A_KIND; + } + auto bestToak = counts.best(); + auto cleared = counts.clearAt(bestToak); + auto pairs = cleared.greaterEqual<2>(); + if(pairs) { return ep::FULL_HOUSE; } + + auto flushCards = flush(cards); + if(flushCards) { return rankFlushCheat(flushCards); } + auto s = straights(counts); + if(s) { return ep::STRAIGHT; } + + return ep::THREE_OF_A_KIND; + } + + auto flushCards = flush(cards); + if(flushCards) { return rankFlushCheat(flushCards); } + auto s = straights(counts); + if(s) { return ep::STRAIGHT; } + + auto pairs = counts.greaterEqual<2>(); + if(pairs) { + auto bestPair = pairs.best(); + auto cleared = counts.clearAt(bestPair); + auto secondPair = cleared.greaterEqual<2>(); + if(secondPair) { return ep::TWO_PAIRS; } + return ep::PAIR; + } + return ep::HIGH_CARDS; +} + +uint64_t sideEffect = 0; + +template +long driver(ProgressiveGenerator gen, int count, Callable fun) { + auto nested = [&]() { + for(auto c = count; c--; ) { + auto cards = gen.next(); + sideEffect ^= cards; + fun(cards); + } + }; + return ep::timing::benchmark(nested); +}; + + +int (*operation)(uint64_t) = isTOAKplusPAIRv1; + +#include + +#include + +int +#ifdef BENCHMARKS +main +#else +benchmarks +#endif +(int argc, const char *argv[]) { + std::cout << std::setprecision(4); + ProgressiveGenerator gen(0x7F); + auto count = 133784560; + auto toakPlusPairs = 0; + auto xorCheck = -1; + auto empty = [](uint64_t) {}; + auto v1fun = [&](uint64_t cards) { + auto result = operation(cards); + if(ep::THREE_OF_A_KIND == result) { ++toakPlusPairs; } + xorCheck ^= result; + /*auto st = straights(ep::SWARRank(cards)); + if(0x11111 == (0x11111 & cards)) { + std::cout << std::endl << ep::SWARRank(cards) << ' ' << st << std::endl; + } + if(st) { + static auto strs = 10; + if(0 < strs--) { + std::cout << std::endl << ep::SWARRank(cards); + } + }*/ + }; + auto indicators = [=](long time, long base) { + auto divisor = (time == base ? base : time - base); + std::cout << time << ' ' << 1.0*divisor/base << ' ' << count/divisor << + ' ' << 1000.0*divisor/count; + }; + + auto base = driver(gen, count, empty); + indicators(base, base); + std::cout.flush(); + + if(1 == argc) { + auto v1 = driver(gen, count, v1fun); + std::cout << " |1 "; + indicators(v1, base); + std::cout.flush(); + operation = [](uint64_t c) { return colorBlindRank(c); }; + auto v2 = driver(gen, count, v1fun); + std::cout << " |cb "; + indicators(v2, base); + std::cout.flush(); + operation = [](uint64_t c) -> int { return flush(c); }; + auto v3 = driver(gen, count, v1fun); + std::cout << " |f "; + indicators(v3, base); + std::cout.flush(); + operation = [](uint64_t c) -> int { return whole(c); }; + auto v4 = driver(gen, count, v1fun); + std::cout << " |w "; + indicators(v4, base); + std::cout.flush(); + } + + operation = [](uint64_t c) -> int { + //ep::SWARRank ranks(c); ep::SWARSuit ss = ep::convert(ranks); + //ep::CSet cs = { ss, ranks }; + return ep::handRank(c).code; + }; + auto v5 = driver(gen, count, v1fun); + std::cout << " |h "; + indicators(v5, base); + std::cout.flush(); + + if(1 == argc) { + operation = [](uint64_t c) -> int { + /*ep::SWARRank ranks(c); ep::SWARSuit ss = ep::convert(ranks); + ep::CSet cs = { ss, ranks }; + return ep::naive::handRank(cs);*/ + return wholeCheat(c); + }; + auto v6 = driver(gen, count, v1fun); + std::cout << " |c "; + indicators(v6, base); + } + + std::cout << ' ' << toakPlusPairs << ' ' << std::hex << sideEffect << ' ' << + xorCheck << std::endl; + return 0; +} + diff --git a/pokerbotic/src/comparisonBenchmark.cpp b/pokerbotic/src/comparisonBenchmark.cpp new file mode 100644 index 00000000..2f9eb8f3 --- /dev/null +++ b/pokerbotic/src/comparisonBenchmark.cpp @@ -0,0 +1,60 @@ +#include "ep/Compare.h" +#include "ep/Floyd.h" +#include "ep/nextSubset.h" +#include "ep/timing/benchmark.h" + +#include "ep/Cases.h" + +#include + +template struct Comm { + template + static int execute(C *c, ep::CardSet community, long &count, int &rv, Start start) { + constexpr auto stop = uint64_t(1) << 52; + for(auto p1 = uint64_t(3);;) { + auto player1 = ep::core::deposit(community.cards(), p1); + auto commP1 = community | player1; + for(auto p2 = uint64_t(3);;) { + auto player2 = ep::core::deposit(commP1.cards(), p2); + rv ^= ep::compareByCommunity(c, community, player1, player2); + //rv ^= ep::compare(community, player1, player2); + //std::cout << std::hex << player2 << ' ' << player1 << ' ' << community << std::endl; + ++count; + if(!(count % 100'000'000)) { + auto now = std::chrono::high_resolution_clock::now(); + auto elapsed = now - start; + auto micros = + std::chrono::duration_cast( + elapsed + ).count(); + std::cout << count << ' ' << micros/count << ' ' << + 1000*count/micros << ' ' << micros << ' ' << rv << std::endl; + } + p2 = ep::nextSubset(p2); + if(stop <= p2) { break; } + } + p1 = ep::nextSubset(p1); + if(stop <= p1) { break; } + } + return rv; + } +}; + +int +#ifdef HAND_COMPARISON +main +#else +handComparison +#endif +(int argc, const char *argv[]) { + auto rv = 0; + long count = 0; + constexpr auto stop = uint64_t(1) << 52; + auto start = std::chrono::high_resolution_clock::now(); + for(auto community = uint64_t(0x1F) << 0;;) { + ep::classifyCommunity(community, count, rv, start); + community = ep::nextSubset(community); + if(stop <= community) { break; } + } + return rv; +} \ No newline at end of file diff --git a/pokerbotic/src/handRankBenchmark.cpp b/pokerbotic/src/handRankBenchmark.cpp new file mode 100644 index 00000000..34d0479d --- /dev/null +++ b/pokerbotic/src/handRankBenchmark.cpp @@ -0,0 +1,93 @@ +#include "ep/timing/benchmark.h" +#include "obsolete/Poker.h" +#include "ep/Poker_io.h" +#include "ep/Floyd.h" +#include "ep/nextSubset.h" + + +#include + +template struct ProgressiveGen { + uint64_t m_state = ~(~uint64_t(0) << K); + + uint64_t generate() { + auto rv = m_state; + m_state = ep::nextSubset(m_state); + /*if(K != __builtin_popcountll(rv) || rv <= m_state) { + std::cerr << "Error, " << std::hex << m_state << ' ' << rv << std::endl; + throw; + }*/ + return rv; + } +}; + +int +#ifdef TIME_HAND_RANK +main +#else +time_hand_rank +#endif +(int argc, char **argv) { + //std::random_device device; + //std::mt19937 generator(device()); + //std::minstd_rand generator(device()); + ProgressiveGen<7> pg; + auto count = 1 << 25; + long base; + + if(1 < argc) { + count = std::stol(argv[1]); + } + + auto fraction = [&](const char *n, int t) { + std::cout << n << ' ' << t/1000 << ' ' << 1.0*(t - base)/base << ' '; + if(t - base) { + std::cout << count/(t - base); + } else { + std::cout << count/base; + } + std::cout << " | "; + }; + + uint64_t sideEffect1 = 0; + unsigned sideEffect2 = 0; + + auto justHandGeneration = [&]() { + //auto cards = ep::floydSample(generator); + auto cards = pg.generate(); + sideEffect1 ^= cards; + }; + + auto straightFlushes = 0; + auto handGenerationAndRanking = [&]() { + //auto cards = ep::floydSample(generator); + auto cards = pg.generate(); + sideEffect1 ^= cards; + auto ranks = ep::SWARRank(cards); + using namespace ep; + ep::CSet hand = { ep::convert(ranks), ranks }; + auto hr = ep::handRank(hand); + sideEffect2 ^= hr.code; + }; + + auto suitGeneration = [&]() { + //auto cards = ep::floydSample(generator); + auto cards = pg.generate(); + auto ranks = ep::SWARRank(cards); + sideEffect1 ^= ep::convert(ranks).value(); + }; + + std::cout << "Elements " << count << ':'; + base = ep::timing::countedBenchmark(justHandGeneration, count); + fraction("Subset generation", base); + + pg = ProgressiveGen<7>(); + auto ranking = ep::timing::countedBenchmark(handGenerationAndRanking, count); + auto conversion = ep::timing::countedBenchmark(suitGeneration, count); + + fraction("Ranked", ranking); + fraction("Conversion to suit", conversion); + std::cout << std::hex << sideEffect1 << ' ' << sideEffect2 << std::endl; + return 0; +} + diff --git a/pokerbotic/src/kazone.cpp b/pokerbotic/src/kazone.cpp new file mode 100644 index 00000000..e69de29b diff --git a/pokerbotic/src/oldMain.cpp b/pokerbotic/src/oldMain.cpp new file mode 100644 index 00000000..0a3df572 --- /dev/null +++ b/pokerbotic/src/oldMain.cpp @@ -0,0 +1,84 @@ +#include "obsolete/Poker.h" +#include "ep/Poker_io.h" +#include "ep/CascadeComparisons.h" + +#include +#include +#include +#include "ep/Floyd.h" + +template +long benchmark(Callable &&call, Args &&... arguments) { + auto now = std::chrono::high_resolution_clock::now(); + call(std::forward(arguments)...); + auto end = std::chrono::high_resolution_clock::now(); + auto diff = std::chrono::duration_cast(end - now); + return diff.count(); +} + +uint64_t (*sampleGen)(std::mt19937 &generator) = + [](std::mt19937 &generator) { return ep::floydSample(generator); }; +unsigned (*checkStraight)(uint64_t) = [](uint64_t) -> unsigned { return 0; }; +unsigned straights; + +void experiment(unsigned count, std::mt19937 &generator) { + while(count--) { + auto cards =// ep::floydSample(generator); + sampleGen(generator); + if(checkStraight(cards)) { ++straights; } + } +}; + +auto names = "AKQJT98765432"; + +int maino(int argc, char** argv) { + std::random_device device; + std::mt19937 generator(device()); + auto count = 1 << 24; + straights = 0; + auto reallyCheck = + [](uint64_t cards) -> unsigned { + return ep::straights(ep::CSet::rankSet(cards)); + }; + auto toakCheck = [](uint64_t cards) { + ep::core::SWAR<4, uint64_t> nibbles(cards); + ep::RankCounts counts(nibbles); + auto toaks = counts.greaterEqual<3>(); + if(toaks) { + static auto count = 5; + if(0 < --count) { + std::cout << nibbles << ' ' << names[toaks.best()] << std::endl; + } + } + return unsigned(bool(toaks)); + }; + auto nullgen = [](std::mt19937 &generator) -> uint64_t { return 0xB02; }; + //sampleGen = nullgen; + auto empty = benchmark(experiment, count, generator); + //checkStraight = reallyCheck; + checkStraight = toakCheck; + auto nonEmpty = benchmark(experiment, count, generator); + auto normal = straights; + auto diff = 1.0*(nonEmpty - empty); + + /*checkStraight = [](uint64_t cards) { + return ep::straightFrontCheck(ep::CSet::rankSet(cards)); + }; + straights = 0; + auto frontCheck = benchmark(experiment, count, generator); + auto fronted = straights; + straights = 0; + checkStraight = [](uint64_t cards) { + return ep::uncheckStraight(ep::CSet::rankSet(cards)); + }; + auto unchecked = benchmark(experiment, count, generator); + auto uncheckCount = straights;*/ + std::cout << empty << std::endl; + std::cout << normal << ' ' << nonEmpty << ' ' << diff/empty << ' ' << count/diff << std::endl; + /*auto diff2 = 1.0*(frontCheck - empty); + std::cout << fronted << ' ' << frontCheck << ' ' << diff2/diff << std::endl; + auto diff3 = 1.0*(unchecked - empty); + std::cout << uncheckCount << ' ' << unchecked << ' ' << diff3/diff << std::endl;*/ + return 0; +} + diff --git a/pokerbotic/src/popcountBenchmark.cpp b/pokerbotic/src/popcountBenchmark.cpp new file mode 100644 index 00000000..8dffbf1d --- /dev/null +++ b/pokerbotic/src/popcountBenchmark.cpp @@ -0,0 +1,136 @@ +#include "ep/PokerTypes.h" + +using ul = uint64_t; + +ul popcountsByFourLogic(ul input) { + return ep::core::popcount<1>(input); +} + +uint16_t popcountLookupBy4Nibbles[1 << 16]; +uint16_t popcountLookupBy1Short[1 << 16]; + +union bits64 { + ul all64; + uint16_t by16[4]; + uint8_t by8[8]; + + + bits64() = default; + bits64(ul v): all64(v) {} +}; + +ul popcountsByLookupBy4(bits64 arg) { + arg.by16[0] = popcountLookupBy4Nibbles[arg.by16[0]]; + arg.by16[1] = popcountLookupBy4Nibbles[arg.by16[1]]; + arg.by16[2] = popcountLookupBy4Nibbles[arg.by16[2]]; + arg.by16[3] = popcountLookupBy4Nibbles[arg.by16[3]]; + return arg.all64; +} + +ul popcountsBy16Logic(ul input) { + return ep::core::popcount<3>(input); +} + +ul popcountsByAssembler16(bits64 arg) { + arg.by8[0] = __builtin_popcount(arg.by8[0]); + arg.by8[1] = __builtin_popcount(arg.by8[1]); + arg.by8[2] = __builtin_popcount(arg.by8[2]); + arg.by8[3] = __builtin_popcount(arg.by8[3]); + arg.by8[4] = __builtin_popcount(arg.by8[4]); + arg.by8[5] = __builtin_popcount(arg.by8[5]); + arg.by8[6] = __builtin_popcount(arg.by8[6]); + arg.by8[7] = __builtin_popcount(arg.by8[7]); + return arg.all64; +} + +unsigned table24[1 << 24]; + +ul by24(ul v) { + ul mask = (1 << 24) - 1; + ul rv = table24[v & mask]; + v >>= 24; + rv |= (uint64_t(table24[v & mask]) << 24); + v >>= 24; + rv |= (uint64_t(table24[v & mask]) << 48); + return rv; +} + +ul popcountsByLookupBy16(bits64 arg) { + constexpr auto mask = ep::core::makeBitmask<16>((uint64_t(1) << 13) - 1); + arg.all64 &= mask; + arg.by16[0] = popcountLookupBy1Short[arg.by16[0]]; + arg.by16[1] = popcountLookupBy1Short[arg.by16[1]]; + arg.by16[2] = popcountLookupBy1Short[arg.by16[2]]; + arg.by16[3] = popcountLookupBy1Short[arg.by16[3]]; + return arg.all64; +} + +#include +#include + +ul result = 0; + +uint64_t LCG(uint64_t v) { + return 134775813 * v + 1; +} + +uint64_t seed = 134775811; + +template +long benchmark(Callable &&call, int count, Args &&... arguments) { + auto now = std::chrono::high_resolution_clock::now(); + for(auto n = count; n--; ) { + seed ^= call(std::forward(arguments)...); + seed = LCG(seed); + } + auto end = std::chrono::high_resolution_clock::now(); + auto diff = std::chrono::duration_cast(end - now); + return diff.count(); +} + +#include + +int +#ifdef BENCHMARK +main +#else +mainpc +#endif +(int argc, const char *argv[]) { + constexpr auto count = 1 << 27; + seed = std::chrono::high_resolution_clock::now().time_since_epoch().count(); + auto doLCG = []() { + return 0; + }; + auto justLCG = benchmark(doLCG, count); + auto doLookups = []() { + return popcountsByLookupBy4(seed); + }; + auto lookups = benchmark(doLookups, count); + auto doLogic = []() { + return popcountsBy16Logic(seed); + }; + auto logic = benchmark(doLogic, count); + auto regularPattern = []() { + auto static n = 0ull; + return popcountsByLookupBy4(n++); + }; + auto base = 1.0 * justLCG; + auto fraction = [&](const char *n, int t) { + std::cout << n << ' ' << t/1000 << ' ' << (t - base)/base << ' ' << + count/(t - base) << " | "; + }; + fraction("LCG ", justLCG); + fraction("Lookups", lookups); + fraction("Assembler", logic); + fraction("Regular", benchmark(regularPattern, count)); + fraction("Lookup/truncated", benchmark( + []() { return popcountsByLookupBy16(seed); }, + count + )); + fraction("Logic4", benchmark([]() { return ep::core::popcount<1>(seed); }, count)); + fraction("Logic-16", benchmark([]() { return ep::core::popcount_logic<3>(seed); }, count)); + std::cout << std::hex << seed << std::endl; + return 0; +} + diff --git a/pokerbotic/test/CMakeLists.txt b/pokerbotic/test/CMakeLists.txt new file mode 100644 index 00000000..a5e1416b --- /dev/null +++ b/pokerbotic/test/CMakeLists.txt @@ -0,0 +1,26 @@ +cmake_minimum_required (VERSION 3.8) + +set(CMAKE_BUILD_TYPE Debug) + + +project(ZooPokerbotic VERSION 1.0 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) + +add_subdirectory( + ../../test/third_party EXCLUDE_FROM_ALL + ${CMAKE_CURRENT_BINARY_DIR}/3rdParty +) + +MESSAGE(STATUS "TTP ${TEST_THIRD_PARTY_INCLUDE_PATH} ") + +include_directories( + "${PROJECT_BINARY_DIR}" + ./inc # inc inside pokerbotic/test + ../../inc # the zoo library headers + ${TEST_THIRD_PARTY_INCLUDE_PATH} +) + +add_executable( + pokerbotic-tests catch2_main.cpp tests.cpp +) diff --git a/pokerbotic/test/catch2_main.cpp b/pokerbotic/test/catch2_main.cpp new file mode 100644 index 00000000..4ed06df1 --- /dev/null +++ b/pokerbotic/test/catch2_main.cpp @@ -0,0 +1,2 @@ +#define CATCH_CONFIG_MAIN +#include diff --git a/pokerbotic/test/pokerbotic.cpp b/pokerbotic/test/pokerbotic.cpp new file mode 100644 index 00000000..e9e6a682 --- /dev/null +++ b/pokerbotic/test/pokerbotic.cpp @@ -0,0 +1,182 @@ +#include "ep/Poker_io.h" +#include "ep/Poker.h" +#include "ep/Floyd.h" +#include "ep/Cases.h" + +#ifdef TESTS + #define CATCH_CONFIG_MAIN +#else + #define CATCH_CONFIG_RUNNER +#endif +#include "catch.hpp" + +TEST_CASE("Classification generation", "[bit]") { + SECTION("SuitedPocket") { + using namespace ep; + using namespace abbreviations; + SuitedPocket sp; + REQUIRE(0x11 == sp.next()); + REQUIRE(bool(sp)); + REQUIRE(0x101 == sp.next()); + REQUIRE(0x110 == sp.next()); + sp.m_current = 3 << 11; + REQUIRE((sA | sK) == sp.next()); + REQUIRE(!sp); + } + SECTION("PocketPair") { + using namespace ep; + using namespace abbreviations; + PocketPair pp; + REQUIRE((s2 | h2) == pp.next()); + REQUIRE((s3 | h3) == pp.next()); + pp.m_current = sA; + REQUIRE(bool(pp)); + pp.next(); + REQUIRE(!pp); + } + SECTION("SuitedPocket") { + using namespace ep; + using namespace abbreviations; + UnsuitedPocket sp; + REQUIRE((s3 | h2) == sp.next()); + REQUIRE(bool(sp)); + REQUIRE((s4 | h2) == sp.next()); + REQUIRE((s4 | h3) == sp.next()); + sp.m_current = 3 << 11; + REQUIRE((sA | hK) == sp.next()); + REQUIRE(!sp); + } +} + +constexpr uint64_t royalFlush() { + using namespace ep::abbreviations; + return hA | hK | hQ | hJ | hT; +} + +TEST_CASE("Bit operations", "[bit]") { + auto sevenNibbles = ep::core::makeBitmask<4, uint64_t>(7); + auto octals = 01234567ull; + auto deposited = __builtin_ia32_pdep_di(octals, sevenNibbles); + REQUIRE(0x1234567 == deposited); + + auto alreadySet = 0xC63; // 1 1 0 0 .0 1 1 0 .0 0 1 1 + auto interleave = 0x2A; // 1a0b.1c0d1e0f + auto expected = 0xEEB; // 1 1 1a0b.1c1 1 0d.1e0f1 1 + auto byEp = ep::core::deposit(alreadySet, interleave); + byEp |= alreadySet; + REQUIRE(expected == byEp); +} + +TEST_CASE("Poker operations", "[basic]") { + using namespace ep::abbreviations; + SECTION("HandRank equality") { + ep::HandRank hr1(ep::HIGH_CARDS, 1, 0); + ep::HandRank hr2(ep::HIGH_CARDS, 2, 0); + auto equal = hr1 == hr2; + REQUIRE(!equal); + } + SECTION("Flush") { + auto noFlush = sQ; + REQUIRE(0 == ep::flush(noFlush)); + + auto flushOfDiamonds = dK | dT | d7 | d6 | d2; + auto sevenCardFlush5x2 = noFlush | sA | flushOfDiamonds; + auto flush = ep::flush(sevenCardFlush5x2); + auto flushOfClubs = flushOfDiamonds >> (sD - sS); + REQUIRE(flush == flushOfClubs); + + auto heartsRoyalFlush = royalFlush(); + auto hrf = ep::SWARRank(heartsRoyalFlush); + auto hrcounted = ep::RankCounts(hrf); + auto present = hrcounted.greaterEqual<1>(); + auto straightsResult = ep::straights_rankRepresentation(present); + REQUIRE(rA == straightsResult.top()); + } + + SECTION("Straight") { + auto straightTo5 = (royalFlush() ^ hT) | d2 | c3 | d4 | s5; + auto sr5 = ep::SWARRank(straightTo5); + auto p5 = ep::RankCounts(sr5); + auto p5Present = p5.greaterEqual<1>(); + auto straightTo5Result = ep::straights_rankRepresentation(p5Present); + REQUIRE(r5 == straightTo5Result.top()); + + auto no = straightTo5 ^ s5; + auto noRanks = ep::RankCounts(ep::SWARRank(no)).greaterEqual<1>(); + auto notStraight = ep::straights_rankRepresentation(noRanks); + REQUIRE(!notStraight); + } +} + +TEST_CASE("Hands", "[basic]") { + using namespace ep::abbreviations; + + SECTION("Straight Flush") { + auto straightFlushCards = h4 | h5 | h6 | h7 | h8 | c8 | d8; + auto result = ep::handRank(straightFlushCards); + REQUIRE(ep::STRAIGHT_FLUSH == result.hand); + REQUIRE(0 == result.high); + REQUIRE((1 << r8) == result.low); + } + + SECTION("Four of a kind") { + auto foakCards = s5 | h5 | d5 | c5 | sK | cK | hK; + auto result = ep::handRank(foakCards); + REQUIRE(ep::FOUR_OF_A_KIND == result.hand); + REQUIRE((1 << r5) == result.high); + REQUIRE((1 << rK) == result.low); + } + + SECTION("Full House") { + auto threysOverKings = s3 | d3 | h3 | dK | cK | sA | dQ; + auto r = ep::handRank(threysOverKings); + REQUIRE(ep::HandRank(ep::FULL_HOUSE, 0 | r3, 0 | rK) == r); + auto doubleToak = s3 | d3 | h3 | hA | dA | cA | dK; + auto acesOverThreys = ep::handRank(doubleToak); + REQUIRE(ep::HandRank(ep::FULL_HOUSE, 0 | rA, 0 | r3) == acesOverThreys); + } + + SECTION("Flush") { + auto baseFlush = (royalFlush() ^ hA) | h8; + auto extraKings = sK | cK; + auto wholeHand = baseFlush | extraKings; + auto r = ep::handRank(wholeHand); + auto kqjt8 = ep::HandRank(ep::FLUSH, 0, rK | rQ | rJ | rT | r8); + REQUIRE(kqjt8 == r); + + auto withoutTOAK = baseFlush | h5 | h4; + auto without = ep::handRank(withoutTOAK); + REQUIRE(kqjt8 == without); + } + + SECTION("Straight") { + auto fromAce = sA | h2 | d3 | c4 | s5 | d5 | c5; + auto r = ep::handRank(fromAce); + REQUIRE(ep::HandRank(ep::STRAIGHT, 0, 0 | r5) == r); + } + + SECTION("Three of a kind") { + auto threysOverAceKing = (royalFlush() ^ hT) | s3 | d3 | c3; + auto r = ep::handRank(threysOverAceKing); + REQUIRE(ep::HandRank(ep::THREE_OF_A_KIND, 0 | r3, rA | rK) == r); + } + + SECTION("Two Pairs") { + auto kingsOverTensAceHigh = (royalFlush() ^ hJ) | cK | cT | c9; + auto r = ep::handRank(kingsOverTensAceHigh); + REQUIRE(ep::HandRank(ep::TWO_PAIRS, rK | rT, 0 | rA) == r); + } + + SECTION("Pairs") { + auto hooks = (royalFlush() ^ hA) | cJ | s2 | s3; + auto r = ep::handRank(hooks); + ep::HandRank comparator(ep::PAIR, 0 | rJ, rK | rQ | rT); + REQUIRE(comparator == r); + } + + SECTION("High Cards") { + auto nothing = (royalFlush() ^ hA) | c8 | c7 | c6; + auto r = ep::handRank(nothing); + REQUIRE(ep::HandRank(ep::HIGH_CARDS, 0, rK | rQ | rJ | rT | r8) == r); + } +} diff --git a/pokerbotic/test/tests.cpp b/pokerbotic/test/tests.cpp new file mode 100644 index 00000000..cde433a6 --- /dev/null +++ b/pokerbotic/test/tests.cpp @@ -0,0 +1,5 @@ +#include + +TEST_CASE("Pokerbotic main test") { + REQUIRE(true); +} diff --git a/test/third_party/CMakeLists.txt b/test/third_party/CMakeLists.txt index d16f86f6..ed8bfe81 100644 --- a/test/third_party/CMakeLists.txt +++ b/test/third_party/CMakeLists.txt @@ -1,19 +1,21 @@ # CMake build : third party -#configure directories -set(THIRD_PARTY_MODULE_PATH "${PROJECT_SOURCE_DIR}/third_party") +message(STATUS "cmake-csd ${CMAKE_CURRENT_SOURCE_DIR}") -# catch +# Configure directories +set(THIRD_PARTY_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}") -#configure directories -set (CATCH_MODULE_PATH "${THIRD_PARTY_MODULE_PATH}/Catch2") -set (CATCH_INCLUDE_PATH "${CATCH_MODULE_PATH}/single_include") +# Catch -#include custom cmake function +# Configure directories +set(CATCH_MODULE_PATH "${THIRD_PARTY_MODULE_PATH}/Catch2") +set(CATCH_INCLUDE_PATH "${CATCH_MODULE_PATH}/single_include") + +# Include custom cmake function include("${CATCH_MODULE_PATH}/contrib/ParseAndAddCatchTests.cmake") -#set variables for tests +# Set variables for tests set(TEST_THIRD_PARTY_INCLUDE_PATH ${CATCH_INCLUDE_PATH}) -#export vars +# Export vars set(TEST_THIRD_PARTY_INCLUDE_PATH ${TEST_THIRD_PARTY_INCLUDE_PATH} PARENT_SCOPE) diff --git a/third_party/CMakeLists.txt b/third_party/CMakeLists.txt deleted file mode 100644 index bfd5df4b..00000000 --- a/third_party/CMakeLists.txt +++ /dev/null @@ -1,13 +0,0 @@ -# CMake build : third party - -#configure directories -set (THIRD_PARTY_MODULE_PATH "${PROJECT_SOURCE_DIR}/third_party") - -# catch2 - -#configure directories -set (CATCH2_MODULE_PATH "${THIRD_PARTY_MODULE_PATH}/Catch2") -set (CATCH2_INCLUDE_PATH "${CATCH2_MODULE_PATH}/single_include") - -#include custom cmake function -include ( "${CATCH2_MODULE_PATH}/contrib/ParseAndAddCatchTests.cmake") diff --git a/vscode/zoo.code-workspace b/vscode/zoo.code-workspace new file mode 100644 index 00000000..f4afd0d3 --- /dev/null +++ b/vscode/zoo.code-workspace @@ -0,0 +1,11 @@ +{ + "folders": [ + { + "path": ".." + }, + { + "path": "../pokerbotic" + } + ], + "settings": {} +}