Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .clang-format
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BasedOnStyle: Google
53 changes: 53 additions & 0 deletions .github/workflows/lint-suggest.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: lint suggest

# On pull requests, runs clang-format over the changed C/C++ files and posts the
# required formatting fixes as inline review suggestions via reviewdog. This is
# suggestion-only; the lint workflow is the blocking gate.

on:
pull_request:

permissions:
contents: read
pull-requests: write

jobs:
clang-format:
name: clang-format
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install clang-format
run: pip install "clang-format==18.1.8"

- name: Collect changed C/C++ files
id: changed
run: |
base="${{ github.event.pull_request.base.sha }}"
files=$(git diff --name-only --diff-filter=ACMR "$base" HEAD \
| grep -E '\.(c|cc|cpp|cxx|h|hh|hpp)$' \
| grep -E '^(bayes|state_machine)/' \
| tr '\n' ' ' || true)
echo "files=$files" >> "$GITHUB_OUTPUT"

- name: Run clang-format
if: steps.changed.outputs.files != ''
run: clang-format -i ${{ steps.changed.outputs.files }}

- name: Suggest changes
if: steps.changed.outputs.files != ''
uses: reviewdog/action-suggester@v1
with:
tool_name: clang-format
level: warning
fail_level: none
cleanup: "false"
36 changes: 36 additions & 0 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: lint

# Blocking clang-format check. Runs on every push and pull request and fails if
# any tracked C/C++ source in the project is not formatted according to
# .clang-format (Google style). The lint-suggest workflow posts inline fix
# suggestions on pull requests.

on:
push:
pull_request:

jobs:
clang-format:
name: clang-format
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.x"

- name: Install clang-format
run: pip install "clang-format==18.1.8"

- name: Check formatting
run: |
files=$(git ls-files '*.h' '*.cpp' '*.cc' '*.hpp' \
| grep -E '^(bayes|state_machine)/' || true)
if [ -z "$files" ]; then
echo "No C/C++ files to check."
exit 0
fi
clang-format --dry-run --Werror $files
22 changes: 11 additions & 11 deletions bayes/include/bayes.h
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
#include <utility>

#include "expected_value.h"
#include "hand.h"
#include "ruleset.h"

#include <utility>

namespace BlackjackEngine::Bayes {

using StateMachine::DealerHand;
using StateMachine::PlayerHand;
using StateMachine::RuleSet;

ExpectedValue EvPlayerBestAction(const RuleSet& ruleset,
PlayerHand playerHand, DealerHand dealerHand);
ExpectedValue EvPlayerBestAction(const RuleSet& ruleset, PlayerHand playerHand,
DealerHand dealerHand);

ExpectedValue EvPlayerStands(const RuleSet& ruleset,
PlayerHand playerHand, DealerHand dealerHand);
ExpectedValue EvPlayerStands(const RuleSet& ruleset, PlayerHand playerHand,
DealerHand dealerHand);

ExpectedValue EvPlayerHits(const RuleSet& ruleset,
PlayerHand playerHand, DealerHand dealerHand);
ExpectedValue EvPlayerHits(const RuleSet& ruleset, PlayerHand playerHand,
DealerHand dealerHand);

ExpectedValue EvPlayerDoubles(const RuleSet& ruleset,
PlayerHand playerHand, DealerHand dealerHand);
ExpectedValue EvPlayerDoubles(const RuleSet& ruleset, PlayerHand playerHand,
DealerHand dealerHand);

} // namespace BlackjackEngine::Bayes
} // namespace BlackjackEngine::Bayes
8 changes: 4 additions & 4 deletions bayes/include/expected_value.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@ namespace BlackjackEngine::Bayes {
struct Probability {
double value = 0;

explicit constexpr Probability(double v) : value(v) { }
explicit constexpr Probability(double v) : value(v) {}
};

struct ExpectedValue {
double value = 0;

explicit constexpr ExpectedValue(double v) : value(v) { }
explicit constexpr ExpectedValue(double v) : value(v) {}

bool operator<(ExpectedValue other) const {
return this->value < other.value;
}
};

} // namespace
} // namespace BlackjackEngine::Bayes

#endif // EXPECTED_VALUE_H
#endif // EXPECTED_VALUE_H
68 changes: 34 additions & 34 deletions bayes/src/bayes.cpp
Original file line number Diff line number Diff line change
@@ -1,28 +1,22 @@
#include "bayes.h"
#include "state_machine.h"

#include <algorithm>
#include <array>
#include <cassert>
#include <limits>
#include <numeric>
#include <ranges>

#include "state_machine.h"

using namespace BlackjackEngine::StateMachine;

namespace BlackjackEngine::Bayes {

constexpr std::array<Card, 10> AllCards() {
return {
Card(2),
Card(3),
Card(4),
Card(5),
Card(6),
Card(7),
Card(8),
Card(9),
Card(10),
Card(11),
Card(2), Card(3), Card(4), Card(5), Card(6),
Card(7), Card(8), Card(9), Card(10), Card(11),
};
};

Expand All @@ -43,11 +37,13 @@ ExpectedValue EvBest(const RuleSet& ruleset, const State& state);
// EV of hitting: draw a card, then play the resulting state optimally.
ExpectedValue EvHits(const RuleSet& ruleset, const State& state) {
auto allCards = std::views::all(AllCards());
return std::accumulate(allCards.begin(), allCards.end(), ExpectedValue(0.0),
[&ruleset, state] (ExpectedValue current, Card card) {
return ExpectedValue(current.value +
return std::accumulate(
allCards.begin(), allCards.end(), ExpectedValue(0.0),
[&ruleset, state](ExpectedValue current, Card card) {
return ExpectedValue(
current.value +
ProbOfGettingOneCard(card).value *
EvBest(ruleset, Hit(ruleset, state, card)).value);
EvBest(ruleset, Hit(ruleset, state, card)).value);
});
}

Expand All @@ -60,11 +56,12 @@ ExpectedValue EvStands(const RuleSet& ruleset, const State& state) {
// resulting state optimally (only standing remains).
ExpectedValue EvDoubles(const RuleSet& ruleset, const State& state) {
auto allCards = std::views::all(AllCards());
return std::accumulate(allCards.begin(), allCards.end(), ExpectedValue(0.0),
[&ruleset, state] (ExpectedValue current, Card card) {
return std::accumulate(
allCards.begin(), allCards.end(), ExpectedValue(0.0),
[&ruleset, state](ExpectedValue current, Card card) {
return ExpectedValue(current.value +
2.0 * ProbOfGettingOneCard(card).value *
EvBest(ruleset, Double(state, card)).value);
2.0 * ProbOfGettingOneCard(card).value *
EvBest(ruleset, Double(state, card)).value);
});
}

Expand All @@ -73,8 +70,7 @@ ExpectedValue EvDoubles(const RuleSet& ruleset, const State& state) {
// drives both the player's choices and the dealer's forced play, since a
// dealer-turn state only ever allows Hit.
ExpectedValue EvBest(const RuleSet& ruleset, const State& state) {
if (IsTerminal(state))
return OutcomeValue(Result(state));
if (IsTerminal(state)) return OutcomeValue(Result(state));

// A non-terminal state must offer at least one action, otherwise the fallback
// below would return negative infinity.
Expand All @@ -94,24 +90,28 @@ ExpectedValue EvBest(const RuleSet& ruleset, const State& state) {
return best;
}

ExpectedValue EvPlayerBestAction(const RuleSet& ruleset,
PlayerHand playerHand, DealerHand dealerHand) {
return EvBest(ruleset, InitiateState(ruleset, Turn::Player, playerHand, dealerHand));
ExpectedValue EvPlayerBestAction(const RuleSet& ruleset, PlayerHand playerHand,
DealerHand dealerHand) {
return EvBest(ruleset,
InitiateState(ruleset, Turn::Player, playerHand, dealerHand));
}

ExpectedValue EvPlayerStands(const RuleSet& ruleset,
PlayerHand playerHand, DealerHand dealerHand) {
return EvStands(ruleset, InitiateState(ruleset, Turn::Player, playerHand, dealerHand));
ExpectedValue EvPlayerStands(const RuleSet& ruleset, PlayerHand playerHand,
DealerHand dealerHand) {
return EvStands(ruleset,
InitiateState(ruleset, Turn::Player, playerHand, dealerHand));
}

ExpectedValue EvPlayerHits(const RuleSet& ruleset,
PlayerHand playerHand, DealerHand dealerHand) {
return EvHits(ruleset, InitiateState(ruleset, Turn::Player, playerHand, dealerHand));
ExpectedValue EvPlayerHits(const RuleSet& ruleset, PlayerHand playerHand,
DealerHand dealerHand) {
return EvHits(ruleset,
InitiateState(ruleset, Turn::Player, playerHand, dealerHand));
}

ExpectedValue EvPlayerDoubles(const RuleSet& ruleset,
PlayerHand playerHand, DealerHand dealerHand) {
return EvDoubles(ruleset, InitiateState(ruleset, Turn::Player, playerHand, dealerHand));
ExpectedValue EvPlayerDoubles(const RuleSet& ruleset, PlayerHand playerHand,
DealerHand dealerHand) {
return EvDoubles(
ruleset, InitiateState(ruleset, Turn::Player, playerHand, dealerHand));
}

} // namespace BlackjackEngine::Bayes
} // namespace BlackjackEngine::Bayes
Loading
Loading