Skip to content

Commit

Permalink
Code dump. TODO: Cleanup & documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
artificial-lshamis committed May 4, 2018
1 parent 8992f54 commit 0974a84
Show file tree
Hide file tree
Showing 27 changed files with 6,418 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bazel-*
tables/*.phe
6 changes: 6 additions & 0 deletions BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package(default_visibility = ["//visibility:public"])

cc_library(
name = "poker_hand_eval",
hdrs = ["poker_hand_eval.h"],
)
18 changes: 18 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
new_git_repository(
name = "git_sparsehash",
build_file_content = """
cc_library(
name = "sparsehash",
includes = ["."],
hdrs = glob(["sparsehash/**/*"]),
visibility = ["//visibility:public"],
)""",
commit = "47a55825ca3b35eab1ca22b7ab82b9544e32a9af",
remote = "https://github.com/sparsehash/sparsehash-c11.git",
)

git_repository(
name = "com_github_google_benchmark",
remote = "https://github.com/google/benchmark.git",
tag = "v1.4.0",
)
10 changes: 10 additions & 0 deletions benchmarks/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
cc_binary(
name = "benchmarks",
srcs = ["benchmarks.cc"],
data = ["//tables:phe"],
linkopts = ["-lpthread"], # "@com_github_google_benchmark//:benchmark" should link to pthread :/
deps = [
"//:poker_hand_eval",
"@com_github_google_benchmark//:benchmark",
],
)
96 changes: 96 additions & 0 deletions benchmarks/benchmarks.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#include <algorithm>
#include <array>
#include <iostream>
#include <utility>
#include <vector>

#include <benchmark/benchmark.h>

#include "poker_hand_eval.h"

template <size_t HandSize>
using HandType = std::array<uint32_t, HandSize>;
using DeckType = std::array<uint32_t, 52>;

template <size_t HandSize>
HandType<HandSize> random_hand() {
static DeckType deck = []() {
DeckType d;
for (size_t i = 0; i < 52; i++) { d[i] = i; }
return d;
}();
static size_t deal_index = 52;
if (deal_index + HandSize >= 52) {
std::random_shuffle(std::begin(deck), std::end(deck));
deal_index = 0;
}

HandType<HandSize> hand;
std::copy_n(std::next(std::begin(deck), deal_index), HandSize, std::begin(hand));
deal_index += HandSize;
return hand;
}

void BM_Control5(benchmark::State& state) {
for (auto _ : state) {
benchmark::DoNotOptimize(random_hand<5>());
}
}
BENCHMARK(BM_Control5);

void BM_phe_dfs5(benchmark::State& state) {
PokerHandEval<5> phe("tables/dfs5.phe");
for (auto _ : state) {
benchmark::DoNotOptimize(phe.eval(random_hand<5>()));
}
}
BENCHMARK(BM_phe_dfs5);

void BM_phe_bfs5(benchmark::State& state) {
PokerHandEval<5> phe("tables/bfs5.phe");
for (auto _ : state) {
benchmark::DoNotOptimize(phe.eval(random_hand<5>()));
}
}
BENCHMARK(BM_phe_bfs5);

void BM_phe_veb5(benchmark::State& state) {
PokerHandEval<5> phe("tables/veb5.phe");
for (auto _ : state) {
benchmark::DoNotOptimize(phe.eval(random_hand<5>()));
}
}
BENCHMARK(BM_phe_veb5);

void BM_Control7(benchmark::State& state) {
for (auto _ : state) {
benchmark::DoNotOptimize(random_hand<7>());
}
}
BENCHMARK(BM_Control7);

void BM_phe_dfs7(benchmark::State& state) {
PokerHandEval<7> phe("tables/dfs7.phe");
for (auto _ : state) {
benchmark::DoNotOptimize(phe.eval(random_hand<7>()));
}
}
BENCHMARK(BM_phe_dfs7);

void BM_phe_bfs7(benchmark::State& state) {
PokerHandEval<7> phe("tables/bfs7.phe");
for (auto _ : state) {
benchmark::DoNotOptimize(phe.eval(random_hand<7>()));
}
}
BENCHMARK(BM_phe_bfs7);

void BM_phe_veb7(benchmark::State& state) {
PokerHandEval<7> phe("tables/veb7.phe");
for (auto _ : state) {
benchmark::DoNotOptimize(phe.eval(random_hand<7>()));
}
}
BENCHMARK(BM_phe_veb7);

BENCHMARK_MAIN();
52 changes: 52 additions & 0 deletions generate_tables/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package(default_visibility = ["//visibility:private"])

cc_binary(
name = "generate_tables",
srcs = ["generate_tables.cc"],
deps = [
":memory_layout",
":phe",
"//third_party/senzee",
],
)

cc_library(
name = "fsm",
hdrs = [
"fsm.h",
"fsm.inl",
],
deps = [
":common",
"@git_sparsehash//:sparsehash",
],
)

cc_library(
name = "phe",
hdrs = [
"phe.h",
"phe.inl",
],
deps = [
":common",
":fsm",
":memory_layout",
"//:poker_hand_eval",
],
)

cc_library(
name = "memory_layout",
hdrs = [
"memory_layout.h",
"memory_layout.inl",
],
deps = [":fsm"],
)

cc_library(
name = "common",
srcs = ["common.cc"],
hdrs = ["common.h"],
)
66 changes: 66 additions & 0 deletions generate_tables/common.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#include "generate_tables/common.h"

#include <sstream>

namespace poker_eval {

Hand Hand::decode(EncodedHand encoded) {
static_assert(sizeof(Hand) == sizeof(EncodedHand),
"EncodedHand cannot be reinterpreted as Hand.");
return *reinterpret_cast<Hand*>(&encoded);
}

EncodedHand Hand::encode() const {
static_assert(sizeof(Hand) == sizeof(EncodedHand),
"Hand cannot be reinterpreted as EncodedHand.");
return *reinterpret_cast<const EncodedHand*>(this);
}

std::string Hand::debug_string() const {
std::stringstream ss;
ss << "{";
for (size_t i = 0; i < size; i++) {
if (i > 0) {
ss << ", ";
}
ss << int(cards[i]);
}
ss << "}";
return ss.str();
}

void for_each_hand(uint8_t desired_hand_size,
std::function<void(const Hand&)> fn) {
Hand current_hand;
current_hand.size = desired_hand_size;

for (uint8_t i = 0; i < desired_hand_size; ++i) {
current_hand.cards[i] = i;
}
fn(current_hand);

if (desired_hand_size == 0) {
return;
}

while (true) {
int i = desired_hand_size - 1;
++current_hand.cards[i];

while (current_hand.cards[i] > 52 + i - desired_hand_size) {
--i;
if (i < 0) {
return;
}
++current_hand.cards[i];
}

for (; i < desired_hand_size - 1; ++i) {
current_hand.cards[i + 1] = current_hand.cards[i] + 1;
}

fn(current_hand);
}
}

} // namespace poker_eval
71 changes: 71 additions & 0 deletions generate_tables/common.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#pragma once

#include <array>
#include <cstdint>
#include <functional>

namespace poker_eval {

// Cards are represented as integers in the range [0, 52).
// The interpretation of those values are at the discretion of the
// EvalFn, defined below.
using Card = uint8_t;

// For simplicity and efficiency, a hand of cards is defined to have seven
// or fewer cards.
// Since each card takes one byte (with one byte reserved for size), a hand can
// be encoded into an eight byte structure.
using EncodedHand = uint64_t;

// A score is the valuation of a complete hand of cards, as provided by the
// EvalFn, defined below.
// Following previous convention and to more easily fit into a flattened
// finite-state-machine, scores are defined to be 32-bit unsigned integers.
using Score = uint32_t;

// A union of EncodedHand and Score, used to label nodes in the
// finite-state-machine.
// EncodedHand are used for non-terminal nodes.
// Scores are used for terminal nodes.
//
// Lucky for us, both are defined as uint64_t, so we don't need to delve into
// real union shenanigans.
// If we decide to switch Score to be a double, this might need to become a real
// union.
using HandOrScore = uint64_t;

// Hand is a container of up-to seven Cards.
struct Hand {
// The number of Cards in the `cards` field that are meaningfully populated.
// The remaining entries are garbage memory.
uint8_t size = 0;
// The set of cards in the hand.
// These should be kept sorted. This struct does not guarantee this invariant.
Card cards[7] = {0, 0, 0, 0, 0, 0, 0};

// Constructs a Hand object from an encoded version of the hand.
static Hand decode(EncodedHand encoded);

// Returns an encoded version of this hand.
EncodedHand encode() const;

// Returns a human-readable string describing the content of this hand.
std::string debug_string() const;
};

// Efficient associative container, keyed off cards.
template <typename T>
using MapCardTo = std::array<T, 52>;

// Function that returns the valuation of a completed hand of cards.
// This is used as a bootstrap to construct a more efficient evaluator.
// This is the only place where card values, integers in the range [0, 52),
// are given an interpretation.
using EvalFn = std::function<Score(const Hand&)>;

// Utility method that executes a given callback for each valid hand of a given
// size.
void for_each_hand(uint8_t desired_hand_size,
std::function<void(const Hand&)> fn);

} // namespace poker_eval
28 changes: 28 additions & 0 deletions generate_tables/fsm.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#pragma once

#include <unordered_map>
#include <vector>

#include "generate_tables/common.h"

namespace poker_eval {

// A finite-state-machine that uses cards, as defined in common.h, as the input
// alphabet. The states are representative hands from the equivalence class of
// possible scores.
// After hand_size hops, the state becomes the score.
//
// fsm[card_0][card_1][card_2]...[card_(max_hand_size-1)] -> score
using FSM = std::unordered_map<EncodedHand, MapCardTo<HandOrScore>>;

// Builds a finite-state-machine for hands of the current size, using the given
// evaluation function.
//
// Note: for efficiency reasons, hand representations are compacted and
// hand_size cannot exceed seven.
template <uint8_t hand_size>
FSM build_fsm(EvalFn eval_fn);

} // namespace poker_eval

#include "generate_tables/fsm.inl"
Loading

0 comments on commit 0974a84

Please sign in to comment.