From f717bf6a211c3fdca45e77543741201af45575b1 Mon Sep 17 00:00:00 2001 From: willmcgowan <54598089+willmcgowan@users.noreply.github.com> Date: Sun, 12 Nov 2023 22:15:30 +0000 Subject: [PATCH 01/30] Create german_whist_foregame --- open_spiel/games/german_whist_foregame | 1 + 1 file changed, 1 insertion(+) create mode 100644 open_spiel/games/german_whist_foregame diff --git a/open_spiel/games/german_whist_foregame b/open_spiel/games/german_whist_foregame new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/open_spiel/games/german_whist_foregame @@ -0,0 +1 @@ + From 11096d03d07d9b845861aa2aa735167352a16802 Mon Sep 17 00:00:00 2001 From: willmcgowan <54598089+willmcgowan@users.noreply.github.com> Date: Sun, 12 Nov 2023 22:15:58 +0000 Subject: [PATCH 02/30] Delete open_spiel/games/german_whist_foregame --- open_spiel/games/german_whist_foregame | 1 - 1 file changed, 1 deletion(-) delete mode 100644 open_spiel/games/german_whist_foregame diff --git a/open_spiel/games/german_whist_foregame b/open_spiel/games/german_whist_foregame deleted file mode 100644 index 8b13789179..0000000000 --- a/open_spiel/games/german_whist_foregame +++ /dev/null @@ -1 +0,0 @@ - From 8485343f4430f10478d0692f620c85a0622128a3 Mon Sep 17 00:00:00 2001 From: willmcgowan <54598089+willmcgowan@users.noreply.github.com> Date: Sun, 12 Nov 2023 22:17:30 +0000 Subject: [PATCH 03/30] Create german_whist_foregame.cc --- open_spiel/games/german_whist_foregame/german_whist_foregame.cc | 1 + 1 file changed, 1 insertion(+) create mode 100644 open_spiel/games/german_whist_foregame/german_whist_foregame.cc diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -0,0 +1 @@ + From 397489a04d39a3db6fd2e15e5e713c851d93f8ca Mon Sep 17 00:00:00 2001 From: willmcgowan <54598089+willmcgowan@users.noreply.github.com> Date: Sun, 12 Nov 2023 22:18:03 +0000 Subject: [PATCH 04/30] Create german_whist_foregame.h --- open_spiel/games/german_whist_foregame/german_whist_foregame.h | 1 + 1 file changed, 1 insertion(+) create mode 100644 open_spiel/games/german_whist_foregame/german_whist_foregame.h diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.h b/open_spiel/games/german_whist_foregame/german_whist_foregame.h new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.h @@ -0,0 +1 @@ + From 9851a650b78db6c5fa8bc9c02944dad9e6e3219d Mon Sep 17 00:00:00 2001 From: willmcgowan <54598089+willmcgowan@users.noreply.github.com> Date: Sun, 12 Nov 2023 22:18:36 +0000 Subject: [PATCH 05/30] Create solver.cc --- open_spiel/games/german_whist_foregame/solver.cc | 1 + 1 file changed, 1 insertion(+) create mode 100644 open_spiel/games/german_whist_foregame/solver.cc diff --git a/open_spiel/games/german_whist_foregame/solver.cc b/open_spiel/games/german_whist_foregame/solver.cc new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/open_spiel/games/german_whist_foregame/solver.cc @@ -0,0 +1 @@ + From 8b2aea97d93e4635928215ce443028938365c284 Mon Sep 17 00:00:00 2001 From: willmcgowan <54598089+willmcgowan@users.noreply.github.com> Date: Sun, 12 Nov 2023 22:39:18 +0000 Subject: [PATCH 06/30] Create german_whist_foregame_test.cc --- .../games/german_whist_foregame/german_whist_foregame_test.cc | 1 + 1 file changed, 1 insertion(+) create mode 100644 open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc @@ -0,0 +1 @@ + From 5ac7efe612687144fe05e3158666b27947b2525d Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Mon, 13 Nov 2023 06:07:42 +0000 Subject: [PATCH 07/30] Beginning --- .../german_whist_foregame.cc | 373 ++++++++++++++++++ .../german_whist_foregame.h | 109 +++++ .../german_whist_foregame_test.cc | 66 ++++ 3 files changed, 548 insertions(+) diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 8b13789179..2d35d87237 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -1 +1,374 @@ +#include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" +#include +#include +#include +#include + +#include "open_spiel/abseil-cpp/absl/strings/str_cat.h" +#include "open_spiel/game_parameters.h" +#include "open_spiel/observer.h" +#include "open_spiel/policy.h" +#include "open_spiel/spiel.h" +#include "open_spiel/spiel_utils.h" + +namespace open_spiel { +namespace german_whist_foregame { +namespace { + +// Default parameters. + + +// Facts about the game +const GameType kGameType{/*short_name=*/"german_whist_foregame", + /*long_name=*/"german_whist_foregame", + GameType::Dynamics::kSequential, + GameType::ChanceMode::kExplicitStochastic, + GameType::Information::kImperfectInformation, + GameType::Utility::kZeroSum, + GameType::RewardModel::kTerminal, + /*max_num_players=*/2, + /*min_num_players=*/2, + /*provides_information_state_string=*/true, + /*provides_information_state_tensor=*/true, + /*provides_observation_string=*/true, + /*provides_observation_tensor=*/true, + /*parameter_specification=*/ + {{"players", GameParameter(kDefaultPlayers)}}, + /*default_loadable=*/true, + /*provides_factored_observation_string=*/true, + }; + +std::shared_ptr Factory(const GameParameters& params) { + return std::shared_ptr(new GermanWhistForegameGame(params)); +} + +REGISTER_SPIEL_GAME(kGameType, Factory); + +open_spiel::RegisterSingleTensorObserver single_tensor(kGameType.short_name); +} // namespace + +class GermanWhistForegameObserver : public Observer { + public: + GermanWhistForegameObserver(IIGObservationType iig_obs_type) + : Observer(/*has_string=*/true, /*has_tensor=*/true), + iig_obs_type_(iig_obs_type) {} + + void WriteTensor(const State& observed_state, int player, + Allocator* allocator) const override { + } + + std::string StringFrom(const State& observed_state, + int player) const override { + } + + private: + IIGObservationType iig_obs_type_; +}; + +GermanWhistForegameState::GermanWhistForegameState(std::shared_ptr game) + : State(game), + first_bettor_(kInvalidPlayer), + card_dealt_(game->NumPlayers() + 1, kInvalidPlayer), + winner_(kInvalidPlayer), + pot_(kAnte * game->NumPlayers()), + // How much each player has contributed to the pot, indexed by pid. + ante_(game->NumPlayers(), kAnte) {} + +int GermanWhistForegameState::CurrentPlayer() const { + if (IsTerminal()) { + return kTerminalPlayerId; + } else { + return (history_.size() < num_players_) ? kChancePlayerId + : history_.size() % num_players_; + } +} + +void GermanWhistForegameState::DoApplyAction(Action move) { + // Additional book-keeping + if (history_.size() < num_players_) { + // Give card `move` to player `history_.size()` (CurrentPlayer will return + // kChancePlayerId, so we use that instead). + card_dealt_[move] = history_.size(); + } else if (move == ActionType::kBet) { + if (first_bettor_ == kInvalidPlayer) first_bettor_ = CurrentPlayer(); + pot_ += 1; + ante_[CurrentPlayer()] += kAnte; + } + + // We undo that before exiting the method. + // This is used in `DidBet`. + history_.push_back({CurrentPlayer(), move}); + + // Check for the game being over. + const int num_actions = history_.size() - num_players_; + if (first_bettor_ == kInvalidPlayer && num_actions == num_players_) { + // Nobody bet; the winner is the person with the highest card dealt, + // which is either the highest or the next-highest card. + // Losers lose 1, winner wins 1 * (num_players - 1) + winner_ = card_dealt_[num_players_]; + if (winner_ == kInvalidPlayer) winner_ = card_dealt_[num_players_ - 1]; + } else if (first_bettor_ != kInvalidPlayer && + num_actions == num_players_ + first_bettor_) { + // There was betting; so the winner is the person with the highest card + // who stayed in the hand. + // Check players in turn starting with the highest card. + for (int card = num_players_; card >= 0; --card) { + const Player player = card_dealt_[card]; + if (player != kInvalidPlayer && DidBet(player)) { + winner_ = player; + break; + } + } + SPIEL_CHECK_NE(winner_, kInvalidPlayer); + } + history_.pop_back(); +} + +std::vector GermanWhistForegameState::LegalActions() const { + if (IsTerminal()) return {}; + if (IsChanceNode()) { + std::vector actions; + for (int card = 0; card < card_dealt_.size(); ++card) { + if (card_dealt_[card] == kInvalidPlayer) actions.push_back(card); + } + return actions; + } else { + return {ActionType::kPass, ActionType::kBet}; + } +} + +std::string GermanWhistForegameState::ActionToString(Player player, Action move) const { + if (player == kChancePlayerId) + return absl::StrCat("Deal:", move); + else if (move == ActionType::kPass) + return "Pass"; + else + return "Bet"; +} + +std::string GermanWhistForegameState::ToString() const { + // The deal: space separated card per player + std::string str; + for (int i = 0; i < history_.size() && i < num_players_; ++i) { + if (!str.empty()) str.push_back(' '); + absl::StrAppend(&str, history_[i].action); + } + + // The betting history: p for Pass, b for Bet + if (history_.size() > num_players_) str.push_back(' '); + for (int i = num_players_; i < history_.size(); ++i) { + str.push_back(history_[i].action ? 'b' : 'p'); + } + + return str; +} + +bool GermanWhistForegameState::IsTerminal() const { return winner_ != kInvalidPlayer; } + +std::vector GermanWhistForegameState::Returns() const { + if (!IsTerminal()) { + return std::vector(num_players_, 0.0); + } + + std::vector returns(num_players_); + for (auto player = Player{0}; player < num_players_; ++player) { + const int bet = DidBet(player) ? 2 : 1; + returns[player] = (player == winner_) ? (pot_ - bet) : -bet; + } + return returns; +} + +std::string GermanWhistForegameState::InformationStateString(Player player) const { + const GermanWhistForegameGame& game = open_spiel::down_cast(*game_); + return game.info_state_observer_->StringFrom(*this, player); +} + +std::string GermanWhistForegameState::ObservationString(Player player) const { + const GermanWhistForegameGame& game = open_spiel::down_cast(*game_); + return game.default_observer_->StringFrom(*this, player); +} + +void GermanWhistForegameState::InformationStateTensor(Player player, + absl::Span values) const { + ContiguousAllocator allocator(values); + const GermanWhistForegameGame& game = open_spiel::down_cast(*game_); + game.info_state_observer_->WriteTensor(*this, player, &allocator); +} + +void GermanWhistForegameState::ObservationTensor(Player player, + absl::Span values) const { + ContiguousAllocator allocator(values); + const GermanWhistForegameGame& game = open_spiel::down_cast(*game_); + game.default_observer_->WriteTensor(*this, player, &allocator); +} + +std::unique_ptr GermanWhistForegameState::Clone() const { + return std::unique_ptr(new GermanWhistForegameState(*this)); +} + +void GermanWhistForegameState::UndoAction(Player player, Action move) { + if (history_.size() <= num_players_) { + // Undoing a deal move. + card_dealt_[move] = kInvalidPlayer; + } else { + // Undoing a bet / pass. + if (move == ActionType::kBet) { + pot_ -= 1; + if (player == first_bettor_) first_bettor_ = kInvalidPlayer; + } + winner_ = kInvalidPlayer; + } + history_.pop_back(); + --move_number_; +} + +std::vector> GermanWhistForegameState::ChanceOutcomes() const { + SPIEL_CHECK_TRUE(IsChanceNode()); + std::vector> outcomes; + const double p = 1.0 / (num_players_ + 1 - history_.size()); + for (int card = 0; card < card_dealt_.size(); ++card) { + if (card_dealt_[card] == kInvalidPlayer) outcomes.push_back({card, p}); + } + return outcomes; +} + +bool GermanWhistForegameState::DidBet(Player player) const { + if (first_bettor_ == kInvalidPlayer) { + return false; + } else if (player == first_bettor_) { + return true; + } else if (player > first_bettor_) { + return history_[num_players_ + player].action == ActionType::kBet; + } else { + return history_[num_players_ * 2 + player].action == ActionType::kBet; + } +} + +std::unique_ptr GermanWhistForegameState::ResampleFromInfostate( + int player_id, std::function rng) const { + std::unique_ptr state = game_->NewInitialState(); + Action player_chance = history_.at(player_id).action; + for (int p = 0; p < game_->NumPlayers(); ++p) { + if (p == history_.size()) return state; + if (p == player_id) { + state->ApplyAction(player_chance); + } else { + Action other_chance = player_chance; + while (other_chance == player_chance) { + other_chance = SampleAction(state->ChanceOutcomes(), rng()).first; + } + state->ApplyAction(other_chance); + } + } + SPIEL_CHECK_GE(state->CurrentPlayer(), 0); + if (game_->NumPlayers() == history_.size()) return state; + for (int i = game_->NumPlayers(); i < history_.size(); ++i) { + state->ApplyAction(history_.at(i).action); + } + return state; +} + +GermanWhistForegameGame::GermanWhistForegameGame(const GameParameters& params) + : Game(kGameType, params), num_players_(ParameterValue("players")) { + SPIEL_CHECK_GE(num_players_, kGameType.min_num_players); + SPIEL_CHECK_LE(num_players_, kGameType.max_num_players); + default_observer_ = std::make_shared(kDefaultObsType); + info_state_observer_ = std::make_shared(kInfoStateObsType); + private_observer_ = std::make_shared( + IIGObservationType{/*public_info*/false, + /*perfect_recall*/false, + /*private_info*/PrivateInfoType::kSinglePlayer}); + public_observer_ = std::make_shared( + IIGObservationType{/*public_info*/true, + /*perfect_recall*/false, + /*private_info*/PrivateInfoType::kNone}); +} + +std::unique_ptr GermanWhistForegameGame::NewInitialState() const { + return std::unique_ptr(new GermanWhistForegameState(shared_from_this())); +} + +std::vector GermanWhistForegameGame::InformationStateTensorShape() const { + // One-hot for whose turn it is. + // One-hot encoding for the single private card. (n+1 cards = n+1 bits) + // Followed by 2 (n - 1 + n) bits for betting sequence (longest sequence: + // everyone except one player can pass and then everyone can bet/pass). + // n + n + 1 + 2 (n-1 + n) = 6n - 1. + return {6 * num_players_ - 1}; +} + +std::vector GermanWhistForegameGame::ObservationTensorShape() const { + // One-hot for whose turn it is. + // One-hot encoding for the single private card. (n+1 cards = n+1 bits) + // Followed by the contribution of each player to the pot (n). + // n + n + 1 + n = 3n + 1. + return {3 * num_players_ + 1}; +} + +double GermanWhistForegameGame::MaxUtility() const { + // In poker, the utility is defined as the money a player has at the end + // of the game minus then money the player had before starting the game. + // Everyone puts a chip in at the start, and then they each have one more + // chip. Most that a player can gain is (#opponents)*2. + return (num_players_ - 1) * 2; +} + +double GermanWhistForegameGame::MinUtility() const { + // In poker, the utility is defined as the money a player has at the end + // of the game minus then money the player had before starting the game. + // In GermanWhistForegame, the most any one player can lose is the single chip they paid + // to play and the single chip they paid to raise/call. + return -2; +} + +std::shared_ptr GermanWhistForegameGame::MakeObserver( + absl::optional iig_obs_type, + const GameParameters& params) const { + if (params.empty()) { + return std::make_shared( + iig_obs_type.value_or(kDefaultObsType)); + } else { + return MakeRegisteredObserver(iig_obs_type, params); + } +} + +TabularPolicy GetAlwaysPassPolicy(const Game& game) { + SPIEL_CHECK_TRUE( + dynamic_cast(const_cast(&game)) != nullptr); + return GetPrefActionPolicy(game, {ActionType::kPass}); +} + +TabularPolicy GetAlwaysBetPolicy(const Game& game) { + SPIEL_CHECK_TRUE( + dynamic_cast(const_cast(&game)) != nullptr); + return GetPrefActionPolicy(game, {ActionType::kBet}); +} + +TabularPolicy GetOptimalPolicy(double alpha) { + SPIEL_CHECK_GE(alpha, 0.); + SPIEL_CHECK_LE(alpha, 1. / 3); + const double three_alpha = 3 * alpha; + std::unordered_map policy; + + // All infostates have two actions: Pass (0) and Bet (1). + // Player 0 + policy["0"] = {{0, 1 - alpha}, {1, alpha}}; + policy["0pb"] = {{0, 1}, {1, 0}}; + policy["1"] = {{0, 1}, {1, 0}}; + policy["1pb"] = {{0, 2. / 3. - alpha}, {1, 1. / 3. + alpha}}; + policy["2"] = {{0, 1 - three_alpha}, {1, three_alpha}}; + policy["2pb"] = {{0, 0}, {1, 1}}; + + // Player 1 + policy["0p"] = {{0, 2. / 3.}, {1, 1. / 3.}}; + policy["0b"] = {{0, 1}, {1, 0}}; + policy["1p"] = {{0, 1}, {1, 0}}; + policy["1b"] = {{0, 2. / 3.}, {1, 1. / 3.}}; + policy["2p"] = {{0, 0}, {1, 1}}; + policy["2b"] = {{0, 0}, {1, 1}}; + return TabularPolicy(policy); +} + +} // namespace GermanWhistForegame_poker +} // namespace open_spiel diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.h b/open_spiel/games/german_whist_foregame/german_whist_foregame.h index 8b13789179..9a60b1dbb3 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.h +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.h @@ -1 +1,110 @@ +#ifndef OPEN_SPIEL_GAMES_GERMAN_WHIST_FOREGAME_H +#define OPEN_SPIEL_GAMES_GERMAN_WHIST_FOREGAME_H +#include +#include +#include +#include + +#include "open_spiel/policy.h" +#include "open_spiel/spiel.h" +#include "open_spiel/spiel_utils.h" + +//The imperfect information part of 2 player whist variant +//https://en.wikipedia.org/wiki/German_Whist +// +// + +// +// Parameters: +// kNumSuits, kNumRanks + +namespace open_spiel { +namespace german_whist_foregame { + + +enum ActionType { kPass = 0, kBet = 1 }; + +class GermanWhistForegameGame; +class GermanWhistForegameĆ’Observer; + +class GermanWhistForegameState : public State { +public: + explicit GermanWhistForegameState(std::shared_ptr game); + GermanWhistForegameState(const GermanWhistForegameState&) = default; + + Player CurrentPlayer() const override; + + std::string ActionToString(Player player, Action move) const override; + std::string ToString() const override; + bool IsTerminal() const override; + std::vector Returns() const override; + std::string InformationStateString(Player player) const override; + std::string ObservationString(Player player) const override; + void InformationStateTensor(Player player, + absl::Span values) const override; + void ObservationTensor(Player player, + absl::Span values) const override; + std::unique_ptr Clone() const override; + void UndoAction(Player player, Action move) override; + std::vector> ChanceOutcomes() const override; + std::vector LegalActions() const override; + std::vector hand() const { return {card_dealt_[CurrentPlayer()]}; } + std::unique_ptr ResampleFromInfostate( + int player_id, std::function rng) const override; + + const std::vector& CardDealt() const { return card_dealt_; } + +protected: + void DoApplyAction(Action move) override; + +private: + friend class GermanWhistForegameObserver; + + // Whether the specified player made a bet + bool DidBet(Player player) const; + + // The move history and number of players are sufficient information to + // specify the state of the game. We keep track of more information to make + // extracting legal actions and utilities easier. + // The cost of the additional book-keeping is more complex ApplyAction() and + // UndoAction() functions. + int first_bettor_; // the player (if any) who was first to bet + std::vector card_dealt_; // the player (if any) who has each card + int winner_; // winning player, or kInvalidPlayer if the + // game isn't over yet. + int pot_; // the size of the pot + // How much each player has contributed to the pot, indexed by pid. + std::vector ante_; +}; + +class GermanWhistForegameGame : public Game { +public: + explicit GermanWhistForegameGame(const GameParameters& params); + int NumDistinctActions() const override { return 2; } + std::unique_ptr NewInitialState() const override; + int MaxChanceOutcomes() const override { return num_players_ + 1; } + int NumPlayers() const override { return num_players_; } + double MinUtility() const override; + double MaxUtility() const override; + absl::optional UtilitySum() const override { return 0; } + std::vector InformationStateTensorShape() const override; + std::vector ObservationTensorShape() const override; + int MaxGameLength() const override { return num_players_ * 2 - 1; } + int MaxChanceNodesInHistory() const override { return num_players_; } + std::shared_ptr MakeObserver( + absl::optional iig_obs_type, + const GameParameters& params) const override; + + // Used to implement the old observation API. + std::shared_ptr default_observer_; + std::shared_ptr info_state_observer_; + std::shared_ptr public_observer_; + std::shared_ptr private_observer_; + +private: + // Number of players. + int num_players_; +}; + +#endif OPEN_SPIEL_GAMES_GERMAN_WHIST_FOREGAME_H diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc index 8b13789179..a90876f291 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc @@ -1 +1,67 @@ +#include "open_spiel/games/GermanWhistForegame_/GermanWhistForegame_.h" +#include "open_spiel/algorithms/get_all_states.h" +#include "open_spiel/policy.h" +#include "open_spiel/spiel_utils.h" +#include "open_spiel/tests/basic_tests.h" + +namespace open_spiel { +namespace german_whist_foregame { +namespace { + +namespace testing = open_spiel::testing; + +void BasicGermanWhistForegameTests() { + testing::LoadGameTest("GermanWhistForegame"); + testing::ChanceOutcomesTest(*LoadGame("GermanWhistForegame")); + testing::RandomSimTest(*LoadGame("GermanWhistForegame"), 100); + testing::RandomSimTestWithUndo(*LoadGame("GermanWhistForegame"), 1); + for (Player players = 3; players <= 5; players++) { + testing::RandomSimTest( + *LoadGame("GermanWhistForegame_", {{"players", GameParameter(players)}}), 100); + } + auto observer = LoadGame("GermanWhistForegame") + ->MakeObserver(kDefaultObsType, + GameParametersFromString("single_tensor")); + testing::RandomSimTestCustomObserver(*LoadGame("GermanWhistForegame"), observer); +} + +void CountStates() { + std::shared_ptr game = LoadGame("GermanWhistForegame"); + auto states = algorithms::GetAllStates(*game, /*depth_limit=*/-1, + /*include_terminals=*/true, + /*include_chance_states=*/false); + // 6 deals * 9 betting sequences (-, p, b, pp, pb, bp, bb, pbp, pbb) = 54 + SPIEL_CHECK_EQ(states.size(), 54); +} + +void PolicyTest() { + using PolicyGenerator = std::function; + std::vector policy_generators = { + GetAlwaysPassPolicy, + GetAlwaysBetPolicy, + }; + + std::shared_ptr game = LoadGame("GermanWhistForegame"); + for (const auto& policy_generator : policy_generators) { + testing::TestEveryInfostateInPolicy(policy_generator, *game); + testing::TestPoliciesCanPlay(policy_generator, *game); + } +} + +} // namespace +} // namespace GermanWhistForegame_ +} // namespace open_spiel + +int main(int argc, char **argv) { + open_spiel::GermanWhistForegame_::BasicGermanWhistForegameTests(); + open_spiel::GermanWhistForegame_::CountStates(); + open_spiel::GermanWhistForegame_::PolicyTest(); + open_spiel::testing::CheckChanceOutcomes(*open_spiel::LoadGame( + "GermanWhistForegame", {{"players", open_spiel::GameParameter(3)}})); + open_spiel::testing::RandomSimTest(*open_spiel::LoadGame("GermanWhistForegame"), + /*num_sims=*/10); + open_spiel::testing::ResampleInfostateTest( + *open_spiel::LoadGame("GermanWhistForegame"), + /*num_sims=*/10); +} From f1ce917ae13fb67e491831f5367f591eb7846cf9 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Sat, 27 Jan 2024 10:55:31 +0000 Subject: [PATCH 08/30] Majority of changes Lots of changes, currently endgame compiles but does not run which is an issue --- open_spiel/CMakeLists.txt | 13 +- open_spiel/examples/CMakeLists.txt | 2 + open_spiel/examples/is_mcts_gwhist.cc | 85 ++ open_spiel/games/CMakeLists.txt | 9 + .../german_whist_endgame.cc | 716 ++++++++++++++ .../german_whist_foregame.cc | 878 +++++++++++------- .../german_whist_foregame.h | 142 +-- .../german_whist_foregame_test.cc | 53 +- .../games/german_whist_foregame/solver.cc | 1 - open_spiel/spiel.h | 2 +- open_spiel/tests/basic_tests.cc | 5 + 11 files changed, 1474 insertions(+), 432 deletions(-) create mode 100644 open_spiel/examples/is_mcts_gwhist.cc create mode 100644 open_spiel/games/german_whist_foregame/german_whist_endgame.cc delete mode 100644 open_spiel/games/german_whist_foregame/solver.cc diff --git a/open_spiel/CMakeLists.txt b/open_spiel/CMakeLists.txt index 880a9365ae..1efd34f0cf 100644 --- a/open_spiel/CMakeLists.txt +++ b/open_spiel/CMakeLists.txt @@ -34,7 +34,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # Set default build type. set (BUILD_TYPE $ENV{BUILD_TYPE}) if(NOT BUILD_TYPE) - set(BUILD_TYPE Testing + set(BUILD_TYPE Release CACHE STRING "Choose the type of build: Debug Release Testing." FORCE) endif() @@ -50,14 +50,14 @@ if(${BUILD_TYPE} STREQUAL "Testing") # A build used for running tests: keep all runtime checks (assert, # SPIEL_CHECK_*, SPIEL_DCHECK_*), but turn on some speed optimizations, # otherwise tests run for too long. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -march=x86-64-v3") endif() if(${BUILD_TYPE} STREQUAL "Release") # Optimized release build: turn off debug runtime checks (assert, # SPIEL_DCHECK_*) and turn on highest speed optimizations. # The difference in perfomance can be up to 10x higher. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -march=x86-64-v3") endif() if(APPLE) @@ -135,6 +135,8 @@ openspiel_optional_dependency(OPEN_SPIEL_BUILD_WITH_ORTOOLS OFF "Build with C++ optimization library OR-Tools.") openspiel_optional_dependency(OPEN_SPIEL_BUILD_WITH_RUST OFF "Build with support for Rust API.") +openspiel_optional_dependency(OPEN_SPIEL_BUILD_WITH_BMI2 ON + "Build with support for BMI2 instructions.") if (WIN32) if (OPEN_SPIEL_BUILD_WITH_HIGC) @@ -295,6 +297,7 @@ if (OPEN_SPIEL_BUILD_WITH_TENSORFLOW_CC) find_package(TensorflowCC REQUIRED) endif() + # We have the parent of this directory in the include path, so that we can # include for example "open_spiel/spiel.h" (assuming this directory is named # open_spiel). @@ -315,6 +318,10 @@ if (OPEN_SPIEL_BUILD_WITH_RUST) add_subdirectory(rust) endif() +if(OPEN_SPIEL_BUILD_WITH_BMI2) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mbmi2") +endif() + if (OPEN_SPIEL_BUILD_WITH_PYTHON) add_subdirectory (python) # HIGC needs pyspiel.so and corresponding PYTHONPATH to be set diff --git a/open_spiel/examples/CMakeLists.txt b/open_spiel/examples/CMakeLists.txt index 91934c09d3..96f8954033 100644 --- a/open_spiel/examples/CMakeLists.txt +++ b/open_spiel/examples/CMakeLists.txt @@ -17,6 +17,8 @@ add_executable(fsicfr_liars_dice fsicfr_liars_dice.cc ${OPEN_SPIEL_OBJECTS}) add_executable(gtp gtp.cc ${OPEN_SPIEL_OBJECTS}) +add_executable(is_mcts_gwhist is_mcts_gwhist.cc ${OPEN_SPIEL_OBJECTS}) + add_executable(matrix_example matrix_example.cc ${OPEN_SPIEL_OBJECTS}) add_test(matrix_example_test matrix_example) diff --git a/open_spiel/examples/is_mcts_gwhist.cc b/open_spiel/examples/is_mcts_gwhist.cc new file mode 100644 index 0000000000..51440f49f0 --- /dev/null +++ b/open_spiel/examples/is_mcts_gwhist.cc @@ -0,0 +1,85 @@ +// Copyright 2021 DeepMind Technologies Limited +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "open_spiel/algorithms/is_mcts.h" + +#include + +#include "open_spiel/abseil-cpp/absl/random/distributions.h" +#include "open_spiel/algorithms/mcts.h" +#include "open_spiel/spiel.h" +#include "open_spiel/spiel_bots.h" +#include "open_spiel/spiel_utils.h" + +namespace open_spiel { +namespace { + +constexpr const int kSeed = 9492110;//93879211; + +void PlayGWhist(int human_player, std::mt19937* rng) { + std::shared_ptr game = LoadGame("german_whist_foregame"); + std::random_device rd; + int eval_seed = rd(); + int bot_seed = rd(); + auto evaluator = std::make_shared(1, eval_seed); + auto bot = std::make_unique( + bot_seed, evaluator, 0.7, 500000, algorithms::kUnlimitedNumWorldSamples, + algorithms::ISMCTSFinalPolicyType::kMaxVisitCount,true, false); + std::unique_ptr state = game->NewInitialState(); + while (!state->IsTerminal()) { + //std::cout << "State:" << std::endl; + //std::cout << state->ToString() << std::endl; + + Action chosen_action = kInvalidAction; + if (state->IsChanceNode()) { + chosen_action = + SampleAction(state->ChanceOutcomes(), absl::Uniform(*rng, 0.0, 1.0)) + .first; + } else if(state->CurrentPlayer()!=human_player) { + chosen_action = bot->Step(*state); + } + else{ + std::cout<InformationStateString(human_player)<LegalActions(); + for(int i =0;iActionToString(legal_actions[i])<<","; + } + std::cout<>input; + chosen_action = state->StringToAction(input); + std::cout<ApplyAction(chosen_action); + } + + std::cout << "Terminal state:" << std::endl; + std::cout << state->ToString() << std::endl; + std::cout << "Returns: " << absl::StrJoin(state->Returns(), " ") << std::endl; +} + + +} // namespace +} // namespace open_spiel + + +//current issues: +//infostate display for player is inaccurate and unreadable// +//endgame parsing/RETURNS SEEMS to be inaccurate as i got destroyed everytime despite strong play? +int main(int argc, char** argv) { + std::random_device rd; + std::mt19937 rng(rd()); + open_spiel::PlayGWhist(0,&rng); +} diff --git a/open_spiel/games/CMakeLists.txt b/open_spiel/games/CMakeLists.txt index 6af3133c2c..b8ff3ff3cc 100644 --- a/open_spiel/games/CMakeLists.txt +++ b/open_spiel/games/CMakeLists.txt @@ -74,6 +74,8 @@ set(GAME_SOURCES euchre/euchre.h first_sealed_auction/first_sealed_auction.cc first_sealed_auction/first_sealed_auction.h + german_whist_foregame/german_whist_foregame.cc + german_whist_foregame/german_whist_foregame.h gin_rummy/gin_rummy.cc gin_rummy/gin_rummy.h gin_rummy/gin_rummy_utils.cc @@ -192,6 +194,7 @@ if (${OPEN_SPIEL_BUILD_WITH_ACPC}) set(GAME_SOURCES ${GAME_SOURCES} universal_poker/universal_poker.cc universal_poker/universal_poker.h) endif() + add_library (games OBJECT ${GAME_SOURCES}) target_include_directories (games PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) @@ -428,6 +431,12 @@ add_executable(garnet_test mfg/garnet_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(garnet_test garnet_test) +add_executable(german_whist_foregame_test german_whist_foregame/german_whist_foregame_test.cc ${OPEN_SPIEL_OBJECTS} + $) +add_test(german_whist_foregame_test german_whist_foregame_test) +add_executable(german_whist_endgame german_whist_foregame/german_whist_endgame.cc ${OPEN_SPIEL_OBJECTS} + $) + add_executable(gin_rummy_test gin_rummy/gin_rummy_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(gin_rummy_test gin_rummy_test) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc new file mode 100644 index 0000000000..617c76f66d --- /dev/null +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -0,0 +1,716 @@ +//Source Code for an Executable Generating an Endgame Tablebase for German Whist +// + +#include +#include +#include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" + +//#define DEBUG +namespace open_spiel{ +namespace german_whist_foregame{ + +struct Pair { + char index; + char value; + Pair(char index_, char value_) { + index = index_; + value = value_; + } + bool operator<(const Pair &pair) const { + return value < pair.value; + } +}; +struct ActionStruct{ + uint32_t index; + unsigned char suit; + bool player; + ActionStruct(uint32_t index_, unsigned char suit_, bool player_) { + index = index_; + suit = suit_; + player = player_; + } +}; +struct ActionValue { + ActionStruct action; + int value; + bool operator<(const ActionValue& aval) const { + return value < aval.value; + } +}; + +class Node { +private: + uint32_t cards_; + std::array suit_masks_; + char total_tricks_; + char trump_; + char score_; + char moves_; + bool player_; + std::vector history_; + uint64_t key_; +public: + Node(uint32_t cards, std::array suit_masks, char trump,bool player) { + cards_ = cards; + suit_masks_ = suit_masks; + total_tricks_ = __builtin_popcount(cards); + trump_ = trump; + moves_ = 0; + player_ = player; + score_ = 0; + history_ = {}; + }; + bool Player() { return player_; }; + char Score() { return score_; }; + char Moves() { return moves_; }; + bool IsTerminal() { + return (moves_ == 2 * total_tricks_); + } + char RemainingTricks() { + return (char)(total_tricks_-(moves_>>1)); + } + char TotalTricks() { + return total_tricks_; + } + uint32_t Cards() { return cards_; } + std::array SuitMasks() { return suit_masks_; } + uint64_t GetNodeKey() { return key_; } + bool Trick(ActionStruct lead, ActionStruct follow) { + //true if leader won// + return (lead.suit != follow.suit && lead.suit == trump_) || (lead.suit == follow.suit && lead.index <= follow.index); + } + + void RemoveCard(ActionStruct action) { + //Removes card from cards_// + uint32_t mask_b = ~0; + mask_b =_bzhi_u32(mask_b, action.index); + uint32_t mask_a = ~mask_b; + mask_a = _blsr_u32(mask_a); + uint32_t copy_a = cards_ & mask_a; + uint32_t copy_b = cards_ & mask_b; + copy_a = copy_a >> 1; + cards_ = copy_a | copy_b; + //decrements appropriate suits// + suit_masks_[action.suit] = _blsr_u32(suit_masks_[action.suit])>>1; + char suit = action.suit; + suit++; + while (suit < kNumSuits) { + suit_masks_[suit]=suit_masks_[suit] >> 1; + suit++; + } + } + void InsertCard(ActionStruct action) { + //inserts card into cards_// + uint32_t mask_b = ~0; + mask_b = _bzhi_u32(mask_b, action.index); + uint32_t mask_a = ~mask_b; + uint32_t copy_b = cards_ & mask_b; + uint32_t copy_a = cards_ & mask_a; + copy_a = copy_a << 1; + uint32_t card = action.player<< action.index; + cards_ = card | copy_a | copy_b; + //increments appropriate suits// + uint32_t new_suit = (suit_masks_[action.suit] & mask_b )| (1 << action.index); + suit_masks_[action.suit] = ((suit_masks_[action.suit] & mask_a) << 1 )| new_suit; + char suit = action.suit; + suit++; + while (suit < kNumSuits) { + suit_masks_[suit] = suit_masks_[suit] << 1; + suit++; + } + } + void UpdateNodeKey() { + //recasts the cards and suitlengths into quasi-canonical form// + //least sig part of 32bit card is trump, then suits in ascending length// + + //note this canonical form does not take advantage of all isomorphisms// + //suppose a game is transformed as follows: all card bits flipped and the player bit flipped, ie player 1 has the lead and has player 0s cards from the original game// + //this implies player 1 achieves the minimax value of the original game ie the value is remaining tricks - value of the original game for this transformed game// + //also does not take advantage of single suit isomorphism. Namely all single suit games with the same card distribution are isomorphic. Currently this considers all trump, all no trump games as distinct// + uint64_t suit_sig = 0; + char trump_length = __builtin_popcount(suit_masks_[trump_]); + if (trump_length > kNumRanks) { + throw; + } + std::vector non_trump_lengths; + for (char i = 0; i < kNumSuits; ++i) { + if (i != trump_) { + char length = __builtin_popcount(suit_masks_[i]); + uint32_t sig = suit_masks_[i]&cards_; + if (suit_masks_[i] != 0) { + sig = (sig >> (_tzcnt_u32(suit_masks_[i]))); + } + if (length > kNumRanks) { + throw 1; + } + non_trump_lengths.push_back(Triple{i,length,sig }); + } + } + //sorting takes advantage of two isomorphisms namely nontrump suits of nonequal length can be exchanged and the value of the game does not change// + //and this more complicated suppose two games with two or more (non_trump)suits of equal length, permuting those suits should not change the value of solved game ie it is an isomorphism// + std::sort(non_trump_lengths.begin(), non_trump_lengths.end()); + suit_sig = suit_sig | trump_length; + for (size_t i = 0; i < non_trump_lengths.size(); ++i) { + suit_sig = suit_sig | ((uint64_t)non_trump_lengths[i].length << (4*(i+1))); + } + suit_sig = suit_sig << 32; + std::array suit_cards; + suit_cards[0] = cards_ & suit_masks_[trump_]; + if (suit_masks_[trump_] != 0) { + suit_cards[0] = suit_cards[0] >> _tzcnt_u32(suit_masks_[trump_]); + } + uint32_t sum = __builtin_popcount(suit_masks_[trump_]); + uint32_t cards = 0|suit_cards[0]; + for (size_t i = 0; i < non_trump_lengths.size(); ++i) { + suit_cards[i] = cards_ & suit_masks_[non_trump_lengths[i].index]; + uint32_t val = 0; + if (suit_masks_[non_trump_lengths[i].index] != 0) { + val = _tzcnt_u32(suit_masks_[non_trump_lengths[i].index]); + } + suit_cards[i]= suit_cards[i] >>val; + suit_cards[i] = suit_cards[i] << sum; + sum += __builtin_popcount(suit_masks_[non_trump_lengths[i].index]); + cards = cards | suit_cards[i]; + } + //cards = cards | (player_ << 31); + key_ = suit_sig | (uint64_t)cards; +#ifdef DEBUG_KEY + std::cout <<"CARDS_ " << cards_ << std::endl; + std::cout << "CARDS " << cards << std::endl; + std::cout << "SUIT MASKS " << std::endl; + for (int i = 0; i < kNumSuits; ++i) { + std::cout << suit_masks_[i] << std::endl; + } + std::cout << "SUIT_SIG " << suit_sig << std::endl; + std::cout<<"KEY " << key_ << std::endl; +#endif + } + uint64_t AltKey() { + uint32_t mask = _bzhi_u32(~0, 2 * RemainingTricks()); + return key_ ^ (uint64_t)mask; + } + //Move Ordering Heuristics// + //These could Definitely be improved, very hacky// + int LeadOrdering(ActionStruct action) { + char suit = action.suit; + uint32_t copy_cards = cards_; + if (player_ == 0) { + copy_cards = ~copy_cards; + } + uint32_t suit_cards = copy_cards & suit_masks_[suit]; + uint32_t mask = suit_cards & ~(suit_cards >> 1); + //represents out of the stategically inequivalent cards in a suit that a player holds, what rank is it, rank 0 is highest rank etc// + int suit_rank = __builtin_popcount(_bzhi_u32(mask, action.index)); + ApplyAction(action); + std::vector moves = LegalActions(); + UndoAction(action); + int sum = 0; + for (size_t i = 0; i < moves.size(); ++i) { + sum += Trick(action, moves[i]); + } + if (sum == moves.size()) { + return action.suit == trump_ ? 0 - suit_rank : -1 * kNumRanks - suit_rank;//intriguing this seems to produce small perfomance increase// + } + if (sum == 0) { + return 2 * kNumRanks - suit_rank; + } + else { + return 1 * kNumRanks - suit_rank; + } + } + int FollowOrdering(ActionStruct action) { + ActionStruct lead = history_.back(); + //follow ordering for fast cut offs// + //win as cheaply as possible, followed by lose as cheaply as possible + char suit = action.suit; + uint32_t copy_cards = cards_; + if (player_ == 0) { + copy_cards = ~copy_cards; + } + uint32_t suit_cards = copy_cards & suit_masks_[suit]; + uint32_t mask = suit_cards & ~(suit_cards >> 1); + //represents out of the stategically inequivalent cards in a suit that a player holds, what rank is it, rank 0 is highest rank etc// + int suit_rank = __builtin_popcount(_bzhi_u32(mask, action.index)); + if (!Trick(lead, action)) { + return -kNumRanks - suit_rank; + } + else { + return -suit_rank; + } + } + + + + std::vector LegalActions() { + //Features// + //Move fusion and move ordering// + std::vector out; + out.reserve(kNumRanks); + uint32_t copy_cards = cards_; + std::array player_suit_masks; + if (player_ == 0) { + copy_cards = ~copy_cards; + } + for (size_t i = 0; i < kNumSuits; ++i) { + uint32_t suit_cards = copy_cards & suit_masks_[i]; + player_suit_masks[i] = suit_cards & ~(suit_cards >> 1); +#ifdef DEBUG + std::cout << "Cards " << cards_ << std::endl; + std::cout << "Suit Mask " << i << " " << suit_masks_[i] << std::endl; + std::cout << "Player " << player_ << " suit mask " << (int)i << " " << player_suit_masks[i] << std::endl; +#endif + } + std::vector temp; + temp.reserve(kNumRanks); + for (char i = 0; i < kNumSuits; ++i) { + uint32_t suit_mask = player_suit_masks[i]; + bool lead = (moves_ % 2 == 0); + bool follow = (moves_ % 2 == 1); + bool correct_suit = 0; + bool void_in_suit = 0; + if (follow == true) { + correct_suit = (history_.back().suit == i); + void_in_suit = (player_suit_masks[history_.back().suit] == 0); + } + if ((lead || (follow && (correct_suit || void_in_suit)))) { + while (suit_mask != 0) { + uint32_t best = _tzcnt_u32(suit_mask); + if (moves_ % 2 == 0) { + temp.push_back({ ActionStruct(best, i, player_),LeadOrdering(ActionStruct(best, i, player_)) }); + } + else { + temp.push_back({ ActionStruct(best, i, player_),FollowOrdering(ActionStruct(best, i, player_)) }); + } + suit_mask = _blsr_u32(suit_mask); + } + } + } + std::sort(temp.begin(), temp.end()); + for (size_t i = 0; i < temp.size(); ++i) { + out.push_back(temp[i].action); + } + +#ifdef DEBUG + std::cout << "Player " << player_ << " MoveGen " << std::endl; + for (size_t i = 0; i < out.size(); ++i) { + std::cout << out[i].index << " " << (int)out[i].suit << std::endl; + } +#endif + return out; + } + void ApplyAction(ActionStruct action) { +#ifdef DEBUG + std::cout << "Player " << player_ << " ApplyAction " << action.index << " " << (int)action.suit << std::endl; +#endif + if (moves_ % 2 == 1) { + ActionStruct lead = history_.back(); + bool winner = !((Trick(lead, action)) ^ lead.player); +#ifdef DEBUG + std::cout << "Player " << winner << " won this trick" << std::endl; +#endif + score_ += (winner == 0); + player_ = (winner); + } + else { + player_ = !player_; + } +#ifdef DEBUG + assert((suit_masks_[0] & suit_masks_[1]) == 0); + assert((suit_masks_[0] & suit_masks_[2])== 0); + assert((suit_masks_[0] & suit_masks_[3]) == 0); + assert((suit_masks_[1] & suit_masks_[2]) == 0); + assert((suit_masks_[1] & suit_masks_[3]) == 0); + assert((suit_masks_[2] & suit_masks_[3]) == 0); +#endif + RemoveCard(action); + moves_++; + history_.push_back(action); + } + void UndoAction(ActionStruct action) { + if (moves_ % 2 == 0) { + ActionStruct lead = history_[history_.size() - 2]; + ActionStruct follow = history_[history_.size() - 1]; + bool winner = !(Trick(lead, follow) ^ lead.player); + score_ -= (winner == 0); + } + InsertCard(action); + moves_--; + player_=history_.back().player; + history_.pop_back(); +#ifdef DEBUG + std::cout << "Player " << player_ << " UndoAction " << action.index << " " << (int)action.suit << std::endl; +#endif + } +}; + + + +//solvers below +int AlphaBeta(Node* node, int alpha, int beta) { + //fail soft ab search + if (node->IsTerminal()) { + return node->Score(); + } + else if (node->Player() == 0) { + int val = 0; + std::vector actions = node->LegalActions(); + for (int i = 0; i < actions.size(); ++i) { + node->ApplyAction(actions[i]); + val = std::max(val, AlphaBeta(node, alpha, beta)); + node->UndoAction(actions[i]); + alpha = std::max(val, alpha); + if (val >= beta) { + break; + } + } + return val; + } + else if (node->Player() == 1) { + int val =node->TotalTricks(); + std::vector actions = node->LegalActions(); + for (int i = 0; i < actions.size(); ++i) { + node->ApplyAction(actions[i]); + val = std::min(val, AlphaBeta(node, alpha, beta)); + node->UndoAction(actions[i]); + beta = std::min(val, beta); + if (val <= alpha) { + break; + } + } + return val; + } + return -1; +}; + + + +//Helper Functions// + + +//Credit to computationalcombinatorics.wordpress.com +//hideous code for generating the next colexicographical combination// +bool NextColex(std::vector& v, int k) { + int num = 0; + for (int i = 0; i < v.size(); ++i) { + if (i == v.size() - 1) { + v[i] = v[i] + 1; + if (v[i] > k - v.size() + i) { + return false; + } + num = i; + break; + } + else if (v[i + 1] - v[i] > 1 && v[i + 1] != i) { + v[i] = v[i] + 1; + if (v[i] > k - v.size() + i) { + return false; + } + num = i; + break; + } + } + for (int i = 0; i < num; ++i) { + v[i] = i; + } + return true; +} + + + +char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, std::vector* TTable,std::unordered_map* SuitRanks, std::vector>& bin_coeffs) { + //fail soft ab search + char val = 0; + uint64_t key = 0; + bool player = node->Player(); + if (node->IsTerminal()) { + return node->Score(); + } + if (node->Moves() % 2 == 0&& depth==0) { + node->UpdateNodeKey(); + key = (player) ? node->AltKey() : node->GetNodeKey(); + uint32_t cards = key & _bzhi_u64(~0, 32); + uint32_t colex = HalfColexer(cards, &bin_coeffs); + uint32_t suits = (key & (~0 ^ _bzhi_u64(~0, 32))) >> 32; + uint32_t suit_rank = SuitRanks->at(suits); + char value = (player) ? node->RemainingTricks() - TTable->at(colex).Get(suit_rank) :TTable->at(colex).Get(suit_rank); + return value+node->Score(); + } + else if (node->Player() == 0) { + val = 0; + std::vector actions = node->LegalActions(); + for (int i = 0; i < actions.size(); ++i) { + node->ApplyAction(actions[i]); + val = std::max(val,IncrementalAlphaBetaMemoryIso(node, alpha, beta,depth-1, TTable,SuitRanks,bin_coeffs)); + node->UndoAction(actions[i]); + alpha = std::max(val, alpha); + if (val >= beta) { + break; + } + } + } + else if (node->Player() == 1) { + val =node->TotalTricks(); + std::vector actions = node->LegalActions(); + for (int i = 0; i < actions.size(); ++i) { + node->ApplyAction(actions[i]); + val = std::min(val, IncrementalAlphaBetaMemoryIso(node, alpha, beta,depth-1, TTable,SuitRanks,bin_coeffs)); + node->UndoAction(actions[i]); + beta = std::min(val, beta); + if (val <= alpha) { + break; + } + } + } + return val; +}; + + +char IncrementalMTD(Node* node, char guess,int depth, std::vector* TTable,std::unordered_map* SuitRanks,std::vector>& bin_coeffs) { + char g = guess; + char upperbound = node->TotalTricks(); + char lowerbound = 0; + while (lowerbound < upperbound) { + char beta; + (g == lowerbound) ? beta = g + 1 : beta = g; + g = IncrementalAlphaBetaMemoryIso(node, beta - 1, beta,depth,TTable,SuitRanks, bin_coeffs); + (g < beta) ? upperbound = g : lowerbound = g; + } + return g; +} +std::vector GWhistGenerator(int num,unsigned int seed){ + //generates pseudorandom endgames// + std::vector out; + out.reserve(num); + std::mt19937 g(seed); + std::array nums; + for (int i = 0; i < 2 * kNumRanks; ++i) { + nums[i] = i; + } + for (int i = 0; i < num; ++i) { + std::shuffle(nums.begin(), nums.end(), g); + uint32_t cards = 0; + std::array suits; + for (int j = 0; j < kNumRanks; ++j) { + cards = cards | (1 << nums[j]); + } + int sum = 0; + std::vector suit_lengths = {0,0,0,0}; + for(int j =0;j distrib(min,max); + suit_lengths[j] = distrib(g); + sum+= suit_lengths[j]; + } + suit_lengths[kNumSuits-1]=2*kNumRanks-sum; + sum =0; + for(int j =0;jkNumRanks){ + throw; + } + } + if(sum!= 2*kNumRanks){ + for(int j =0;j* outTTable, std::vector* TTable, std::vector>& bin_coeffs, std::vector& suit_splits, std::unordered_map& SuitRanks, size_t start_id, size_t end_id) { + //takes endgames solved to depth d-1 and returns endgames solved to depth d // + std::cout<<"in threadsolver"<<"\n"; + std::vector combination; + combination.reserve(size_endgames); + for (int i = 0; i < size_endgames; ++i) { + combination.push_back(i); + } + bool control = true; + int count = 0; + uint32_t cards = 0; + for (int i = 0; i < combination.size(); ++i) { + cards = cards | (1 << combination[i]); + } + while (count < start_id) { + NextColex(combination, 2 * size_endgames); + count++; + } + while (count < end_id && control) { + uint32_t cards = 0; + for (int i = 0; i < combination.size(); ++i) { + cards = cards | (1 << combination[i]); + } + for (int i = 0; i < suit_splits.size(); ++i) { + std::array suit_arr; + suit_arr[0] = _bzhi_u32(~0, suit_splits[i] & 0b1111); + int sum = suit_splits[i] & 0b1111; + for (int j = 1; j < kNumSuits; ++j) { + uint32_t mask = _bzhi_u32(~0, sum); + sum += (suit_splits[i] & (0b1111 << (4 * j))) >> 4 * j; + suit_arr[j] = _bzhi_u32(~0, sum); + suit_arr[j] = suit_arr[j] ^ mask; + } + Node node(cards, suit_arr, 0, false); + char result = IncrementalMTD(&node, (size_endgames >> 1), 2, TTable, &SuitRanks, bin_coeffs); + outTTable->at(count).Set(i, result); + } + control = NextColex(combination, 2 * size_endgames); + count++; + } +} +std::vector RetroSolver(int size_endgames, std::vector* TTable, std::vector>& bin_coeffs) { + //takes endgames solved to depth d-1 and returns endgames solved to depth d // + std::cout<<"In retrosolver"<<"\n"; + std::vector outTTable = InitialiseTTable(size_endgames, bin_coeffs); + std::vector suit_splits = GenQuads(size_endgames); + std::unordered_map SuitRanks; + GenSuitRankingsRel(size_endgames - 1, &SuitRanks); + std::vector combination; + combination.reserve(size_endgames); + for (int i = 0; i < size_endgames; ++i) { + combination.push_back(i); + } + uint32_t v_length = (suit_splits.size() >> 1) + 1; + uint32_t min_block_size = 256; + uint32_t hard_threads = std::thread::hardware_concurrency(); + uint32_t num_threads = 1; + uint32_t num_outers =outTTable.size(); + //a haphazard attempt to mitigate false sharing// + for (uint32_t i = hard_threads; i >= 1; i--) { + if ((num_outers * v_length / i) >= min_block_size) { + num_threads = i; + break; + } + } + std::vector threads = {}; + for (int i = 0; i < num_threads; ++i) { + uint32_t block_size = num_outers / num_threads; + uint32_t start_id; + uint32_t end_id; + if (num_threads == 1) { + start_id = 0; + end_id = num_outers; + } + else if (i == num_threads - 1) { + start_id = block_size * (num_threads - 1); + end_id = num_outers; + } + else { + start_id = block_size * i; + end_id = block_size * (i + 1); + } + threads.push_back(std::thread(ThreadSolver, size_endgames, &outTTable, TTable,std::ref(bin_coeffs), std::ref(suit_splits), std::ref(SuitRanks), start_id, end_id)); + } + for (int i = 0; i >& bin_coeffs) { + //Tests endgame solution with TTable vs raw seach + std::vector nodes = GWhistGenerator(samples, seed); + std::vector v; + for (int i = 1; i <= depth; ++i) { + v = RetroSolver(i, &v, bin_coeffs); + } + std::unordered_map SuitRanks; + GenSuitRankingsRel(depth, &SuitRanks); + for (auto it = nodes.begin(); it != nodes.end(); ++it) { + char abm_unsafe = IncrementalMTD(&*it, 6, 2 * (kNumRanks - depth), &v, &SuitRanks, bin_coeffs); + char abm_safe = AlphaBeta(&*it, 0, kNumRanks); + if (abm_unsafe != abm_safe) { + return false; + } + } + return true; +} +std::vector BuildTablebase(std::vector>& bin_coeffs) { + std::vector v; + std::cout<<"Building Tablebase"<<"\n"; + for (int i = 1; i <= kNumRanks; ++i) { + v = RetroSolver(i, &v, bin_coeffs); + std::cout<<"Done "<& table_base, std::vector>& bin_coeffs) { + std::vector nodes = GWhistGenerator(samples, seed); + std::unordered_map SuitRanks; + GenSuitRankingsRel(kNumRanks, &SuitRanks); + for (auto it = nodes.begin(); it != nodes.end(); ++it) { + char abm_unsafe = IncrementalMTD(&*it, 6, 0, &table_base, &SuitRanks, bin_coeffs); + char abm_safe = AlphaBeta(&*it, 0, kNumRanks); + if (abm_unsafe != abm_safe) { + return false; + } + } + return true; +} +void StoreTTable(const std::string filename, const std::vector& solution){ + //stores solution into a text file// + std::ofstream file(filename); + for(int i =0;i& v,int depth,std::vector>& bin_coeffs){ + //Tests storage fidelity// + StoreTTable(filename,v); + std::vector new_v = LoadTTable(filename,depth,bin_coeffs); + for(int i =0;i> bin_coeffs = open_spiel::german_whist_foregame::BinCoeffs(2*open_spiel::german_whist_foregame::kNumRanks); + std::cout<<"Hello"<<"\n"; + std::vector tablebase = open_spiel::german_whist_foregame::BuildTablebase(bin_coeffs); + std::random_device rd; + int num_samples = 100; + if(open_spiel::german_whist_foregame::TestTablebase(num_samples,rd(),tablebase,bin_coeffs)){ + std::cout<<"Tablebase accurate"< +//to do +//InfostateTensor implementation +// PR!!!!! -#include -#include -#include -#include #include "open_spiel/abseil-cpp/absl/strings/str_cat.h" #include "open_spiel/game_parameters.h" @@ -11,364 +11,604 @@ #include "open_spiel/policy.h" #include "open_spiel/spiel.h" #include "open_spiel/spiel_utils.h" +#include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" namespace open_spiel { namespace german_whist_foregame { -namespace { - -// Default parameters. -// Facts about the game -const GameType kGameType{/*short_name=*/"german_whist_foregame", - /*long_name=*/"german_whist_foregame", - GameType::Dynamics::kSequential, - GameType::ChanceMode::kExplicitStochastic, - GameType::Information::kImperfectInformation, - GameType::Utility::kZeroSum, - GameType::RewardModel::kTerminal, - /*max_num_players=*/2, - /*min_num_players=*/2, - /*provides_information_state_string=*/true, - /*provides_information_state_tensor=*/true, - /*provides_observation_string=*/true, - /*provides_observation_tensor=*/true, - /*parameter_specification=*/ - {{"players", GameParameter(kDefaultPlayers)}}, - /*default_loadable=*/true, - /*provides_factored_observation_string=*/true, - }; - -std::shared_ptr Factory(const GameParameters& params) { - return std::shared_ptr(new GermanWhistForegameGame(params)); +std::string kTTablePath="./Documents/Github/open_spiel/open_spiel/games/german_whist_foregame/TTables/TTable13.txt"; +bool Triple::operator<(const Triple& triple)const{ + return (length < triple.length)|| (length == triple.length && sig < triple.sig); } -REGISTER_SPIEL_GAME(kGameType, Factory); - -open_spiel::RegisterSingleTensorObserver single_tensor(kGameType.short_name); -} // namespace - -class GermanWhistForegameObserver : public Observer { - public: - GermanWhistForegameObserver(IIGObservationType iig_obs_type) - : Observer(/*has_string=*/true, /*has_tensor=*/true), - iig_obs_type_(iig_obs_type) {} - - void WriteTensor(const State& observed_state, int player, - Allocator* allocator) const override { - } - - std::string StringFrom(const State& observed_state, - int player) const override { - } - - private: - IIGObservationType iig_obs_type_; -}; - -GermanWhistForegameState::GermanWhistForegameState(std::shared_ptr game) - : State(game), - first_bettor_(kInvalidPlayer), - card_dealt_(game->NumPlayers() + 1, kInvalidPlayer), - winner_(kInvalidPlayer), - pot_(kAnte * game->NumPlayers()), - // How much each player has contributed to the pot, indexed by pid. - ante_(game->NumPlayers(), kAnte) {} - -int GermanWhistForegameState::CurrentPlayer() const { - if (IsTerminal()) { - return kTerminalPlayerId; - } else { - return (history_.size() < num_players_) ? kChancePlayerId - : history_.size() % num_players_; - } +inline int CardRank(int card, int suit) { + uint64_t card_mask = ((uint64_t)1 << card); + card_mask = (card_mask >> (suit * kNumRanks)); + return _tzcnt_u64(card_mask); } - -void GermanWhistForegameState::DoApplyAction(Action move) { - // Additional book-keeping - if (history_.size() < num_players_) { - // Give card `move` to player `history_.size()` (CurrentPlayer will return - // kChancePlayerId, so we use that instead). - card_dealt_[move] = history_.size(); - } else if (move == ActionType::kBet) { - if (first_bettor_ == kInvalidPlayer) first_bettor_ = CurrentPlayer(); - pot_ += 1; - ante_[CurrentPlayer()] += kAnte; - } - - // We undo that before exiting the method. - // This is used in `DidBet`. - history_.push_back({CurrentPlayer(), move}); - - // Check for the game being over. - const int num_actions = history_.size() - num_players_; - if (first_bettor_ == kInvalidPlayer && num_actions == num_players_) { - // Nobody bet; the winner is the person with the highest card dealt, - // which is either the highest or the next-highest card. - // Losers lose 1, winner wins 1 * (num_players - 1) - winner_ = card_dealt_[num_players_]; - if (winner_ == kInvalidPlayer) winner_ = card_dealt_[num_players_ - 1]; - } else if (first_bettor_ != kInvalidPlayer && - num_actions == num_players_ + first_bettor_) { - // There was betting; so the winner is the person with the highest card - // who stayed in the hand. - // Check players in turn starting with the highest card. - for (int card = num_players_; card >= 0; --card) { - const Player player = card_dealt_[card]; - if (player != kInvalidPlayer && DidBet(player)) { - winner_ = player; - break; - } - } - SPIEL_CHECK_NE(winner_, kInvalidPlayer); - } - history_.pop_back(); +inline int CardSuit(int card) { + uint64_t card_mask = ((uint64_t)1 << card); + for (int i = 0; i < kNumSuits; ++i) { + if (_mm_popcnt_u64(card_mask & kSuitMasks[i]) == 1) { + return i; + } + } + return kNumSuits; +} +std::string CardString(int card) { + int suit = CardSuit(card); + return { kSuitChar[suit],kRankChar[CardRank(card,suit)] }; } -std::vector GermanWhistForegameState::LegalActions() const { - if (IsTerminal()) return {}; - if (IsChanceNode()) { - std::vector actions; - for (int card = 0; card < card_dealt_.size(); ++card) { - if (card_dealt_[card] == kInvalidPlayer) actions.push_back(card); +std::vector GenQuads(int size_endgames) { + //Generates Suit splittings for endgames of a certain size// + std::vector v; + for (char i = 0; i <= std::min(size_endgames * 2, kNumRanks); ++i) { + int sum = size_endgames * 2 - i; + for (char j = 0; j <= std::min(sum, kNumRanks); ++j) { + for (char k = std::max((int)j, sum - j - kNumRanks); k <= std::min(sum - j, kNumRanks); ++k) { + char l = sum - j - k; + if (l < k) { + break; + } + else { + uint32_t num = 0; + num = num | (i); + num = num | (j << 4); + num = num | (k << 8); + num = num | (l << 12); + v.push_back(num); + } + } + } } - return actions; - } else { - return {ActionType::kPass, ActionType::kBet}; - } + return v; } - -std::string GermanWhistForegameState::ActionToString(Player player, Action move) const { - if (player == kChancePlayerId) - return absl::StrCat("Deal:", move); - else if (move == ActionType::kPass) - return "Pass"; - else - return "Bet"; +std::vector> BinCoeffs(uint32_t max_n) { + //tabulates binomial coefficients// + std::vector> C(max_n+1,std::vector(max_n+1)); + for (uint32_t i = 1; i <= max_n; ++i) { + C[0][i] = 0; + } + for (uint32_t i = 0; i <= max_n; ++i) { + C[i][0] = 1; + } + for (uint32_t i = 1; i <= max_n; ++i) { + for (uint32_t j = 1; j <= max_n; ++j) { + C[i][j] = C[i - 1][j] + C[i - 1][j - 1]; + } + } + return C; +} +uint32_t HalfColexer(uint32_t cards,const std::vector>* bin_coeffs) { + //returns the colexicographical ranking of a combination of indices where the the size of the combination is half that of the set of indices// + uint32_t out = 0; + uint32_t count = 0; + while (cards != 0) { + uint32_t ind = _tzcnt_u32(cards); + uint32_t val = bin_coeffs->at(ind)[count+1]; + out += val; + cards = _blsr_u32(cards); + count++; + } + return out; +} +void GenSuitRankingsRel(uint32_t size, std::unordered_map* Ranks) { + //Generates ranking Table for suit splittings for endgames of a certain size// + std::vector v=GenQuads(size); + for (uint32_t i = 0; i < v.size(); ++i) { + Ranks->insert({ v[i],i }); + } } -std::string GermanWhistForegameState::ToString() const { - // The deal: space separated card per player - std::string str; - for (int i = 0; i < history_.size() && i < num_players_; ++i) { - if (!str.empty()) str.push_back(' '); - absl::StrAppend(&str, history_[i].action); - } - - // The betting history: p for Pass, b for Bet - if (history_.size() > num_players_) str.push_back(' '); - for (int i = num_players_; i < history_.size(); ++i) { - str.push_back(history_[i].action ? 'b' : 'p'); - } - - return str; +vectorNa::vectorNa(size_t num,char val){ + data=std::vector((num>>1)+1,val); +} +size_t vectorNa::size() const{ + return data.size(); +} +char const& vectorNa::operator[](size_t index) const{ + return data[index]; +} +void vectorNa::SetChar(size_t index,char value){ + data[index]=value; +} +char vectorNa::Get(size_t index) const{ + int remainder = index&0b1; + if(remainder==0){ + return 0b1111&data[index>>1]; + } + else{ + return ((0b11110000&data[index>>1])>>4); + } +} +void vectorNa::Set(size_t index,char value){ + int remainder = index & 0b1; + if (remainder == 0) { + char datastore = 0b11110000 & data[index>>1]; + data[index>>1] = datastore|value; + } + else { + char datastore = (0b1111 & data[index >> 1]); + data[index >> 1] = datastore|(value << 4); + } +} +std::vector InitialiseTTable(int size,std::vector>& bin_coeffs) { + //initialises TTable for a certain depth// + size_t suit_size = GenQuads(size).size(); + return std::vector(bin_coeffs[2 * size][size], vectorNa(suit_size, 0)); +} +std::vector LoadTTable(const std::string filename, int depth,std::vector>& bin_coeffs){ + //loads solution from a text file into a vector for use// + std::cout<<"Loading Tablebase"< v = InitialiseTTable(depth,bin_coeffs); + std::ifstream file(filename,std::ios::binary); + //std::cout< GermanWhistForegameState::Returns() const { - if (!IsTerminal()) { - return std::vector(num_players_, 0.0); - } +namespace {//namespace +// Facts about the game +const GameType kGameType{/*short_name=*/"german_whist_foregame", + /*long_name=*/"german_whist_foregame", + GameType::Dynamics::kSequential, + GameType::ChanceMode::kExplicitStochastic, + GameType::Information::kImperfectInformation, + GameType::Utility::kZeroSum, + GameType::RewardModel::kTerminal, + /*max_num_players=*/2, + /*min_num_players=*/2, + /*provides_information_state_string=*/true, + /*provides_information_state_tensor=*/false, + /*provides_observation_string=*/true, + /*provides_observation_tensor=*/false, +}; - std::vector returns(num_players_); - for (auto player = Player{0}; player < num_players_; ++player) { - const int bet = DidBet(player) ? 2 : 1; - returns[player] = (player == winner_) ? (pot_ - bet) : -bet; - } - return returns; +std::shared_ptr Factory(const GameParameters& params) { + return std::shared_ptr(new GWhistFGame(params)); } -std::string GermanWhistForegameState::InformationStateString(Player player) const { - const GermanWhistForegameGame& game = open_spiel::down_cast(*game_); - return game.info_state_observer_->StringFrom(*this, player); +REGISTER_SPIEL_GAME(kGameType, Factory); +}//namespace + +GWhistFGame::GWhistFGame(const GameParameters& params):Game(kGameType, params){ + bin_coeffs_=BinCoeffs(2*kNumRanks); + std::unordered_map temp; + GenSuitRankingsRel(13,&temp); + suit_ranks_=temp; + ttable_ = LoadTTable(kTTablePath,13,bin_coeffs_); +}; +std::unique_ptr GWhistFGame::NewInitialState() const { + const auto ptr=std::dynamic_pointer_cast(shared_from_this()); + return std::make_unique(ptr); } -std::string GermanWhistForegameState::ObservationString(Player player) const { - const GermanWhistForegameGame& game = open_spiel::down_cast(*game_); - return game.default_observer_->StringFrom(*this, player); -} -void GermanWhistForegameState::InformationStateTensor(Player player, - absl::Span values) const { - ContiguousAllocator allocator(values); - const GermanWhistForegameGame& game = open_spiel::down_cast(*game_); - game.info_state_observer_->WriteTensor(*this, player, &allocator); +GWhistFState::GWhistFState(std::shared_ptr game):State(game) { + player_ = kChancePlayerId; + move_number_ = 0; + trump_ = -1; + deck_ = _bzhi_u64(~0,kNumRanks*kNumSuits); + discard_ = 0; + hands_ = { 0,0 }; + history_.reserve(78); + ttable_ = &(game->ttable_); + suit_ranks_ =&(game->suit_ranks_); + bin_coeffs_=&(game->bin_coeffs_); } - -void GermanWhistForegameState::ObservationTensor(Player player, - absl::Span values) const { - ContiguousAllocator allocator(values); - const GermanWhistForegameGame& game = open_spiel::down_cast(*game_); - game.default_observer_->WriteTensor(*this, player, &allocator); +bool GWhistFState::Trick(int lead, int follow) const { + int lead_suit = CardSuit(lead); + int follow_suit = CardSuit(follow); + int lead_rank = CardRank(lead,lead_suit); + int follow_rank = CardRank(follow,follow_suit); + return (lead_suit == follow_suit && lead_rank < follow_rank) || (lead_suit != follow_suit && follow_suit != trump_); } - -std::unique_ptr GermanWhistForegameState::Clone() const { - return std::unique_ptr(new GermanWhistForegameState(*this)); +bool GWhistFState::IsTerminal() const { + return(_mm_popcnt_u64(deck_) == 0); } - -void GermanWhistForegameState::UndoAction(Player player, Action move) { - if (history_.size() <= num_players_) { - // Undoing a deal move. - card_dealt_[move] = kInvalidPlayer; - } else { - // Undoing a bet / pass. - if (move == ActionType::kBet) { - pot_ -= 1; - if (player == first_bettor_) first_bettor_ = kInvalidPlayer; - } - winner_ = kInvalidPlayer; - } - history_.pop_back(); - --move_number_; +uint64_t GWhistFState::EndgameKey(int player_to_move) const{ + //generates a 64 bit unsigned int where the first 32 are the suit ownerships from the perspective of the opponent using canonical rankings// + //example: if Spade suit is to_move = A3, opp =2, suit = 0b100 + //least significant part of first 32 bits is the trump suit, then the remaining suits ascending length order. + uint64_t cards_in_play = hands_[0]|hands_[1]; + std::vector suit_lengths = {}; + int opp = (player_to_move==0)?1:0; + //sort trump suits by length,then sig// + for(int i =0;i hand0; + std::array hand1; + hand0[0]=_pext_u64(hands_[0],kSuitMasks[trump_]); + hand1[0]=_pext_u64(hands_[1],kSuitMasks[trump_]); + for(int i =0;ihands_shuffled = {0,0}; + for(int i =0;i> GermanWhistForegameState::ChanceOutcomes() const { - SPIEL_CHECK_TRUE(IsChanceNode()); - std::vector> outcomes; - const double p = 1.0 / (num_players_ + 1 - history_.size()); - for (int card = 0; card < card_dealt_.size(); ++card) { - if (card_dealt_[card] == kInvalidPlayer) outcomes.push_back({card, p}); - } - return outcomes; +std::vector GWhistFState::Returns() const{ + if(IsTerminal()){ + std::vector out = {0,0}; + int lead_win = Trick(history_[move_number_ - 3].action, history_[move_number_ - 2].action); + int player_to_move=(lead_win)?history_[move_number_-3].player:history_[move_number_-2].player; + int opp = (player_to_move==0)?1:0; + uint64_t key = EndgameKey(player_to_move); + uint32_t cards = (key&_bzhi_u64(~0,32)); + uint32_t colex = HalfColexer(cards,bin_coeffs_); + uint32_t suits = (key&(~0^_bzhi_u64(~0,32)))>>32; + uint32_t suit_rank = suit_ranks_->at(suits); + char value =ttable_->at(colex).Get(suit_rank); + out[player_to_move] = 2*value-kNumRanks; + out[opp]=-out[player_to_move]; + return out; + } + else{ + std::vector out = {0,0}; + return out; + } } -bool GermanWhistForegameState::DidBet(Player player) const { - if (first_bettor_ == kInvalidPlayer) { - return false; - } else if (player == first_bettor_) { - return true; - } else if (player > first_bettor_) { - return history_[num_players_ + player].action == ActionType::kBet; - } else { - return history_[num_players_ * 2 + player].action == ActionType::kBet; - } -} -std::unique_ptr GermanWhistForegameState::ResampleFromInfostate( - int player_id, std::function rng) const { - std::unique_ptr state = game_->NewInitialState(); - Action player_chance = history_.at(player_id).action; - for (int p = 0; p < game_->NumPlayers(); ++p) { - if (p == history_.size()) return state; - if (p == player_id) { - state->ApplyAction(player_chance); - } else { - Action other_chance = player_chance; - while (other_chance == player_chance) { - other_chance = SampleAction(state->ChanceOutcomes(), rng()).first; - } - state->ApplyAction(other_chance); - } - } - SPIEL_CHECK_GE(state->CurrentPlayer(), 0); - if (game_->NumPlayers() == history_.size()) return state; - for (int i = game_->NumPlayers(); i < history_.size(); ++i) { - state->ApplyAction(history_.at(i).action); - } - return state; -} +int GWhistFState::CurrentPlayer() const { return player_; } -GermanWhistForegameGame::GermanWhistForegameGame(const GameParameters& params) - : Game(kGameType, params), num_players_(ParameterValue("players")) { - SPIEL_CHECK_GE(num_players_, kGameType.min_num_players); - SPIEL_CHECK_LE(num_players_, kGameType.max_num_players); - default_observer_ = std::make_shared(kDefaultObsType); - info_state_observer_ = std::make_shared(kInfoStateObsType); - private_observer_ = std::make_shared( - IIGObservationType{/*public_info*/false, - /*perfect_recall*/false, - /*private_info*/PrivateInfoType::kSinglePlayer}); - public_observer_ = std::make_shared( - IIGObservationType{/*public_info*/true, - /*perfect_recall*/false, - /*private_info*/PrivateInfoType::kNone}); +std::vector> GWhistFState::ChanceOutcomes() const { + std::vector> outcomes; + std::vector legal_actions = LegalActions(); + for(int i =0;i pair; + pair.first =legal_actions[i]; + pair.second = 1/double(legal_actions.size()); + outcomes.push_back(pair); + } + return outcomes; } - -std::unique_ptr GermanWhistForegameGame::NewInitialState() const { - return std::unique_ptr(new GermanWhistForegameState(shared_from_this())); +std::string GWhistFState::ActionToString(Player player,Action move) const { + return CardString(move); } - -std::vector GermanWhistForegameGame::InformationStateTensorShape() const { - // One-hot for whose turn it is. - // One-hot encoding for the single private card. (n+1 cards = n+1 bits) - // Followed by 2 (n - 1 + n) bits for betting sequence (longest sequence: - // everyone except one player can pass and then everyone can bet/pass). - // n + n + 1 + 2 (n-1 + n) = 6n - 1. - return {6 * num_players_ - 1}; +std::string GWhistFState::ToString() const{ + std::string out; + for (int i = 0; i < history_.size(); ++i) { + out += ActionToString(history_[i].player, history_[i].action); + out += "\n"; + } + return out; } - -std::vector GermanWhistForegameGame::ObservationTensorShape() const { - // One-hot for whose turn it is. - // One-hot encoding for the single private card. (n+1 cards = n+1 bits) - // Followed by the contribution of each player to the pot (n). - // n + n + 1 + n = 3n + 1. - return {3 * num_players_ + 1}; +std::unique_ptr GWhistFState::Clone() const{ + return std::unique_ptr(new GWhistFState(*this)); } -double GermanWhistForegameGame::MaxUtility() const { - // In poker, the utility is defined as the money a player has at the end - // of the game minus then money the player had before starting the game. - // Everyone puts a chip in at the start, and then they each have one more - // chip. Most that a player can gain is (#opponents)*2. - return (num_players_ - 1) * 2; -} +std::string GWhistFState::StateToString() const { + //doesnt use history in case of a resampled state with unreconciled history// + std::string out; + uint64_t copy_deck = deck_; + uint64_t copy_discard = discard_; + std::array copy_hands = hands_; + std::vector deck_cards; + std::vector player0_cards; + std::vector player1_cards; + std::vector discard; + while (copy_deck != 0) { + deck_cards.push_back(_tzcnt_u64(copy_deck)); + copy_deck = _blsr_u64(copy_deck); + } + while (copy_discard != 0) { + discard.push_back(_tzcnt_u64(copy_discard)); + copy_discard = _blsr_u64(copy_discard); + } -double GermanWhistForegameGame::MinUtility() const { - // In poker, the utility is defined as the money a player has at the end - // of the game minus then money the player had before starting the game. - // In GermanWhistForegame, the most any one player can lose is the single chip they paid - // to play and the single chip they paid to raise/call. - return -2; -} + while (copy_hands[0] != 0) { + player0_cards.push_back(_tzcnt_u64(copy_hands[0])); + copy_hands[0] = _blsr_u64(copy_hands[0]); + } + while (copy_hands[1] != 0) { + player1_cards.push_back(_tzcnt_u64(copy_hands[1])); + copy_hands[1] = _blsr_u64(copy_hands[1]); + } + out += "Deck \n"; + for (int i = 0; i < deck_cards.size(); ++i) { + out += CardString(deck_cards[i]) + "\n"; + } + out += "Discard \n"; + for (int i = 0; i < discard.size(); ++i) { + out += CardString(discard[i]) + "\n"; + } -std::shared_ptr GermanWhistForegameGame::MakeObserver( - absl::optional iig_obs_type, - const GameParameters& params) const { - if (params.empty()) { - return std::make_shared( - iig_obs_type.value_or(kDefaultObsType)); - } else { - return MakeRegisteredObserver(iig_obs_type, params); - } + for (int i = 0; i < 2; ++i) { + out += "Player " + std::to_string(i) + "\n"; + std::vector var; + if (i == 0) { + var = player0_cards; + } + else { + var = player1_cards; + } + for (int j = 0; j < var.size(); ++j) { + out += CardString(var[j]) + "\n"; + } + } + return out; } - -TabularPolicy GetAlwaysPassPolicy(const Game& game) { - SPIEL_CHECK_TRUE( - dynamic_cast(const_cast(&game)) != nullptr); - return GetPrefActionPolicy(game, {ActionType::kPass}); +std::string GWhistFState::InformationStateString(Player player) const{ + //THIS IS WHAT A PLAYER IS SHOWN WHEN PLAYING// + std::string p = std::to_string(player)+","; + std::string cur_hand = ""; + std::string observations=""; + std::vector v_hand = {}; + uint64_t p_hand = hands_[player]; + while(p_hand!=0){ + v_hand.push_back(_tzcnt_u64(p_hand)); + p_hand = _blsr_u64(p_hand); + } + std::sort(v_hand.begin(),v_hand.end()); + for(int i =0;i(const_cast(&game)) != nullptr); - return GetPrefActionPolicy(game, {ActionType::kBet}); +std::unique_ptr GWhistFState::ResampleFromInfostate(int player_id,std::function rng) const{ + //only valid when called from a position where a player can act// + auto resampled_state = std::unique_ptr(new GWhistFState(*this)); + //seeding mt19937// + std::random_device rd; + std::mt19937 gen(rd()); + uint64_t necessary_cards = 0; + for (int i = 2 * kNumRanks; i < history_.size(); i+=4) { + //face up cards from deck// + necessary_cards = (necessary_cards | (uint64_t(1) << history_[i].action)); + } + int move_index = move_number_ - ((kNumRanks * kNumSuits) / 2); + int move_remainder = move_index % 4; + int opp = (player_id == 0) ? 1 : 0; + int recent_faceup = move_number_ - move_remainder; + uint64_t recent_faceup_card = (uint64_t(1) << history_[recent_faceup].action); + // if a face up card from the deck is not in players hand or discard it must be in opps unless it is the most recent face up// + necessary_cards = (necessary_cards & (~(hands_[player_id] | discard_|recent_faceup_card))); + //sufficient cards are all cards not in players hand,the discard, or the recent face up// + uint64_t sufficient_cards = (_bzhi_u64(~0, kNumRanks * kNumSuits) ^(hands_[player_id] | discard_|recent_faceup_card)); + //sufficient_cards are not necessary // + sufficient_cards = (sufficient_cards & (~(necessary_cards))); + //we must now take into account the observation of voids// + std::array when_voided = {0,0,0,0}; + std::array voids = {-1,-1,-1,-1}; + std::vector opp_dealt_hidden; + for (int i = 2 * kNumRanks; i < history_.size(); ++i) { + if (history_[i - 1].player == player_id && history_[i].player == (opp) && CardSuit(history_[i-1].action)!=CardSuit(history_[i].action)) { + when_voided[CardSuit(history_[i - 1].action)] = i - 1; + } + if (history_[i - 1].player == player_id && history_[i].player == (opp) && Trick(history_[i - 1].action, history_[i].action)) { + opp_dealt_hidden.push_back(i - 1); + } + if (history_[i - 1].player == (opp) && history_[i].player == (player_id) && !Trick(history_[i - 1].action, history_[i].action)) { + opp_dealt_hidden.push_back(i - 1); + } + } + //now voids contains the number of hidden cards dealt to opp since it showed a void in that suit, i.e the maximum number of cards held in that suit// + //if the suit is unvoided, then this number is -1// + for (int i = 0; i < kNumSuits; ++i) { + if (when_voided[i] != 0) { + voids[i] = 0; + for (int j = 0; j < opp_dealt_hidden.size(); ++j) { + if (opp_dealt_hidden[j] >= when_voided[i]) { + voids[i] += 1; + } + } + } + } + //we now perform a sequence of shuffles to generate a possible opponent hand, and make no attempt to reconcile the history with this new deal// + int nec = _mm_popcnt_u64(necessary_cards); + for (int i = 0; i < kNumSuits; ++i) { + if (voids[i] != -1&&_mm_popcnt_u64(sufficient_cards&kSuitMasks[i])>voids[i]) { + uint64_t suit_subset = (sufficient_cards & kSuitMasks[i]); + std::vector temp; + while (suit_subset != 0) { + temp.push_back(_tzcnt_u64(suit_subset)); + suit_subset = _blsr_u64(suit_subset); + } + std::shuffle(temp.begin(), temp.end(), gen); + sufficient_cards = (sufficient_cards &~(kSuitMasks[i])); + for (int j = 0; j < voids[i]; ++j) { + sufficient_cards = (sufficient_cards | (uint64_t(1) << temp[j])); + } + } + } + //finally generating a possible hand for opponent// + std::vector hand_vec; + while (sufficient_cards != 0) { + hand_vec.push_back(_tzcnt_u64(sufficient_cards)); + sufficient_cards = _blsr_u64(sufficient_cards); + } + std::shuffle(hand_vec.begin(), hand_vec.end(), gen); + uint64_t suff_hand = 0; + uint64_t opp_hand=0; + for (int i = 0; i < _mm_popcnt_u64(hands_[opp])-nec; ++i) { + suff_hand = suff_hand | (uint64_t(1) << hand_vec[i]); + } + opp_hand = suff_hand | necessary_cards; + resampled_state->hands_[opp] = opp_hand; + resampled_state->deck_ = _bzhi_u64(~0, kNumRanks * kNumSuits) ^ (discard_ | opp_hand | hands_[player_id]|recent_faceup_card); + return resampled_state; + } +std::string GWhistFState::ObservationString(Player player) const { + //note this is a lie, this is not the observation state string but it is used for ISMCTS to label nodes// + std::string p = "p"+std::to_string(player)+","; + std::string cur_hand=""; + std::string public_info = ""; + uint64_t p_hand = hands_[player]; + std::vector v_hand = {}; + while(p_hand!=0){ + v_hand.push_back(_tzcnt_u64(p_hand)); + p_hand = _blsr_u64(p_hand); + } + std::sort(v_hand.begin(),v_hand.end()); + for(int i =0;i policy; - - // All infostates have two actions: Pass (0) and Bet (1). - // Player 0 - policy["0"] = {{0, 1 - alpha}, {1, alpha}}; - policy["0pb"] = {{0, 1}, {1, 0}}; - policy["1"] = {{0, 1}, {1, 0}}; - policy["1pb"] = {{0, 2. / 3. - alpha}, {1, 1. / 3. + alpha}}; - policy["2"] = {{0, 1 - three_alpha}, {1, three_alpha}}; - policy["2pb"] = {{0, 0}, {1, 1}}; +std::vector GWhistFState::LegalActions() const{ + std::vector actions; + if (IsTerminal()) return {}; + if (IsChanceNode()) { + actions.reserve(_mm_popcnt_u64(deck_)); + uint64_t copy_deck = deck_; + while (copy_deck != 0) { + actions.push_back(_tzcnt_u64(copy_deck)); + copy_deck = _blsr_u64(copy_deck); + } + } + else { + //lead// + actions.reserve(kNumRanks); + if (history_.back().player == kChancePlayerId) { + uint64_t copy_hand = hands_[player_]; + while (copy_hand != 0) { + actions.push_back(_tzcnt_u64(copy_hand)); + copy_hand = _blsr_u64(copy_hand); + } + } + + //follow// + else { + uint64_t copy_hand = hands_[player_] & kSuitMasks[CardSuit(history_.back().action)]; + if (copy_hand == 0) { + copy_hand = hands_[player_]; + } + while (copy_hand != 0) { + actions.push_back(_tzcnt_u64(copy_hand)); + copy_hand = _blsr_u64(copy_hand); + } + } + } + return actions; +} - // Player 1 - policy["0p"] = {{0, 2. / 3.}, {1, 1. / 3.}}; - policy["0b"] = {{0, 1}, {1, 0}}; - policy["1p"] = {{0, 1}, {1, 0}}; - policy["1b"] = {{0, 2. / 3.}, {1, 1. / 3.}}; - policy["2p"] = {{0, 0}, {1, 1}}; - policy["2b"] = {{0, 0}, {1, 1}}; - return TabularPolicy(policy); +void GWhistFState::DoApplyAction(Action move) { + // Additional book-keeping + //initial deal// + int player_start = player_; + if (move_number_ < (kNumSuits * kNumRanks) / 2) { + hands_[move_number_ % 2] = (hands_[move_number_ % 2] |((uint64_t)1 << move)); + deck_ = (deck_ ^ ((uint64_t)1 << move)); + } + else if (move_number_ == (kNumSuits * kNumRanks / 2)) { + trump_ = CardSuit(move); + deck_ = (deck_ ^ ((uint64_t)1 << move)); + player_ = 0; + } + //cardplay// + else if (move_number_ > (kNumSuits * kNumRanks) / 2) { + int move_index = (move_number_ - ((kNumSuits * kNumRanks) / 2)) % 4; + switch (move_index) { + bool lead_win; + int winner; + int loser; + case 0: + //revealing face up card// + deck_ = (deck_ ^ ((uint64_t)1 << move)); + lead_win = Trick(history_[move_number_ - 3].action, history_[move_number_ - 2].action); + winner = ((lead_win) ^ (history_[move_number_ - 3].player == 0)) ? 1 : 0; + player_ = winner; + break; + case 1: + //establishing lead// + discard_ = (discard_|((uint64_t)1< #include #include +#include +#include +#include +#include +#include +#include #include "open_spiel/policy.h" #include "open_spiel/spiel.h" @@ -16,95 +22,103 @@ // // -// Parameters: -// kNumSuits, kNumRanks namespace open_spiel { namespace german_whist_foregame { -enum ActionType { kPass = 0, kBet = 1 }; -class GermanWhistForegameGame; -class GermanWhistForegameĆ’Observer; +class GWhistFGame; +class GWhistFObserver; -class GermanWhistForegameState : public State { +inline constexpr int kNumRanks = 13; +inline constexpr int kNumSuits = 4; +inline constexpr char kRankChar[] = "AKQJT98765432"; +inline constexpr char kSuitChar[] = "CDHS"; +inline const std::array kSuitMasks = { _bzhi_u64(~0,kNumRanks),_bzhi_u64(~0,2 * kNumRanks) ^ _bzhi_u64(~0,kNumRanks),_bzhi_u64(~0,3 * kNumRanks) ^ _bzhi_u64(~0,2 * kNumRanks),_bzhi_u64(~0,4 * kNumRanks) ^ _bzhi_u64(~0,3 * kNumRanks) }; +extern std::string kTTablePath ; +struct Triple{ + char index; + char length; + uint32_t sig; + bool operator<(const Triple& triple) const; +}; +std::vector GenQuads(int size_endgames); +std::vector> BinCoeffs(uint32_t max_n); +uint32_t HalfColexer(uint32_t cards,std::vector>* bin_coeffs); +void GenSuitRankingsRel(uint32_t size, std::unordered_map* Ranks); +class vectorNa{ +private: + std::vector data; public: - explicit GermanWhistForegameState(std::shared_ptr game); - GermanWhistForegameState(const GermanWhistForegameState&) = default; - + vectorNa(size_t num,char val); + size_t size()const; + char const& operator[](size_t index)const; + void SetChar(size_t index,char value); + char Get(size_t index) const; + void Set(size_t index,char value); +}; +std::vector InitialiseTTable(int size,std::vector>& bin_coeffs); +std::vector LoadTTable(const std::string filename,int depth,std::vector>& bin_coeffs); +class GWhistFGame : public Game { +public: + explicit GWhistFGame(const GameParameters& params); + int NumDistinctActions() const override { return kNumRanks*kNumSuits; } + std::unique_ptr NewInitialState() const override; + int MaxChanceOutcomes() const override { return kNumRanks*kNumSuits ; } + int NumPlayers() const override { return num_players_; } + double MinUtility() const override {return -kNumRanks;}; + double MaxUtility() const override {return kNumRanks;}; + absl::optional UtilitySum() const override { return 0; }; + int MaxGameLength() const override{kNumRanks*(kNumSuits+2);}; + int MaxChanceNodesInHistory() const override{return kNumRanks*kNumSuits;}; + std::vector ttable_; + std::unordered_map suit_ranks_; + std::vector>bin_coeffs_; +private: + // Number of players. + int num_players_=2; +}; +class GWhistFState : public State { +public: + explicit GWhistFState(std::shared_ptr game); + GWhistFState(const GWhistFState&) = default; Player CurrentPlayer() const override; - std::string ActionToString(Player player, Action move) const override; std::string ToString() const override; bool IsTerminal() const override; std::vector Returns() const override; - std::string InformationStateString(Player player) const override; - std::string ObservationString(Player player) const override; - void InformationStateTensor(Player player, - absl::Span values) const override; - void ObservationTensor(Player player, - absl::Span values) const override; std::unique_ptr Clone() const override; - void UndoAction(Player player, Action move) override; - std::vector> ChanceOutcomes() const override; + ActionsAndProbs ChanceOutcomes() const override; std::vector LegalActions() const override; - std::vector hand() const { return {card_dealt_[CurrentPlayer()]}; } - std::unique_ptr ResampleFromInfostate( - int player_id, std::function rng) const override; - - const std::vector& CardDealt() const { return card_dealt_; } - + std::string InformationStateString(Player player) const override; + std::string ObservationString(Player player) const override; + std::unique_ptr ResampleFromInfostate(int player_id,std::function rng) const override; + std::string StateToString() const ; + uint64_t EndgameKey(int player_to_move) const; protected: void DoApplyAction(Action move) override; - private: - friend class GermanWhistForegameObserver; + uint64_t deck_; + uint64_t discard_; + const std::vector* ttable_; + const std::unordered_map* suit_ranks_; + const std::vector>* bin_coeffs_; + std::array hands_; + int player_; + int trump_; + bool Trick(int lead,int follow) const; - // Whether the specified player made a bet - bool DidBet(Player player) const; // The move history and number of players are sufficient information to // specify the state of the game. We keep track of more information to make // extracting legal actions and utilities easier. // The cost of the additional book-keeping is more complex ApplyAction() and - // UndoAction() functions. - int first_bettor_; // the player (if any) who was first to bet - std::vector card_dealt_; // the player (if any) who has each card - int winner_; // winning player, or kInvalidPlayer if the - // game isn't over yet. - int pot_; // the size of the pot - // How much each player has contributed to the pot, indexed by pid. - std::vector ante_; -}; - -class GermanWhistForegameGame : public Game { -public: - explicit GermanWhistForegameGame(const GameParameters& params); - int NumDistinctActions() const override { return 2; } - std::unique_ptr NewInitialState() const override; - int MaxChanceOutcomes() const override { return num_players_ + 1; } - int NumPlayers() const override { return num_players_; } - double MinUtility() const override; - double MaxUtility() const override; - absl::optional UtilitySum() const override { return 0; } - std::vector InformationStateTensorShape() const override; - std::vector ObservationTensorShape() const override; - int MaxGameLength() const override { return num_players_ * 2 - 1; } - int MaxChanceNodesInHistory() const override { return num_players_; } - std::shared_ptr MakeObserver( - absl::optional iig_obs_type, - const GameParameters& params) const override; - - // Used to implement the old observation API. - std::shared_ptr default_observer_; - std::shared_ptr info_state_observer_; - std::shared_ptr public_observer_; - std::shared_ptr private_observer_; + // UndoAction() functions -private: - // Number of players. - int num_players_; }; +}//g_whist_foregame +}//open_spiel + #endif OPEN_SPIEL_GAMES_GERMAN_WHIST_FOREGAME_H diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc index a90876f291..b73a687d54 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame_test.cc @@ -1,4 +1,6 @@ -#include "open_spiel/games/GermanWhistForegame_/GermanWhistForegame_.h" + + +#include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" #include "open_spiel/algorithms/get_all_states.h" #include "open_spiel/policy.h" @@ -11,57 +13,20 @@ namespace { namespace testing = open_spiel::testing; -void BasicGermanWhistForegameTests() { - testing::LoadGameTest("GermanWhistForegame"); - testing::ChanceOutcomesTest(*LoadGame("GermanWhistForegame")); - testing::RandomSimTest(*LoadGame("GermanWhistForegame"), 100); - testing::RandomSimTestWithUndo(*LoadGame("GermanWhistForegame"), 1); - for (Player players = 3; players <= 5; players++) { - testing::RandomSimTest( - *LoadGame("GermanWhistForegame_", {{"players", GameParameter(players)}}), 100); - } - auto observer = LoadGame("GermanWhistForegame") - ->MakeObserver(kDefaultObsType, - GameParametersFromString("single_tensor")); - testing::RandomSimTestCustomObserver(*LoadGame("GermanWhistForegame"), observer); -} -void CountStates() { - std::shared_ptr game = LoadGame("GermanWhistForegame"); - auto states = algorithms::GetAllStates(*game, /*depth_limit=*/-1, - /*include_terminals=*/true, - /*include_chance_states=*/false); - // 6 deals * 9 betting sequences (-, p, b, pp, pb, bp, bb, pbp, pbb) = 54 - SPIEL_CHECK_EQ(states.size(), 54); +void BasicGermanWhistForegameTests() { + testing::LoadGameTest("german_whist_foregame"); + //testing::ChanceOutcomesTest(*LoadGame("german_whist_foregame")); + testing::RandomSimTest(*LoadGame("german_whist_foregame"),100,false,true); } -void PolicyTest() { - using PolicyGenerator = std::function; - std::vector policy_generators = { - GetAlwaysPassPolicy, - GetAlwaysBetPolicy, - }; - std::shared_ptr game = LoadGame("GermanWhistForegame"); - for (const auto& policy_generator : policy_generators) { - testing::TestEveryInfostateInPolicy(policy_generator, *game); - testing::TestPoliciesCanPlay(policy_generator, *game); - } -} } // namespace } // namespace GermanWhistForegame_ } // namespace open_spiel int main(int argc, char **argv) { - open_spiel::GermanWhistForegame_::BasicGermanWhistForegameTests(); - open_spiel::GermanWhistForegame_::CountStates(); - open_spiel::GermanWhistForegame_::PolicyTest(); - open_spiel::testing::CheckChanceOutcomes(*open_spiel::LoadGame( - "GermanWhistForegame", {{"players", open_spiel::GameParameter(3)}})); - open_spiel::testing::RandomSimTest(*open_spiel::LoadGame("GermanWhistForegame"), - /*num_sims=*/10); - open_spiel::testing::ResampleInfostateTest( - *open_spiel::LoadGame("GermanWhistForegame"), - /*num_sims=*/10); + open_spiel::german_whist_foregame::BasicGermanWhistForegameTests(); + //open_spiel::testing::ResampleInfostateTest(*open_spiel::LoadGame("german_whist_foregame"),*num_sims=*10); } diff --git a/open_spiel/games/german_whist_foregame/solver.cc b/open_spiel/games/german_whist_foregame/solver.cc deleted file mode 100644 index 8b13789179..0000000000 --- a/open_spiel/games/german_whist_foregame/solver.cc +++ /dev/null @@ -1 +0,0 @@ - diff --git a/open_spiel/spiel.h b/open_spiel/spiel.h index c249c4697d..f30357c83e 100644 --- a/open_spiel/spiel.h +++ b/open_spiel/spiel.h @@ -448,7 +448,7 @@ class State { // // Games that do not have imperfect information do not need to implement // these methods, but most algorithms intended for imperfect information - // games will work on perfect information games provided the InformationState + // games will work on perfect information games provided the Information // is returned in a form they support. For example, InformationState() // could simply return the history for a perfect information game. // diff --git a/open_spiel/tests/basic_tests.cc b/open_spiel/tests/basic_tests.cc index e38aeb4eb9..4b7f0dffa4 100644 --- a/open_spiel/tests/basic_tests.cc +++ b/open_spiel/tests/basic_tests.cc @@ -356,7 +356,9 @@ void RandomSimulation(std::mt19937* rng, const Game& game, bool undo, CheckActionStringsAreUnique(game, *state); // Test cloning the state. + //std::cout<<"pre clone"< state_copy = state->Clone(); + //std::cout<<"post clone"<ToString(), state_copy->ToString()); SPIEL_CHECK_EQ(state->History(), state_copy->History()); @@ -373,8 +375,11 @@ void RandomSimulation(std::mt19937* rng, const Game& game, bool undo, if (mask_test) LegalActionsMaskTest(game, *state, kChancePlayerId, state->LegalActions()); // Chance node; sample one according to underlying distribution + //std::cout<<"pre chance outcomes"<> outcomes = state->ChanceOutcomes(); + //std::cout<<"post chance outcomes"< Date: Sat, 27 Jan 2024 12:04:11 +0000 Subject: [PATCH 09/30] DONEEEE TESTING BUILDTABLEBAASSE --- open_spiel/games/CMakeLists.txt | 3 +-- .../games/german_whist_foregame/german_whist_endgame.cc | 5 ++--- .../games/german_whist_foregame/german_whist_foregame.cc | 2 +- .../games/german_whist_foregame/german_whist_foregame.h | 2 +- 4 files changed, 5 insertions(+), 7 deletions(-) diff --git a/open_spiel/games/CMakeLists.txt b/open_spiel/games/CMakeLists.txt index b8ff3ff3cc..a6533cf52e 100644 --- a/open_spiel/games/CMakeLists.txt +++ b/open_spiel/games/CMakeLists.txt @@ -434,8 +434,7 @@ add_test(garnet_test garnet_test) add_executable(german_whist_foregame_test german_whist_foregame/german_whist_foregame_test.cc ${OPEN_SPIEL_OBJECTS} $) add_test(german_whist_foregame_test german_whist_foregame_test) -add_executable(german_whist_endgame german_whist_foregame/german_whist_endgame.cc ${OPEN_SPIEL_OBJECTS} - $) +add_executable(german_whist_endgame german_whist_foregame/german_whist_endgame.cc ${OPEN_SPIEL_OBJECTS}) add_executable(gin_rummy_test gin_rummy/gin_rummy_test.cc ${OPEN_SPIEL_OBJECTS} $) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index 617c76f66d..02f4f2a902 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -540,7 +540,6 @@ std::vector GWhistGenerator(int num,unsigned int seed){ void ThreadSolver(int size_endgames, std::vector* outTTable, std::vector* TTable, std::vector>& bin_coeffs, std::vector& suit_splits, std::unordered_map& SuitRanks, size_t start_id, size_t end_id) { //takes endgames solved to depth d-1 and returns endgames solved to depth d // - std::cout<<"in threadsolver"<<"\n"; std::vector combination; combination.reserve(size_endgames); for (int i = 0; i < size_endgames; ++i) { @@ -581,7 +580,6 @@ void ThreadSolver(int size_endgames, std::vector* outTTable, std::vect } std::vector RetroSolver(int size_endgames, std::vector* TTable, std::vector>& bin_coeffs) { //takes endgames solved to depth d-1 and returns endgames solved to depth d // - std::cout<<"In retrosolver"<<"\n"; std::vector outTTable = InitialiseTTable(size_endgames, bin_coeffs); std::vector suit_splits = GenQuads(size_endgames); std::unordered_map SuitRanks; @@ -652,8 +650,9 @@ std::vector BuildTablebase(std::vector>& bin_coe std::cout<<"Building Tablebase"<<"\n"; for (int i = 1; i <= kNumRanks; ++i) { v = RetroSolver(i, &v, bin_coeffs); - std::cout<<"Done "<& table_base, std::vector>& bin_coeffs) { diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 1b700b8e35..55d54b7b5c 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -1,4 +1,4 @@ -#include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" + #include //to do //InfostateTensor implementation diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.h b/open_spiel/games/german_whist_foregame/german_whist_foregame.h index d038857b62..8c56348c29 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.h +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.h @@ -45,7 +45,7 @@ struct Triple{ }; std::vector GenQuads(int size_endgames); std::vector> BinCoeffs(uint32_t max_n); -uint32_t HalfColexer(uint32_t cards,std::vector>* bin_coeffs); +uint32_t HalfColexer(uint32_t cards,const std::vector>* bin_coeffs); void GenSuitRankingsRel(uint32_t size, std::unordered_map* Ranks); class vectorNa{ private: From b29507a53116047732f8b89755a519ef503ece79 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Sun, 28 Jan 2024 07:23:47 +0000 Subject: [PATCH 10/30] Modified TTable datastructure All works --- .../german_whist_endgame.cc | 45 +++++++------- .../german_whist_foregame.cc | 61 ++++++++++++------- .../german_whist_foregame.h | 22 ++++--- 3 files changed, 74 insertions(+), 54 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index 02f4f2a902..23cf3ee5d7 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -417,7 +417,7 @@ bool NextColex(std::vector& v, int k) { -char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, std::vector* TTable,std::unordered_map* SuitRanks, std::vector>& bin_coeffs) { +char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, vectorNa* TTable,std::unordered_map* SuitRanks, std::vector>& bin_coeffs) { //fail soft ab search char val = 0; uint64_t key = 0; @@ -432,7 +432,7 @@ char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, uint32_t colex = HalfColexer(cards, &bin_coeffs); uint32_t suits = (key & (~0 ^ _bzhi_u64(~0, 32))) >> 32; uint32_t suit_rank = SuitRanks->at(suits); - char value = (player) ? node->RemainingTricks() - TTable->at(colex).Get(suit_rank) :TTable->at(colex).Get(suit_rank); + char value = (player) ? node->RemainingTricks() - TTable->Get(colex,suit_rank) :TTable->Get(colex,suit_rank); return value+node->Score(); } else if (node->Player() == 0) { @@ -465,7 +465,7 @@ char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, }; -char IncrementalMTD(Node* node, char guess,int depth, std::vector* TTable,std::unordered_map* SuitRanks,std::vector>& bin_coeffs) { +char IncrementalMTD(Node* node, char guess,int depth, vectorNa* TTable,std::unordered_map* SuitRanks,std::vector>& bin_coeffs) { char g = guess; char upperbound = node->TotalTricks(); char lowerbound = 0; @@ -538,7 +538,7 @@ std::vector GWhistGenerator(int num,unsigned int seed){ } -void ThreadSolver(int size_endgames, std::vector* outTTable, std::vector* TTable, std::vector>& bin_coeffs, std::vector& suit_splits, std::unordered_map& SuitRanks, size_t start_id, size_t end_id) { +void ThreadSolver(int size_endgames, vectorNa* outTTable, vectorNa* TTable, std::vector>& bin_coeffs, std::vector& suit_splits, std::unordered_map& SuitRanks, size_t start_id, size_t end_id) { //takes endgames solved to depth d-1 and returns endgames solved to depth d // std::vector combination; combination.reserve(size_endgames); @@ -572,15 +572,15 @@ void ThreadSolver(int size_endgames, std::vector* outTTable, std::vect } Node node(cards, suit_arr, 0, false); char result = IncrementalMTD(&node, (size_endgames >> 1), 2, TTable, &SuitRanks, bin_coeffs); - outTTable->at(count).Set(i, result); + outTTable->Set(count,i, result); } control = NextColex(combination, 2 * size_endgames); count++; } } -std::vector RetroSolver(int size_endgames, std::vector* TTable, std::vector>& bin_coeffs) { +vectorNa RetroSolver(int size_endgames, vectorNa* TTable, std::vector>& bin_coeffs) { //takes endgames solved to depth d-1 and returns endgames solved to depth d // - std::vector outTTable = InitialiseTTable(size_endgames, bin_coeffs); + vectorNa outTTable = InitialiseTTable(size_endgames, bin_coeffs); std::vector suit_splits = GenQuads(size_endgames); std::unordered_map SuitRanks; GenSuitRankingsRel(size_endgames - 1, &SuitRanks); @@ -593,7 +593,7 @@ std::vector RetroSolver(int size_endgames, std::vector* TTab uint32_t min_block_size = 256; uint32_t hard_threads = std::thread::hardware_concurrency(); uint32_t num_threads = 1; - uint32_t num_outers =outTTable.size(); + uint32_t num_outers =outTTable.GetOuterSize(); //a haphazard attempt to mitigate false sharing// for (uint32_t i = hard_threads; i >= 1; i--) { if ((num_outers * v_length / i) >= min_block_size) { @@ -630,7 +630,7 @@ std::vector RetroSolver(int size_endgames, std::vector* TTab bool TestRetroSolve(int samples, int depth, uint32_t seed, std::vector>& bin_coeffs) { //Tests endgame solution with TTable vs raw seach std::vector nodes = GWhistGenerator(samples, seed); - std::vector v; + vectorNa v; for (int i = 1; i <= depth; ++i) { v = RetroSolver(i, &v, bin_coeffs); } @@ -645,8 +645,8 @@ bool TestRetroSolve(int samples, int depth, uint32_t seed, std::vector BuildTablebase(std::vector>& bin_coeffs) { - std::vector v; +vectorNa BuildTablebase(std::vector>& bin_coeffs) { + vectorNa v; std::cout<<"Building Tablebase"<<"\n"; for (int i = 1; i <= kNumRanks; ++i) { v = RetroSolver(i, &v, bin_coeffs); @@ -655,7 +655,7 @@ std::vector BuildTablebase(std::vector>& bin_coe std::cout<<"Built Tablebase"<<"\n"; return v; } -bool TestTablebase(int samples,uint32_t seed,std::vector& table_base, std::vector>& bin_coeffs) { +bool TestTablebase(int samples,uint32_t seed,vectorNa& table_base, std::vector>& bin_coeffs) { std::vector nodes = GWhistGenerator(samples, seed); std::unordered_map SuitRanks; GenSuitRankingsRel(kNumRanks, &SuitRanks); @@ -668,24 +668,24 @@ bool TestTablebase(int samples,uint32_t seed,std::vector& table_base, } return true; } -void StoreTTable(const std::string filename, const std::vector& solution){ +void StoreTTable(const std::string filename, const vectorNa& solution){ //stores solution into a text file// std::ofstream file(filename); - for(int i =0;i& v,int depth,std::vector>& bin_coeffs){ +bool TestTTableStorage(std::string filename, vectorNa& v,int depth,std::vector>& bin_coeffs){ //Tests storage fidelity// StoreTTable(filename,v); - std::vector new_v = LoadTTable(filename,depth,bin_coeffs); - for(int i =0;i& v,int depth, int main(){ std::vector> bin_coeffs = open_spiel::german_whist_foregame::BinCoeffs(2*open_spiel::german_whist_foregame::kNumRanks); - std::cout<<"Hello"<<"\n"; - std::vector tablebase = open_spiel::german_whist_foregame::BuildTablebase(bin_coeffs); + open_spiel::german_whist_foregame::vectorNa tablebase = open_spiel::german_whist_foregame::BuildTablebase(bin_coeffs); std::random_device rd; int num_samples = 100; if(open_spiel::german_whist_foregame::TestTablebase(num_samples,rd(),tablebase,bin_coeffs)){ diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 55d54b7b5c..f813702793 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -102,56 +102,71 @@ void GenSuitRankingsRel(uint32_t size, std::unordered_map* R } } -vectorNa::vectorNa(size_t num,char val){ - data=std::vector((num>>1)+1,val); +vectorNa::vectorNa(size_t card_combs,size_t suit_splits,char val){ + data=std::vector(card_combs*((suit_splits>>1)+1),val); + inner_size =(suit_splits>>1)+1; + outer_size = card_combs; +} +vectorNa::vectorNa(){ + data={}; + inner_size=0; + outer_size=0; } size_t vectorNa::size() const{ return data.size(); } +size_t vectorNa::GetInnerSize()const{ + return inner_size; +} +size_t vectorNa::GetOuterSize()const{ + return outer_size; +} char const& vectorNa::operator[](size_t index) const{ return data[index]; } -void vectorNa::SetChar(size_t index,char value){ - data[index]=value; +char vectorNa::GetChar(size_t i,size_t j)const{ + return data[i*inner_size+j]; +} +void vectorNa::SetChar(size_t i,size_t j,char value){ + data[i*inner_size+j]=value; } -char vectorNa::Get(size_t index) const{ - int remainder = index&0b1; +char vectorNa::Get(size_t i,size_t j) const{ + int remainder = j&0b1; if(remainder==0){ - return 0b1111&data[index>>1]; + return 0b1111&data[i*inner_size+(j>>1)]; } else{ - return ((0b11110000&data[index>>1])>>4); + return ((0b11110000&data[i*inner_size+(j>>1)])>>4); } } -void vectorNa::Set(size_t index,char value){ - int remainder = index & 0b1; +void vectorNa::Set(size_t i,size_t j,char value){ + int remainder = j & 0b1; if (remainder == 0) { - char datastore = 0b11110000 & data[index>>1]; - data[index>>1] = datastore|value; + char datastore = 0b11110000 & data[i*inner_size+(j>>1)]; + data[i*inner_size+(j>>1)] = datastore|value; } else { - char datastore = (0b1111 & data[index >> 1]); - data[index >> 1] = datastore|(value << 4); + char datastore = (0b1111 & data[i*inner_size+(j>>1)]); + data[i*inner_size+(j>>1)] = datastore|(value << 4); } } -std::vector InitialiseTTable(int size,std::vector>& bin_coeffs) { +vectorNa InitialiseTTable(int size,std::vector>& bin_coeffs) { //initialises TTable for a certain depth// size_t suit_size = GenQuads(size).size(); - return std::vector(bin_coeffs[2 * size][size], vectorNa(suit_size, 0)); + return vectorNa(bin_coeffs[2 * size][size],suit_size, 0); } -std::vector LoadTTable(const std::string filename, int depth,std::vector>& bin_coeffs){ +vectorNa LoadTTable(const std::string filename, int depth,std::vector>& bin_coeffs){ //loads solution from a text file into a vector for use// std::cout<<"Loading Tablebase"< v = InitialiseTTable(depth,bin_coeffs); + vectorNa v = InitialiseTTable(depth,bin_coeffs); std::ifstream file(filename,std::ios::binary); //std::cout< GWhistFState::Returns() const{ uint32_t colex = HalfColexer(cards,bin_coeffs_); uint32_t suits = (key&(~0^_bzhi_u64(~0,32)))>>32; uint32_t suit_rank = suit_ranks_->at(suits); - char value =ttable_->at(colex).Get(suit_rank); + char value =ttable_->Get(colex,suit_rank); out[player_to_move] = 2*value-kNumRanks; out[opp]=-out[player_to_move]; return out; diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.h b/open_spiel/games/german_whist_foregame/german_whist_foregame.h index 8c56348c29..61a973b760 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.h +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.h @@ -50,16 +50,22 @@ void GenSuitRankingsRel(uint32_t size, std::unordered_map* Ra class vectorNa{ private: std::vector data; + size_t inner_size; + size_t outer_size; public: - vectorNa(size_t num,char val); + vectorNa(size_t card_combs,size_t suit_splits,char val); + vectorNa(); size_t size()const; + size_t GetInnerSize()const; + size_t GetOuterSize()const; char const& operator[](size_t index)const; - void SetChar(size_t index,char value); - char Get(size_t index) const; - void Set(size_t index,char value); + char GetChar(size_t i,size_t j)const; + void SetChar(size_t i,size_t j,char value); + char Get(size_t i,size_t j) const; + void Set(size_t i,size_t j,char value); }; -std::vector InitialiseTTable(int size,std::vector>& bin_coeffs); -std::vector LoadTTable(const std::string filename,int depth,std::vector>& bin_coeffs); +vectorNa InitialiseTTable(int size,std::vector>& bin_coeffs); +vectorNa LoadTTable(const std::string filename,int depth,std::vector>& bin_coeffs); class GWhistFGame : public Game { public: explicit GWhistFGame(const GameParameters& params); @@ -72,7 +78,7 @@ class GWhistFGame : public Game { absl::optional UtilitySum() const override { return 0; }; int MaxGameLength() const override{kNumRanks*(kNumSuits+2);}; int MaxChanceNodesInHistory() const override{return kNumRanks*kNumSuits;}; - std::vector ttable_; + vectorNa ttable_; std::unordered_map suit_ranks_; std::vector>bin_coeffs_; private: @@ -101,7 +107,7 @@ class GWhistFState : public State { private: uint64_t deck_; uint64_t discard_; - const std::vector* ttable_; + const vectorNa* ttable_; const std::unordered_map* suit_ranks_; const std::vector>* bin_coeffs_; std::array hands_; From 927e1f6eb10bbbced08138a965d6825f93fd9356 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Sun, 28 Jan 2024 07:33:58 +0000 Subject: [PATCH 11/30] Update spiel.h --- open_spiel/spiel.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open_spiel/spiel.h b/open_spiel/spiel.h index f30357c83e..c249c4697d 100644 --- a/open_spiel/spiel.h +++ b/open_spiel/spiel.h @@ -448,7 +448,7 @@ class State { // // Games that do not have imperfect information do not need to implement // these methods, but most algorithms intended for imperfect information - // games will work on perfect information games provided the Information + // games will work on perfect information games provided the InformationState // is returned in a form they support. For example, InformationState() // could simply return the history for a perfect information game. // From 6095750584d6c003e1433e1135dd18f2effdd587 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Sun, 28 Jan 2024 07:37:30 +0000 Subject: [PATCH 12/30] Update basic_tests.cc --- open_spiel/tests/basic_tests.cc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/open_spiel/tests/basic_tests.cc b/open_spiel/tests/basic_tests.cc index 4b7f0dffa4..e38aeb4eb9 100644 --- a/open_spiel/tests/basic_tests.cc +++ b/open_spiel/tests/basic_tests.cc @@ -356,9 +356,7 @@ void RandomSimulation(std::mt19937* rng, const Game& game, bool undo, CheckActionStringsAreUnique(game, *state); // Test cloning the state. - //std::cout<<"pre clone"< state_copy = state->Clone(); - //std::cout<<"post clone"<ToString(), state_copy->ToString()); SPIEL_CHECK_EQ(state->History(), state_copy->History()); @@ -375,11 +373,8 @@ void RandomSimulation(std::mt19937* rng, const Game& game, bool undo, if (mask_test) LegalActionsMaskTest(game, *state, kChancePlayerId, state->LegalActions()); // Chance node; sample one according to underlying distribution - //std::cout<<"pre chance outcomes"<> outcomes = state->ChanceOutcomes(); - //std::cout<<"post chance outcomes"< Date: Sun, 28 Jan 2024 07:42:43 +0000 Subject: [PATCH 13/30] Small clean up --- open_spiel/games/german_whist_foregame/german_whist_endgame.cc | 1 - open_spiel/games/german_whist_foregame/german_whist_foregame.cc | 1 - 2 files changed, 2 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index 23cf3ee5d7..8996cc758c 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -523,7 +523,6 @@ std::vector GWhistGenerator(int num,unsigned int seed){ } else { suits[j] = (_bzhi_u32(~0, suit_lengths[j]+cum_sum)) ^ _bzhi_u32(~0,cum_sum); - //assert((suits[j] & suits[j - 1])== 0); } cum_sum+= suit_lengths[j]; } diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index f813702793..a4b744e92a 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -564,7 +564,6 @@ std::vector GWhistFState::LegalActions() const{ } void GWhistFState::DoApplyAction(Action move) { - // Additional book-keeping //initial deal// int player_start = player_; if (move_number_ < (kNumSuits * kNumRanks) / 2) { From 76781589597bcee9f11bd744b792af46fe1cd311 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Sun, 28 Jan 2024 07:49:18 +0000 Subject: [PATCH 14/30] More cleanup --- open_spiel/examples/is_mcts_gwhist.cc | 6 +----- .../games/german_whist_foregame/german_whist_endgame.cc | 2 +- .../games/german_whist_foregame/german_whist_foregame.cc | 2 -- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/open_spiel/examples/is_mcts_gwhist.cc b/open_spiel/examples/is_mcts_gwhist.cc index 51440f49f0..5831cb0cda 100644 --- a/open_spiel/examples/is_mcts_gwhist.cc +++ b/open_spiel/examples/is_mcts_gwhist.cc @@ -38,8 +38,6 @@ void PlayGWhist(int human_player, std::mt19937* rng) { algorithms::ISMCTSFinalPolicyType::kMaxVisitCount,true, false); std::unique_ptr state = game->NewInitialState(); while (!state->IsTerminal()) { - //std::cout << "State:" << std::endl; - //std::cout << state->ToString() << std::endl; Action chosen_action = kInvalidAction; if (state->IsChanceNode()) { @@ -75,9 +73,7 @@ void PlayGWhist(int human_player, std::mt19937* rng) { } // namespace open_spiel -//current issues: -//infostate display for player is inaccurate and unreadable// -//endgame parsing/RETURNS SEEMS to be inaccurate as i got destroyed everytime despite strong play? + int main(int argc, char** argv) { std::random_device rd; std::mt19937 rng(rd()); diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index 8996cc758c..b832532381 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -1,5 +1,5 @@ //Source Code for an Executable Generating an Endgame Tablebase for German Whist -// + #include #include diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index a4b744e92a..b4702a2fa1 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -620,8 +620,6 @@ void GWhistFState::DoApplyAction(Action move) { std::cout << ActionToString(player_start, move) << std::endl; std::cout << move << std::endl; #endif - //history_.push_back(PlayerAction{ player_start,move }); - //move_number_++; } } // namespace german_whist_foregame From 5484fa97cb5ea846e912ea500b4b8821cb64f202 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Sun, 28 Jan 2024 08:04:25 +0000 Subject: [PATCH 15/30] set kTTablePath to empty string --- open_spiel/games/german_whist_foregame/german_whist_foregame.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index b4702a2fa1..5028361adc 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -17,7 +17,7 @@ namespace open_spiel { namespace german_whist_foregame { -std::string kTTablePath="./Documents/Github/open_spiel/open_spiel/games/german_whist_foregame/TTables/TTable13.txt"; +std::string kTTablePath=""; bool Triple::operator<(const Triple& triple)const{ return (length < triple.length)|| (length == triple.length && sig < triple.sig); } From 3184a108d4f844439b08578d5a81c8d99e5cd4a7 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Mon, 29 Jan 2024 09:28:11 +0000 Subject: [PATCH 16/30] Modified all intrinsics to generics removed dependence on bmi2 intrinsics and x86intrin.h --- open_spiel/CMakeLists.txt | 4 +- .../german_whist_endgame.cc | 52 +++---- .../german_whist_foregame.cc | 132 +++++++++++------- .../german_whist_foregame.h | 22 ++- 4 files changed, 127 insertions(+), 83 deletions(-) diff --git a/open_spiel/CMakeLists.txt b/open_spiel/CMakeLists.txt index 1efd34f0cf..1fa956151c 100644 --- a/open_spiel/CMakeLists.txt +++ b/open_spiel/CMakeLists.txt @@ -50,14 +50,14 @@ if(${BUILD_TYPE} STREQUAL "Testing") # A build used for running tests: keep all runtime checks (assert, # SPIEL_CHECK_*, SPIEL_DCHECK_*), but turn on some speed optimizations, # otherwise tests run for too long. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 -march=x86-64-v3") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") endif() if(${BUILD_TYPE} STREQUAL "Release") # Optimized release build: turn off debug runtime checks (assert, # SPIEL_DCHECK_*) and turn on highest speed optimizations. # The difference in perfomance can be up to 10x higher. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 -march=x86-64-v3") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 ") endif() if(APPLE) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index b832532381..3fcf2f138e 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -53,7 +53,7 @@ class Node { Node(uint32_t cards, std::array suit_masks, char trump,bool player) { cards_ = cards; suit_masks_ = suit_masks; - total_tricks_ = __builtin_popcount(cards); + total_tricks_ = popcnt_u32(cards); trump_ = trump; moves_ = 0; player_ = player; @@ -83,15 +83,15 @@ class Node { void RemoveCard(ActionStruct action) { //Removes card from cards_// uint32_t mask_b = ~0; - mask_b =_bzhi_u32(mask_b, action.index); + mask_b =bzhi_u32(mask_b, action.index); uint32_t mask_a = ~mask_b; - mask_a = _blsr_u32(mask_a); + mask_a = blsr_u32(mask_a); uint32_t copy_a = cards_ & mask_a; uint32_t copy_b = cards_ & mask_b; copy_a = copy_a >> 1; cards_ = copy_a | copy_b; //decrements appropriate suits// - suit_masks_[action.suit] = _blsr_u32(suit_masks_[action.suit])>>1; + suit_masks_[action.suit] = blsr_u32(suit_masks_[action.suit])>>1; char suit = action.suit; suit++; while (suit < kNumSuits) { @@ -102,7 +102,7 @@ class Node { void InsertCard(ActionStruct action) { //inserts card into cards_// uint32_t mask_b = ~0; - mask_b = _bzhi_u32(mask_b, action.index); + mask_b = bzhi_u32(mask_b, action.index); uint32_t mask_a = ~mask_b; uint32_t copy_b = cards_ & mask_b; uint32_t copy_a = cards_ & mask_a; @@ -128,17 +128,17 @@ class Node { //this implies player 1 achieves the minimax value of the original game ie the value is remaining tricks - value of the original game for this transformed game// //also does not take advantage of single suit isomorphism. Namely all single suit games with the same card distribution are isomorphic. Currently this considers all trump, all no trump games as distinct// uint64_t suit_sig = 0; - char trump_length = __builtin_popcount(suit_masks_[trump_]); + char trump_length = popcnt_u32(suit_masks_[trump_]); if (trump_length > kNumRanks) { throw; } std::vector non_trump_lengths; for (char i = 0; i < kNumSuits; ++i) { if (i != trump_) { - char length = __builtin_popcount(suit_masks_[i]); + char length = popcnt_u32(suit_masks_[i]); uint32_t sig = suit_masks_[i]&cards_; if (suit_masks_[i] != 0) { - sig = (sig >> (_tzcnt_u32(suit_masks_[i]))); + sig = (sig >> (tzcnt_u32(suit_masks_[i]))); } if (length > kNumRanks) { throw 1; @@ -157,19 +157,19 @@ class Node { std::array suit_cards; suit_cards[0] = cards_ & suit_masks_[trump_]; if (suit_masks_[trump_] != 0) { - suit_cards[0] = suit_cards[0] >> _tzcnt_u32(suit_masks_[trump_]); + suit_cards[0] = suit_cards[0] >> tzcnt_u32(suit_masks_[trump_]); } - uint32_t sum = __builtin_popcount(suit_masks_[trump_]); + uint32_t sum = popcnt_u32(suit_masks_[trump_]); uint32_t cards = 0|suit_cards[0]; for (size_t i = 0; i < non_trump_lengths.size(); ++i) { suit_cards[i] = cards_ & suit_masks_[non_trump_lengths[i].index]; uint32_t val = 0; if (suit_masks_[non_trump_lengths[i].index] != 0) { - val = _tzcnt_u32(suit_masks_[non_trump_lengths[i].index]); + val = tzcnt_u32(suit_masks_[non_trump_lengths[i].index]); } suit_cards[i]= suit_cards[i] >>val; suit_cards[i] = suit_cards[i] << sum; - sum += __builtin_popcount(suit_masks_[non_trump_lengths[i].index]); + sum += popcnt_u32(suit_masks_[non_trump_lengths[i].index]); cards = cards | suit_cards[i]; } //cards = cards | (player_ << 31); @@ -186,7 +186,7 @@ class Node { #endif } uint64_t AltKey() { - uint32_t mask = _bzhi_u32(~0, 2 * RemainingTricks()); + uint32_t mask = bzhi_u32(~0, 2 * RemainingTricks()); return key_ ^ (uint64_t)mask; } //Move Ordering Heuristics// @@ -200,7 +200,7 @@ class Node { uint32_t suit_cards = copy_cards & suit_masks_[suit]; uint32_t mask = suit_cards & ~(suit_cards >> 1); //represents out of the stategically inequivalent cards in a suit that a player holds, what rank is it, rank 0 is highest rank etc// - int suit_rank = __builtin_popcount(_bzhi_u32(mask, action.index)); + int suit_rank = popcnt_u32(bzhi_u32(mask, action.index)); ApplyAction(action); std::vector moves = LegalActions(); UndoAction(action); @@ -230,7 +230,7 @@ class Node { uint32_t suit_cards = copy_cards & suit_masks_[suit]; uint32_t mask = suit_cards & ~(suit_cards >> 1); //represents out of the stategically inequivalent cards in a suit that a player holds, what rank is it, rank 0 is highest rank etc// - int suit_rank = __builtin_popcount(_bzhi_u32(mask, action.index)); + int suit_rank = popcnt_u32(bzhi_u32(mask, action.index)); if (!Trick(lead, action)) { return -kNumRanks - suit_rank; } @@ -274,14 +274,14 @@ class Node { } if ((lead || (follow && (correct_suit || void_in_suit)))) { while (suit_mask != 0) { - uint32_t best = _tzcnt_u32(suit_mask); + uint32_t best = tzcnt_u32(suit_mask); if (moves_ % 2 == 0) { temp.push_back({ ActionStruct(best, i, player_),LeadOrdering(ActionStruct(best, i, player_)) }); } else { temp.push_back({ ActionStruct(best, i, player_),FollowOrdering(ActionStruct(best, i, player_)) }); } - suit_mask = _blsr_u32(suit_mask); + suit_mask = blsr_u32(suit_mask); } } } @@ -428,9 +428,9 @@ char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, if (node->Moves() % 2 == 0&& depth==0) { node->UpdateNodeKey(); key = (player) ? node->AltKey() : node->GetNodeKey(); - uint32_t cards = key & _bzhi_u64(~0, 32); + uint32_t cards = key & bzhi_u64(~0, 32); uint32_t colex = HalfColexer(cards, &bin_coeffs); - uint32_t suits = (key & (~0 ^ _bzhi_u64(~0, 32))) >> 32; + uint32_t suits = (key & (~0 ^ bzhi_u64(~0, 32))) >> 32; uint32_t suit_rank = SuitRanks->at(suits); char value = (player) ? node->RemainingTricks() - TTable->Get(colex,suit_rank) :TTable->Get(colex,suit_rank); return value+node->Score(); @@ -519,16 +519,16 @@ std::vector GWhistGenerator(int num,unsigned int seed){ int cum_sum =0; for (int j = 0; j < kNumSuits; ++j) { if (j == 0) { - suits[j] = _bzhi_u32(~0, suit_lengths[j]); + suits[j] = bzhi_u32(~0, suit_lengths[j]); } else { - suits[j] = (_bzhi_u32(~0, suit_lengths[j]+cum_sum)) ^ _bzhi_u32(~0,cum_sum); + suits[j] = (bzhi_u32(~0, suit_lengths[j]+cum_sum)) ^ bzhi_u32(~0,cum_sum); } cum_sum+= suit_lengths[j]; } out.push_back(Node(cards, suits, 0,false)); #ifdef DEBUG - std::cout << __builtin_popcount(cards) << " " << __builtin_popcount(suits[0]) + __builtin_popcount(suits[1]) + __builtin_popcount(suits[2]) + __builtin_popcount(suits[3]) << std::endl; + std::cout << popcnt_u32(cards) << " " << popcnt_u32(suits[0]) + popcnt_u32(suits[1]) + popcnt_u32(suits[2]) + popcnt_u32(suits[3]) << std::endl; std::cout << cards << " " << suits[0] << " " << suits[1] << " " << suits[2] << " " << suits[3] << std::endl; #endif @@ -561,12 +561,12 @@ void ThreadSolver(int size_endgames, vectorNa* outTTable, vectorNa* TTable, std: } for (int i = 0; i < suit_splits.size(); ++i) { std::array suit_arr; - suit_arr[0] = _bzhi_u32(~0, suit_splits[i] & 0b1111); - int sum = suit_splits[i] & 0b1111; + suit_arr[0] = bzhi_u32(~0, suit_splits[i] & 0b1111); + uint32_t sum = suit_splits[i] & 0b1111; for (int j = 1; j < kNumSuits; ++j) { - uint32_t mask = _bzhi_u32(~0, sum); + uint32_t mask = bzhi_u32(~0, sum); sum += (suit_splits[i] & (0b1111 << (4 * j))) >> 4 * j; - suit_arr[j] = _bzhi_u32(~0, sum); + suit_arr[j] = bzhi_u32(~0, sum); suit_arr[j] = suit_arr[j] ^ mask; } Node node(cards, suit_arr, 0, false); diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 5028361adc..13e15252b9 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -1,10 +1,5 @@ #include -//to do -//InfostateTensor implementation -// PR!!!!! - - #include "open_spiel/abseil-cpp/absl/strings/str_cat.h" #include "open_spiel/game_parameters.h" #include "open_spiel/observer.h" @@ -18,6 +13,45 @@ namespace german_whist_foregame { std::string kTTablePath=""; + +uint32_t tzcnt_u32(uint32_t a){ + return __builtin_ctz(a); +} +uint64_t tzcnt_u64(uint64_t a){ + return __builtin_ctzll(a); +} +uint32_t bzhi_u32(uint32_t a,uint32_t b){ + return (b==0)?0:((a<<(32-b))>>(32-b)); +} +uint64_t bzhi_u64(uint64_t a,uint64_t b){ + return (b==0)?0:((a<<(64-b))>>(64-b)); +} +uint32_t blsr_u32(uint32_t a){ + return(a-1)&a; +} +uint64_t blsr_u64(uint64_t a){ + return (a-1)&a; +} +uint32_t popcnt_u32(uint32_t a){ + return __builtin_popcount(a); +} +uint64_t popcnt_u64(uint64_t a){ + return __builtin_popcountll(a); +} +uint64_t pext_u64(uint64_t x,uint64_t m){ + uint64_t r = 0; + uint64_t s = 0; + uint64_t b = 0; + do{ + b =m&1; + r = r|((x&b)<>1; + m = m>>1; + }while(m!=0); + return r; +} + bool Triple::operator<(const Triple& triple)const{ return (length < triple.length)|| (length == triple.length && sig < triple.sig); } @@ -25,12 +59,12 @@ bool Triple::operator<(const Triple& triple)const{ inline int CardRank(int card, int suit) { uint64_t card_mask = ((uint64_t)1 << card); card_mask = (card_mask >> (suit * kNumRanks)); - return _tzcnt_u64(card_mask); + return tzcnt_u64(card_mask); } inline int CardSuit(int card) { uint64_t card_mask = ((uint64_t)1 << card); for (int i = 0; i < kNumSuits; ++i) { - if (_mm_popcnt_u64(card_mask & kSuitMasks[i]) == 1) { + if (popcnt_u64(card_mask & kSuitMasks[i]) == 1) { return i; } } @@ -86,10 +120,10 @@ uint32_t HalfColexer(uint32_t cards,const std::vector>* bi uint32_t out = 0; uint32_t count = 0; while (cards != 0) { - uint32_t ind = _tzcnt_u32(cards); + uint32_t ind = tzcnt_u32(cards); uint32_t val = bin_coeffs->at(ind)[count+1]; out += val; - cards = _blsr_u32(cards); + cards = blsr_u32(cards); count++; } return out; @@ -217,7 +251,7 @@ GWhistFState::GWhistFState(std::shared_ptr game):State(game) player_ = kChancePlayerId; move_number_ = 0; trump_ = -1; - deck_ = _bzhi_u64(~0,kNumRanks*kNumSuits); + deck_ = bzhi_u64(~0,kNumRanks*kNumSuits); discard_ = 0; hands_ = { 0,0 }; history_.reserve(78); @@ -233,7 +267,7 @@ bool GWhistFState::Trick(int lead, int follow) const { return (lead_suit == follow_suit && lead_rank < follow_rank) || (lead_suit != follow_suit && follow_suit != trump_); } bool GWhistFState::IsTerminal() const { - return(_mm_popcnt_u64(deck_) == 0); + return(popcnt_u64(deck_) == 0); } uint64_t GWhistFState::EndgameKey(int player_to_move) const{ //generates a 64 bit unsigned int where the first 32 are the suit ownerships from the perspective of the opponent using canonical rankings// @@ -245,18 +279,18 @@ uint64_t GWhistFState::EndgameKey(int player_to_move) const{ //sort trump suits by length,then sig// for(int i =0;i hand0; std::array hand1; - hand0[0]=_pext_u64(hands_[0],kSuitMasks[trump_]); - hand1[0]=_pext_u64(hands_[1],kSuitMasks[trump_]); + hand0[0]=pext_u64(hands_[0],kSuitMasks[trump_]); + hand1[0]=pext_u64(hands_[1],kSuitMasks[trump_]); for(int i =0;ihands_shuffled = {0,0}; for(int i =0;i GWhistFState::Returns() const{ int player_to_move=(lead_win)?history_[move_number_-3].player:history_[move_number_-2].player; int opp = (player_to_move==0)?1:0; uint64_t key = EndgameKey(player_to_move); - uint32_t cards = (key&_bzhi_u64(~0,32)); + uint32_t cards = (key&bzhi_u64(~0,32)); uint32_t colex = HalfColexer(cards,bin_coeffs_); - uint32_t suits = (key&(~0^_bzhi_u64(~0,32)))>>32; + uint32_t suits = (key&(~0^bzhi_u64(~0,32)))>>32; uint32_t suit_rank = suit_ranks_->at(suits); char value =ttable_->Get(colex,suit_rank); out[player_to_move] = 2*value-kNumRanks; @@ -336,21 +370,21 @@ std::string GWhistFState::StateToString() const { std::vector player1_cards; std::vector discard; while (copy_deck != 0) { - deck_cards.push_back(_tzcnt_u64(copy_deck)); - copy_deck = _blsr_u64(copy_deck); + deck_cards.push_back(tzcnt_u64(copy_deck)); + copy_deck = blsr_u64(copy_deck); } while (copy_discard != 0) { - discard.push_back(_tzcnt_u64(copy_discard)); - copy_discard = _blsr_u64(copy_discard); + discard.push_back(tzcnt_u64(copy_discard)); + copy_discard = blsr_u64(copy_discard); } while (copy_hands[0] != 0) { - player0_cards.push_back(_tzcnt_u64(copy_hands[0])); - copy_hands[0] = _blsr_u64(copy_hands[0]); + player0_cards.push_back(tzcnt_u64(copy_hands[0])); + copy_hands[0] = blsr_u64(copy_hands[0]); } while (copy_hands[1] != 0) { - player1_cards.push_back(_tzcnt_u64(copy_hands[1])); - copy_hands[1] = _blsr_u64(copy_hands[1]); + player1_cards.push_back(tzcnt_u64(copy_hands[1])); + copy_hands[1] = blsr_u64(copy_hands[1]); } out += "Deck \n"; for (int i = 0; i < deck_cards.size(); ++i) { @@ -384,8 +418,8 @@ std::string GWhistFState::InformationStateString(Player player) const{ std::vector v_hand = {}; uint64_t p_hand = hands_[player]; while(p_hand!=0){ - v_hand.push_back(_tzcnt_u64(p_hand)); - p_hand = _blsr_u64(p_hand); + v_hand.push_back(tzcnt_u64(p_hand)); + p_hand = blsr_u64(p_hand); } std::sort(v_hand.begin(),v_hand.end()); for(int i =0;i GWhistFState::ResampleFromInfostate(int player_id,std::fu // if a face up card from the deck is not in players hand or discard it must be in opps unless it is the most recent face up// necessary_cards = (necessary_cards & (~(hands_[player_id] | discard_|recent_faceup_card))); //sufficient cards are all cards not in players hand,the discard, or the recent face up// - uint64_t sufficient_cards = (_bzhi_u64(~0, kNumRanks * kNumSuits) ^(hands_[player_id] | discard_|recent_faceup_card)); + uint64_t sufficient_cards = (bzhi_u64(~0, kNumRanks * kNumSuits) ^(hands_[player_id] | discard_|recent_faceup_card)); //sufficient_cards are not necessary // sufficient_cards = (sufficient_cards & (~(necessary_cards))); //we must now take into account the observation of voids// @@ -469,14 +503,14 @@ std::unique_ptr GWhistFState::ResampleFromInfostate(int player_id,std::fu } } //we now perform a sequence of shuffles to generate a possible opponent hand, and make no attempt to reconcile the history with this new deal// - int nec = _mm_popcnt_u64(necessary_cards); + int nec = popcnt_u64(necessary_cards); for (int i = 0; i < kNumSuits; ++i) { - if (voids[i] != -1&&_mm_popcnt_u64(sufficient_cards&kSuitMasks[i])>voids[i]) { + if (voids[i] != -1&&popcnt_u64(sufficient_cards&kSuitMasks[i])>voids[i]) { uint64_t suit_subset = (sufficient_cards & kSuitMasks[i]); std::vector temp; while (suit_subset != 0) { - temp.push_back(_tzcnt_u64(suit_subset)); - suit_subset = _blsr_u64(suit_subset); + temp.push_back(tzcnt_u64(suit_subset)); + suit_subset = blsr_u64(suit_subset); } std::shuffle(temp.begin(), temp.end(), gen); sufficient_cards = (sufficient_cards &~(kSuitMasks[i])); @@ -488,18 +522,18 @@ std::unique_ptr GWhistFState::ResampleFromInfostate(int player_id,std::fu //finally generating a possible hand for opponent// std::vector hand_vec; while (sufficient_cards != 0) { - hand_vec.push_back(_tzcnt_u64(sufficient_cards)); - sufficient_cards = _blsr_u64(sufficient_cards); + hand_vec.push_back(tzcnt_u64(sufficient_cards)); + sufficient_cards = blsr_u64(sufficient_cards); } std::shuffle(hand_vec.begin(), hand_vec.end(), gen); uint64_t suff_hand = 0; uint64_t opp_hand=0; - for (int i = 0; i < _mm_popcnt_u64(hands_[opp])-nec; ++i) { + for (int i = 0; i < popcnt_u64(hands_[opp])-nec; ++i) { suff_hand = suff_hand | (uint64_t(1) << hand_vec[i]); } opp_hand = suff_hand | necessary_cards; resampled_state->hands_[opp] = opp_hand; - resampled_state->deck_ = _bzhi_u64(~0, kNumRanks * kNumSuits) ^ (discard_ | opp_hand | hands_[player_id]|recent_faceup_card); + resampled_state->deck_ = bzhi_u64(~0, kNumRanks * kNumSuits) ^ (discard_ | opp_hand | hands_[player_id]|recent_faceup_card); return resampled_state; } std::string GWhistFState::ObservationString(Player player) const { @@ -510,8 +544,8 @@ std::string GWhistFState::ObservationString(Player player) const { uint64_t p_hand = hands_[player]; std::vector v_hand = {}; while(p_hand!=0){ - v_hand.push_back(_tzcnt_u64(p_hand)); - p_hand = _blsr_u64(p_hand); + v_hand.push_back(tzcnt_u64(p_hand)); + p_hand = blsr_u64(p_hand); } std::sort(v_hand.begin(),v_hand.end()); for(int i =0;i GWhistFState::LegalActions() const{ std::vector actions; if (IsTerminal()) return {}; if (IsChanceNode()) { - actions.reserve(_mm_popcnt_u64(deck_)); + actions.reserve(popcnt_u64(deck_)); uint64_t copy_deck = deck_; while (copy_deck != 0) { - actions.push_back(_tzcnt_u64(copy_deck)); - copy_deck = _blsr_u64(copy_deck); + actions.push_back(tzcnt_u64(copy_deck)); + copy_deck = blsr_u64(copy_deck); } } else { @@ -543,8 +577,8 @@ std::vector GWhistFState::LegalActions() const{ if (history_.back().player == kChancePlayerId) { uint64_t copy_hand = hands_[player_]; while (copy_hand != 0) { - actions.push_back(_tzcnt_u64(copy_hand)); - copy_hand = _blsr_u64(copy_hand); + actions.push_back(tzcnt_u64(copy_hand)); + copy_hand = blsr_u64(copy_hand); } } @@ -555,8 +589,8 @@ std::vector GWhistFState::LegalActions() const{ copy_hand = hands_[player_]; } while (copy_hand != 0) { - actions.push_back(_tzcnt_u64(copy_hand)); - copy_hand = _blsr_u64(copy_hand); + actions.push_back(tzcnt_u64(copy_hand)); + copy_hand = blsr_u64(copy_hand); } } } diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.h b/open_spiel/games/german_whist_foregame/german_whist_foregame.h index 61a973b760..6540033c6e 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.h +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.h @@ -6,7 +6,6 @@ #include #include #include -#include #include #include #include @@ -18,10 +17,6 @@ //The imperfect information part of 2 player whist variant //https://en.wikipedia.org/wiki/German_Whist -// -// - -// namespace open_spiel { namespace german_whist_foregame { @@ -35,8 +30,23 @@ inline constexpr int kNumRanks = 13; inline constexpr int kNumSuits = 4; inline constexpr char kRankChar[] = "AKQJT98765432"; inline constexpr char kSuitChar[] = "CDHS"; -inline const std::array kSuitMasks = { _bzhi_u64(~0,kNumRanks),_bzhi_u64(~0,2 * kNumRanks) ^ _bzhi_u64(~0,kNumRanks),_bzhi_u64(~0,3 * kNumRanks) ^ _bzhi_u64(~0,2 * kNumRanks),_bzhi_u64(~0,4 * kNumRanks) ^ _bzhi_u64(~0,3 * kNumRanks) }; + extern std::string kTTablePath ; + +//Reimplementing bmi2 intrinsics with bit operations that will work on all platforms// +uint32_t tzcnt_u32(uint32_t a); +uint64_t tzcnt_u64(uint64_t a); +uint32_t bzhi_u32(uint32_t a,uint32_t b); +uint64_t bzhi_u64(uint64_t a,uint64_t b); +uint32_t blsr_u32(uint32_t a); +uint64_t blsr_u64(uint64_t a); +uint32_t popcnt_u32(uint32_t a); +uint64_t popcnt_u64(uint64_t a); +uint64_t pext_u64(uint64_t a,uint64_t b); + +inline const std::array kSuitMasks = { bzhi_u64(~0,kNumRanks),bzhi_u64(~0,2 * kNumRanks) ^ bzhi_u64(~0,kNumRanks),bzhi_u64(~0,3 * kNumRanks) ^ bzhi_u64(~0,2 * kNumRanks),bzhi_u64(~0,4 * kNumRanks) ^ bzhi_u64(~0,3 * kNumRanks) }; + + struct Triple{ char index; char length; From aee203b88d5eb68a67c9e09f9a7df1935b1ee67a Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Mon, 29 Jan 2024 20:00:18 +0000 Subject: [PATCH 17/30] added game name to pyspiel test --- open_spiel/python/tests/pyspiel_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/open_spiel/python/tests/pyspiel_test.py b/open_spiel/python/tests/pyspiel_test.py index b4524936f1..038e927f74 100644 --- a/open_spiel/python/tests/pyspiel_test.py +++ b/open_spiel/python/tests/pyspiel_test.py @@ -57,6 +57,7 @@ "efg_game", "euchre", "first_sealed_auction", + "german_whist_foregame" "gin_rummy", "go", "goofspiel", From 51d92b095d9c4029f0ad027740ef43640ee0984f Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Tue, 30 Jan 2024 01:32:41 +0000 Subject: [PATCH 18/30] Code cleanup/speedup Removed MTD as it slows generation of tablebase. Removed move ordering heuristics from legalactions as it slows tablebase generation. Hacky move ordering heuristics remain for speeding up verification. Tablebase generation is 50% faster --- .../german_whist_endgame.cc | 61 ++++++++----------- 1 file changed, 26 insertions(+), 35 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index 3fcf2f138e..1ec4be3a3c 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -243,7 +243,7 @@ class Node { std::vector LegalActions() { //Features// - //Move fusion and move ordering// + //Move fusion// std::vector out; out.reserve(kNumRanks); uint32_t copy_cards = cards_; @@ -260,8 +260,6 @@ class Node { std::cout << "Player " << player_ << " suit mask " << (int)i << " " << player_suit_masks[i] << std::endl; #endif } - std::vector temp; - temp.reserve(kNumRanks); for (char i = 0; i < kNumSuits; ++i) { uint32_t suit_mask = player_suit_masks[i]; bool lead = (moves_ % 2 == 0); @@ -275,21 +273,11 @@ class Node { if ((lead || (follow && (correct_suit || void_in_suit)))) { while (suit_mask != 0) { uint32_t best = tzcnt_u32(suit_mask); - if (moves_ % 2 == 0) { - temp.push_back({ ActionStruct(best, i, player_),LeadOrdering(ActionStruct(best, i, player_)) }); - } - else { - temp.push_back({ ActionStruct(best, i, player_),FollowOrdering(ActionStruct(best, i, player_)) }); - } + out.push_back(ActionStruct(best,i,player_)); suit_mask = blsr_u32(suit_mask); } } } - std::sort(temp.begin(), temp.end()); - for (size_t i = 0; i < temp.size(); ++i) { - out.push_back(temp[i].action); - } - #ifdef DEBUG std::cout << "Player " << player_ << " MoveGen " << std::endl; for (size_t i = 0; i < out.size(); ++i) { @@ -347,13 +335,30 @@ class Node { //solvers below int AlphaBeta(Node* node, int alpha, int beta) { - //fail soft ab search + //fail soft ab search// + //uses move ordering to speed up search// if (node->IsTerminal()) { return node->Score(); } - else if (node->Player() == 0) { + //move ordering code// + std::vector actions = node->LegalActions(); + std::vector temp; + temp.reserve(kNumRanks); + for(int i =0;iMoves()%2==0){ + temp.push_back({actions[i],node->LeadOrdering(actions[i])}); + } + else{ + temp.push_back({actions[i],node->FollowOrdering(actions[i])}); + } + } + std::sort(temp.begin(),temp.end()); + for(int i=0;iPlayer() == 0) { int val = 0; - std::vector actions = node->LegalActions(); for (int i = 0; i < actions.size(); ++i) { node->ApplyAction(actions[i]); val = std::max(val, AlphaBeta(node, alpha, beta)); @@ -367,7 +372,6 @@ int AlphaBeta(Node* node, int alpha, int beta) { } else if (node->Player() == 1) { int val =node->TotalTricks(); - std::vector actions = node->LegalActions(); for (int i = 0; i < actions.size(); ++i) { node->ApplyAction(actions[i]); val = std::min(val, AlphaBeta(node, alpha, beta)); @@ -464,19 +468,6 @@ char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, return val; }; - -char IncrementalMTD(Node* node, char guess,int depth, vectorNa* TTable,std::unordered_map* SuitRanks,std::vector>& bin_coeffs) { - char g = guess; - char upperbound = node->TotalTricks(); - char lowerbound = 0; - while (lowerbound < upperbound) { - char beta; - (g == lowerbound) ? beta = g + 1 : beta = g; - g = IncrementalAlphaBetaMemoryIso(node, beta - 1, beta,depth,TTable,SuitRanks, bin_coeffs); - (g < beta) ? upperbound = g : lowerbound = g; - } - return g; -} std::vector GWhistGenerator(int num,unsigned int seed){ //generates pseudorandom endgames// std::vector out; @@ -570,7 +561,7 @@ void ThreadSolver(int size_endgames, vectorNa* outTTable, vectorNa* TTable, std: suit_arr[j] = suit_arr[j] ^ mask; } Node node(cards, suit_arr, 0, false); - char result = IncrementalMTD(&node, (size_endgames >> 1), 2, TTable, &SuitRanks, bin_coeffs); + char result = IncrementalAlphaBetaMemoryIso(&node,0,size_endgames,2,TTable,&SuitRanks,bin_coeffs); outTTable->Set(count,i, result); } control = NextColex(combination, 2 * size_endgames); @@ -636,7 +627,7 @@ bool TestRetroSolve(int samples, int depth, uint32_t seed, std::vector SuitRanks; GenSuitRankingsRel(depth, &SuitRanks); for (auto it = nodes.begin(); it != nodes.end(); ++it) { - char abm_unsafe = IncrementalMTD(&*it, 6, 2 * (kNumRanks - depth), &v, &SuitRanks, bin_coeffs); + char abm_unsafe = IncrementalAlphaBetaMemoryIso(&*it, 0,kNumRanks, 2 * (kNumRanks - depth), &v, &SuitRanks, bin_coeffs); char abm_safe = AlphaBeta(&*it, 0, kNumRanks); if (abm_unsafe != abm_safe) { return false; @@ -654,12 +645,12 @@ vectorNa BuildTablebase(std::vector>& bin_coeffs) { std::cout<<"Built Tablebase"<<"\n"; return v; } -bool TestTablebase(int samples,uint32_t seed,vectorNa& table_base, std::vector>& bin_coeffs) { +bool TestTablebase(int samples,uint32_t seed,vectorNa& table_base,std::vector>& bin_coeffs){ std::vector nodes = GWhistGenerator(samples, seed); std::unordered_map SuitRanks; GenSuitRankingsRel(kNumRanks, &SuitRanks); for (auto it = nodes.begin(); it != nodes.end(); ++it) { - char abm_unsafe = IncrementalMTD(&*it, 6, 0, &table_base, &SuitRanks, bin_coeffs); + char abm_unsafe = IncrementalAlphaBetaMemoryIso(&*it, 0,kNumRanks, 0, &table_base, &SuitRanks, bin_coeffs); char abm_safe = AlphaBeta(&*it, 0, kNumRanks); if (abm_unsafe != abm_safe) { return false; From c7b337a5a6545e64c24a4383f56375b1a6ee231d Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Tue, 30 Jan 2024 05:34:53 +0000 Subject: [PATCH 19/30] Bithack modification & LoadTTable warning Modified bzhi bithack so it will compile to bzhi when __bmi2__ is defined. Modified pext bithack so it will call _pext_u64 when __bmi2__ is defined, otherwise it will use the bithack. LoadTTable now warns on failing to load and sets TTable to default value(all 0) --- open_spiel/CMakeLists.txt | 6 +-- .../german_whist_foregame.cc | 45 +++++++++++++------ 2 files changed, 34 insertions(+), 17 deletions(-) diff --git a/open_spiel/CMakeLists.txt b/open_spiel/CMakeLists.txt index 1fa956151c..1dc6780bc5 100644 --- a/open_spiel/CMakeLists.txt +++ b/open_spiel/CMakeLists.txt @@ -34,7 +34,7 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE) # Set default build type. set (BUILD_TYPE $ENV{BUILD_TYPE}) if(NOT BUILD_TYPE) - set(BUILD_TYPE Release + set(BUILD_TYPE Testing CACHE STRING "Choose the type of build: Debug Release Testing." FORCE) endif() @@ -50,14 +50,14 @@ if(${BUILD_TYPE} STREQUAL "Testing") # A build used for running tests: keep all runtime checks (assert, # SPIEL_CHECK_*, SPIEL_DCHECK_*), but turn on some speed optimizations, # otherwise tests run for too long. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O2 ") endif() if(${BUILD_TYPE} STREQUAL "Release") # Optimized release build: turn off debug runtime checks (assert, # SPIEL_DCHECK_*) and turn on highest speed optimizations. # The difference in perfomance can be up to 10x higher. - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3 ") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DNDEBUG -O3") endif() if(APPLE) diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 13e15252b9..2a7f147862 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -8,10 +8,15 @@ #include "open_spiel/spiel_utils.h" #include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" +//define BMI2 only if your system supports BMI2 intrinsics, modify compiler flags so that bmi2 instructions are compiled// +//#define __BMI2__ +#ifdef __BMI2__ +#include +#endif namespace open_spiel { namespace german_whist_foregame { - +//set this to the path you expect TTable to be once you have made it so recompilation is not necessary// std::string kTTablePath=""; uint32_t tzcnt_u32(uint32_t a){ @@ -21,10 +26,10 @@ uint64_t tzcnt_u64(uint64_t a){ return __builtin_ctzll(a); } uint32_t bzhi_u32(uint32_t a,uint32_t b){ - return (b==0)?0:((a<<(32-b))>>(32-b)); + return a&((1u<>(64-b)); + return a&((1ULL<>1; }while(m!=0); return r; +#endif } bool Triple::operator<(const Triple& triple)const{ @@ -191,21 +202,27 @@ vectorNa InitialiseTTable(int size,std::vector>& bin_coeff } vectorNa LoadTTable(const std::string filename, int depth,std::vector>& bin_coeffs){ //loads solution from a text file into a vector for use// - std::cout<<"Loading Tablebase"< Date: Tue, 30 Jan 2024 05:47:33 +0000 Subject: [PATCH 20/30] Removed attempt to add compile with BMI2 --- open_spiel/CMakeLists.txt | 6 ------ 1 file changed, 6 deletions(-) diff --git a/open_spiel/CMakeLists.txt b/open_spiel/CMakeLists.txt index 1dc6780bc5..b0f11435d3 100644 --- a/open_spiel/CMakeLists.txt +++ b/open_spiel/CMakeLists.txt @@ -135,8 +135,6 @@ openspiel_optional_dependency(OPEN_SPIEL_BUILD_WITH_ORTOOLS OFF "Build with C++ optimization library OR-Tools.") openspiel_optional_dependency(OPEN_SPIEL_BUILD_WITH_RUST OFF "Build with support for Rust API.") -openspiel_optional_dependency(OPEN_SPIEL_BUILD_WITH_BMI2 ON - "Build with support for BMI2 instructions.") if (WIN32) if (OPEN_SPIEL_BUILD_WITH_HIGC) @@ -318,10 +316,6 @@ if (OPEN_SPIEL_BUILD_WITH_RUST) add_subdirectory(rust) endif() -if(OPEN_SPIEL_BUILD_WITH_BMI2) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mbmi2") -endif() - if (OPEN_SPIEL_BUILD_WITH_PYTHON) add_subdirectory (python) # HIGC needs pyspiel.so and corresponding PYTHONPATH to be set From 20ecb71d552f70eeb1e3358226cfd85e4db5288a Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Tue, 30 Jan 2024 09:12:58 +0000 Subject: [PATCH 21/30] Removing text --- .../games/german_whist_foregame/german_whist_foregame.h | 8 -------- 1 file changed, 8 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.h b/open_spiel/games/german_whist_foregame/german_whist_foregame.h index 6540033c6e..5e6fc3c82a 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.h +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.h @@ -124,14 +124,6 @@ class GWhistFState : public State { int player_; int trump_; bool Trick(int lead,int follow) const; - - - // The move history and number of players are sufficient information to - // specify the state of the game. We keep track of more information to make - // extracting legal actions and utilities easier. - // The cost of the additional book-keeping is more complex ApplyAction() and - // UndoAction() functions - }; }//g_whist_foregame }//open_spiel From 931113a5b214a9f8834368a6831327cf9c786fbd Mon Sep 17 00:00:00 2001 From: willmcgowan <54598089+willmcgowan@users.noreply.github.com> Date: Sun, 18 Feb 2024 02:59:06 +0000 Subject: [PATCH 22/30] Update pyspiel_test.py --- open_spiel/python/tests/pyspiel_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open_spiel/python/tests/pyspiel_test.py b/open_spiel/python/tests/pyspiel_test.py index 038e927f74..f54c5133fb 100644 --- a/open_spiel/python/tests/pyspiel_test.py +++ b/open_spiel/python/tests/pyspiel_test.py @@ -57,7 +57,7 @@ "efg_game", "euchre", "first_sealed_auction", - "german_whist_foregame" + "german_whist_foregame", "gin_rummy", "go", "goofspiel", From 6727aee898f671e9389e76f861739332d8a99a38 Mon Sep 17 00:00:00 2001 From: willmcgowan <54598089+willmcgowan@users.noreply.github.com> Date: Sun, 18 Feb 2024 03:54:10 +0000 Subject: [PATCH 23/30] Update german_whist_foregame.cc to pass api_test --- open_spiel/games/german_whist_foregame/german_whist_foregame.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 2a7f147862..15fae640d9 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -429,6 +429,7 @@ std::string GWhistFState::StateToString() const { } std::string GWhistFState::InformationStateString(Player player) const{ //THIS IS WHAT A PLAYER IS SHOWN WHEN PLAYING// + SPIEL_CHECK_GE(player,0); std::string p = std::to_string(player)+","; std::string cur_hand = ""; std::string observations=""; From a705df1b39aa4aa0a635b775955dedb932fa8983 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Thu, 7 Mar 2024 21:10:26 +0000 Subject: [PATCH 24/30] Changes to pass integration testss Added playthrough with incorrect tablebase value. Checks for InfoStateString and ObservationString --- open_spiel/examples/is_mcts_gwhist.cc | 14 +- .../german_whist_foregame.cc | 4 +- .../german_whist_foregame.h | 20 +- .../playthroughs/german_whist_foregame.txt | 905 ++++++++++++++++++ 4 files changed, 930 insertions(+), 13 deletions(-) create mode 100644 open_spiel/integration_tests/playthroughs/german_whist_foregame.txt diff --git a/open_spiel/examples/is_mcts_gwhist.cc b/open_spiel/examples/is_mcts_gwhist.cc index 5831cb0cda..4abf43473b 100644 --- a/open_spiel/examples/is_mcts_gwhist.cc +++ b/open_spiel/examples/is_mcts_gwhist.cc @@ -27,14 +27,14 @@ namespace { constexpr const int kSeed = 9492110;//93879211; -void PlayGWhist(int human_player, std::mt19937* rng) { +void PlayGWhist(int human_player, std::mt19937* rng,int num_rollouts) { std::shared_ptr game = LoadGame("german_whist_foregame"); std::random_device rd; int eval_seed = rd(); int bot_seed = rd(); auto evaluator = std::make_shared(1, eval_seed); auto bot = std::make_unique( - bot_seed, evaluator, 0.7, 500000, algorithms::kUnlimitedNumWorldSamples, + bot_seed, evaluator, 0.7*13, num_rollouts, algorithms::kUnlimitedNumWorldSamples, algorithms::ISMCTSFinalPolicyType::kMaxVisitCount,true, false); std::unique_ptr state = game->NewInitialState(); while (!state->IsTerminal()) { @@ -77,5 +77,13 @@ void PlayGWhist(int human_player, std::mt19937* rng) { int main(int argc, char** argv) { std::random_device rd; std::mt19937 rng(rd()); - open_spiel::PlayGWhist(0,&rng); + int human_player; + int num_rollouts; + std::cout<<"human_player:"; + std::cin>>human_player; + std::cout<<"\n"; + std::cout<<"num_rollouts:"; + std::cin>>num_rollouts; + std::cout<<"\n"; + open_spiel::PlayGWhist(human_player,&rng,num_rollouts); } diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 2a7f147862..34f14b2ca0 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -428,7 +428,8 @@ std::string GWhistFState::StateToString() const { return out; } std::string GWhistFState::InformationStateString(Player player) const{ - //THIS IS WHAT A PLAYER IS SHOWN WHEN PLAYING// + // THIS IS WHAT A PLAYER IS SHOWN WHEN PLAYING// + SPIEL_CHECK_TRUE(player >= 0 && player < 2); std::string p = std::to_string(player)+","; std::string cur_hand = ""; std::string observations=""; @@ -555,6 +556,7 @@ std::unique_ptr GWhistFState::ResampleFromInfostate(int player_id,std::fu } std::string GWhistFState::ObservationString(Player player) const { //note this is a lie, this is not the observation state string but it is used for ISMCTS to label nodes// + SPIEL_CHECK_TRUE(player >= 0 && player < 2); std::string p = "p"+std::to_string(player)+","; std::string cur_hand=""; std::string public_info = ""; diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.h b/open_spiel/games/german_whist_foregame/german_whist_foregame.h index 5e6fc3c82a..430b7a0915 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.h +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.h @@ -15,8 +15,8 @@ #include "open_spiel/spiel.h" #include "open_spiel/spiel_utils.h" -//The imperfect information part of 2 player whist variant -//https://en.wikipedia.org/wiki/German_Whist +// The imperfect information part of 2 player whist variant +// https://en.wikipedia.org/wiki/German_Whist namespace open_spiel { namespace german_whist_foregame { @@ -31,9 +31,9 @@ inline constexpr int kNumSuits = 4; inline constexpr char kRankChar[] = "AKQJT98765432"; inline constexpr char kSuitChar[] = "CDHS"; -extern std::string kTTablePath ; +extern std::string kTTablePath; -//Reimplementing bmi2 intrinsics with bit operations that will work on all platforms// +// Reimplementing bmi2 intrinsics with bit operations that will work on all platforms// uint32_t tzcnt_u32(uint32_t a); uint64_t tzcnt_u64(uint64_t a); uint32_t bzhi_u32(uint32_t a,uint32_t b); @@ -44,6 +44,8 @@ uint32_t popcnt_u32(uint32_t a); uint64_t popcnt_u64(uint64_t a); uint64_t pext_u64(uint64_t a,uint64_t b); +//containers of cards are 64 bits,with the least significant 52bits being the suits CDHS,with the least sig bit of each suit being the highest rank card// +//this container of masks is used to extract only the cards from a suit// inline const std::array kSuitMasks = { bzhi_u64(~0,kNumRanks),bzhi_u64(~0,2 * kNumRanks) ^ bzhi_u64(~0,kNumRanks),bzhi_u64(~0,3 * kNumRanks) ^ bzhi_u64(~0,2 * kNumRanks),bzhi_u64(~0,4 * kNumRanks) ^ bzhi_u64(~0,3 * kNumRanks) }; @@ -56,7 +58,7 @@ struct Triple{ std::vector GenQuads(int size_endgames); std::vector> BinCoeffs(uint32_t max_n); uint32_t HalfColexer(uint32_t cards,const std::vector>* bin_coeffs); -void GenSuitRankingsRel(uint32_t size, std::unordered_map* Ranks); +void GenSuitRankingsRel(uint32_t size,std::unordered_map* Ranks); class vectorNa{ private: std::vector data; @@ -93,7 +95,7 @@ class GWhistFGame : public Game { std::vector>bin_coeffs_; private: // Number of players. - int num_players_=2; + int num_players_ = 2; }; class GWhistFState : public State { public: @@ -110,7 +112,7 @@ class GWhistFState : public State { std::string InformationStateString(Player player) const override; std::string ObservationString(Player player) const override; std::unique_ptr ResampleFromInfostate(int player_id,std::function rng) const override; - std::string StateToString() const ; + std::string StateToString() const; uint64_t EndgameKey(int player_to_move) const; protected: void DoApplyAction(Action move) override; @@ -125,8 +127,8 @@ class GWhistFState : public State { int trump_; bool Trick(int lead,int follow) const; }; -}//g_whist_foregame -}//open_spiel +}// namespace german_whist_foregame +}// namespace open_spiel #endif OPEN_SPIEL_GAMES_GERMAN_WHIST_FOREGAME_H diff --git a/open_spiel/integration_tests/playthroughs/german_whist_foregame.txt b/open_spiel/integration_tests/playthroughs/german_whist_foregame.txt new file mode 100644 index 0000000000..99b5a9bb80 --- /dev/null +++ b/open_spiel/integration_tests/playthroughs/german_whist_foregame.txt @@ -0,0 +1,905 @@ +game: german_whist_foregame + +GameType.chance_mode = ChanceMode.EXPLICIT_STOCHASTIC +GameType.dynamics = Dynamics.SEQUENTIAL +GameType.information = Information.IMPERFECT_INFORMATION +GameType.long_name = "german_whist_foregame" +GameType.max_num_players = 2 +GameType.min_num_players = 2 +GameType.parameter_specification = [] +GameType.provides_information_state_string = True +GameType.provides_information_state_tensor = False +GameType.provides_observation_string = True +GameType.provides_observation_tensor = False +GameType.provides_factored_observation_string = False +GameType.reward_model = RewardModel.TERMINAL +GameType.short_name = "german_whist_foregame" +GameType.utility = Utility.ZERO_SUM + +NumDistinctActions() = 52 +PolicyTensorShape() = [52] +MaxChanceOutcomes() = 52 +GetParameters() = {} +NumPlayers() = 2 +MinUtility() = -13.0 +MaxUtility() = 13.0 +UtilitySum() = 0.0 +MaxGameLength() = 2129677584 +ToString() = "german_whist_foregame()" + +# State 0 +IsTerminal() = False +History() = [] +HistoryString() = "" +IsChanceNode() = True +IsSimultaneousNode() = False +CurrentPlayer() = -1 +InformationStateString(0) = "0,\n" +InformationStateString(1) = "1,\n" +ObservationString(0) = "p0," +ObservationString(1) = "p1," +ChanceOutcomes() = [(0,0.0192308), (1,0.0192308), (2,0.0192308), (3,0.0192308), (4,0.0192308), (5,0.0192308), (6,0.0192308), (7,0.0192308), (8,0.0192308), (9,0.0192308), (10,0.0192308), (11,0.0192308), (12,0.0192308), (13,0.0192308), (14,0.0192308), (15,0.0192308), (16,0.0192308), (17,0.0192308), (18,0.0192308), (19,0.0192308), (20,0.0192308), (21,0.0192308), (22,0.0192308), (23,0.0192308), (24,0.0192308), (25,0.0192308), (26,0.0192308), (27,0.0192308), (28,0.0192308), (29,0.0192308), (30,0.0192308), (31,0.0192308), (32,0.0192308), (33,0.0192308), (34,0.0192308), (35,0.0192308), (36,0.0192308), (37,0.0192308), (38,0.0192308), (39,0.0192308), (40,0.0192308), (41,0.0192308), (42,0.0192308), (43,0.0192308), (44,0.0192308), (45,0.0192308), (46,0.0192308), (47,0.0192308), (48,0.0192308), (49,0.0192308), (50,0.0192308), (51,0.0192308)] +LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51] +StringLegalActions() = ["CA", "CK", "CQ", "CJ", "CT", "C9", "C8", "C7", "C6", "C5", "C4", "C3", "C2", "DA", "DK", "DQ", "DJ", "DT", "D9", "D8", "D7", "D6", "D5", "D4", "D3", "D2", "HA", "HK", "HQ", "HJ", "HT", "H9", "H8", "H7", "H6", "H5", "H4", "H3", "H2", "SA", "SK", "SQ", "SJ", "ST", "S9", "S8", "S7", "S6", "S5", "S4", "S3", "S2"] + +# Apply action "C2" +action: 12 + +# State 1 +# C2 +IsTerminal() = False +History() = [12] +HistoryString() = "12" +IsChanceNode() = True +IsSimultaneousNode() = False +CurrentPlayer() = -1 +InformationStateString(0) = "0,C2,\n" +InformationStateString(1) = "1,\n" +ObservationString(0) = "p0,C2," +ObservationString(1) = "p1," +ChanceOutcomes() = [(0,0.0196078), (1,0.0196078), (2,0.0196078), (3,0.0196078), (4,0.0196078), (5,0.0196078), (6,0.0196078), (7,0.0196078), (8,0.0196078), (9,0.0196078), (10,0.0196078), (11,0.0196078), (13,0.0196078), (14,0.0196078), (15,0.0196078), (16,0.0196078), (17,0.0196078), (18,0.0196078), (19,0.0196078), (20,0.0196078), (21,0.0196078), (22,0.0196078), (23,0.0196078), (24,0.0196078), (25,0.0196078), (26,0.0196078), (27,0.0196078), (28,0.0196078), (29,0.0196078), (30,0.0196078), (31,0.0196078), (32,0.0196078), (33,0.0196078), (34,0.0196078), (35,0.0196078), (36,0.0196078), (37,0.0196078), (38,0.0196078), (39,0.0196078), (40,0.0196078), (41,0.0196078), (42,0.0196078), (43,0.0196078), (44,0.0196078), (45,0.0196078), (46,0.0196078), (47,0.0196078), (48,0.0196078), (49,0.0196078), (50,0.0196078), (51,0.0196078)] +LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51] +StringLegalActions() = ["CA", "CK", "CQ", "CJ", "CT", "C9", "C8", "C7", "C6", "C5", "C4", "C3", "DA", "DK", "DQ", "DJ", "DT", "D9", "D8", "D7", "D6", "D5", "D4", "D3", "D2", "HA", "HK", "HQ", "HJ", "HT", "H9", "H8", "H7", "H6", "H5", "H4", "H3", "H2", "SA", "SK", "SQ", "SJ", "ST", "S9", "S8", "S7", "S6", "S5", "S4", "S3", "S2"] + +# Apply action "HJ" +action: 29 + +# State 2 +# Apply action "CQ" +action: 2 + +# State 3 +# Apply action "H9" +action: 31 + +# State 4 +# Apply action "C9" +action: 5 + +# State 5 +# Apply action "HT" +action: 30 + +# State 6 +# Apply action "DQ" +action: 15 + +# State 7 +# Apply action "SA" +action: 39 + +# State 8 +# Apply action "S3" +action: 50 + +# State 9 +# Apply action "CT" +action: 4 + +# State 10 +# Apply action "HK" +action: 27 + +# State 11 +# Apply action "C5" +action: 9 + +# State 12 +# Apply action "HQ" +action: 28 + +# State 13 +# Apply action "SK" +action: 40 + +# State 14 +# Apply action "D3" +action: 24 + +# State 15 +# Apply action "DK" +action: 14 + +# State 16 +# Apply action "S8" +action: 45 + +# State 17 +# Apply action "D7" +action: 20 + +# State 18 +# Apply action "SQ" +action: 41 + +# State 19 +# Apply action "DJ" +action: 16 + +# State 20 +# Apply action "D9" +action: 18 + +# State 21 +# Apply action "D5" +action: 22 + +# State 22 +# Apply action "S9" +action: 44 + +# State 23 +# Apply action "C7" +action: 7 + +# State 24 +# Apply action "CK" +action: 1 + +# State 25 +# Apply action "H3" +action: 37 + +# State 26 +# Apply action "HA" +action: 26 + +# State 27 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +IsTerminal() = False +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "0,CK,CQ,C9,C2,DQ,D9,D3,HK,HQ,SQ,S9,S8,S3,\nc_public:HA," +InformationStateString(1) = "1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H9,H3,SA,SK,\nc_public:HA," +ObservationString(0) = "p0,CK,CQ,C9,C2,DQ,D9,D3,HK,HQ,SQ,S9,S8,S3,-1:HA," +ObservationString(1) = "p1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H9,H3,SA,SK,-1:HA," +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [1, 2, 5, 12, 15, 18, 24, 27, 28, 41, 44, 45, 50] +StringLegalActions() = ["CK", "CQ", "C9", "C2", "DQ", "D9", "D3", "HK", "HQ", "SQ", "S9", "S8", "S3"] + +# Apply action "HK" +action: 27 + +# State 28 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +# HK +IsTerminal() = False +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "0,CK,CQ,C9,C2,DQ,D9,D3,HQ,SQ,S9,S8,S3,\nc_public:HA,p0:HK," +InformationStateString(1) = "1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H9,H3,SA,SK,\nc_public:HA,p0:HK," +ObservationString(0) = "p0,CK,CQ,C9,C2,DQ,D9,D3,HQ,SQ,S9,S8,S3,-1:HA,0:HK," +ObservationString(1) = "p1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H9,H3,SA,SK,-1:HA,0:HK," +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [29, 30, 31, 37] +StringLegalActions() = ["HJ", "HT", "H9", "H3"] + +# Apply action "H9" +action: 31 + +# State 29 +# Apply action "S4" +action: 49 + +# State 30 +# Apply action "H6" +action: 34 + +# State 31 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +# HK +# H9 +# S4 +# H6 +IsTerminal() = False +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "0,CK,CQ,C9,C2,DQ,D9,D3,HA,HQ,SQ,S9,S8,S3,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6," +InformationStateString(1) = "1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H3,SA,SK,S4,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6," +ObservationString(0) = "p0,CK,CQ,C9,C2,DQ,D9,D3,HA,HQ,SQ,S9,S8,S3,-1:HA,0:HK,1:H9,-1:H6," +ObservationString(1) = "p1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H3,SA,SK,S4,-1:HA,0:HK,1:H9,-1:H6," +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [1, 2, 5, 12, 15, 18, 24, 26, 28, 41, 44, 45, 50] +StringLegalActions() = ["CK", "CQ", "C9", "C2", "DQ", "D9", "D3", "HA", "HQ", "SQ", "S9", "S8", "S3"] + +# Apply action "DQ" +action: 15 + +# State 32 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +# HK +# H9 +# S4 +# H6 +# DQ +IsTerminal() = False +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "0,CK,CQ,C9,C2,D9,D3,HA,HQ,SQ,S9,S8,S3,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ," +InformationStateString(1) = "1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H3,SA,SK,S4,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ," +ObservationString(0) = "p0,CK,CQ,C9,C2,D9,D3,HA,HQ,SQ,S9,S8,S3,-1:HA,0:HK,1:H9,-1:H6,0:DQ," +ObservationString(1) = "p1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H3,SA,SK,S4,-1:HA,0:HK,1:H9,-1:H6,0:DQ," +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [14, 16, 20, 22] +StringLegalActions() = ["DK", "DJ", "D7", "D5"] + +# Apply action "D5" +action: 22 + +# State 33 +# Apply action "DA" +action: 13 + +# State 34 +# Apply action "D4" +action: 23 + +# State 35 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +# HK +# H9 +# S4 +# H6 +# DQ +# D5 +# DA +# D4 +IsTerminal() = False +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "0,CK,CQ,C9,C2,D9,D3,HA,HQ,H6,SQ,S9,S8,S3,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4," +InformationStateString(1) = "1,CT,C7,C5,DA,DK,DJ,D7,HJ,HT,H3,SA,SK,S4,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4," +ObservationString(0) = "p0,CK,CQ,C9,C2,D9,D3,HA,HQ,H6,SQ,S9,S8,S3,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4," +ObservationString(1) = "p1,CT,C7,C5,DA,DK,DJ,D7,HJ,HT,H3,SA,SK,S4,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4," +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [1, 2, 5, 12, 18, 24, 26, 28, 34, 41, 44, 45, 50] +StringLegalActions() = ["CK", "CQ", "C9", "C2", "D9", "D3", "HA", "HQ", "H6", "SQ", "S9", "S8", "S3"] + +# Apply action "SQ" +action: 41 + +# State 36 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +# HK +# H9 +# S4 +# H6 +# DQ +# D5 +# DA +# D4 +# SQ +IsTerminal() = False +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "0,CK,CQ,C9,C2,D9,D3,HA,HQ,H6,S9,S8,S3,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4,p0:SQ," +InformationStateString(1) = "1,CT,C7,C5,DA,DK,DJ,D7,HJ,HT,H3,SA,SK,S4,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4,p0:SQ," +ObservationString(0) = "p0,CK,CQ,C9,C2,D9,D3,HA,HQ,H6,S9,S8,S3,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ," +ObservationString(1) = "p1,CT,C7,C5,DA,DK,DJ,D7,HJ,HT,H3,SA,SK,S4,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ," +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [39, 40, 49] +StringLegalActions() = ["SA", "SK", "S4"] + +# Apply action "S4" +action: 49 + +# State 37 +# Apply action "C3" +action: 11 + +# State 38 +# Apply action "SJ" +action: 42 + +# State 39 +# Apply action "H6" +action: 34 + +# State 40 +# Apply action "HT" +action: 30 + +# State 41 +# Apply action "S2" +action: 51 + +# State 42 +# Apply action "H7" +action: 33 + +# State 43 +# Apply action "C7" +action: 7 + +# State 44 +# Apply action "CK" +action: 1 + +# State 45 +# Apply action "D8" +action: 19 + +# State 46 +# Apply action "S5" +action: 48 + +# State 47 +# Apply action "CQ" +action: 2 + +# State 48 +# Apply action "C3" +action: 11 + +# State 49 +# Apply action "S7" +action: 46 + +# State 50 +# Apply action "H2" +action: 38 + +# State 51 +# Apply action "S8" +action: 45 + +# State 52 +# Apply action "S7" +action: 46 + +# State 53 +# Apply action "S6" +action: 47 + +# State 54 +# Apply action "C6" +action: 8 + +# State 55 +# Apply action "D3" +action: 24 + +# State 56 +# Apply action "D7" +action: 20 + +# State 57 +# Apply action "CJ" +action: 3 + +# State 58 +# Apply action "ST" +action: 43 + +# State 59 +# Apply action "DK" +action: 14 + +# State 60 +# Apply action "D4" +action: 23 + +# State 61 +# Apply action "H5" +action: 35 + +# State 62 +# Apply action "CA" +action: 0 + +# State 63 +# Apply action "CT" +action: 4 + +# State 64 +# Apply action "CJ" +action: 3 + +# State 65 +# Apply action "H4" +action: 36 + +# State 66 +# Apply action "D6" +action: 21 + +# State 67 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +# HK +# H9 +# S4 +# H6 +# DQ +# D5 +# DA +# D4 +# SQ +# S4 +# C3 +# SJ +# H6 +# HT +# S2 +# H7 +# C7 +# CK +# D8 +# S5 +# CQ +# C3 +# S7 +# H2 +# S8 +# S7 +# S6 +# C6 +# D3 +# D7 +# CJ +# ST +# DK +# D4 +# H5 +# CA +# CT +# CJ +# H4 +# D6 +IsTerminal() = False +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 0 +InformationStateString(0) = "0,CA,C9,C2,D9,HA,HQ,H7,H5,H2,S9,S5,S3,S2,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4,p0:SQ,p1:S4,c_unobserved:\nc_public:SJ,p0:H6,p1:HT,c_observed:S2\nc_public:H7,p1:C7,p0:CK,c_unobserved:\nc_public:S5,p0:CQ,p1:C3,c_unobserved:\nc_public:H2,p0:S8,p1:S7,c_unobserved:\nc_public:C6,p0:D3,p1:D7,c_observed:CJ\nc_public:ST,p1:DK,p0:D4,c_observed:H5\nc_public:CA,p1:CT,p0:CJ,c_unobserved:\nc_public:D6," +InformationStateString(1) = "1,C6,C5,DA,DJ,D8,HJ,H4,H3,SA,SK,SJ,ST,S6,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4,p0:SQ,p1:S4,c_observed:C3\nc_public:SJ,p0:H6,p1:HT,c_unobserved:\nc_public:H7,p1:C7,p0:CK,c_observed:D8\nc_public:S5,p0:CQ,p1:C3,c_observed:S7\nc_public:H2,p0:S8,p1:S7,c_observed:S6\nc_public:C6,p0:D3,p1:D7,c_unobserved:\nc_public:ST,p1:DK,p0:D4,c_unobserved:\nc_public:CA,p1:CT,p0:CJ,c_observed:H4\nc_public:D6," +ObservationString(0) = "p0,CA,C9,C2,D9,HA,HQ,H7,H5,H2,S9,S5,S3,S2,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6," +ObservationString(1) = "p1,C6,C5,DA,DJ,D8,HJ,H4,H3,SA,SK,SJ,ST,S6,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6," +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [0, 5, 12, 18, 26, 28, 33, 35, 38, 44, 48, 50, 51] +StringLegalActions() = ["CA", "C9", "C2", "D9", "HA", "HQ", "H7", "H5", "H2", "S9", "S5", "S3", "S2"] + +# Apply action "H7" +action: 33 + +# State 68 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +# HK +# H9 +# S4 +# H6 +# DQ +# D5 +# DA +# D4 +# SQ +# S4 +# C3 +# SJ +# H6 +# HT +# S2 +# H7 +# C7 +# CK +# D8 +# S5 +# CQ +# C3 +# S7 +# H2 +# S8 +# S7 +# S6 +# C6 +# D3 +# D7 +# CJ +# ST +# DK +# D4 +# H5 +# CA +# CT +# CJ +# H4 +# D6 +# H7 +IsTerminal() = False +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21, 33] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21, 33" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = 1 +InformationStateString(0) = "0,CA,C9,C2,D9,HA,HQ,H5,H2,S9,S5,S3,S2,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4,p0:SQ,p1:S4,c_unobserved:\nc_public:SJ,p0:H6,p1:HT,c_observed:S2\nc_public:H7,p1:C7,p0:CK,c_unobserved:\nc_public:S5,p0:CQ,p1:C3,c_unobserved:\nc_public:H2,p0:S8,p1:S7,c_unobserved:\nc_public:C6,p0:D3,p1:D7,c_observed:CJ\nc_public:ST,p1:DK,p0:D4,c_observed:H5\nc_public:CA,p1:CT,p0:CJ,c_unobserved:\nc_public:D6,p0:H7," +InformationStateString(1) = "1,C6,C5,DA,DJ,D8,HJ,H4,H3,SA,SK,SJ,ST,S6,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4,p0:SQ,p1:S4,c_observed:C3\nc_public:SJ,p0:H6,p1:HT,c_unobserved:\nc_public:H7,p1:C7,p0:CK,c_observed:D8\nc_public:S5,p0:CQ,p1:C3,c_observed:S7\nc_public:H2,p0:S8,p1:S7,c_observed:S6\nc_public:C6,p0:D3,p1:D7,c_unobserved:\nc_public:ST,p1:DK,p0:D4,c_unobserved:\nc_public:CA,p1:CT,p0:CJ,c_observed:H4\nc_public:D6,p0:H7," +ObservationString(0) = "p0,CA,C9,C2,D9,HA,HQ,H5,H2,S9,S5,S3,S2,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6,0:H7," +ObservationString(1) = "p1,C6,C5,DA,DJ,D8,HJ,H4,H3,SA,SK,SJ,ST,S6,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6,0:H7," +Rewards() = [0, 0] +Returns() = [0, 0] +LegalActions() = [29, 36, 37] +StringLegalActions() = ["HJ", "H4", "H3"] + +# Apply action "HJ" +action: 29 + +# State 69 +# Apply action "D2" +action: 25 + +# State 70 +# Apply action "H8" +action: 32 + +# State 71 +# Apply action "DA" +action: 13 + +# State 72 +# Apply action "D2" +action: 25 + +# State 73 +# Apply action "C4" +action: 10 + +# State 74 +# Apply action "C8" +action: 6 + +# State 75 +# Apply action "D6" +action: 21 + +# State 76 +# Apply action "D9" +action: 18 + +# State 77 +# Apply action "DT" +action: 17 + +# State 78 +# C2 +# HJ +# CQ +# H9 +# C9 +# HT +# DQ +# SA +# S3 +# CT +# HK +# C5 +# HQ +# SK +# D3 +# DK +# S8 +# D7 +# SQ +# DJ +# D9 +# D5 +# S9 +# C7 +# CK +# H3 +# HA +# HK +# H9 +# S4 +# H6 +# DQ +# D5 +# DA +# D4 +# SQ +# S4 +# C3 +# SJ +# H6 +# HT +# S2 +# H7 +# C7 +# CK +# D8 +# S5 +# CQ +# C3 +# S7 +# H2 +# S8 +# S7 +# S6 +# C6 +# D3 +# D7 +# CJ +# ST +# DK +# D4 +# H5 +# CA +# CT +# CJ +# H4 +# D6 +# H7 +# HJ +# D2 +# H8 +# DA +# D2 +# C4 +# C8 +# D6 +# D9 +# DT +IsTerminal() = True +History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21, 33, 29, 25, 32, 13, 25, 10, 6, 21, 18, 17] +HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21, 33, 29, 25, 32, 13, 25, 10, 6, 21, 18, 17" +IsChanceNode() = False +IsSimultaneousNode() = False +CurrentPlayer() = -4 +InformationStateString(0) = "0,CA,C9,C8,C4,C2,HA,HQ,H5,H2,S9,S5,S3,S2,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4,p0:SQ,p1:S4,c_unobserved:\nc_public:SJ,p0:H6,p1:HT,c_observed:S2\nc_public:H7,p1:C7,p0:CK,c_unobserved:\nc_public:S5,p0:CQ,p1:C3,c_unobserved:\nc_public:H2,p0:S8,p1:S7,c_unobserved:\nc_public:C6,p0:D3,p1:D7,c_observed:CJ\nc_public:ST,p1:DK,p0:D4,c_observed:H5\nc_public:CA,p1:CT,p0:CJ,c_unobserved:\nc_public:D6,p0:H7,p1:HJ,c_observed:D2\nc_public:H8,p1:DA,p0:D2,c_observed:C4\nc_public:C8,p1:D6,p0:D9,c_unobserved:\n" +InformationStateString(1) = "1,C6,C5,DJ,DT,D8,H8,H4,H3,SA,SK,SJ,ST,S6,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4,p0:SQ,p1:S4,c_observed:C3\nc_public:SJ,p0:H6,p1:HT,c_unobserved:\nc_public:H7,p1:C7,p0:CK,c_observed:D8\nc_public:S5,p0:CQ,p1:C3,c_observed:S7\nc_public:H2,p0:S8,p1:S7,c_observed:S6\nc_public:C6,p0:D3,p1:D7,c_unobserved:\nc_public:ST,p1:DK,p0:D4,c_unobserved:\nc_public:CA,p1:CT,p0:CJ,c_observed:H4\nc_public:D6,p0:H7,p1:HJ,c_unobserved:\nc_public:H8,p1:DA,p0:D2,c_unobserved:\nc_public:C8,p1:D6,p0:D9,c_observed:DT\n" +ObservationString(0) = "p0,CA,C9,C8,C4,C2,HA,HQ,H5,H2,S9,S5,S3,S2,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6,0:H7,1:HJ,-1:H8,1:DA,0:D2,-1:C8,1:D6,0:D9," +ObservationString(1) = "p1,C6,C5,DJ,DT,D8,H8,H4,H3,SA,SK,SJ,ST,S6,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6,0:H7,1:HJ,-1:H8,1:DA,0:D2,-1:C8,1:D6,0:D9," +Rewards() = [-13, 13] +Returns() = [-13, 13] From c65002d5007530d68c45d7118addb271fe77a1fc Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Fri, 8 Mar 2024 13:19:01 +0000 Subject: [PATCH 25/30] Formatting --- .../german_whist_foregame.cc | 110 +- .../german_whist_foregame.h | 6 +- .../playthroughs/german_whist_foregame.txt | 1098 ++++++++--------- 3 files changed, 607 insertions(+), 607 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 34f14b2ca0..7d0200c6f8 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -8,43 +8,43 @@ #include "open_spiel/spiel_utils.h" #include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" -//define BMI2 only if your system supports BMI2 intrinsics, modify compiler flags so that bmi2 instructions are compiled// -//#define __BMI2__ +// define BMI2 only if your system supports BMI2 intrinsics, modify compiler flags so that bmi2 instructions are compiled// +// #define __BMI2__ #ifdef __BMI2__ #include #endif namespace open_spiel { namespace german_whist_foregame { -//set this to the path you expect TTable to be once you have made it so recompilation is not necessary// +// set this to the path you expect TTable to be once you have made it so recompilation is not necessary// std::string kTTablePath=""; -uint32_t tzcnt_u32(uint32_t a){ +uint32_t tzcnt_u32(uint32_t a) { return __builtin_ctz(a); } -uint64_t tzcnt_u64(uint64_t a){ +uint64_t tzcnt_u64(uint64_t a) { return __builtin_ctzll(a); } -uint32_t bzhi_u32(uint32_t a,uint32_t b){ +uint32_t bzhi_u32(uint32_t a,uint32_t b) { return a&((1u< GenQuads(int size_endgames) { - //Generates Suit splittings for endgames of a certain size// + // Generates Suit splittings for endgames of a certain size// std::vector v; for (char i = 0; i <= std::min(size_endgames * 2, kNumRanks); ++i) { int sum = size_endgames * 2 - i; @@ -147,35 +147,35 @@ void GenSuitRankingsRel(uint32_t size, std::unordered_map* R } } -vectorNa::vectorNa(size_t card_combs,size_t suit_splits,char val){ +vectorNa::vectorNa(size_t card_combs,size_t suit_splits,char val) { data=std::vector(card_combs*((suit_splits>>1)+1),val); inner_size =(suit_splits>>1)+1; outer_size = card_combs; } -vectorNa::vectorNa(){ +vectorNa::vectorNa() { data={}; inner_size=0; outer_size=0; } -size_t vectorNa::size() const{ +size_t vectorNa::size() const { return data.size(); } -size_t vectorNa::GetInnerSize()const{ +size_t vectorNa::GetInnerSize() const { return inner_size; } -size_t vectorNa::GetOuterSize()const{ +size_t vectorNa::GetOuterSize() const { return outer_size; } -char const& vectorNa::operator[](size_t index) const{ +char const& vectorNa::operator[](size_t index) const { return data[index]; } -char vectorNa::GetChar(size_t i,size_t j)const{ +char vectorNa::GetChar(size_t i,size_t j) const { return data[i*inner_size+j]; } void vectorNa::SetChar(size_t i,size_t j,char value){ data[i*inner_size+j]=value; } -char vectorNa::Get(size_t i,size_t j) const{ +char vectorNa::Get(size_t i,size_t j) const { int remainder = j&0b1; if(remainder==0){ return 0b1111&data[i*inner_size+(j>>1)]; @@ -184,7 +184,7 @@ char vectorNa::Get(size_t i,size_t j) const{ return ((0b11110000&data[i*inner_size+(j>>1)])>>4); } } -void vectorNa::Set(size_t i,size_t j,char value){ +void vectorNa::Set(size_t i,size_t j,char value) { int remainder = j & 0b1; if (remainder == 0) { char datastore = 0b11110000 & data[i*inner_size+(j>>1)]; @@ -200,21 +200,21 @@ vectorNa InitialiseTTable(int size,std::vector>& bin_coeff size_t suit_size = GenQuads(size).size(); return vectorNa(bin_coeffs[2 * size][size],suit_size, 0); } -vectorNa LoadTTable(const std::string filename, int depth,std::vector>& bin_coeffs){ +vectorNa LoadTTable(const std::string filename, int depth,std::vector>& bin_coeffs) { //loads solution from a text file into a vector for use// std::cout<<"Loading Tablebase"<<"\n"; vectorNa v = InitialiseTTable(depth,bin_coeffs); std::ifstream file(filename,std::ios::binary); - if(!file.is_open()){ + if (!file.is_open()) { std::cout<<"Failed to load Tablebase"<<"\n"; std::cout<<"Tablebase will be set to all 0"<<"\n"; file.close(); return v; } - else{ + else { char c; - for(int i =0;i Factory(const GameParameters& params) { REGISTER_SPIEL_GAME(kGameType, Factory); }//namespace -GWhistFGame::GWhistFGame(const GameParameters& params):Game(kGameType, params){ +GWhistFGame::GWhistFGame(const GameParameters& params):Game(kGameType, params) { bin_coeffs_=BinCoeffs(2*kNumRanks); std::unordered_map temp; GenSuitRankingsRel(13,&temp); @@ -286,7 +286,7 @@ bool GWhistFState::Trick(int lead, int follow) const { bool GWhistFState::IsTerminal() const { return(popcnt_u64(deck_) == 0); } -uint64_t GWhistFState::EndgameKey(int player_to_move) const{ +uint64_t GWhistFState::EndgameKey(int player_to_move) const { //generates a 64 bit unsigned int where the first 32 are the suit ownerships from the perspective of the opponent using canonical rankings// //example: if Spade suit is to_move = A3, opp =2, suit = 0b100 //least significant part of first 32 bits is the trump suit, then the remaining suits ascending length order. @@ -294,8 +294,8 @@ uint64_t GWhistFState::EndgameKey(int player_to_move) const{ std::vector suit_lengths = {}; int opp = (player_to_move==0)?1:0; //sort trump suits by length,then sig// - for(int i =0;i hand1; hand0[0]=pext_u64(hands_[0],kSuitMasks[trump_]); hand1[0]=pext_u64(hands_[1],kSuitMasks[trump_]); - for(int i =0;ihands_shuffled = {0,0}; - for(int i =0;i GWhistFState::Returns() const{ - if(IsTerminal()){ +std::vector GWhistFState::Returns() const { + if (IsTerminal()) { std::vector out = {0,0}; int lead_win = Trick(history_[move_number_ - 3].action, history_[move_number_ - 2].action); int player_to_move=(lead_win)?history_[move_number_-3].player:history_[move_number_-2].player; @@ -341,7 +341,7 @@ std::vector GWhistFState::Returns() const{ out[opp]=-out[player_to_move]; return out; } - else{ + else { std::vector out = {0,0}; return out; } @@ -353,10 +353,10 @@ int GWhistFState::CurrentPlayer() const { return player_; } std::vector> GWhistFState::ChanceOutcomes() const { std::vector> outcomes; std::vector legal_actions = LegalActions(); - for(int i =0;i pair; pair.first =legal_actions[i]; - pair.second = 1/double(legal_actions.size()); + pair.second = 1.0/legal_actions.size(); outcomes.push_back(pair); } return outcomes; @@ -364,7 +364,7 @@ std::vector> GWhistFState::ChanceOutcomes() const { std::string GWhistFState::ActionToString(Player player,Action move) const { return CardString(move); } -std::string GWhistFState::ToString() const{ +std::string GWhistFState::ToString() const { std::string out; for (int i = 0; i < history_.size(); ++i) { out += ActionToString(history_[i].player, history_[i].action); @@ -372,7 +372,7 @@ std::string GWhistFState::ToString() const{ } return out; } -std::unique_ptr GWhistFState::Clone() const{ +std::unique_ptr GWhistFState::Clone() const { return std::unique_ptr(new GWhistFState(*this)); } @@ -427,7 +427,7 @@ std::string GWhistFState::StateToString() const { } return out; } -std::string GWhistFState::InformationStateString(Player player) const{ +std::string GWhistFState::InformationStateString(Player player) const { // THIS IS WHAT A PLAYER IS SHOWN WHEN PLAYING// SPIEL_CHECK_TRUE(player >= 0 && player < 2); std::string p = std::to_string(player)+","; @@ -435,19 +435,19 @@ std::string GWhistFState::InformationStateString(Player player) const{ std::string observations=""; std::vector v_hand = {}; uint64_t p_hand = hands_[player]; - while(p_hand!=0){ + while (p_hand!=0) { v_hand.push_back(tzcnt_u64(p_hand)); p_hand = blsr_u64(p_hand); } std::sort(v_hand.begin(),v_hand.end()); - for(int i =0;i GWhistFState::ResampleFromInfostate(int player_id,std::function rng) const{ +std::unique_ptr GWhistFState::ResampleFromInfostate(int player_id,std::function rng) const { //only valid when called from a position where a player can act// auto resampled_state = std::unique_ptr(new GWhistFState(*this)); //seeding mt19937// @@ -562,15 +562,15 @@ std::string GWhistFState::ObservationString(Player player) const { std::string public_info = ""; uint64_t p_hand = hands_[player]; std::vector v_hand = {}; - while(p_hand!=0){ + while (p_hand!=0) { v_hand.push_back(tzcnt_u64(p_hand)); p_hand = blsr_u64(p_hand); } std::sort(v_hand.begin(),v_hand.end()); - for(int i =0;i kSuitMasks = { bzhi_u64(~0,kNumRanks),bzhi_u64(~0,2 * kNumRanks) ^ bzhi_u64(~0,kNumRanks),bzhi_u64(~0,3 * kNumRanks) ^ bzhi_u64(~0,2 * kNumRanks),bzhi_u64(~0,4 * kNumRanks) ^ bzhi_u64(~0,3 * kNumRanks) }; @@ -88,7 +88,7 @@ class GWhistFGame : public Game { double MinUtility() const override {return -kNumRanks;}; double MaxUtility() const override {return kNumRanks;}; absl::optional UtilitySum() const override { return 0; }; - int MaxGameLength() const override{kNumRanks*(kNumSuits+2);}; + int MaxGameLength() const override{return kNumRanks*(kNumSuits+2);}; int MaxChanceNodesInHistory() const override{return kNumRanks*kNumSuits;}; vectorNa ttable_; std::unordered_map suit_ranks_; diff --git a/open_spiel/integration_tests/playthroughs/german_whist_foregame.txt b/open_spiel/integration_tests/playthroughs/german_whist_foregame.txt index 99b5a9bb80..f068c1fd86 100644 --- a/open_spiel/integration_tests/playthroughs/german_whist_foregame.txt +++ b/open_spiel/integration_tests/playthroughs/german_whist_foregame.txt @@ -24,7 +24,7 @@ NumPlayers() = 2 MinUtility() = -13.0 MaxUtility() = 13.0 UtilitySum() = 0.0 -MaxGameLength() = 2129677584 +MaxGameLength() = 78 ToString() = "german_whist_foregame()" # State 0 @@ -42,864 +42,864 @@ ChanceOutcomes() = [(0,0.0192308), (1,0.0192308), (2,0.0192308), (3,0.0192308), LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51] StringLegalActions() = ["CA", "CK", "CQ", "CJ", "CT", "C9", "C8", "C7", "C6", "C5", "C4", "C3", "C2", "DA", "DK", "DQ", "DJ", "DT", "D9", "D8", "D7", "D6", "D5", "D4", "D3", "D2", "HA", "HK", "HQ", "HJ", "HT", "H9", "H8", "H7", "H6", "H5", "H4", "H3", "H2", "SA", "SK", "SQ", "SJ", "ST", "S9", "S8", "S7", "S6", "S5", "S4", "S3", "S2"] -# Apply action "C2" -action: 12 +# Apply action "SA" +action: 39 # State 1 -# C2 +# SA IsTerminal() = False -History() = [12] -HistoryString() = "12" +History() = [39] +HistoryString() = "39" IsChanceNode() = True IsSimultaneousNode() = False CurrentPlayer() = -1 -InformationStateString(0) = "0,C2,\n" +InformationStateString(0) = "0,SA,\n" InformationStateString(1) = "1,\n" -ObservationString(0) = "p0,C2," +ObservationString(0) = "p0,SA," ObservationString(1) = "p1," -ChanceOutcomes() = [(0,0.0196078), (1,0.0196078), (2,0.0196078), (3,0.0196078), (4,0.0196078), (5,0.0196078), (6,0.0196078), (7,0.0196078), (8,0.0196078), (9,0.0196078), (10,0.0196078), (11,0.0196078), (13,0.0196078), (14,0.0196078), (15,0.0196078), (16,0.0196078), (17,0.0196078), (18,0.0196078), (19,0.0196078), (20,0.0196078), (21,0.0196078), (22,0.0196078), (23,0.0196078), (24,0.0196078), (25,0.0196078), (26,0.0196078), (27,0.0196078), (28,0.0196078), (29,0.0196078), (30,0.0196078), (31,0.0196078), (32,0.0196078), (33,0.0196078), (34,0.0196078), (35,0.0196078), (36,0.0196078), (37,0.0196078), (38,0.0196078), (39,0.0196078), (40,0.0196078), (41,0.0196078), (42,0.0196078), (43,0.0196078), (44,0.0196078), (45,0.0196078), (46,0.0196078), (47,0.0196078), (48,0.0196078), (49,0.0196078), (50,0.0196078), (51,0.0196078)] -LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51] -StringLegalActions() = ["CA", "CK", "CQ", "CJ", "CT", "C9", "C8", "C7", "C6", "C5", "C4", "C3", "DA", "DK", "DQ", "DJ", "DT", "D9", "D8", "D7", "D6", "D5", "D4", "D3", "D2", "HA", "HK", "HQ", "HJ", "HT", "H9", "H8", "H7", "H6", "H5", "H4", "H3", "H2", "SA", "SK", "SQ", "SJ", "ST", "S9", "S8", "S7", "S6", "S5", "S4", "S3", "S2"] +ChanceOutcomes() = [(0,0.0196078), (1,0.0196078), (2,0.0196078), (3,0.0196078), (4,0.0196078), (5,0.0196078), (6,0.0196078), (7,0.0196078), (8,0.0196078), (9,0.0196078), (10,0.0196078), (11,0.0196078), (12,0.0196078), (13,0.0196078), (14,0.0196078), (15,0.0196078), (16,0.0196078), (17,0.0196078), (18,0.0196078), (19,0.0196078), (20,0.0196078), (21,0.0196078), (22,0.0196078), (23,0.0196078), (24,0.0196078), (25,0.0196078), (26,0.0196078), (27,0.0196078), (28,0.0196078), (29,0.0196078), (30,0.0196078), (31,0.0196078), (32,0.0196078), (33,0.0196078), (34,0.0196078), (35,0.0196078), (36,0.0196078), (37,0.0196078), (38,0.0196078), (40,0.0196078), (41,0.0196078), (42,0.0196078), (43,0.0196078), (44,0.0196078), (45,0.0196078), (46,0.0196078), (47,0.0196078), (48,0.0196078), (49,0.0196078), (50,0.0196078), (51,0.0196078)] +LegalActions() = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51] +StringLegalActions() = ["CA", "CK", "CQ", "CJ", "CT", "C9", "C8", "C7", "C6", "C5", "C4", "C3", "C2", "DA", "DK", "DQ", "DJ", "DT", "D9", "D8", "D7", "D6", "D5", "D4", "D3", "D2", "HA", "HK", "HQ", "HJ", "HT", "H9", "H8", "H7", "H6", "H5", "H4", "H3", "H2", "SK", "SQ", "SJ", "ST", "S9", "S8", "S7", "S6", "S5", "S4", "S3", "S2"] -# Apply action "HJ" -action: 29 +# Apply action "CJ" +action: 3 # State 2 -# Apply action "CQ" -action: 2 +# Apply action "C3" +action: 11 # State 3 -# Apply action "H9" -action: 31 +# Apply action "H7" +action: 33 # State 4 -# Apply action "C9" -action: 5 +# Apply action "H9" +action: 31 # State 5 -# Apply action "HT" -action: 30 +# Apply action "SJ" +action: 42 # State 6 -# Apply action "DQ" -action: 15 +# Apply action "H3" +action: 37 # State 7 -# Apply action "SA" -action: 39 +# Apply action "CK" +action: 1 # State 8 -# Apply action "S3" -action: 50 +# Apply action "H5" +action: 35 # State 9 -# Apply action "CT" -action: 4 +# Apply action "D7" +action: 20 # State 10 -# Apply action "HK" -action: 27 +# Apply action "D5" +action: 22 # State 11 -# Apply action "C5" -action: 9 +# Apply action "DT" +action: 17 # State 12 -# Apply action "HQ" -action: 28 +# Apply action "D8" +action: 19 # State 13 -# Apply action "SK" -action: 40 +# Apply action "H4" +action: 36 # State 14 -# Apply action "D3" -action: 24 +# Apply action "SQ" +action: 41 # State 15 -# Apply action "DK" -action: 14 +# Apply action "C9" +action: 5 # State 16 -# Apply action "S8" -action: 45 +# Apply action "DQ" +action: 15 # State 17 -# Apply action "D7" -action: 20 +# Apply action "HT" +action: 30 # State 18 -# Apply action "SQ" -action: 41 +# Apply action "D3" +action: 24 # State 19 -# Apply action "DJ" -action: 16 +# Apply action "HQ" +action: 28 # State 20 -# Apply action "D9" -action: 18 +# Apply action "S5" +action: 48 # State 21 -# Apply action "D5" -action: 22 +# Apply action "SK" +action: 40 # State 22 -# Apply action "S9" -action: 44 +# Apply action "HJ" +action: 29 # State 23 -# Apply action "C7" -action: 7 +# Apply action "ST" +action: 43 # State 24 -# Apply action "CK" -action: 1 +# Apply action "H2" +action: 38 # State 25 -# Apply action "H3" -action: 37 +# Apply action "C4" +action: 10 # State 26 -# Apply action "HA" -action: 26 +# Apply action "S8" +action: 45 # State 27 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK -# H3 -# HA IsTerminal() = False -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26" +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45" IsChanceNode() = False IsSimultaneousNode() = False CurrentPlayer() = 0 -InformationStateString(0) = "0,CK,CQ,C9,C2,DQ,D9,D3,HK,HQ,SQ,S9,S8,S3,\nc_public:HA," -InformationStateString(1) = "1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H9,H3,SA,SK,\nc_public:HA," -ObservationString(0) = "p0,CK,CQ,C9,C2,DQ,D9,D3,HK,HQ,SQ,S9,S8,S3,-1:HA," -ObservationString(1) = "p1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H9,H3,SA,SK,-1:HA," +InformationStateString(0) = "0,C3,DQ,D8,D5,D3,HJ,H9,H5,H3,H2,SA,SQ,S5,\nc_public:S8," +InformationStateString(1) = "1,CK,CJ,C9,C4,DT,D7,HQ,HT,H7,H4,SK,SJ,ST,\nc_public:S8," +ObservationString(0) = "p0,C3,DQ,D8,D5,D3,HJ,H9,H5,H3,H2,SA,SQ,S5,-1:S8," +ObservationString(1) = "p1,CK,CJ,C9,C4,DT,D7,HQ,HT,H7,H4,SK,SJ,ST,-1:S8," Rewards() = [0, 0] Returns() = [0, 0] -LegalActions() = [1, 2, 5, 12, 15, 18, 24, 27, 28, 41, 44, 45, 50] -StringLegalActions() = ["CK", "CQ", "C9", "C2", "DQ", "D9", "D3", "HK", "HQ", "SQ", "S9", "S8", "S3"] +LegalActions() = [11, 15, 19, 22, 24, 29, 31, 35, 37, 38, 39, 41, 48] +StringLegalActions() = ["C3", "DQ", "D8", "D5", "D3", "HJ", "H9", "H5", "H3", "H2", "SA", "SQ", "S5"] -# Apply action "HK" -action: 27 +# Apply action "H3" +action: 37 # State 28 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK # H3 -# HA -# HK IsTerminal() = False -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27" +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37" IsChanceNode() = False IsSimultaneousNode() = False CurrentPlayer() = 1 -InformationStateString(0) = "0,CK,CQ,C9,C2,DQ,D9,D3,HQ,SQ,S9,S8,S3,\nc_public:HA,p0:HK," -InformationStateString(1) = "1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H9,H3,SA,SK,\nc_public:HA,p0:HK," -ObservationString(0) = "p0,CK,CQ,C9,C2,DQ,D9,D3,HQ,SQ,S9,S8,S3,-1:HA,0:HK," -ObservationString(1) = "p1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H9,H3,SA,SK,-1:HA,0:HK," +InformationStateString(0) = "0,C3,DQ,D8,D5,D3,HJ,H9,H5,H2,SA,SQ,S5,\nc_public:S8,p0:H3," +InformationStateString(1) = "1,CK,CJ,C9,C4,DT,D7,HQ,HT,H7,H4,SK,SJ,ST,\nc_public:S8,p0:H3," +ObservationString(0) = "p0,C3,DQ,D8,D5,D3,HJ,H9,H5,H2,SA,SQ,S5,-1:S8,0:H3," +ObservationString(1) = "p1,CK,CJ,C9,C4,DT,D7,HQ,HT,H7,H4,SK,SJ,ST,-1:S8,0:H3," Rewards() = [0, 0] Returns() = [0, 0] -LegalActions() = [29, 30, 31, 37] -StringLegalActions() = ["HJ", "HT", "H9", "H3"] +LegalActions() = [28, 30, 33, 36] +StringLegalActions() = ["HQ", "HT", "H7", "H4"] -# Apply action "H9" -action: 31 +# Apply action "HQ" +action: 28 # State 29 -# Apply action "S4" -action: 49 +# Apply action "D4" +action: 23 # State 30 -# Apply action "H6" -action: 34 +# Apply action "C2" +action: 12 # State 31 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK # H3 -# HA -# HK -# H9 -# S4 -# H6 -IsTerminal() = False -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34" +# HQ +# D4 +# C2 +IsTerminal() = False +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12" IsChanceNode() = False IsSimultaneousNode() = False -CurrentPlayer() = 0 -InformationStateString(0) = "0,CK,CQ,C9,C2,DQ,D9,D3,HA,HQ,SQ,S9,S8,S3,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6," -InformationStateString(1) = "1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H3,SA,SK,S4,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6," -ObservationString(0) = "p0,CK,CQ,C9,C2,DQ,D9,D3,HA,HQ,SQ,S9,S8,S3,-1:HA,0:HK,1:H9,-1:H6," -ObservationString(1) = "p1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H3,SA,SK,S4,-1:HA,0:HK,1:H9,-1:H6," +CurrentPlayer() = 1 +InformationStateString(0) = "0,C3,DQ,D8,D5,D4,D3,HJ,H9,H5,H2,SA,SQ,S5,\nc_public:S8,p0:H3,p1:HQ,c_observed:D4\nc_public:C2," +InformationStateString(1) = "1,CK,CJ,C9,C4,DT,D7,HT,H7,H4,SK,SJ,ST,S8,\nc_public:S8,p0:H3,p1:HQ,c_unobserved:\nc_public:C2," +ObservationString(0) = "p0,C3,DQ,D8,D5,D4,D3,HJ,H9,H5,H2,SA,SQ,S5,-1:S8,0:H3,1:HQ,-1:C2," +ObservationString(1) = "p1,CK,CJ,C9,C4,DT,D7,HT,H7,H4,SK,SJ,ST,S8,-1:S8,0:H3,1:HQ,-1:C2," Rewards() = [0, 0] Returns() = [0, 0] -LegalActions() = [1, 2, 5, 12, 15, 18, 24, 26, 28, 41, 44, 45, 50] -StringLegalActions() = ["CK", "CQ", "C9", "C2", "DQ", "D9", "D3", "HA", "HQ", "SQ", "S9", "S8", "S3"] +LegalActions() = [1, 3, 5, 10, 17, 20, 30, 33, 36, 40, 42, 43, 45] +StringLegalActions() = ["CK", "CJ", "C9", "C4", "DT", "D7", "HT", "H7", "H4", "SK", "SJ", "ST", "S8"] -# Apply action "DQ" -action: 15 +# Apply action "C4" +action: 10 # State 32 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK # H3 -# HA -# HK -# H9 -# S4 -# H6 -# DQ +# HQ +# D4 +# C2 +# C4 IsTerminal() = False -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15" +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10" IsChanceNode() = False IsSimultaneousNode() = False -CurrentPlayer() = 1 -InformationStateString(0) = "0,CK,CQ,C9,C2,D9,D3,HA,HQ,SQ,S9,S8,S3,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ," -InformationStateString(1) = "1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H3,SA,SK,S4,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ," -ObservationString(0) = "p0,CK,CQ,C9,C2,D9,D3,HA,HQ,SQ,S9,S8,S3,-1:HA,0:HK,1:H9,-1:H6,0:DQ," -ObservationString(1) = "p1,CT,C7,C5,DK,DJ,D7,D5,HJ,HT,H3,SA,SK,S4,-1:HA,0:HK,1:H9,-1:H6,0:DQ," +CurrentPlayer() = 0 +InformationStateString(0) = "0,C3,DQ,D8,D5,D4,D3,HJ,H9,H5,H2,SA,SQ,S5,\nc_public:S8,p0:H3,p1:HQ,c_observed:D4\nc_public:C2,p1:C4," +InformationStateString(1) = "1,CK,CJ,C9,DT,D7,HT,H7,H4,SK,SJ,ST,S8,\nc_public:S8,p0:H3,p1:HQ,c_unobserved:\nc_public:C2,p1:C4," +ObservationString(0) = "p0,C3,DQ,D8,D5,D4,D3,HJ,H9,H5,H2,SA,SQ,S5,-1:S8,0:H3,1:HQ,-1:C2,1:C4," +ObservationString(1) = "p1,CK,CJ,C9,DT,D7,HT,H7,H4,SK,SJ,ST,S8,-1:S8,0:H3,1:HQ,-1:C2,1:C4," Rewards() = [0, 0] Returns() = [0, 0] -LegalActions() = [14, 16, 20, 22] -StringLegalActions() = ["DK", "DJ", "D7", "D5"] +LegalActions() = [11] +StringLegalActions() = ["C3"] -# Apply action "D5" -action: 22 +# Apply action "C3" +action: 11 # State 33 -# Apply action "DA" -action: 13 +# Apply action "HA" +action: 26 # State 34 -# Apply action "D4" -action: 23 +# Apply action "DJ" +action: 16 # State 35 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK # H3 -# HA -# HK -# H9 -# S4 -# H6 -# DQ -# D5 -# DA +# HQ # D4 +# C2 +# C4 +# C3 +# HA +# DJ IsTerminal() = False -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23" +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16" IsChanceNode() = False IsSimultaneousNode() = False -CurrentPlayer() = 0 -InformationStateString(0) = "0,CK,CQ,C9,C2,D9,D3,HA,HQ,H6,SQ,S9,S8,S3,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4," -InformationStateString(1) = "1,CT,C7,C5,DA,DK,DJ,D7,HJ,HT,H3,SA,SK,S4,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4," -ObservationString(0) = "p0,CK,CQ,C9,C2,D9,D3,HA,HQ,H6,SQ,S9,S8,S3,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4," -ObservationString(1) = "p1,CT,C7,C5,DA,DK,DJ,D7,HJ,HT,H3,SA,SK,S4,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4," +CurrentPlayer() = 1 +InformationStateString(0) = "0,DQ,D8,D5,D4,D3,HA,HJ,H9,H5,H2,SA,SQ,S5,\nc_public:S8,p0:H3,p1:HQ,c_observed:D4\nc_public:C2,p1:C4,p0:C3,c_observed:HA\nc_public:DJ," +InformationStateString(1) = "1,CK,CJ,C9,C2,DT,D7,HT,H7,H4,SK,SJ,ST,S8,\nc_public:S8,p0:H3,p1:HQ,c_unobserved:\nc_public:C2,p1:C4,p0:C3,c_unobserved:\nc_public:DJ," +ObservationString(0) = "p0,DQ,D8,D5,D4,D3,HA,HJ,H9,H5,H2,SA,SQ,S5,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ," +ObservationString(1) = "p1,CK,CJ,C9,C2,DT,D7,HT,H7,H4,SK,SJ,ST,S8,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ," Rewards() = [0, 0] Returns() = [0, 0] -LegalActions() = [1, 2, 5, 12, 18, 24, 26, 28, 34, 41, 44, 45, 50] -StringLegalActions() = ["CK", "CQ", "C9", "C2", "D9", "D3", "HA", "HQ", "H6", "SQ", "S9", "S8", "S3"] +LegalActions() = [1, 3, 5, 12, 17, 20, 30, 33, 36, 40, 42, 43, 45] +StringLegalActions() = ["CK", "CJ", "C9", "C2", "DT", "D7", "HT", "H7", "H4", "SK", "SJ", "ST", "S8"] -# Apply action "SQ" -action: 41 +# Apply action "H7" +action: 33 # State 36 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK # H3 -# HA -# HK -# H9 -# S4 -# H6 -# DQ -# D5 -# DA +# HQ # D4 -# SQ +# C2 +# C4 +# C3 +# HA +# DJ +# H7 IsTerminal() = False -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41" +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16, 33] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16, 33" IsChanceNode() = False IsSimultaneousNode() = False -CurrentPlayer() = 1 -InformationStateString(0) = "0,CK,CQ,C9,C2,D9,D3,HA,HQ,H6,S9,S8,S3,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4,p0:SQ," -InformationStateString(1) = "1,CT,C7,C5,DA,DK,DJ,D7,HJ,HT,H3,SA,SK,S4,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4,p0:SQ," -ObservationString(0) = "p0,CK,CQ,C9,C2,D9,D3,HA,HQ,H6,S9,S8,S3,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ," -ObservationString(1) = "p1,CT,C7,C5,DA,DK,DJ,D7,HJ,HT,H3,SA,SK,S4,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ," +CurrentPlayer() = 0 +InformationStateString(0) = "0,DQ,D8,D5,D4,D3,HA,HJ,H9,H5,H2,SA,SQ,S5,\nc_public:S8,p0:H3,p1:HQ,c_observed:D4\nc_public:C2,p1:C4,p0:C3,c_observed:HA\nc_public:DJ,p1:H7," +InformationStateString(1) = "1,CK,CJ,C9,C2,DT,D7,HT,H4,SK,SJ,ST,S8,\nc_public:S8,p0:H3,p1:HQ,c_unobserved:\nc_public:C2,p1:C4,p0:C3,c_unobserved:\nc_public:DJ,p1:H7," +ObservationString(0) = "p0,DQ,D8,D5,D4,D3,HA,HJ,H9,H5,H2,SA,SQ,S5,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ,1:H7," +ObservationString(1) = "p1,CK,CJ,C9,C2,DT,D7,HT,H4,SK,SJ,ST,S8,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ,1:H7," Rewards() = [0, 0] Returns() = [0, 0] -LegalActions() = [39, 40, 49] -StringLegalActions() = ["SA", "SK", "S4"] +LegalActions() = [26, 29, 31, 35, 38] +StringLegalActions() = ["HA", "HJ", "H9", "H5", "H2"] -# Apply action "S4" -action: 49 +# Apply action "HJ" +action: 29 # State 37 -# Apply action "C3" -action: 11 +# Apply action "S3" +action: 50 # State 38 -# Apply action "SJ" -action: 42 +# Apply action "H8" +action: 32 # State 39 -# Apply action "H6" -action: 34 +# Apply action "S5" +action: 48 # State 40 -# Apply action "HT" -action: 30 +# Apply action "S8" +action: 45 # State 41 -# Apply action "S2" -action: 51 +# Apply action "DK" +action: 14 # State 42 -# Apply action "H7" -action: 33 +# Apply action "C6" +action: 8 # State 43 -# Apply action "C7" -action: 7 +# Apply action "ST" +action: 43 # State 44 -# Apply action "CK" -action: 1 +# Apply action "SQ" +action: 41 # State 45 -# Apply action "D8" -action: 19 +# Apply action "DA" +action: 13 # State 46 -# Apply action "S5" -action: 48 +# Apply action "D6" +action: 21 # State 47 -# Apply action "CQ" -action: 2 +# Apply action "D5" +action: 22 # State 48 -# Apply action "C3" -action: 11 +# Apply action "DA" +action: 13 # State 49 -# Apply action "S7" -action: 46 +# Apply action "S9" +action: 44 # State 50 -# Apply action "H2" -action: 38 +# Apply action "C8" +action: 6 # State 51 -# Apply action "S8" -action: 45 +# Apply action "DT" +action: 17 # State 52 -# Apply action "S7" -action: 46 +# Apply action "D4" +action: 23 # State 53 -# Apply action "S6" -action: 47 +# Apply action "CQ" +action: 2 # State 54 -# Apply action "C6" -action: 8 +# Apply action "C5" +action: 9 # State 55 -# Apply action "D3" -action: 24 +# Apply action "H4" +action: 36 # State 56 -# Apply action "D7" -action: 20 +# Apply action "H5" +action: 35 # State 57 -# Apply action "CJ" -action: 3 +# Apply action "CT" +action: 4 # State 58 -# Apply action "ST" -action: 43 +# Apply action "S4" +action: 49 # State 59 -# Apply action "DK" -action: 14 +# Apply action "C6" +action: 8 # State 60 -# Apply action "D4" -action: 23 +# Apply action "CK" +action: 1 # State 61 -# Apply action "H5" -action: 35 +# Apply action "C7" +action: 7 # State 62 -# Apply action "CA" -action: 0 +# Apply action "D9" +action: 18 # State 63 -# Apply action "CT" -action: 4 +# Apply action "C8" +action: 6 # State 64 -# Apply action "CJ" -action: 3 +# Apply action "C5" +action: 9 # State 65 -# Apply action "H4" -action: 36 +# Apply action "CA" +action: 0 # State 66 -# Apply action "D6" -action: 21 +# Apply action "HK" +action: 27 # State 67 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK # H3 -# HA -# HK -# H9 -# S4 -# H6 -# DQ -# D5 -# DA +# HQ # D4 -# SQ -# S4 +# C2 +# C4 # C3 -# SJ -# H6 -# HT -# S2 +# HA +# DJ # H7 -# C7 -# CK -# D8 +# HJ +# S3 +# H8 # S5 -# CQ -# C3 -# S7 -# H2 # S8 -# S7 -# S6 +# DK # C6 -# D3 -# D7 -# CJ # ST -# DK +# SQ +# DA +# D6 +# D5 +# DA +# S9 +# C8 +# DT # D4 +# CQ +# C5 +# H4 # H5 -# CA # CT -# CJ -# H4 -# D6 +# S4 +# C6 +# CK +# C7 +# D9 +# C8 +# C5 +# CA +# HK IsTerminal() = False -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21" +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16, 33, 29, 50, 32, 48, 45, 14, 8, 43, 41, 13, 21, 22, 13, 44, 6, 17, 23, 2, 9, 36, 35, 4, 49, 8, 1, 7, 18, 6, 9, 0, 27] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16, 33, 29, 50, 32, 48, 45, 14, 8, 43, 41, 13, 21, 22, 13, 44, 6, 17, 23, 2, 9, 36, 35, 4, 49, 8, 1, 7, 18, 6, 9, 0, 27" IsChanceNode() = False IsSimultaneousNode() = False -CurrentPlayer() = 0 -InformationStateString(0) = "0,CA,C9,C2,D9,HA,HQ,H7,H5,H2,S9,S5,S3,S2,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4,p0:SQ,p1:S4,c_unobserved:\nc_public:SJ,p0:H6,p1:HT,c_observed:S2\nc_public:H7,p1:C7,p0:CK,c_unobserved:\nc_public:S5,p0:CQ,p1:C3,c_unobserved:\nc_public:H2,p0:S8,p1:S7,c_unobserved:\nc_public:C6,p0:D3,p1:D7,c_observed:CJ\nc_public:ST,p1:DK,p0:D4,c_observed:H5\nc_public:CA,p1:CT,p0:CJ,c_unobserved:\nc_public:D6," -InformationStateString(1) = "1,C6,C5,DA,DJ,D8,HJ,H4,H3,SA,SK,SJ,ST,S6,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4,p0:SQ,p1:S4,c_observed:C3\nc_public:SJ,p0:H6,p1:HT,c_unobserved:\nc_public:H7,p1:C7,p0:CK,c_observed:D8\nc_public:S5,p0:CQ,p1:C3,c_observed:S7\nc_public:H2,p0:S8,p1:S7,c_observed:S6\nc_public:C6,p0:D3,p1:D7,c_unobserved:\nc_public:ST,p1:DK,p0:D4,c_unobserved:\nc_public:CA,p1:CT,p0:CJ,c_observed:H4\nc_public:D6," -ObservationString(0) = "p0,CA,C9,C2,D9,HA,HQ,H7,H5,H2,S9,S5,S3,S2,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6," -ObservationString(1) = "p1,C6,C5,DA,DJ,D8,HJ,H4,H3,SA,SK,SJ,ST,S6,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6," +CurrentPlayer() = 1 +InformationStateString(0) = "0,CA,CQ,C7,DK,DQ,DJ,D8,D3,HA,H9,H2,SA,S9,\nc_public:S8,p0:H3,p1:HQ,c_observed:D4\nc_public:C2,p1:C4,p0:C3,c_observed:HA\nc_public:DJ,p1:H7,p0:HJ,c_unobserved:\nc_public:H8,p0:S5,p1:S8,c_observed:DK\nc_public:C6,p1:ST,p0:SQ,c_unobserved:\nc_public:D6,p0:D5,p1:DA,c_observed:S9\nc_public:C8,p1:DT,p0:D4,c_observed:CQ\nc_public:C5,p1:H4,p0:H5,c_unobserved:\nc_public:S4,p0:C6,p1:CK,c_observed:C7\nc_public:D9,p1:C8,p0:C5,c_observed:CA\nc_public:HK," +InformationStateString(1) = "1,CJ,CT,C9,C2,D9,D7,D6,HT,H8,SK,SJ,S4,S3,\nc_public:S8,p0:H3,p1:HQ,c_unobserved:\nc_public:C2,p1:C4,p0:C3,c_unobserved:\nc_public:DJ,p1:H7,p0:HJ,c_observed:S3\nc_public:H8,p0:S5,p1:S8,c_unobserved:\nc_public:C6,p1:ST,p0:SQ,c_observed:DA\nc_public:D6,p0:D5,p1:DA,c_unobserved:\nc_public:C8,p1:DT,p0:D4,c_unobserved:\nc_public:C5,p1:H4,p0:H5,c_observed:CT\nc_public:S4,p0:C6,p1:CK,c_unobserved:\nc_public:D9,p1:C8,p0:C5,c_unobserved:\nc_public:HK," +ObservationString(0) = "p0,CA,CQ,C7,DK,DQ,DJ,D8,D3,HA,H9,H2,SA,S9,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ,1:H7,0:HJ,-1:H8,0:S5,1:S8,-1:C6,1:ST,0:SQ,-1:D6,0:D5,1:DA,-1:C8,1:DT,0:D4,-1:C5,1:H4,0:H5,-1:S4,0:C6,1:CK,-1:D9,1:C8,0:C5,-1:HK," +ObservationString(1) = "p1,CJ,CT,C9,C2,D9,D7,D6,HT,H8,SK,SJ,S4,S3,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ,1:H7,0:HJ,-1:H8,0:S5,1:S8,-1:C6,1:ST,0:SQ,-1:D6,0:D5,1:DA,-1:C8,1:DT,0:D4,-1:C5,1:H4,0:H5,-1:S4,0:C6,1:CK,-1:D9,1:C8,0:C5,-1:HK," Rewards() = [0, 0] Returns() = [0, 0] -LegalActions() = [0, 5, 12, 18, 26, 28, 33, 35, 38, 44, 48, 50, 51] -StringLegalActions() = ["CA", "C9", "C2", "D9", "HA", "HQ", "H7", "H5", "H2", "S9", "S5", "S3", "S2"] +LegalActions() = [3, 4, 5, 12, 18, 20, 21, 30, 32, 40, 42, 49, 50] +StringLegalActions() = ["CJ", "CT", "C9", "C2", "D9", "D7", "D6", "HT", "H8", "SK", "SJ", "S4", "S3"] -# Apply action "H7" -action: 33 +# Apply action "CJ" +action: 3 # State 68 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK # H3 -# HA -# HK -# H9 -# S4 -# H6 -# DQ -# D5 -# DA +# HQ # D4 -# SQ -# S4 +# C2 +# C4 # C3 -# SJ -# H6 -# HT -# S2 +# HA +# DJ # H7 -# C7 -# CK -# D8 +# HJ +# S3 +# H8 # S5 -# CQ -# C3 -# S7 -# H2 # S8 -# S7 -# S6 +# DK # C6 -# D3 -# D7 -# CJ # ST -# DK +# SQ +# DA +# D6 +# D5 +# DA +# S9 +# C8 +# DT # D4 +# CQ +# C5 +# H4 # H5 -# CA # CT +# S4 +# C6 +# CK +# C7 +# D9 +# C8 +# C5 +# CA +# HK # CJ -# H4 -# D6 -# H7 IsTerminal() = False -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21, 33] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21, 33" +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16, 33, 29, 50, 32, 48, 45, 14, 8, 43, 41, 13, 21, 22, 13, 44, 6, 17, 23, 2, 9, 36, 35, 4, 49, 8, 1, 7, 18, 6, 9, 0, 27, 3] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16, 33, 29, 50, 32, 48, 45, 14, 8, 43, 41, 13, 21, 22, 13, 44, 6, 17, 23, 2, 9, 36, 35, 4, 49, 8, 1, 7, 18, 6, 9, 0, 27, 3" IsChanceNode() = False IsSimultaneousNode() = False -CurrentPlayer() = 1 -InformationStateString(0) = "0,CA,C9,C2,D9,HA,HQ,H5,H2,S9,S5,S3,S2,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4,p0:SQ,p1:S4,c_unobserved:\nc_public:SJ,p0:H6,p1:HT,c_observed:S2\nc_public:H7,p1:C7,p0:CK,c_unobserved:\nc_public:S5,p0:CQ,p1:C3,c_unobserved:\nc_public:H2,p0:S8,p1:S7,c_unobserved:\nc_public:C6,p0:D3,p1:D7,c_observed:CJ\nc_public:ST,p1:DK,p0:D4,c_observed:H5\nc_public:CA,p1:CT,p0:CJ,c_unobserved:\nc_public:D6,p0:H7," -InformationStateString(1) = "1,C6,C5,DA,DJ,D8,HJ,H4,H3,SA,SK,SJ,ST,S6,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4,p0:SQ,p1:S4,c_observed:C3\nc_public:SJ,p0:H6,p1:HT,c_unobserved:\nc_public:H7,p1:C7,p0:CK,c_observed:D8\nc_public:S5,p0:CQ,p1:C3,c_observed:S7\nc_public:H2,p0:S8,p1:S7,c_observed:S6\nc_public:C6,p0:D3,p1:D7,c_unobserved:\nc_public:ST,p1:DK,p0:D4,c_unobserved:\nc_public:CA,p1:CT,p0:CJ,c_observed:H4\nc_public:D6,p0:H7," -ObservationString(0) = "p0,CA,C9,C2,D9,HA,HQ,H5,H2,S9,S5,S3,S2,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6,0:H7," -ObservationString(1) = "p1,C6,C5,DA,DJ,D8,HJ,H4,H3,SA,SK,SJ,ST,S6,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6,0:H7," +CurrentPlayer() = 0 +InformationStateString(0) = "0,CA,CQ,C7,DK,DQ,DJ,D8,D3,HA,H9,H2,SA,S9,\nc_public:S8,p0:H3,p1:HQ,c_observed:D4\nc_public:C2,p1:C4,p0:C3,c_observed:HA\nc_public:DJ,p1:H7,p0:HJ,c_unobserved:\nc_public:H8,p0:S5,p1:S8,c_observed:DK\nc_public:C6,p1:ST,p0:SQ,c_unobserved:\nc_public:D6,p0:D5,p1:DA,c_observed:S9\nc_public:C8,p1:DT,p0:D4,c_observed:CQ\nc_public:C5,p1:H4,p0:H5,c_unobserved:\nc_public:S4,p0:C6,p1:CK,c_observed:C7\nc_public:D9,p1:C8,p0:C5,c_observed:CA\nc_public:HK,p1:CJ," +InformationStateString(1) = "1,CT,C9,C2,D9,D7,D6,HT,H8,SK,SJ,S4,S3,\nc_public:S8,p0:H3,p1:HQ,c_unobserved:\nc_public:C2,p1:C4,p0:C3,c_unobserved:\nc_public:DJ,p1:H7,p0:HJ,c_observed:S3\nc_public:H8,p0:S5,p1:S8,c_unobserved:\nc_public:C6,p1:ST,p0:SQ,c_observed:DA\nc_public:D6,p0:D5,p1:DA,c_unobserved:\nc_public:C8,p1:DT,p0:D4,c_unobserved:\nc_public:C5,p1:H4,p0:H5,c_observed:CT\nc_public:S4,p0:C6,p1:CK,c_unobserved:\nc_public:D9,p1:C8,p0:C5,c_unobserved:\nc_public:HK,p1:CJ," +ObservationString(0) = "p0,CA,CQ,C7,DK,DQ,DJ,D8,D3,HA,H9,H2,SA,S9,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ,1:H7,0:HJ,-1:H8,0:S5,1:S8,-1:C6,1:ST,0:SQ,-1:D6,0:D5,1:DA,-1:C8,1:DT,0:D4,-1:C5,1:H4,0:H5,-1:S4,0:C6,1:CK,-1:D9,1:C8,0:C5,-1:HK,1:CJ," +ObservationString(1) = "p1,CT,C9,C2,D9,D7,D6,HT,H8,SK,SJ,S4,S3,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ,1:H7,0:HJ,-1:H8,0:S5,1:S8,-1:C6,1:ST,0:SQ,-1:D6,0:D5,1:DA,-1:C8,1:DT,0:D4,-1:C5,1:H4,0:H5,-1:S4,0:C6,1:CK,-1:D9,1:C8,0:C5,-1:HK,1:CJ," Rewards() = [0, 0] Returns() = [0, 0] -LegalActions() = [29, 36, 37] -StringLegalActions() = ["HJ", "H4", "H3"] +LegalActions() = [0, 2, 7] +StringLegalActions() = ["CA", "CQ", "C7"] -# Apply action "HJ" -action: 29 +# Apply action "C7" +action: 7 # State 69 -# Apply action "D2" -action: 25 +# Apply action "S7" +action: 46 # State 70 -# Apply action "H8" -action: 32 +# Apply action "S6" +action: 47 # State 71 -# Apply action "DA" -action: 13 +# Apply action "HK" +action: 27 # State 72 -# Apply action "D2" -action: 25 +# Apply action "H9" +action: 31 # State 73 -# Apply action "C4" -action: 10 +# Apply action "S2" +action: 51 # State 74 -# Apply action "C8" -action: 6 +# Apply action "D2" +action: 25 # State 75 -# Apply action "D6" -action: 21 +# Apply action "SK" +action: 40 # State 76 -# Apply action "D9" -action: 18 +# Apply action "S9" +action: 44 # State 77 -# Apply action "DT" -action: 17 +# Apply action "H6" +action: 34 # State 78 -# C2 -# HJ -# CQ +# SA +# CJ +# C3 +# H7 # H9 +# SJ +# H3 +# CK +# H5 +# D7 +# D5 +# DT +# D8 +# H4 +# SQ # C9 -# HT # DQ -# SA -# S3 -# CT -# HK -# C5 +# HT +# D3 # HQ +# S5 # SK -# D3 -# DK +# HJ +# ST +# H2 +# C4 # S8 -# D7 -# SQ -# DJ -# D9 -# D5 -# S9 -# C7 -# CK # H3 -# HA -# HK -# H9 -# S4 -# H6 -# DQ -# D5 -# DA +# HQ # D4 -# SQ -# S4 +# C2 +# C4 # C3 -# SJ -# H6 -# HT -# S2 +# HA +# DJ # H7 -# C7 -# CK -# D8 +# HJ +# S3 +# H8 # S5 -# CQ -# C3 -# S7 -# H2 # S8 -# S7 -# S6 +# DK # C6 -# D3 -# D7 -# CJ # ST -# DK +# SQ +# DA +# D6 +# D5 +# DA +# S9 +# C8 +# DT # D4 +# CQ +# C5 +# H4 # H5 -# CA # CT +# S4 +# C6 +# CK +# C7 +# D9 +# C8 +# C5 +# CA +# HK # CJ -# H4 -# D6 -# H7 -# HJ -# D2 -# H8 -# DA +# C7 +# S7 +# S6 +# HK +# H9 +# S2 # D2 -# C4 -# C8 -# D6 -# D9 -# DT +# SK +# S9 +# H6 IsTerminal() = True -History() = [12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21, 33, 29, 25, 32, 13, 25, 10, 6, 21, 18, 17] -HistoryString() = "12, 29, 2, 31, 5, 30, 15, 39, 50, 4, 27, 9, 28, 40, 24, 14, 45, 20, 41, 16, 18, 22, 44, 7, 1, 37, 26, 27, 31, 49, 34, 15, 22, 13, 23, 41, 49, 11, 42, 34, 30, 51, 33, 7, 1, 19, 48, 2, 11, 46, 38, 45, 46, 47, 8, 24, 20, 3, 43, 14, 23, 35, 0, 4, 3, 36, 21, 33, 29, 25, 32, 13, 25, 10, 6, 21, 18, 17" +History() = [39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16, 33, 29, 50, 32, 48, 45, 14, 8, 43, 41, 13, 21, 22, 13, 44, 6, 17, 23, 2, 9, 36, 35, 4, 49, 8, 1, 7, 18, 6, 9, 0, 27, 3, 7, 46, 47, 27, 31, 51, 25, 40, 44, 34] +HistoryString() = "39, 3, 11, 33, 31, 42, 37, 1, 35, 20, 22, 17, 19, 36, 41, 5, 15, 30, 24, 28, 48, 40, 29, 43, 38, 10, 45, 37, 28, 23, 12, 10, 11, 26, 16, 33, 29, 50, 32, 48, 45, 14, 8, 43, 41, 13, 21, 22, 13, 44, 6, 17, 23, 2, 9, 36, 35, 4, 49, 8, 1, 7, 18, 6, 9, 0, 27, 3, 7, 46, 47, 27, 31, 51, 25, 40, 44, 34" IsChanceNode() = False IsSimultaneousNode() = False CurrentPlayer() = -4 -InformationStateString(0) = "0,CA,C9,C8,C4,C2,HA,HQ,H5,H2,S9,S5,S3,S2,\nc_public:HA,p0:HK,p1:H9,c_unobserved:\nc_public:H6,p0:DQ,p1:D5,c_unobserved:\nc_public:D4,p0:SQ,p1:S4,c_unobserved:\nc_public:SJ,p0:H6,p1:HT,c_observed:S2\nc_public:H7,p1:C7,p0:CK,c_unobserved:\nc_public:S5,p0:CQ,p1:C3,c_unobserved:\nc_public:H2,p0:S8,p1:S7,c_unobserved:\nc_public:C6,p0:D3,p1:D7,c_observed:CJ\nc_public:ST,p1:DK,p0:D4,c_observed:H5\nc_public:CA,p1:CT,p0:CJ,c_unobserved:\nc_public:D6,p0:H7,p1:HJ,c_observed:D2\nc_public:H8,p1:DA,p0:D2,c_observed:C4\nc_public:C8,p1:D6,p0:D9,c_unobserved:\n" -InformationStateString(1) = "1,C6,C5,DJ,DT,D8,H8,H4,H3,SA,SK,SJ,ST,S6,\nc_public:HA,p0:HK,p1:H9,c_observed:S4\nc_public:H6,p0:DQ,p1:D5,c_observed:DA\nc_public:D4,p0:SQ,p1:S4,c_observed:C3\nc_public:SJ,p0:H6,p1:HT,c_unobserved:\nc_public:H7,p1:C7,p0:CK,c_observed:D8\nc_public:S5,p0:CQ,p1:C3,c_observed:S7\nc_public:H2,p0:S8,p1:S7,c_observed:S6\nc_public:C6,p0:D3,p1:D7,c_unobserved:\nc_public:ST,p1:DK,p0:D4,c_unobserved:\nc_public:CA,p1:CT,p0:CJ,c_observed:H4\nc_public:D6,p0:H7,p1:HJ,c_unobserved:\nc_public:H8,p1:DA,p0:D2,c_unobserved:\nc_public:C8,p1:D6,p0:D9,c_observed:DT\n" -ObservationString(0) = "p0,CA,C9,C8,C4,C2,HA,HQ,H5,H2,S9,S5,S3,S2,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6,0:H7,1:HJ,-1:H8,1:DA,0:D2,-1:C8,1:D6,0:D9," -ObservationString(1) = "p1,C6,C5,DJ,DT,D8,H8,H4,H3,SA,SK,SJ,ST,S6,-1:HA,0:HK,1:H9,-1:H6,0:DQ,1:D5,-1:D4,0:SQ,1:S4,-1:SJ,0:H6,1:HT,-1:H7,1:C7,0:CK,-1:S5,0:CQ,1:C3,-1:H2,0:S8,1:S7,-1:C6,0:D3,1:D7,-1:ST,1:DK,0:D4,-1:CA,1:CT,0:CJ,-1:D6,0:H7,1:HJ,-1:H8,1:DA,0:D2,-1:C8,1:D6,0:D9," -Rewards() = [-13, 13] -Returns() = [-13, 13] +InformationStateString(0) = "0,CA,CQ,DK,DQ,DJ,D8,D3,HA,H6,H2,SA,S7,S2,\nc_public:S8,p0:H3,p1:HQ,c_observed:D4\nc_public:C2,p1:C4,p0:C3,c_observed:HA\nc_public:DJ,p1:H7,p0:HJ,c_unobserved:\nc_public:H8,p0:S5,p1:S8,c_observed:DK\nc_public:C6,p1:ST,p0:SQ,c_unobserved:\nc_public:D6,p0:D5,p1:DA,c_observed:S9\nc_public:C8,p1:DT,p0:D4,c_observed:CQ\nc_public:C5,p1:H4,p0:H5,c_unobserved:\nc_public:S4,p0:C6,p1:CK,c_observed:C7\nc_public:D9,p1:C8,p0:C5,c_observed:CA\nc_public:HK,p1:CJ,p0:C7,c_observed:S7\nc_public:S6,p1:HK,p0:H9,c_observed:S2\nc_public:D2,p1:SK,p0:S9,c_observed:H6\n" +InformationStateString(1) = "1,CT,C9,C2,D9,D7,D6,D2,HT,H8,SJ,S6,S4,S3,\nc_public:S8,p0:H3,p1:HQ,c_unobserved:\nc_public:C2,p1:C4,p0:C3,c_unobserved:\nc_public:DJ,p1:H7,p0:HJ,c_observed:S3\nc_public:H8,p0:S5,p1:S8,c_unobserved:\nc_public:C6,p1:ST,p0:SQ,c_observed:DA\nc_public:D6,p0:D5,p1:DA,c_unobserved:\nc_public:C8,p1:DT,p0:D4,c_unobserved:\nc_public:C5,p1:H4,p0:H5,c_observed:CT\nc_public:S4,p0:C6,p1:CK,c_unobserved:\nc_public:D9,p1:C8,p0:C5,c_unobserved:\nc_public:HK,p1:CJ,p0:C7,c_unobserved:\nc_public:S6,p1:HK,p0:H9,c_unobserved:\nc_public:D2,p1:SK,p0:S9,c_unobserved:\n" +ObservationString(0) = "p0,CA,CQ,DK,DQ,DJ,D8,D3,HA,H6,H2,SA,S7,S2,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ,1:H7,0:HJ,-1:H8,0:S5,1:S8,-1:C6,1:ST,0:SQ,-1:D6,0:D5,1:DA,-1:C8,1:DT,0:D4,-1:C5,1:H4,0:H5,-1:S4,0:C6,1:CK,-1:D9,1:C8,0:C5,-1:HK,1:CJ,0:C7,-1:S6,1:HK,0:H9,-1:D2,1:SK,0:S9," +ObservationString(1) = "p1,CT,C9,C2,D9,D7,D6,D2,HT,H8,SJ,S6,S4,S3,-1:S8,0:H3,1:HQ,-1:C2,1:C4,0:C3,-1:DJ,1:H7,0:HJ,-1:H8,0:S5,1:S8,-1:C6,1:ST,0:SQ,-1:D6,0:D5,1:DA,-1:C8,1:DT,0:D4,-1:C5,1:H4,0:H5,-1:S4,0:C6,1:CK,-1:D9,1:C8,0:C5,-1:HK,1:CJ,0:C7,-1:S6,1:HK,0:H9,-1:D2,1:SK,0:S9," +Rewards() = [13, -13] +Returns() = [13, -13] From 5e02ea0d2a6c47949017f6cf7f018b8a3eb35e9d Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Fri, 8 Mar 2024 13:49:08 +0000 Subject: [PATCH 26/30] LINTING --- .../german_whist_endgame.cc | 1319 +++++++++-------- .../german_whist_foregame.cc | 1154 +++++++------- .../german_whist_foregame.h | 4 +- 3 files changed, 1260 insertions(+), 1217 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index 1ec4be3a3c..b6a5660873 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -1,704 +1,729 @@ -//Source Code for an Executable Generating an Endgame Tablebase for German Whist - +// Source Code for an Executable Generating an Endgame Tablebase for German +// Whist #include #include + #include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" - -//#define DEBUG -namespace open_spiel{ -namespace german_whist_foregame{ + +// #define DEBUG +namespace open_spiel { +namespace german_whist_foregame { struct Pair { - char index; - char value; - Pair(char index_, char value_) { - index = index_; - value = value_; - } - bool operator<(const Pair &pair) const { - return value < pair.value; - } + char index; + char value; + Pair(char index_, char value_) { + index = index_; + value = value_; + } + bool operator<(const Pair& pair) const { return value < pair.value; } }; -struct ActionStruct{ - uint32_t index; - unsigned char suit; - bool player; - ActionStruct(uint32_t index_, unsigned char suit_, bool player_) { - index = index_; - suit = suit_; - player = player_; - } +struct ActionStruct { + uint32_t index; + unsigned char suit; + bool player; + ActionStruct(uint32_t index_, unsigned char suit_, bool player_) { + index = index_; + suit = suit_; + player = player_; + } }; struct ActionValue { - ActionStruct action; - int value; - bool operator<(const ActionValue& aval) const { - return value < aval.value; - } + ActionStruct action; + int value; + bool operator<(const ActionValue& aval) const { return value < aval.value; } }; class Node { -private: - uint32_t cards_; - std::array suit_masks_; - char total_tricks_; - char trump_; - char score_; - char moves_; - bool player_; - std::vector history_; - uint64_t key_; -public: - Node(uint32_t cards, std::array suit_masks, char trump,bool player) { - cards_ = cards; - suit_masks_ = suit_masks; - total_tricks_ = popcnt_u32(cards); - trump_ = trump; - moves_ = 0; - player_ = player; - score_ = 0; - history_ = {}; - }; - bool Player() { return player_; }; - char Score() { return score_; }; - char Moves() { return moves_; }; - bool IsTerminal() { - return (moves_ == 2 * total_tricks_); - } - char RemainingTricks() { - return (char)(total_tricks_-(moves_>>1)); - } - char TotalTricks() { - return total_tricks_; - } - uint32_t Cards() { return cards_; } - std::array SuitMasks() { return suit_masks_; } - uint64_t GetNodeKey() { return key_; } - bool Trick(ActionStruct lead, ActionStruct follow) { - //true if leader won// - return (lead.suit != follow.suit && lead.suit == trump_) || (lead.suit == follow.suit && lead.index <= follow.index); - } - - void RemoveCard(ActionStruct action) { - //Removes card from cards_// - uint32_t mask_b = ~0; - mask_b =bzhi_u32(mask_b, action.index); - uint32_t mask_a = ~mask_b; - mask_a = blsr_u32(mask_a); - uint32_t copy_a = cards_ & mask_a; - uint32_t copy_b = cards_ & mask_b; - copy_a = copy_a >> 1; - cards_ = copy_a | copy_b; - //decrements appropriate suits// - suit_masks_[action.suit] = blsr_u32(suit_masks_[action.suit])>>1; - char suit = action.suit; - suit++; - while (suit < kNumSuits) { - suit_masks_[suit]=suit_masks_[suit] >> 1; - suit++; - } - } - void InsertCard(ActionStruct action) { - //inserts card into cards_// - uint32_t mask_b = ~0; - mask_b = bzhi_u32(mask_b, action.index); - uint32_t mask_a = ~mask_b; - uint32_t copy_b = cards_ & mask_b; - uint32_t copy_a = cards_ & mask_a; - copy_a = copy_a << 1; - uint32_t card = action.player<< action.index; - cards_ = card | copy_a | copy_b; - //increments appropriate suits// - uint32_t new_suit = (suit_masks_[action.suit] & mask_b )| (1 << action.index); - suit_masks_[action.suit] = ((suit_masks_[action.suit] & mask_a) << 1 )| new_suit; - char suit = action.suit; - suit++; - while (suit < kNumSuits) { - suit_masks_[suit] = suit_masks_[suit] << 1; - suit++; - } - } - void UpdateNodeKey() { - //recasts the cards and suitlengths into quasi-canonical form// - //least sig part of 32bit card is trump, then suits in ascending length// - - //note this canonical form does not take advantage of all isomorphisms// - //suppose a game is transformed as follows: all card bits flipped and the player bit flipped, ie player 1 has the lead and has player 0s cards from the original game// - //this implies player 1 achieves the minimax value of the original game ie the value is remaining tricks - value of the original game for this transformed game// - //also does not take advantage of single suit isomorphism. Namely all single suit games with the same card distribution are isomorphic. Currently this considers all trump, all no trump games as distinct// - uint64_t suit_sig = 0; - char trump_length = popcnt_u32(suit_masks_[trump_]); - if (trump_length > kNumRanks) { - throw; - } - std::vector non_trump_lengths; - for (char i = 0; i < kNumSuits; ++i) { - if (i != trump_) { - char length = popcnt_u32(suit_masks_[i]); - uint32_t sig = suit_masks_[i]&cards_; - if (suit_masks_[i] != 0) { - sig = (sig >> (tzcnt_u32(suit_masks_[i]))); - } - if (length > kNumRanks) { - throw 1; - } - non_trump_lengths.push_back(Triple{i,length,sig }); - } - } - //sorting takes advantage of two isomorphisms namely nontrump suits of nonequal length can be exchanged and the value of the game does not change// - //and this more complicated suppose two games with two or more (non_trump)suits of equal length, permuting those suits should not change the value of solved game ie it is an isomorphism// - std::sort(non_trump_lengths.begin(), non_trump_lengths.end()); - suit_sig = suit_sig | trump_length; - for (size_t i = 0; i < non_trump_lengths.size(); ++i) { - suit_sig = suit_sig | ((uint64_t)non_trump_lengths[i].length << (4*(i+1))); - } - suit_sig = suit_sig << 32; - std::array suit_cards; - suit_cards[0] = cards_ & suit_masks_[trump_]; - if (suit_masks_[trump_] != 0) { - suit_cards[0] = suit_cards[0] >> tzcnt_u32(suit_masks_[trump_]); - } - uint32_t sum = popcnt_u32(suit_masks_[trump_]); - uint32_t cards = 0|suit_cards[0]; - for (size_t i = 0; i < non_trump_lengths.size(); ++i) { - suit_cards[i] = cards_ & suit_masks_[non_trump_lengths[i].index]; - uint32_t val = 0; - if (suit_masks_[non_trump_lengths[i].index] != 0) { - val = tzcnt_u32(suit_masks_[non_trump_lengths[i].index]); - } - suit_cards[i]= suit_cards[i] >>val; - suit_cards[i] = suit_cards[i] << sum; - sum += popcnt_u32(suit_masks_[non_trump_lengths[i].index]); - cards = cards | suit_cards[i]; - } - //cards = cards | (player_ << 31); - key_ = suit_sig | (uint64_t)cards; + private: + uint32_t cards_; + std::array suit_masks_; + char total_tricks_; + char trump_; + char score_; + char moves_; + bool player_; + std::vector history_; + uint64_t key_; + + public: + Node(uint32_t cards, std::array suit_masks, char trump, + bool player) { + cards_ = cards; + suit_masks_ = suit_masks; + total_tricks_ = popcnt_u32(cards); + trump_ = trump; + moves_ = 0; + player_ = player; + score_ = 0; + history_ = {}; + }; + bool Player() { return player_; }; + char Score() { return score_; }; + char Moves() { return moves_; }; + bool IsTerminal() { return (moves_ == 2 * total_tricks_); } + char RemainingTricks() { return (char)(total_tricks_ - (moves_ >> 1)); } + char TotalTricks() { return total_tricks_; } + uint32_t Cards() { return cards_; } + std::array SuitMasks() { return suit_masks_; } + uint64_t GetNodeKey() { return key_; } + bool Trick(ActionStruct lead, ActionStruct follow) { + // true if leader won// + return (lead.suit != follow.suit && lead.suit == trump_) || + (lead.suit == follow.suit && lead.index <= follow.index); + } + + void RemoveCard(ActionStruct action) { + // Removes card from cards_// + uint32_t mask_b = ~0; + mask_b = bzhi_u32(mask_b, action.index); + uint32_t mask_a = ~mask_b; + mask_a = blsr_u32(mask_a); + uint32_t copy_a = cards_ & mask_a; + uint32_t copy_b = cards_ & mask_b; + copy_a = copy_a >> 1; + cards_ = copy_a | copy_b; + // decrements appropriate suits// + suit_masks_[action.suit] = blsr_u32(suit_masks_[action.suit]) >> 1; + char suit = action.suit; + suit++; + while (suit < kNumSuits) { + suit_masks_[suit] = suit_masks_[suit] >> 1; + suit++; + } + } + void InsertCard(ActionStruct action) { + // inserts card into cards_// + uint32_t mask_b = ~0; + mask_b = bzhi_u32(mask_b, action.index); + uint32_t mask_a = ~mask_b; + uint32_t copy_b = cards_ & mask_b; + uint32_t copy_a = cards_ & mask_a; + copy_a = copy_a << 1; + uint32_t card = action.player << action.index; + cards_ = card | copy_a | copy_b; + // increments appropriate suits// + uint32_t new_suit = + (suit_masks_[action.suit] & mask_b) | (1 << action.index); + suit_masks_[action.suit] = + ((suit_masks_[action.suit] & mask_a) << 1) | new_suit; + char suit = action.suit; + suit++; + while (suit < kNumSuits) { + suit_masks_[suit] = suit_masks_[suit] << 1; + suit++; + } + } + void UpdateNodeKey() { + // recasts the cards and suitlengths into quasi-canonical form// + // least sig part of 32bit card is trump, then suits in ascending length// + + // note this canonical form does not take advantage of all isomorphisms// + // suppose a game is transformed as follows: all card bits flipped and the + // player bit flipped, ie player 1 has the lead and has player 0s cards from + // the original game// this implies player 1 achieves the minimax value of + // the original game ie the value is remaining tricks - value of the + // original game for this transformed game// also does not take advantage of + // single suit isomorphism. Namely all single suit games with the same card + // distribution are isomorphic. Currently this considers all trump, all no + // trump games as distinct// + uint64_t suit_sig = 0; + char trump_length = popcnt_u32(suit_masks_[trump_]); + if (trump_length > kNumRanks) { + throw; + } + std::vector non_trump_lengths; + for (char i = 0; i < kNumSuits; ++i) { + if (i != trump_) { + char length = popcnt_u32(suit_masks_[i]); + uint32_t sig = suit_masks_[i] & cards_; + if (suit_masks_[i] != 0) { + sig = (sig >> (tzcnt_u32(suit_masks_[i]))); + } + if (length > kNumRanks) { + throw 1; + } + non_trump_lengths.push_back(Triple{i, length, sig}); + } + } + // sorting takes advantage of two isomorphisms namely nontrump suits of + // nonequal length can be exchanged and the value of the game does not + // change// and this more complicated suppose two games with two or more + // (non_trump)suits of equal length, permuting those suits should not change + // the value of solved game ie it is an isomorphism// + std::sort(non_trump_lengths.begin(), non_trump_lengths.end()); + suit_sig = suit_sig | trump_length; + for (size_t i = 0; i < non_trump_lengths.size(); ++i) { + suit_sig = + suit_sig | ((uint64_t)non_trump_lengths[i].length << (4 * (i + 1))); + } + suit_sig = suit_sig << 32; + std::array suit_cards; + suit_cards[0] = cards_ & suit_masks_[trump_]; + if (suit_masks_[trump_] != 0) { + suit_cards[0] = suit_cards[0] >> tzcnt_u32(suit_masks_[trump_]); + } + uint32_t sum = popcnt_u32(suit_masks_[trump_]); + uint32_t cards = 0 | suit_cards[0]; + for (size_t i = 0; i < non_trump_lengths.size(); ++i) { + suit_cards[i] = cards_ & suit_masks_[non_trump_lengths[i].index]; + uint32_t val = 0; + if (suit_masks_[non_trump_lengths[i].index] != 0) { + val = tzcnt_u32(suit_masks_[non_trump_lengths[i].index]); + } + suit_cards[i] = suit_cards[i] >> val; + suit_cards[i] = suit_cards[i] << sum; + sum += popcnt_u32(suit_masks_[non_trump_lengths[i].index]); + cards = cards | suit_cards[i]; + } + // cards = cards | (player_ << 31); + key_ = suit_sig | (uint64_t)cards; #ifdef DEBUG_KEY - std::cout <<"CARDS_ " << cards_ << std::endl; - std::cout << "CARDS " << cards << std::endl; - std::cout << "SUIT MASKS " << std::endl; - for (int i = 0; i < kNumSuits; ++i) { - std::cout << suit_masks_[i] << std::endl; - } - std::cout << "SUIT_SIG " << suit_sig << std::endl; - std::cout<<"KEY " << key_ << std::endl; + std::cout << "CARDS_ " << cards_ << std::endl; + std::cout << "CARDS " << cards << std::endl; + std::cout << "SUIT MASKS " << std::endl; + for (int i = 0; i < kNumSuits; ++i) { + std::cout << suit_masks_[i] << std::endl; + } + std::cout << "SUIT_SIG " << suit_sig << std::endl; + std::cout << "KEY " << key_ << std::endl; #endif - } - uint64_t AltKey() { - uint32_t mask = bzhi_u32(~0, 2 * RemainingTricks()); - return key_ ^ (uint64_t)mask; - } - //Move Ordering Heuristics// - //These could Definitely be improved, very hacky// - int LeadOrdering(ActionStruct action) { - char suit = action.suit; - uint32_t copy_cards = cards_; - if (player_ == 0) { - copy_cards = ~copy_cards; - } - uint32_t suit_cards = copy_cards & suit_masks_[suit]; - uint32_t mask = suit_cards & ~(suit_cards >> 1); - //represents out of the stategically inequivalent cards in a suit that a player holds, what rank is it, rank 0 is highest rank etc// - int suit_rank = popcnt_u32(bzhi_u32(mask, action.index)); - ApplyAction(action); - std::vector moves = LegalActions(); - UndoAction(action); - int sum = 0; - for (size_t i = 0; i < moves.size(); ++i) { - sum += Trick(action, moves[i]); - } - if (sum == moves.size()) { - return action.suit == trump_ ? 0 - suit_rank : -1 * kNumRanks - suit_rank;//intriguing this seems to produce small perfomance increase// - } - if (sum == 0) { - return 2 * kNumRanks - suit_rank; - } - else { - return 1 * kNumRanks - suit_rank; - } - } - int FollowOrdering(ActionStruct action) { - ActionStruct lead = history_.back(); - //follow ordering for fast cut offs// - //win as cheaply as possible, followed by lose as cheaply as possible - char suit = action.suit; - uint32_t copy_cards = cards_; - if (player_ == 0) { - copy_cards = ~copy_cards; - } - uint32_t suit_cards = copy_cards & suit_masks_[suit]; - uint32_t mask = suit_cards & ~(suit_cards >> 1); - //represents out of the stategically inequivalent cards in a suit that a player holds, what rank is it, rank 0 is highest rank etc// - int suit_rank = popcnt_u32(bzhi_u32(mask, action.index)); - if (!Trick(lead, action)) { - return -kNumRanks - suit_rank; - } - else { - return -suit_rank; - } - } - - - - std::vector LegalActions() { - //Features// - //Move fusion// - std::vector out; - out.reserve(kNumRanks); - uint32_t copy_cards = cards_; - std::array player_suit_masks; - if (player_ == 0) { - copy_cards = ~copy_cards; - } - for (size_t i = 0; i < kNumSuits; ++i) { - uint32_t suit_cards = copy_cards & suit_masks_[i]; - player_suit_masks[i] = suit_cards & ~(suit_cards >> 1); + } + uint64_t AltKey() { + uint32_t mask = bzhi_u32(~0, 2 * RemainingTricks()); + return key_ ^ (uint64_t)mask; + } + // Move Ordering Heuristics// + // These could Definitely be improved, very hacky// + int LeadOrdering(ActionStruct action) { + char suit = action.suit; + uint32_t copy_cards = cards_; + if (player_ == 0) { + copy_cards = ~copy_cards; + } + uint32_t suit_cards = copy_cards & suit_masks_[suit]; + uint32_t mask = suit_cards & ~(suit_cards >> 1); + // represents out of the stategically inequivalent cards in a suit that a + // player holds, what rank is it, rank 0 is highest rank etc// + int suit_rank = popcnt_u32(bzhi_u32(mask, action.index)); + ApplyAction(action); + std::vector moves = LegalActions(); + UndoAction(action); + int sum = 0; + for (size_t i = 0; i < moves.size(); ++i) { + sum += Trick(action, moves[i]); + } + if (sum == moves.size()) { + return action.suit == trump_ + ? 0 - suit_rank + : -1 * kNumRanks - + suit_rank; // intriguing this seems to produce small + // perfomance increase// + } + if (sum == 0) { + return 2 * kNumRanks - suit_rank; + } else { + return 1 * kNumRanks - suit_rank; + } + } + int FollowOrdering(ActionStruct action) { + ActionStruct lead = history_.back(); + // follow ordering for fast cut offs// + // win as cheaply as possible, followed by lose as cheaply as possible + char suit = action.suit; + uint32_t copy_cards = cards_; + if (player_ == 0) { + copy_cards = ~copy_cards; + } + uint32_t suit_cards = copy_cards & suit_masks_[suit]; + uint32_t mask = suit_cards & ~(suit_cards >> 1); + // represents out of the stategically inequivalent cards in a suit that a + // player holds, what rank is it, rank 0 is highest rank etc// + int suit_rank = popcnt_u32(bzhi_u32(mask, action.index)); + if (!Trick(lead, action)) { + return -kNumRanks - suit_rank; + } else { + return -suit_rank; + } + } + + std::vector LegalActions() { + // Features// + // Move fusion// + std::vector out; + out.reserve(kNumRanks); + uint32_t copy_cards = cards_; + std::array player_suit_masks; + if (player_ == 0) { + copy_cards = ~copy_cards; + } + for (size_t i = 0; i < kNumSuits; ++i) { + uint32_t suit_cards = copy_cards & suit_masks_[i]; + player_suit_masks[i] = suit_cards & ~(suit_cards >> 1); #ifdef DEBUG - std::cout << "Cards " << cards_ << std::endl; - std::cout << "Suit Mask " << i << " " << suit_masks_[i] << std::endl; - std::cout << "Player " << player_ << " suit mask " << (int)i << " " << player_suit_masks[i] << std::endl; + std::cout << "Cards " << cards_ << std::endl; + std::cout << "Suit Mask " << i << " " << suit_masks_[i] << std::endl; + std::cout << "Player " << player_ << " suit mask " << (int)i << " " + << player_suit_masks[i] << std::endl; #endif - } - for (char i = 0; i < kNumSuits; ++i) { - uint32_t suit_mask = player_suit_masks[i]; - bool lead = (moves_ % 2 == 0); - bool follow = (moves_ % 2 == 1); - bool correct_suit = 0; - bool void_in_suit = 0; - if (follow == true) { - correct_suit = (history_.back().suit == i); - void_in_suit = (player_suit_masks[history_.back().suit] == 0); - } - if ((lead || (follow && (correct_suit || void_in_suit)))) { - while (suit_mask != 0) { - uint32_t best = tzcnt_u32(suit_mask); - out.push_back(ActionStruct(best,i,player_)); - suit_mask = blsr_u32(suit_mask); - } - } - } + } + for (char i = 0; i < kNumSuits; ++i) { + uint32_t suit_mask = player_suit_masks[i]; + bool lead = (moves_ % 2 == 0); + bool follow = (moves_ % 2 == 1); + bool correct_suit = 0; + bool void_in_suit = 0; + if (follow == true) { + correct_suit = (history_.back().suit == i); + void_in_suit = (player_suit_masks[history_.back().suit] == 0); + } + if ((lead || (follow && (correct_suit || void_in_suit)))) { + while (suit_mask != 0) { + uint32_t best = tzcnt_u32(suit_mask); + out.push_back(ActionStruct(best, i, player_)); + suit_mask = blsr_u32(suit_mask); + } + } + } #ifdef DEBUG - std::cout << "Player " << player_ << " MoveGen " << std::endl; - for (size_t i = 0; i < out.size(); ++i) { - std::cout << out[i].index << " " << (int)out[i].suit << std::endl; - } -#endif - return out; + std::cout << "Player " << player_ << " MoveGen " << std::endl; + for (size_t i = 0; i < out.size(); ++i) { + std::cout << out[i].index << " " << (int)out[i].suit << std::endl; } - void ApplyAction(ActionStruct action) { +#endif + return out; + } + void ApplyAction(ActionStruct action) { #ifdef DEBUG - std::cout << "Player " << player_ << " ApplyAction " << action.index << " " << (int)action.suit << std::endl; + std::cout << "Player " << player_ << " ApplyAction " << action.index << " " + << (int)action.suit << std::endl; #endif - if (moves_ % 2 == 1) { - ActionStruct lead = history_.back(); - bool winner = !((Trick(lead, action)) ^ lead.player); + if (moves_ % 2 == 1) { + ActionStruct lead = history_.back(); + bool winner = !((Trick(lead, action)) ^ lead.player); #ifdef DEBUG - std::cout << "Player " << winner << " won this trick" << std::endl; + std::cout << "Player " << winner << " won this trick" << std::endl; #endif - score_ += (winner == 0); - player_ = (winner); - } - else { - player_ = !player_; - } + score_ += (winner == 0); + player_ = (winner); + } else { + player_ = !player_; + } #ifdef DEBUG - assert((suit_masks_[0] & suit_masks_[1]) == 0); - assert((suit_masks_[0] & suit_masks_[2])== 0); - assert((suit_masks_[0] & suit_masks_[3]) == 0); - assert((suit_masks_[1] & suit_masks_[2]) == 0); - assert((suit_masks_[1] & suit_masks_[3]) == 0); - assert((suit_masks_[2] & suit_masks_[3]) == 0); + assert((suit_masks_[0] & suit_masks_[1]) == 0); + assert((suit_masks_[0] & suit_masks_[2]) == 0); + assert((suit_masks_[0] & suit_masks_[3]) == 0); + assert((suit_masks_[1] & suit_masks_[2]) == 0); + assert((suit_masks_[1] & suit_masks_[3]) == 0); + assert((suit_masks_[2] & suit_masks_[3]) == 0); #endif - RemoveCard(action); - moves_++; - history_.push_back(action); - } - void UndoAction(ActionStruct action) { - if (moves_ % 2 == 0) { - ActionStruct lead = history_[history_.size() - 2]; - ActionStruct follow = history_[history_.size() - 1]; - bool winner = !(Trick(lead, follow) ^ lead.player); - score_ -= (winner == 0); - } - InsertCard(action); - moves_--; - player_=history_.back().player; - history_.pop_back(); + RemoveCard(action); + moves_++; + history_.push_back(action); + } + void UndoAction(ActionStruct action) { + if (moves_ % 2 == 0) { + ActionStruct lead = history_[history_.size() - 2]; + ActionStruct follow = history_[history_.size() - 1]; + bool winner = !(Trick(lead, follow) ^ lead.player); + score_ -= (winner == 0); + } + InsertCard(action); + moves_--; + player_ = history_.back().player; + history_.pop_back(); #ifdef DEBUG - std::cout << "Player " << player_ << " UndoAction " << action.index << " " << (int)action.suit << std::endl; + std::cout << "Player " << player_ << " UndoAction " << action.index << " " + << (int)action.suit << std::endl; #endif - } + } }; - - -//solvers below +// solvers below int AlphaBeta(Node* node, int alpha, int beta) { - //fail soft ab search// - //uses move ordering to speed up search// - if (node->IsTerminal()) { - return node->Score(); - } - //move ordering code// - std::vector actions = node->LegalActions(); - std::vector temp; - temp.reserve(kNumRanks); - for(int i =0;iMoves()%2==0){ - temp.push_back({actions[i],node->LeadOrdering(actions[i])}); - } - else{ - temp.push_back({actions[i],node->FollowOrdering(actions[i])}); - } + // fail soft ab search// + // uses move ordering to speed up search// + if (node->IsTerminal()) { + return node->Score(); + } + // move ordering code// + std::vector actions = node->LegalActions(); + std::vector temp; + temp.reserve(kNumRanks); + for (int i = 0; i < actions.size(); ++i) { + if (node->Moves() % 2 == 0) { + temp.push_back({actions[i], node->LeadOrdering(actions[i])}); + } else { + temp.push_back({actions[i], node->FollowOrdering(actions[i])}); + } + } + std::sort(temp.begin(), temp.end()); + for (int i = 0; i < temp.size(); ++i) { + actions[i] = temp[i].action; + } + // alpha beta search// + if (node->Player() == 0) { + int val = 0; + for (int i = 0; i < actions.size(); ++i) { + node->ApplyAction(actions[i]); + val = std::max(val, AlphaBeta(node, alpha, beta)); + node->UndoAction(actions[i]); + alpha = std::max(val, alpha); + if (val >= beta) { + break; + } } - std::sort(temp.begin(),temp.end()); - for(int i=0;iPlayer() == 0) { - int val = 0; - for (int i = 0; i < actions.size(); ++i) { - node->ApplyAction(actions[i]); - val = std::max(val, AlphaBeta(node, alpha, beta)); - node->UndoAction(actions[i]); - alpha = std::max(val, alpha); - if (val >= beta) { - break; - } - } - return val; - } - else if (node->Player() == 1) { - int val =node->TotalTricks(); - for (int i = 0; i < actions.size(); ++i) { - node->ApplyAction(actions[i]); - val = std::min(val, AlphaBeta(node, alpha, beta)); - node->UndoAction(actions[i]); - beta = std::min(val, beta); - if (val <= alpha) { - break; - } - } - return val; + return val; + } else if (node->Player() == 1) { + int val = node->TotalTricks(); + for (int i = 0; i < actions.size(); ++i) { + node->ApplyAction(actions[i]); + val = std::min(val, AlphaBeta(node, alpha, beta)); + node->UndoAction(actions[i]); + beta = std::min(val, beta); + if (val <= alpha) { + break; + } } - return -1; + return val; + } + return -1; }; +// Helper Functions// - -//Helper Functions// - - -//Credit to computationalcombinatorics.wordpress.com -//hideous code for generating the next colexicographical combination// +// Credit to computationalcombinatorics.wordpress.com +// hideous code for generating the next colexicographical combination// bool NextColex(std::vector& v, int k) { - int num = 0; - for (int i = 0; i < v.size(); ++i) { - if (i == v.size() - 1) { - v[i] = v[i] + 1; - if (v[i] > k - v.size() + i) { - return false; - } - num = i; - break; - } - else if (v[i + 1] - v[i] > 1 && v[i + 1] != i) { - v[i] = v[i] + 1; - if (v[i] > k - v.size() + i) { - return false; - } - num = i; - break; - } - } - for (int i = 0; i < num; ++i) { - v[i] = i; - } - return true; + int num = 0; + for (int i = 0; i < v.size(); ++i) { + if (i == v.size() - 1) { + v[i] = v[i] + 1; + if (v[i] > k - v.size() + i) { + return false; + } + num = i; + break; + } else if (v[i + 1] - v[i] > 1 && v[i + 1] != i) { + v[i] = v[i] + 1; + if (v[i] > k - v.size() + i) { + return false; + } + num = i; + break; + } + } + for (int i = 0; i < num; ++i) { + v[i] = i; + } + return true; } - - -char IncrementalAlphaBetaMemoryIso(Node* node, char alpha, char beta,int depth, vectorNa* TTable,std::unordered_map* SuitRanks, std::vector>& bin_coeffs) { - //fail soft ab search - char val = 0; - uint64_t key = 0; - bool player = node->Player(); - if (node->IsTerminal()) { - return node->Score(); - } - if (node->Moves() % 2 == 0&& depth==0) { - node->UpdateNodeKey(); - key = (player) ? node->AltKey() : node->GetNodeKey(); - uint32_t cards = key & bzhi_u64(~0, 32); - uint32_t colex = HalfColexer(cards, &bin_coeffs); - uint32_t suits = (key & (~0 ^ bzhi_u64(~0, 32))) >> 32; - uint32_t suit_rank = SuitRanks->at(suits); - char value = (player) ? node->RemainingTricks() - TTable->Get(colex,suit_rank) :TTable->Get(colex,suit_rank); - return value+node->Score(); - } - else if (node->Player() == 0) { - val = 0; - std::vector actions = node->LegalActions(); - for (int i = 0; i < actions.size(); ++i) { - node->ApplyAction(actions[i]); - val = std::max(val,IncrementalAlphaBetaMemoryIso(node, alpha, beta,depth-1, TTable,SuitRanks,bin_coeffs)); - node->UndoAction(actions[i]); - alpha = std::max(val, alpha); - if (val >= beta) { - break; - } - } - } - else if (node->Player() == 1) { - val =node->TotalTricks(); - std::vector actions = node->LegalActions(); - for (int i = 0; i < actions.size(); ++i) { - node->ApplyAction(actions[i]); - val = std::min(val, IncrementalAlphaBetaMemoryIso(node, alpha, beta,depth-1, TTable,SuitRanks,bin_coeffs)); - node->UndoAction(actions[i]); - beta = std::min(val, beta); - if (val <= alpha) { - break; - } - } - } - return val; +char IncrementalAlphaBetaMemoryIso( + Node* node, char alpha, char beta, int depth, vectorNa* TTable, + std::unordered_map* SuitRanks, + const std::vector>& bin_coeffs) { + // fail soft ab search + char val = 0; + uint64_t key = 0; + bool player = node->Player(); + if (node->IsTerminal()) { + return node->Score(); + } + if (node->Moves() % 2 == 0 && depth == 0) { + node->UpdateNodeKey(); + key = (player) ? node->AltKey() : node->GetNodeKey(); + uint32_t cards = key & bzhi_u64(~0, 32); + uint32_t colex = HalfColexer(cards, &bin_coeffs); + uint32_t suits = (key & (~0 ^ bzhi_u64(~0, 32))) >> 32; + uint32_t suit_rank = SuitRanks->at(suits); + char value = (player) + ? node->RemainingTricks() - TTable->Get(colex, suit_rank) + : TTable->Get(colex, suit_rank); + return value + node->Score(); + } else if (node->Player() == 0) { + val = 0; + std::vector actions = node->LegalActions(); + for (int i = 0; i < actions.size(); ++i) { + node->ApplyAction(actions[i]); + val = std::max( + val, IncrementalAlphaBetaMemoryIso(node, alpha, beta, depth - 1, + TTable, SuitRanks, bin_coeffs)); + node->UndoAction(actions[i]); + alpha = std::max(val, alpha); + if (val >= beta) { + break; + } + } + } else if (node->Player() == 1) { + val = node->TotalTricks(); + std::vector actions = node->LegalActions(); + for (int i = 0; i < actions.size(); ++i) { + node->ApplyAction(actions[i]); + val = std::min( + val, IncrementalAlphaBetaMemoryIso(node, alpha, beta, depth - 1, + TTable, SuitRanks, bin_coeffs)); + node->UndoAction(actions[i]); + beta = std::min(val, beta); + if (val <= alpha) { + break; + } + } + } + return val; }; -std::vector GWhistGenerator(int num,unsigned int seed){ - //generates pseudorandom endgames// - std::vector out; - out.reserve(num); - std::mt19937 g(seed); - std::array nums; - for (int i = 0; i < 2 * kNumRanks; ++i) { - nums[i] = i; - } - for (int i = 0; i < num; ++i) { - std::shuffle(nums.begin(), nums.end(), g); - uint32_t cards = 0; - std::array suits; - for (int j = 0; j < kNumRanks; ++j) { - cards = cards | (1 << nums[j]); - } - int sum = 0; - std::vector suit_lengths = {0,0,0,0}; - for(int j =0;j distrib(min,max); - suit_lengths[j] = distrib(g); - sum+= suit_lengths[j]; - } - suit_lengths[kNumSuits-1]=2*kNumRanks-sum; - sum =0; - for(int j =0;jkNumRanks){ - throw; - } - } - if(sum!= 2*kNumRanks){ - for(int j =0;j GWhistGenerator(int num, unsigned int seed) { + // generates pseudorandom endgames// + std::vector out; + out.reserve(num); + std::mt19937 g(seed); + std::array nums; + for (int i = 0; i < 2 * kNumRanks; ++i) { + nums[i] = i; + } + for (int i = 0; i < num; ++i) { + std::shuffle(nums.begin(), nums.end(), g); + uint32_t cards = 0; + std::array suits; + for (int j = 0; j < kNumRanks; ++j) { + cards = cards | (1 << nums[j]); + } + int sum = 0; + std::vector suit_lengths = {0, 0, 0, 0}; + for (int j = 0; j < kNumSuits - 1; ++j) { + int max = std::min(kNumRanks, 2 * kNumRanks - sum); + int min = std::max(0, (j - 1) * kNumRanks - sum); + std::uniform_int_distribution<> distrib(min, max); + suit_lengths[j] = distrib(g); + sum += suit_lengths[j]; + } + suit_lengths[kNumSuits - 1] = 2 * kNumRanks - sum; + sum = 0; + for (int j = 0; j < kNumSuits; ++j) { + sum += suit_lengths[j]; + if (suit_lengths[j] > kNumRanks) { + throw; + } + } + if (sum != 2 * kNumRanks) { + for (int j = 0; j < suit_lengths.size(); ++j) { + std::cout << suit_lengths[j] << " " << std::endl; + } + throw; + } + int cum_sum = 0; + for (int j = 0; j < kNumSuits; ++j) { + if (j == 0) { + suits[j] = bzhi_u32(~0, suit_lengths[j]); + } else { + suits[j] = + (bzhi_u32(~0, suit_lengths[j] + cum_sum)) ^ bzhi_u32(~0, cum_sum); + } + cum_sum += suit_lengths[j]; + } + out.push_back(Node(cards, suits, 0, false)); #ifdef DEBUG - std::cout << popcnt_u32(cards) << " " << popcnt_u32(suits[0]) + popcnt_u32(suits[1]) + popcnt_u32(suits[2]) + popcnt_u32(suits[3]) << std::endl; - std::cout << cards << " " << suits[0] << " " << suits[1] << " " << suits[2] << " " << suits[3] << std::endl; + std::cout << popcnt_u32(cards) << " " + << popcnt_u32(suits[0]) + popcnt_u32(suits[1]) + + popcnt_u32(suits[2]) + popcnt_u32(suits[3]) + << std::endl; + std::cout << cards << " " << suits[0] << " " << suits[1] << " " << suits[2] + << " " << suits[3] << std::endl; #endif - - } - return out; + } + return out; } - -void ThreadSolver(int size_endgames, vectorNa* outTTable, vectorNa* TTable, std::vector>& bin_coeffs, std::vector& suit_splits, std::unordered_map& SuitRanks, size_t start_id, size_t end_id) { - //takes endgames solved to depth d-1 and returns endgames solved to depth d // - std::vector combination; - combination.reserve(size_endgames); - for (int i = 0; i < size_endgames; ++i) { - combination.push_back(i); - } - bool control = true; - int count = 0; +void ThreadSolver(int size_endgames, vectorNa* outTTable, vectorNa* TTable, + const std::vector>& bin_coeffs, + const std::vector& suit_splits, + const std::unordered_map& SuitRanks, + size_t start_id, size_t end_id) { + // takes endgames solved to depth d-1 and returns endgames solved to depth d + // // + std::vector combination; + combination.reserve(size_endgames); + for (int i = 0; i < size_endgames; ++i) { + combination.push_back(i); + } + bool control = true; + int count = 0; + uint32_t cards = 0; + for (int i = 0; i < combination.size(); ++i) { + cards = cards | (1 << combination[i]); + } + while (count < start_id) { + NextColex(combination, 2 * size_endgames); + count++; + } + while (count < end_id && control) { uint32_t cards = 0; for (int i = 0; i < combination.size(); ++i) { - cards = cards | (1 << combination[i]); - } - while (count < start_id) { - NextColex(combination, 2 * size_endgames); - count++; - } - while (count < end_id && control) { - uint32_t cards = 0; - for (int i = 0; i < combination.size(); ++i) { - cards = cards | (1 << combination[i]); - } - for (int i = 0; i < suit_splits.size(); ++i) { - std::array suit_arr; - suit_arr[0] = bzhi_u32(~0, suit_splits[i] & 0b1111); - uint32_t sum = suit_splits[i] & 0b1111; - for (int j = 1; j < kNumSuits; ++j) { - uint32_t mask = bzhi_u32(~0, sum); - sum += (suit_splits[i] & (0b1111 << (4 * j))) >> 4 * j; - suit_arr[j] = bzhi_u32(~0, sum); - suit_arr[j] = suit_arr[j] ^ mask; - } - Node node(cards, suit_arr, 0, false); - char result = IncrementalAlphaBetaMemoryIso(&node,0,size_endgames,2,TTable,&SuitRanks,bin_coeffs); - outTTable->Set(count,i, result); - } - control = NextColex(combination, 2 * size_endgames); - count++; - } + cards = cards | (1 << combination[i]); + } + for (int i = 0; i < suit_splits.size(); ++i) { + std::array suit_arr; + suit_arr[0] = bzhi_u32(~0, suit_splits[i] & 0b1111); + uint32_t sum = suit_splits[i] & 0b1111; + for (int j = 1; j < kNumSuits; ++j) { + uint32_t mask = bzhi_u32(~0, sum); + sum += (suit_splits[i] & (0b1111 << (4 * j))) >> 4 * j; + suit_arr[j] = bzhi_u32(~0, sum); + suit_arr[j] = suit_arr[j] ^ mask; + } + Node node(cards, suit_arr, 0, false); + char result = IncrementalAlphaBetaMemoryIso( + &node, 0, size_endgames, 2, TTable, &SuitRanks, bin_coeffs); + outTTable->Set(count, i, result); + } + control = NextColex(combination, 2 * size_endgames); + count++; + } } -vectorNa RetroSolver(int size_endgames, vectorNa* TTable, std::vector>& bin_coeffs) { - //takes endgames solved to depth d-1 and returns endgames solved to depth d // - vectorNa outTTable = InitialiseTTable(size_endgames, bin_coeffs); - std::vector suit_splits = GenQuads(size_endgames); - std::unordered_map SuitRanks; - GenSuitRankingsRel(size_endgames - 1, &SuitRanks); - std::vector combination; - combination.reserve(size_endgames); - for (int i = 0; i < size_endgames; ++i) { - combination.push_back(i); - } - uint32_t v_length = (suit_splits.size() >> 1) + 1; - uint32_t min_block_size = 256; - uint32_t hard_threads = std::thread::hardware_concurrency(); - uint32_t num_threads = 1; - uint32_t num_outers =outTTable.GetOuterSize(); - //a haphazard attempt to mitigate false sharing// - for (uint32_t i = hard_threads; i >= 1; i--) { - if ((num_outers * v_length / i) >= min_block_size) { - num_threads = i; - break; - } - } - std::vector threads = {}; - for (int i = 0; i < num_threads; ++i) { - uint32_t block_size = num_outers / num_threads; - uint32_t start_id; - uint32_t end_id; - if (num_threads == 1) { - start_id = 0; - end_id = num_outers; - } - else if (i == num_threads - 1) { - start_id = block_size * (num_threads - 1); - end_id = num_outers; - } - else { - start_id = block_size * i; - end_id = block_size * (i + 1); - } - threads.push_back(std::thread(ThreadSolver, size_endgames, &outTTable, TTable,std::ref(bin_coeffs), std::ref(suit_splits), std::ref(SuitRanks), start_id, end_id)); - } - for (int i = 0; i >& bin_coeffs) { + // takes endgames solved to depth d-1 and returns endgames solved to depth d + // // + vectorNa outTTable = InitialiseTTable(size_endgames, bin_coeffs); + std::vector suit_splits = GenQuads(size_endgames); + std::unordered_map SuitRanks; + GenSuitRankingsRel(size_endgames - 1, &SuitRanks); + std::vector combination; + combination.reserve(size_endgames); + for (int i = 0; i < size_endgames; ++i) { + combination.push_back(i); + } + uint32_t v_length = (suit_splits.size() >> 1) + 1; + uint32_t min_block_size = 256; + uint32_t hard_threads = std::thread::hardware_concurrency(); + uint32_t num_threads = 1; + uint32_t num_outers = outTTable.GetOuterSize(); + // a haphazard attempt to mitigate false sharing// + for (uint32_t i = hard_threads; i >= 1; i--) { + if ((num_outers * v_length / i) >= min_block_size) { + num_threads = i; + break; + } + } + std::vector threads = {}; + for (int i = 0; i < num_threads; ++i) { + uint32_t block_size = num_outers / num_threads; + uint32_t start_id; + uint32_t end_id; + if (num_threads == 1) { + start_id = 0; + end_id = num_outers; + } else if (i == num_threads - 1) { + start_id = block_size * (num_threads - 1); + end_id = num_outers; + } else { + start_id = block_size * i; + end_id = block_size * (i + 1); + } + threads.push_back(std::thread( + ThreadSolver, size_endgames, &outTTable, TTable, std::ref(bin_coeffs), + std::ref(suit_splits), std::ref(SuitRanks), start_id, end_id)); + } + for (int i = 0; i < num_threads; ++i) { + threads[i].join(); + } + return outTTable; } - -bool TestRetroSolve(int samples, int depth, uint32_t seed, std::vector>& bin_coeffs) { - //Tests endgame solution with TTable vs raw seach - std::vector nodes = GWhistGenerator(samples, seed); - vectorNa v; - for (int i = 1; i <= depth; ++i) { - v = RetroSolver(i, &v, bin_coeffs); - } - std::unordered_map SuitRanks; - GenSuitRankingsRel(depth, &SuitRanks); - for (auto it = nodes.begin(); it != nodes.end(); ++it) { - char abm_unsafe = IncrementalAlphaBetaMemoryIso(&*it, 0,kNumRanks, 2 * (kNumRanks - depth), &v, &SuitRanks, bin_coeffs); - char abm_safe = AlphaBeta(&*it, 0, kNumRanks); - if (abm_unsafe != abm_safe) { - return false; - } - } - return true; +bool TestRetroSolve(int samples, int depth, uint32_t seed, + const std::vector>& bin_coeffs) { + // Tests endgame solution with TTable vs raw seach + std::vector nodes = GWhistGenerator(samples, seed); + vectorNa v; + for (int i = 1; i <= depth; ++i) { + v = RetroSolver(i, &v, bin_coeffs); + } + std::unordered_map SuitRanks; + GenSuitRankingsRel(depth, &SuitRanks); + for (auto it = nodes.begin(); it != nodes.end(); ++it) { + char abm_unsafe = IncrementalAlphaBetaMemoryIso(&*it, 0, kNumRanks, + 2 * (kNumRanks - depth), &v, + &SuitRanks, bin_coeffs); + char abm_safe = AlphaBeta(&*it, 0, kNumRanks); + if (abm_unsafe != abm_safe) { + return false; + } + } + return true; } -vectorNa BuildTablebase(std::vector>& bin_coeffs) { - vectorNa v; - std::cout<<"Building Tablebase"<<"\n"; - for (int i = 1; i <= kNumRanks; ++i) { - v = RetroSolver(i, &v, bin_coeffs); - std::cout<<"Done "<>& bin_coeffs) { + vectorNa v; + std::cout << "Building Tablebase" + << "\n"; + for (int i = 1; i <= kNumRanks; ++i) { + v = RetroSolver(i, &v, bin_coeffs); + std::cout << "Done " << i << "\n"; + } + std::cout << "Built Tablebase" + << "\n"; + return v; } -bool TestTablebase(int samples,uint32_t seed,vectorNa& table_base,std::vector>& bin_coeffs){ - std::vector nodes = GWhistGenerator(samples, seed); - std::unordered_map SuitRanks; - GenSuitRankingsRel(kNumRanks, &SuitRanks); - for (auto it = nodes.begin(); it != nodes.end(); ++it) { - char abm_unsafe = IncrementalAlphaBetaMemoryIso(&*it, 0,kNumRanks, 0, &table_base, &SuitRanks, bin_coeffs); - char abm_safe = AlphaBeta(&*it, 0, kNumRanks); - if (abm_unsafe != abm_safe) { - return false; - } - } - return true; +bool TestTablebase(int samples, uint32_t seed, const vectorNa& table_base, + const std::vector>& bin_coeffs) { + std::vector nodes = GWhistGenerator(samples, seed); + std::unordered_map SuitRanks; + GenSuitRankingsRel(kNumRanks, &SuitRanks); + for (auto it = nodes.begin(); it != nodes.end(); ++it) { + char abm_unsafe = IncrementalAlphaBetaMemoryIso( + &*it, 0, kNumRanks, 0, &table_base, &SuitRanks, bin_coeffs); + char abm_safe = AlphaBeta(&*it, 0, kNumRanks); + if (abm_unsafe != abm_safe) { + return false; + } + } + return true; } -void StoreTTable(const std::string filename, const vectorNa& solution){ - //stores solution into a text file// - std::ofstream file(filename); - for(int i =0;i>& bin_coeffs){ - //Tests storage fidelity// - StoreTTable(filename,v); - vectorNa new_v = LoadTTable(filename,depth,bin_coeffs); - for(int i =0;i>& bin_coeffs) { + // Tests storage fidelity// + StoreTTable(filename, v); + vectorNa new_v = LoadTTable(filename, depth, bin_coeffs); + for (int i = 0; i < v.GetOuterSize(); ++i) { + for (int j = 0; j < v.GetInnerSize(); ++j) { + if (v.GetChar(i, j) != new_v.GetChar(i, j)) { + return false; + } + } + } + return true; } -}//germanwhist -}//open_spiel +} // namespace german_whist_foregame +} // namespace open_spiel -int main(){ - std::vector> bin_coeffs = open_spiel::german_whist_foregame::BinCoeffs(2*open_spiel::german_whist_foregame::kNumRanks); - open_spiel::german_whist_foregame::vectorNa tablebase = open_spiel::german_whist_foregame::BuildTablebase(bin_coeffs); - std::random_device rd; - int num_samples = 100; - if(open_spiel::german_whist_foregame::TestTablebase(num_samples,rd(),tablebase,bin_coeffs)){ - std::cout<<"Tablebase accurate"<> bin_coeffs = + open_spiel::german_whist_foregame::BinCoeffs( + 2 * open_spiel::german_whist_foregame::kNumRanks); + open_spiel::german_whist_foregame::vectorNa tablebase = + open_spiel::german_whist_foregame::BuildTablebase(bin_coeffs); + std::random_device rd; + int num_samples = 100; + if (open_spiel::german_whist_foregame::TestTablebase(num_samples, rd(), + tablebase, bin_coeffs)) { + std::cout << "Tablebase accurate" << std::endl; + } else { + std::cout << "Tablebase inaccurate" << std::endl; + } + std::cout << "Starting Saving Tablebase" << std::endl; + open_spiel::german_whist_foregame::StoreTTable("TTable13.txt", tablebase); + std::cout << "Finished Saving Tablebase" << std::endl; } - diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 7d0200c6f8..97397a4dff 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -1,235 +1,225 @@ +#include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" + #include + #include "open_spiel/abseil-cpp/absl/strings/str_cat.h" #include "open_spiel/game_parameters.h" #include "open_spiel/observer.h" #include "open_spiel/policy.h" #include "open_spiel/spiel.h" #include "open_spiel/spiel_utils.h" -#include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" -// define BMI2 only if your system supports BMI2 intrinsics, modify compiler flags so that bmi2 instructions are compiled// -// #define __BMI2__ +// define BMI2 only if your system supports BMI2 intrinsics, modify compiler +// flags so that bmi2 instructions are compiled// #define __BMI2__ #ifdef __BMI2__ #include #endif namespace open_spiel { namespace german_whist_foregame { -// set this to the path you expect TTable to be once you have made it so recompilation is not necessary// -std::string kTTablePath=""; +// set this to the path you expect TTable to be once you have made it so +// recompilation is not necessary// +std::string kTTablePath = ""; -uint32_t tzcnt_u32(uint32_t a) { - return __builtin_ctz(a); -} -uint64_t tzcnt_u64(uint64_t a) { - return __builtin_ctzll(a); -} -uint32_t bzhi_u32(uint32_t a,uint32_t b) { - return a&((1u<>1; - m = m>>1; - }while(m!=0); - return r; + uint64_t r = 0; + uint64_t s = 0; + uint64_t b = 0; + do { + b = m & 1; + r = r | ((x & b) << s); + s = s + b; + x = x >> 1; + m = m >> 1; + } while (m != 0); + return r; #endif } -bool Triple::operator<(const Triple& triple)const { - return (length < triple.length)|| (length == triple.length && sig < triple.sig); +bool Triple::operator<(const Triple& triple) const { + return (length < triple.length) || + (length == triple.length && sig < triple.sig); } inline int CardRank(int card, int suit) { - uint64_t card_mask = ((uint64_t)1 << card); - card_mask = (card_mask >> (suit * kNumRanks)); - return tzcnt_u64(card_mask); + uint64_t card_mask = ((uint64_t)1 << card); + card_mask = (card_mask >> (suit * kNumRanks)); + return tzcnt_u64(card_mask); } inline int CardSuit(int card) { - uint64_t card_mask = ((uint64_t)1 << card); - for (int i = 0; i < kNumSuits; ++i) { - if (popcnt_u64(card_mask & kSuitMasks[i]) == 1) { - return i; - } + uint64_t card_mask = ((uint64_t)1 << card); + for (int i = 0; i < kNumSuits; ++i) { + if (popcnt_u64(card_mask & kSuitMasks[i]) == 1) { + return i; } - return kNumSuits; + } + return kNumSuits; } std::string CardString(int card) { - int suit = CardSuit(card); - return { kSuitChar[suit],kRankChar[CardRank(card,suit)] }; + int suit = CardSuit(card); + return {kSuitChar[suit], kRankChar[CardRank(card, suit)]}; } std::vector GenQuads(int size_endgames) { - // Generates Suit splittings for endgames of a certain size// - std::vector v; - for (char i = 0; i <= std::min(size_endgames * 2, kNumRanks); ++i) { - int sum = size_endgames * 2 - i; - for (char j = 0; j <= std::min(sum, kNumRanks); ++j) { - for (char k = std::max((int)j, sum - j - kNumRanks); k <= std::min(sum - j, kNumRanks); ++k) { - char l = sum - j - k; - if (l < k) { - break; - } - else { - uint32_t num = 0; - num = num | (i); - num = num | (j << 4); - num = num | (k << 8); - num = num | (l << 12); - v.push_back(num); - } - } + // Generates Suit splittings for endgames of a certain size// + std::vector v; + for (char i = 0; i <= std::min(size_endgames * 2, kNumRanks); ++i) { + int sum = size_endgames * 2 - i; + for (char j = 0; j <= std::min(sum, kNumRanks); ++j) { + for (char k = std::max((int)j, sum - j - kNumRanks); + k <= std::min(sum - j, kNumRanks); ++k) { + char l = sum - j - k; + if (l < k) { + break; + } else { + uint32_t num = 0; + num = num | (i); + num = num | (j << 4); + num = num | (k << 8); + num = num | (l << 12); + v.push_back(num); } + } } - return v; + } + return v; } std::vector> BinCoeffs(uint32_t max_n) { - //tabulates binomial coefficients// - std::vector> C(max_n+1,std::vector(max_n+1)); - for (uint32_t i = 1; i <= max_n; ++i) { - C[0][i] = 0; - } - for (uint32_t i = 0; i <= max_n; ++i) { - C[i][0] = 1; - } - for (uint32_t i = 1; i <= max_n; ++i) { - for (uint32_t j = 1; j <= max_n; ++j) { - C[i][j] = C[i - 1][j] + C[i - 1][j - 1]; - } - } - return C; -} -uint32_t HalfColexer(uint32_t cards,const std::vector>* bin_coeffs) { - //returns the colexicographical ranking of a combination of indices where the the size of the combination is half that of the set of indices// - uint32_t out = 0; - uint32_t count = 0; - while (cards != 0) { - uint32_t ind = tzcnt_u32(cards); - uint32_t val = bin_coeffs->at(ind)[count+1]; - out += val; - cards = blsr_u32(cards); - count++; - } - return out; -} -void GenSuitRankingsRel(uint32_t size, std::unordered_map* Ranks) { - //Generates ranking Table for suit splittings for endgames of a certain size// - std::vector v=GenQuads(size); - for (uint32_t i = 0; i < v.size(); ++i) { - Ranks->insert({ v[i],i }); - } + // tabulates binomial coefficients// + std::vector> C(max_n + 1, + std::vector(max_n + 1)); + for (uint32_t i = 1; i <= max_n; ++i) { + C[0][i] = 0; + } + for (uint32_t i = 0; i <= max_n; ++i) { + C[i][0] = 1; + } + for (uint32_t i = 1; i <= max_n; ++i) { + for (uint32_t j = 1; j <= max_n; ++j) { + C[i][j] = C[i - 1][j] + C[i - 1][j - 1]; + } + } + return C; +} +uint32_t HalfColexer(uint32_t cards, + const std::vector>* bin_coeffs) { + // returns the colexicographical ranking of a combination of indices where the + // the size of the combination is half that of the set of indices// + uint32_t out = 0; + uint32_t count = 0; + while (cards != 0) { + uint32_t ind = tzcnt_u32(cards); + uint32_t val = bin_coeffs->at(ind)[count + 1]; + out += val; + cards = blsr_u32(cards); + count++; + } + return out; +} +void GenSuitRankingsRel(uint32_t size, + std::unordered_map* Ranks) { + // Generates ranking Table for suit splittings for endgames of a certain + // size// + std::vector v = GenQuads(size); + for (uint32_t i = 0; i < v.size(); ++i) { + Ranks->insert({v[i], i}); + } } -vectorNa::vectorNa(size_t card_combs,size_t suit_splits,char val) { - data=std::vector(card_combs*((suit_splits>>1)+1),val); - inner_size =(suit_splits>>1)+1; - outer_size = card_combs; +vectorNa::vectorNa(size_t card_combs, size_t suit_splits, char val) { + data = std::vector(card_combs * ((suit_splits >> 1) + 1), val); + inner_size = (suit_splits >> 1) + 1; + outer_size = card_combs; } vectorNa::vectorNa() { - data={}; - inner_size=0; - outer_size=0; -} -size_t vectorNa::size() const { - return data.size(); -} -size_t vectorNa::GetInnerSize() const { - return inner_size; -} -size_t vectorNa::GetOuterSize() const { - return outer_size; -} -char const& vectorNa::operator[](size_t index) const { - return data[index]; -} -char vectorNa::GetChar(size_t i,size_t j) const { - return data[i*inner_size+j]; -} -void vectorNa::SetChar(size_t i,size_t j,char value){ - data[i*inner_size+j]=value; -} -char vectorNa::Get(size_t i,size_t j) const { - int remainder = j&0b1; - if(remainder==0){ - return 0b1111&data[i*inner_size+(j>>1)]; - } - else{ - return ((0b11110000&data[i*inner_size+(j>>1)])>>4); - } -} -void vectorNa::Set(size_t i,size_t j,char value) { - int remainder = j & 0b1; - if (remainder == 0) { - char datastore = 0b11110000 & data[i*inner_size+(j>>1)]; - data[i*inner_size+(j>>1)] = datastore|value; - } - else { - char datastore = (0b1111 & data[i*inner_size+(j>>1)]); - data[i*inner_size+(j>>1)] = datastore|(value << 4); - } -} -vectorNa InitialiseTTable(int size,std::vector>& bin_coeffs) { - //initialises TTable for a certain depth// - size_t suit_size = GenQuads(size).size(); - return vectorNa(bin_coeffs[2 * size][size],suit_size, 0); -} -vectorNa LoadTTable(const std::string filename, int depth,std::vector>& bin_coeffs) { - //loads solution from a text file into a vector for use// - std::cout<<"Loading Tablebase"<<"\n"; - vectorNa v = InitialiseTTable(depth,bin_coeffs); - std::ifstream file(filename,std::ios::binary); - if (!file.is_open()) { - std::cout<<"Failed to load Tablebase"<<"\n"; - std::cout<<"Tablebase will be set to all 0"<<"\n"; - file.close(); - return v; - } - else { - char c; - for (int i =0;i> 1)]; + } else { + return ((0b11110000 & data[i * inner_size + (j >> 1)]) >> 4); + } +} +void vectorNa::Set(size_t i, size_t j, char value) { + int remainder = j & 0b1; + if (remainder == 0) { + char datastore = 0b11110000 & data[i * inner_size + (j >> 1)]; + data[i * inner_size + (j >> 1)] = datastore | value; + } else { + char datastore = (0b1111 & data[i * inner_size + (j >> 1)]); + data[i * inner_size + (j >> 1)] = datastore | (value << 4); + } +} +vectorNa InitialiseTTable(int size, + const std::vector>& bin_coeffs) { + // initialises TTable for a certain depth// + size_t suit_size = GenQuads(size).size(); + return vectorNa(bin_coeffs[2 * size][size], suit_size, 0); +} +vectorNa LoadTTable(const std::string filename, int depth, + const std::vector>& bin_coeffs) { + // loads solution from a text file into a vector for use// + std::cout << "Loading Tablebase" + << "\n"; + vectorNa v = InitialiseTTable(depth, bin_coeffs); + std::ifstream file(filename, std::ios::binary); + if (!file.is_open()) { + std::cout << "Failed to load Tablebase" + << "\n"; + std::cout << "Tablebase will be set to all 0" + << "\n"; + file.close(); + return v; + } else { + char c; + for (int i = 0; i < v.GetOuterSize(); ++i) { + for (int j = 0; j < v.GetInnerSize(); ++j) { + file.get(c); + v.SetChar(i, j, c); + } + } + file.close(); + std::cout << "Tablebase Loaded" + << "\n"; + return v; + } } // Default parameters. -namespace {//namespace +namespace { // namespace // Facts about the game -const GameType kGameType{/*short_name=*/"german_whist_foregame", +const GameType kGameType{ + /*short_name=*/"german_whist_foregame", /*long_name=*/"german_whist_foregame", GameType::Dynamics::kSequential, GameType::ChanceMode::kExplicitStochastic, @@ -245,433 +235,461 @@ const GameType kGameType{/*short_name=*/"german_whist_foregame", }; std::shared_ptr Factory(const GameParameters& params) { - return std::shared_ptr(new GWhistFGame(params)); + return std::shared_ptr(new GWhistFGame(params)); } REGISTER_SPIEL_GAME(kGameType, Factory); -}//namespace +} // namespace -GWhistFGame::GWhistFGame(const GameParameters& params):Game(kGameType, params) { - bin_coeffs_=BinCoeffs(2*kNumRanks); - std::unordered_map temp; - GenSuitRankingsRel(13,&temp); - suit_ranks_=temp; - ttable_ = LoadTTable(kTTablePath,13,bin_coeffs_); +GWhistFGame::GWhistFGame(const GameParameters& params) + : Game(kGameType, params) { + bin_coeffs_ = BinCoeffs(2 * kNumRanks); + std::unordered_map temp; + GenSuitRankingsRel(13, &temp); + suit_ranks_ = temp; + ttable_ = LoadTTable(kTTablePath, 13, bin_coeffs_); }; std::unique_ptr GWhistFGame::NewInitialState() const { - const auto ptr=std::dynamic_pointer_cast(shared_from_this()); - return std::make_unique(ptr); + const auto ptr = + std::dynamic_pointer_cast(shared_from_this()); + return std::make_unique(ptr); } - -GWhistFState::GWhistFState(std::shared_ptr game):State(game) { - player_ = kChancePlayerId; - move_number_ = 0; - trump_ = -1; - deck_ = bzhi_u64(~0,kNumRanks*kNumSuits); - discard_ = 0; - hands_ = { 0,0 }; - history_.reserve(78); - ttable_ = &(game->ttable_); - suit_ranks_ =&(game->suit_ranks_); - bin_coeffs_=&(game->bin_coeffs_); +GWhistFState::GWhistFState(std::shared_ptr game) + : State(game) { + player_ = kChancePlayerId; + move_number_ = 0; + trump_ = -1; + deck_ = bzhi_u64(~0, kNumRanks * kNumSuits); + discard_ = 0; + hands_ = {0, 0}; + history_.reserve(78); + ttable_ = &(game->ttable_); + suit_ranks_ = &(game->suit_ranks_); + bin_coeffs_ = &(game->bin_coeffs_); } bool GWhistFState::Trick(int lead, int follow) const { - int lead_suit = CardSuit(lead); - int follow_suit = CardSuit(follow); - int lead_rank = CardRank(lead,lead_suit); - int follow_rank = CardRank(follow,follow_suit); - return (lead_suit == follow_suit && lead_rank < follow_rank) || (lead_suit != follow_suit && follow_suit != trump_); -} -bool GWhistFState::IsTerminal() const { - return(popcnt_u64(deck_) == 0); -} + int lead_suit = CardSuit(lead); + int follow_suit = CardSuit(follow); + int lead_rank = CardRank(lead, lead_suit); + int follow_rank = CardRank(follow, follow_suit); + return (lead_suit == follow_suit && lead_rank < follow_rank) || + (lead_suit != follow_suit && follow_suit != trump_); +} +bool GWhistFState::IsTerminal() const { return (popcnt_u64(deck_) == 0); } uint64_t GWhistFState::EndgameKey(int player_to_move) const { - //generates a 64 bit unsigned int where the first 32 are the suit ownerships from the perspective of the opponent using canonical rankings// - //example: if Spade suit is to_move = A3, opp =2, suit = 0b100 - //least significant part of first 32 bits is the trump suit, then the remaining suits ascending length order. - uint64_t cards_in_play = hands_[0]|hands_[1]; - std::vector suit_lengths = {}; - int opp = (player_to_move==0)?1:0; - //sort trump suits by length,then sig// - for (int i =0;i hand0; - std::array hand1; - hand0[0]=pext_u64(hands_[0],kSuitMasks[trump_]); - hand1[0]=pext_u64(hands_[1],kSuitMasks[trump_]); - for (int i =0;ihands_shuffled = {0,0}; - for (int i =0;i suit_lengths = {}; + int opp = (player_to_move == 0) ? 1 : 0; + // sort trump suits by length,then sig// + for (int i = 0; i < kNumSuits; ++i) { + if (i != trump_) { + uint64_t sig = + pext_u64(hands_[opp] & kSuitMasks[i], cards_in_play & kSuitMasks[i]); + suit_lengths.push_back( + Triple{i, popcnt_u64(kSuitMasks[i] & cards_in_play), sig}); + } + } + std::sort(suit_lengths.begin(), suit_lengths.end()); + std::array hand0; + std::array hand1; + hand0[0] = pext_u64(hands_[0], kSuitMasks[trump_]); + hand1[0] = pext_u64(hands_[1], kSuitMasks[trump_]); + for (int i = 0; i < kNumSuits - 1; ++i) { + hand0[i + 1] = pext_u64(hands_[0], kSuitMasks[suit_lengths[i].index]); + hand1[i + 1] = pext_u64(hands_[1], kSuitMasks[suit_lengths[i].index]); + } + std::array hands_shuffled = {0, 0}; + for (int i = 0; i < kNumSuits; ++i) { + hands_shuffled[0] = hands_shuffled[0] | (hand0[i] << (kNumRanks * i)); + hands_shuffled[1] = hands_shuffled[1] | (hand1[i] << (kNumRanks * i)); + } + uint64_t suit_sig = 0; + suit_sig = popcnt_u64(kSuitMasks[trump_] & cards_in_play); + for (int i = 0; i < kNumSuits - 1; ++i) { + suit_sig = suit_sig | ((uint64_t)suit_lengths[i].length << (4 * (i + 1))); + } + suit_sig = (suit_sig << 32); + cards_in_play = hands_shuffled[0] | hands_shuffled[1]; + uint64_t cards = pext_u64(hands_shuffled[opp], cards_in_play); + uint64_t key = cards | suit_sig; + return key; } std::vector GWhistFState::Returns() const { - if (IsTerminal()) { - std::vector out = {0,0}; - int lead_win = Trick(history_[move_number_ - 3].action, history_[move_number_ - 2].action); - int player_to_move=(lead_win)?history_[move_number_-3].player:history_[move_number_-2].player; - int opp = (player_to_move==0)?1:0; - uint64_t key = EndgameKey(player_to_move); - uint32_t cards = (key&bzhi_u64(~0,32)); - uint32_t colex = HalfColexer(cards,bin_coeffs_); - uint32_t suits = (key&(~0^bzhi_u64(~0,32)))>>32; - uint32_t suit_rank = suit_ranks_->at(suits); - char value =ttable_->Get(colex,suit_rank); - out[player_to_move] = 2*value-kNumRanks; - out[opp]=-out[player_to_move]; - return out; - } - else { - std::vector out = {0,0}; - return out; - } + if (IsTerminal()) { + std::vector out = {0, 0}; + int lead_win = Trick(history_[move_number_ - 3].action, + history_[move_number_ - 2].action); + int player_to_move = (lead_win) ? history_[move_number_ - 3].player + : history_[move_number_ - 2].player; + int opp = (player_to_move == 0) ? 1 : 0; + uint64_t key = EndgameKey(player_to_move); + uint32_t cards = (key & bzhi_u64(~0, 32)); + uint32_t colex = HalfColexer(cards, bin_coeffs_); + uint32_t suits = (key & (~0 ^ bzhi_u64(~0, 32))) >> 32; + uint32_t suit_rank = suit_ranks_->at(suits); + char value = ttable_->Get(colex, suit_rank); + out[player_to_move] = 2 * value - kNumRanks; + out[opp] = -out[player_to_move]; + return out; + } else { + std::vector out = {0, 0}; + return out; + } } - int GWhistFState::CurrentPlayer() const { return player_; } std::vector> GWhistFState::ChanceOutcomes() const { - std::vector> outcomes; - std::vector legal_actions = LegalActions(); - for (int i =0;i pair; - pair.first =legal_actions[i]; - pair.second = 1.0/legal_actions.size(); - outcomes.push_back(pair); - } - return outcomes; -} -std::string GWhistFState::ActionToString(Player player,Action move) const { - return CardString(move); + std::vector> outcomes; + std::vector legal_actions = LegalActions(); + for (int i = 0; i < legal_actions.size(); ++i) { + std::pair pair; + pair.first = legal_actions[i]; + pair.second = 1.0 / legal_actions.size(); + outcomes.push_back(pair); + } + return outcomes; +} +std::string GWhistFState::ActionToString(Player player, Action move) const { + return CardString(move); } std::string GWhistFState::ToString() const { - std::string out; - for (int i = 0; i < history_.size(); ++i) { - out += ActionToString(history_[i].player, history_[i].action); - out += "\n"; - } - return out; + std::string out; + for (int i = 0; i < history_.size(); ++i) { + out += ActionToString(history_[i].player, history_[i].action); + out += "\n"; + } + return out; } std::unique_ptr GWhistFState::Clone() const { - return std::unique_ptr(new GWhistFState(*this)); + return std::unique_ptr(new GWhistFState(*this)); } std::string GWhistFState::StateToString() const { - //doesnt use history in case of a resampled state with unreconciled history// - std::string out; - uint64_t copy_deck = deck_; - uint64_t copy_discard = discard_; - std::array copy_hands = hands_; - std::vector deck_cards; - std::vector player0_cards; - std::vector player1_cards; - std::vector discard; - while (copy_deck != 0) { - deck_cards.push_back(tzcnt_u64(copy_deck)); - copy_deck = blsr_u64(copy_deck); - } - while (copy_discard != 0) { - discard.push_back(tzcnt_u64(copy_discard)); - copy_discard = blsr_u64(copy_discard); - } + // doesnt use history in case of a resampled state with unreconciled history// + std::string out; + uint64_t copy_deck = deck_; + uint64_t copy_discard = discard_; + std::array copy_hands = hands_; + std::vector deck_cards; + std::vector player0_cards; + std::vector player1_cards; + std::vector discard; + while (copy_deck != 0) { + deck_cards.push_back(tzcnt_u64(copy_deck)); + copy_deck = blsr_u64(copy_deck); + } + while (copy_discard != 0) { + discard.push_back(tzcnt_u64(copy_discard)); + copy_discard = blsr_u64(copy_discard); + } - while (copy_hands[0] != 0) { - player0_cards.push_back(tzcnt_u64(copy_hands[0])); - copy_hands[0] = blsr_u64(copy_hands[0]); - } - while (copy_hands[1] != 0) { - player1_cards.push_back(tzcnt_u64(copy_hands[1])); - copy_hands[1] = blsr_u64(copy_hands[1]); - } - out += "Deck \n"; - for (int i = 0; i < deck_cards.size(); ++i) { - out += CardString(deck_cards[i]) + "\n"; - } - out += "Discard \n"; - for (int i = 0; i < discard.size(); ++i) { - out += CardString(discard[i]) + "\n"; - } + while (copy_hands[0] != 0) { + player0_cards.push_back(tzcnt_u64(copy_hands[0])); + copy_hands[0] = blsr_u64(copy_hands[0]); + } + while (copy_hands[1] != 0) { + player1_cards.push_back(tzcnt_u64(copy_hands[1])); + copy_hands[1] = blsr_u64(copy_hands[1]); + } + out += "Deck \n"; + for (int i = 0; i < deck_cards.size(); ++i) { + out += CardString(deck_cards[i]) + "\n"; + } + out += "Discard \n"; + for (int i = 0; i < discard.size(); ++i) { + out += CardString(discard[i]) + "\n"; + } - for (int i = 0; i < 2; ++i) { - out += "Player " + std::to_string(i) + "\n"; - std::vector var; - if (i == 0) { - var = player0_cards; - } - else { - var = player1_cards; - } - for (int j = 0; j < var.size(); ++j) { - out += CardString(var[j]) + "\n"; - } + for (int i = 0; i < 2; ++i) { + out += "Player " + std::to_string(i) + "\n"; + std::vector var; + if (i == 0) { + var = player0_cards; + } else { + var = player1_cards; } - return out; + for (int j = 0; j < var.size(); ++j) { + out += CardString(var[j]) + "\n"; + } + } + return out; } std::string GWhistFState::InformationStateString(Player player) const { - // THIS IS WHAT A PLAYER IS SHOWN WHEN PLAYING// - SPIEL_CHECK_TRUE(player >= 0 && player < 2); - std::string p = std::to_string(player)+","; - std::string cur_hand = ""; - std::string observations=""; - std::vector v_hand = {}; - uint64_t p_hand = hands_[player]; - while (p_hand!=0) { - v_hand.push_back(tzcnt_u64(p_hand)); - p_hand = blsr_u64(p_hand); - } - std::sort(v_hand.begin(),v_hand.end()); - for (int i =0;i GWhistFState::ResampleFromInfostate(int player_id,std::function rng) const { - //only valid when called from a position where a player can act// - auto resampled_state = std::unique_ptr(new GWhistFState(*this)); - //seeding mt19937// - std::random_device rd; - std::mt19937 gen(rd()); - uint64_t necessary_cards = 0; - for (int i = 2 * kNumRanks; i < history_.size(); i+=4) { - //face up cards from deck// - necessary_cards = (necessary_cards | (uint64_t(1) << history_[i].action)); - } - int move_index = move_number_ - ((kNumRanks * kNumSuits) / 2); - int move_remainder = move_index % 4; - int opp = (player_id == 0) ? 1 : 0; - int recent_faceup = move_number_ - move_remainder; - uint64_t recent_faceup_card = (uint64_t(1) << history_[recent_faceup].action); - // if a face up card from the deck is not in players hand or discard it must be in opps unless it is the most recent face up// - necessary_cards = (necessary_cards & (~(hands_[player_id] | discard_|recent_faceup_card))); - //sufficient cards are all cards not in players hand,the discard, or the recent face up// - uint64_t sufficient_cards = (bzhi_u64(~0, kNumRanks * kNumSuits) ^(hands_[player_id] | discard_|recent_faceup_card)); - //sufficient_cards are not necessary // - sufficient_cards = (sufficient_cards & (~(necessary_cards))); - //we must now take into account the observation of voids// - std::array when_voided = {0,0,0,0}; - std::array voids = {-1,-1,-1,-1}; - std::vector opp_dealt_hidden; - for (int i = 2 * kNumRanks; i < history_.size(); ++i) { - if (history_[i - 1].player == player_id && history_[i].player == (opp) && CardSuit(history_[i-1].action)!=CardSuit(history_[i].action)) { - when_voided[CardSuit(history_[i - 1].action)] = i - 1; - } - if (history_[i - 1].player == player_id && history_[i].player == (opp) && Trick(history_[i - 1].action, history_[i].action)) { - opp_dealt_hidden.push_back(i - 1); - } - if (history_[i - 1].player == (opp) && history_[i].player == (player_id) && !Trick(history_[i - 1].action, history_[i].action)) { - opp_dealt_hidden.push_back(i - 1); - } - } - //now voids contains the number of hidden cards dealt to opp since it showed a void in that suit, i.e the maximum number of cards held in that suit// - //if the suit is unvoided, then this number is -1// - for (int i = 0; i < kNumSuits; ++i) { - if (when_voided[i] != 0) { - voids[i] = 0; - for (int j = 0; j < opp_dealt_hidden.size(); ++j) { - if (opp_dealt_hidden[j] >= when_voided[i]) { - voids[i] += 1; - } - } - } + // THIS IS WHAT A PLAYER IS SHOWN WHEN PLAYING// + SPIEL_CHECK_TRUE(player >= 0 && player < 2); + std::string p = std::to_string(player) + ","; + std::string cur_hand = ""; + std::string observations = ""; + std::vector v_hand = {}; + uint64_t p_hand = hands_[player]; + while (p_hand != 0) { + v_hand.push_back(tzcnt_u64(p_hand)); + p_hand = blsr_u64(p_hand); + } + std::sort(v_hand.begin(), v_hand.end()); + for (int i = 0; i < v_hand.size(); ++i) { + cur_hand = cur_hand + CardString(v_hand[i]); + cur_hand = cur_hand + ","; + } + cur_hand += "\n"; + for (int i = 2 * kNumRanks; i < history_.size(); ++i) { + int index = (i - 2 * kNumRanks) % 4; + switch (index) { + case 0: + observations = + observations + "c_public:" + CardString(history_[i].action) + ","; + break; + case 1: + observations = observations + "p" + std::to_string(history_[i].player) + + ":" + CardString(history_[i].action) + ","; + break; + case 2: + observations = observations + "p" + std::to_string(history_[i].player) + + ":" + CardString(history_[i].action) + ","; + break; + case 3: + int lead_win = Trick(history_[i - 2].action, history_[i - 1].action); + int loser = ((lead_win) ^ (history_[i - 2].player == 0)) ? 0 : 1; + if (loser == player) { + observations = observations + + "c_observed:" + CardString(history_[i].action) + "\n"; + } else { + observations = observations + "c_unobserved:" + "\n"; } - //we now perform a sequence of shuffles to generate a possible opponent hand, and make no attempt to reconcile the history with this new deal// - int nec = popcnt_u64(necessary_cards); - for (int i = 0; i < kNumSuits; ++i) { - if (voids[i] != -1&&popcnt_u64(sufficient_cards&kSuitMasks[i])>voids[i]) { - uint64_t suit_subset = (sufficient_cards & kSuitMasks[i]); - std::vector temp; - while (suit_subset != 0) { - temp.push_back(tzcnt_u64(suit_subset)); - suit_subset = blsr_u64(suit_subset); - } - std::shuffle(temp.begin(), temp.end(), gen); - sufficient_cards = (sufficient_cards &~(kSuitMasks[i])); - for (int j = 0; j < voids[i]; ++j) { - sufficient_cards = (sufficient_cards | (uint64_t(1) << temp[j])); - } - } + break; + } + } + return p + cur_hand + observations; +} +std::unique_ptr GWhistFState::ResampleFromInfostate( + int player_id, std::function rng) const { + // only valid when called from a position where a player can act// + auto resampled_state = std::unique_ptr(new GWhistFState(*this)); + // seeding mt19937// + std::random_device rd; + std::mt19937 gen(rd()); + uint64_t necessary_cards = 0; + for (int i = 2 * kNumRanks; i < history_.size(); i += 4) { + // face up cards from deck// + necessary_cards = (necessary_cards | (uint64_t(1) << history_[i].action)); + } + int move_index = move_number_ - ((kNumRanks * kNumSuits) / 2); + int move_remainder = move_index % 4; + int opp = (player_id == 0) ? 1 : 0; + int recent_faceup = move_number_ - move_remainder; + uint64_t recent_faceup_card = (uint64_t(1) << history_[recent_faceup].action); + // if a face up card from the deck is not in players hand or discard it must + // be in opps unless it is the most recent face up// + necessary_cards = (necessary_cards & + (~(hands_[player_id] | discard_ | recent_faceup_card))); + // sufficient cards are all cards not in players hand,the discard, or the + // recent face up// + uint64_t sufficient_cards = + (bzhi_u64(~0, kNumRanks * kNumSuits) ^ + (hands_[player_id] | discard_ | recent_faceup_card)); + // sufficient_cards are not necessary // + sufficient_cards = (sufficient_cards & (~(necessary_cards))); + // we must now take into account the observation of voids// + std::array when_voided = {0, 0, 0, 0}; + std::array voids = {-1, -1, -1, -1}; + std::vector opp_dealt_hidden; + for (int i = 2 * kNumRanks; i < history_.size(); ++i) { + if (history_[i - 1].player == player_id && history_[i].player == (opp) && + CardSuit(history_[i - 1].action) != CardSuit(history_[i].action)) { + when_voided[CardSuit(history_[i - 1].action)] = i - 1; + } + if (history_[i - 1].player == player_id && history_[i].player == (opp) && + Trick(history_[i - 1].action, history_[i].action)) { + opp_dealt_hidden.push_back(i - 1); + } + if (history_[i - 1].player == (opp) && history_[i].player == (player_id) && + !Trick(history_[i - 1].action, history_[i].action)) { + opp_dealt_hidden.push_back(i - 1); + } + } + // now voids contains the number of hidden cards dealt to opp since it showed + // a void in that suit, i.e the maximum number of cards held in that suit// if + // the suit is unvoided, then this number is -1// + for (int i = 0; i < kNumSuits; ++i) { + if (when_voided[i] != 0) { + voids[i] = 0; + for (int j = 0; j < opp_dealt_hidden.size(); ++j) { + if (opp_dealt_hidden[j] >= when_voided[i]) { + voids[i] += 1; } - //finally generating a possible hand for opponent// - std::vector hand_vec; - while (sufficient_cards != 0) { - hand_vec.push_back(tzcnt_u64(sufficient_cards)); - sufficient_cards = blsr_u64(sufficient_cards); - } - std::shuffle(hand_vec.begin(), hand_vec.end(), gen); - uint64_t suff_hand = 0; - uint64_t opp_hand=0; - for (int i = 0; i < popcnt_u64(hands_[opp])-nec; ++i) { - suff_hand = suff_hand | (uint64_t(1) << hand_vec[i]); - } - opp_hand = suff_hand | necessary_cards; - resampled_state->hands_[opp] = opp_hand; - resampled_state->deck_ = bzhi_u64(~0, kNumRanks * kNumSuits) ^ (discard_ | opp_hand | hands_[player_id]|recent_faceup_card); - return resampled_state; - } + } + } + } + // we now perform a sequence of shuffles to generate a possible opponent hand, + // and make no attempt to reconcile the history with this new deal// + int nec = popcnt_u64(necessary_cards); + for (int i = 0; i < kNumSuits; ++i) { + if (voids[i] != -1 && + popcnt_u64(sufficient_cards & kSuitMasks[i]) > voids[i]) { + uint64_t suit_subset = (sufficient_cards & kSuitMasks[i]); + std::vector temp; + while (suit_subset != 0) { + temp.push_back(tzcnt_u64(suit_subset)); + suit_subset = blsr_u64(suit_subset); + } + std::shuffle(temp.begin(), temp.end(), gen); + sufficient_cards = (sufficient_cards & ~(kSuitMasks[i])); + for (int j = 0; j < voids[i]; ++j) { + sufficient_cards = (sufficient_cards | (uint64_t(1) << temp[j])); + } + } + } + // finally generating a possible hand for opponent// + std::vector hand_vec; + while (sufficient_cards != 0) { + hand_vec.push_back(tzcnt_u64(sufficient_cards)); + sufficient_cards = blsr_u64(sufficient_cards); + } + std::shuffle(hand_vec.begin(), hand_vec.end(), gen); + uint64_t suff_hand = 0; + uint64_t opp_hand = 0; + for (int i = 0; i < popcnt_u64(hands_[opp]) - nec; ++i) { + suff_hand = suff_hand | (uint64_t(1) << hand_vec[i]); + } + opp_hand = suff_hand | necessary_cards; + resampled_state->hands_[opp] = opp_hand; + resampled_state->deck_ = + bzhi_u64(~0, kNumRanks * kNumSuits) ^ + (discard_ | opp_hand | hands_[player_id] | recent_faceup_card); + return resampled_state; +} std::string GWhistFState::ObservationString(Player player) const { - //note this is a lie, this is not the observation state string but it is used for ISMCTS to label nodes// - SPIEL_CHECK_TRUE(player >= 0 && player < 2); - std::string p = "p"+std::to_string(player)+","; - std::string cur_hand=""; - std::string public_info = ""; - uint64_t p_hand = hands_[player]; - std::vector v_hand = {}; - while (p_hand!=0) { - v_hand.push_back(tzcnt_u64(p_hand)); - p_hand = blsr_u64(p_hand); - } - std::sort(v_hand.begin(),v_hand.end()); - for (int i =0;i= 0 && player < 2); + std::string p = "p" + std::to_string(player) + ","; + std::string cur_hand = ""; + std::string public_info = ""; + uint64_t p_hand = hands_[player]; + std::vector v_hand = {}; + while (p_hand != 0) { + v_hand.push_back(tzcnt_u64(p_hand)); + p_hand = blsr_u64(p_hand); + } + std::sort(v_hand.begin(), v_hand.end()); + for (int i = 0; i < v_hand.size(); ++i) { + cur_hand = cur_hand + CardString(v_hand[i]) + ","; + } + for (int i = 2 * kNumRanks; i < history_.size(); ++i) { + int index = (i - 2 * kNumRanks) % 4; + if (index != 3) { + public_info = public_info + std::to_string(history_[i].player) + ":" + + CardString(history_[i].action) + ","; + } + } + return p + cur_hand + public_info; } -std::vector GWhistFState::LegalActions() const{ - std::vector actions; - if (IsTerminal()) return {}; - if (IsChanceNode()) { - actions.reserve(popcnt_u64(deck_)); - uint64_t copy_deck = deck_; - while (copy_deck != 0) { - actions.push_back(tzcnt_u64(copy_deck)); - copy_deck = blsr_u64(copy_deck); - } +std::vector GWhistFState::LegalActions() const { + std::vector actions; + if (IsTerminal()) return {}; + if (IsChanceNode()) { + actions.reserve(popcnt_u64(deck_)); + uint64_t copy_deck = deck_; + while (copy_deck != 0) { + actions.push_back(tzcnt_u64(copy_deck)); + copy_deck = blsr_u64(copy_deck); + } + } else { + // lead// + actions.reserve(kNumRanks); + if (history_.back().player == kChancePlayerId) { + uint64_t copy_hand = hands_[player_]; + while (copy_hand != 0) { + actions.push_back(tzcnt_u64(copy_hand)); + copy_hand = blsr_u64(copy_hand); + } } - else { - //lead// - actions.reserve(kNumRanks); - if (history_.back().player == kChancePlayerId) { - uint64_t copy_hand = hands_[player_]; - while (copy_hand != 0) { - actions.push_back(tzcnt_u64(copy_hand)); - copy_hand = blsr_u64(copy_hand); - } - } - //follow// - else { - uint64_t copy_hand = hands_[player_] & kSuitMasks[CardSuit(history_.back().action)]; - if (copy_hand == 0) { - copy_hand = hands_[player_]; - } - while (copy_hand != 0) { - actions.push_back(tzcnt_u64(copy_hand)); - copy_hand = blsr_u64(copy_hand); - } - } - } - return actions; + // follow// + else { + uint64_t copy_hand = + hands_[player_] & kSuitMasks[CardSuit(history_.back().action)]; + if (copy_hand == 0) { + copy_hand = hands_[player_]; + } + while (copy_hand != 0) { + actions.push_back(tzcnt_u64(copy_hand)); + copy_hand = blsr_u64(copy_hand); + } + } + } + return actions; } void GWhistFState::DoApplyAction(Action move) { - //initial deal// - int player_start = player_; - if (move_number_ < (kNumSuits * kNumRanks) / 2) { - hands_[move_number_ % 2] = (hands_[move_number_ % 2] |((uint64_t)1 << move)); + // initial deal// + int player_start = player_; + if (move_number_ < (kNumSuits * kNumRanks) / 2) { + hands_[move_number_ % 2] = + (hands_[move_number_ % 2] | ((uint64_t)1 << move)); + deck_ = (deck_ ^ ((uint64_t)1 << move)); + } else if (move_number_ == (kNumSuits * kNumRanks / 2)) { + trump_ = CardSuit(move); + deck_ = (deck_ ^ ((uint64_t)1 << move)); + player_ = 0; + } + // cardplay// + else if (move_number_ > (kNumSuits * kNumRanks) / 2) { + int move_index = (move_number_ - ((kNumSuits * kNumRanks) / 2)) % 4; + switch (move_index) { + bool lead_win; + int winner; + int loser; + case 0: + // revealing face up card// deck_ = (deck_ ^ ((uint64_t)1 << move)); - } - else if (move_number_ == (kNumSuits * kNumRanks / 2)) { - trump_ = CardSuit(move); + lead_win = Trick(history_[move_number_ - 3].action, + history_[move_number_ - 2].action); + winner = + ((lead_win) ^ (history_[move_number_ - 3].player == 0)) ? 1 : 0; + player_ = winner; + break; + case 1: + // establishing lead// + discard_ = (discard_ | ((uint64_t)1 << move)); + hands_[player_] = (hands_[player_] ^ ((uint64_t)1 << move)); + (player_ == 0) ? player_ = 1 : player_ = 0; + break; + case 2: + // following and awarding face up// + discard_ = (discard_ | ((uint64_t)1 << move)); + hands_[player_] = (hands_[player_] ^ ((uint64_t)1 << move)); + lead_win = Trick(history_[move_number_ - 1].action, move); + winner = + ((lead_win) ^ (history_[move_number_ - 1].player == 0)) ? 1 : 0; + hands_[winner] = (hands_[winner] | + ((uint64_t)1 << history_[move_number_ - 2].action)); + player_ = kChancePlayerId; + break; + case 3: + // awarding face down// deck_ = (deck_ ^ ((uint64_t)1 << move)); - player_ = 0; - } - //cardplay// - else if (move_number_ > (kNumSuits * kNumRanks) / 2) { - int move_index = (move_number_ - ((kNumSuits * kNumRanks) / 2)) % 4; - switch (move_index) { - bool lead_win; - int winner; - int loser; - case 0: - //revealing face up card// - deck_ = (deck_ ^ ((uint64_t)1 << move)); - lead_win = Trick(history_[move_number_ - 3].action, history_[move_number_ - 2].action); - winner = ((lead_win) ^ (history_[move_number_ - 3].player == 0)) ? 1 : 0; - player_ = winner; - break; - case 1: - //establishing lead// - discard_ = (discard_|((uint64_t)1<>& bin_coeffs); -vectorNa LoadTTable(const std::string filename,int depth,std::vector>& bin_coeffs); +vectorNa InitialiseTTable(int size,const std::vector>& bin_coeffs); +vectorNa LoadTTable(const std::string filename,int depth,const std::vector>& bin_coeffs); class GWhistFGame : public Game { public: explicit GWhistFGame(const GameParameters& params); From 4cd3016e35437aeeaf271595cb0f4d888749b062 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Fri, 8 Mar 2024 19:26:16 +0000 Subject: [PATCH 27/30] Fixed const correctness --- .../games/german_whist_foregame/german_whist_endgame.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index b6a5660873..1f352e4835 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -421,8 +421,8 @@ bool NextColex(std::vector& v, int k) { } char IncrementalAlphaBetaMemoryIso( - Node* node, char alpha, char beta, int depth, vectorNa* TTable, - std::unordered_map* SuitRanks, + Node* node, char alpha, char beta, int depth, const vectorNa* TTable, + const std::unordered_map* SuitRanks, const std::vector>& bin_coeffs) { // fail soft ab search char val = 0; @@ -536,7 +536,7 @@ std::vector GWhistGenerator(int num, unsigned int seed) { return out; } -void ThreadSolver(int size_endgames, vectorNa* outTTable, vectorNa* TTable, +void ThreadSolver(int size_endgames, vectorNa* outTTable, const vectorNa* TTable, const std::vector>& bin_coeffs, const std::vector& suit_splits, const std::unordered_map& SuitRanks, From b3032e5e228a3aec859e58cc3dde8f3a4aa7ea08 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Tue, 2 Jul 2024 16:53:14 +0100 Subject: [PATCH 28/30] Using Approved Headers --- .../german_whist_endgame.cc | 16 ++++++++++------ .../german_whist_foregame.cc | 4 ---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index 1f352e4835..9d1163654f 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -2,9 +2,10 @@ // Whist #include -#include #include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" +#include "open_spiel/utils/file.h" +#include "open_spiel/utils/thread.h" // #define DEBUG namespace open_spiel { @@ -536,7 +537,8 @@ std::vector GWhistGenerator(int num, unsigned int seed) { return out; } -void ThreadSolver(int size_endgames, vectorNa* outTTable, const vectorNa* TTable, +void ThreadSolver(int size_endgames, vectorNa* outTTable, + const vectorNa* TTable, const std::vector>& bin_coeffs, const std::vector& suit_splits, const std::unordered_map& SuitRanks, @@ -607,7 +609,7 @@ vectorNa RetroSolver(int size_endgames, vectorNa* TTable, break; } } - std::vector threads = {}; + std::vector threads = {}; for (int i = 0; i < num_threads; ++i) { uint32_t block_size = num_outers / num_threads; uint32_t start_id; @@ -622,9 +624,11 @@ vectorNa RetroSolver(int size_endgames, vectorNa* TTable, start_id = block_size * i; end_id = block_size * (i + 1); } - threads.push_back(std::thread( - ThreadSolver, size_endgames, &outTTable, TTable, std::ref(bin_coeffs), - std::ref(suit_splits), std::ref(SuitRanks), start_id, end_id)); + threads.emplace_back([&, start_id, end_id]() { + ThreadSolver(size_endgames, &outTTable, TTable, std::ref(bin_coeffs), + std::ref(suit_splits), std::ref(SuitRanks), start_id, + end_id); + }); } for (int i = 0; i < num_threads; ++i) { threads[i].join(); diff --git a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc index 97397a4dff..4605a0e69b 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_foregame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_foregame.cc @@ -1,15 +1,11 @@ #include "open_spiel/games/german_whist_foregame/german_whist_foregame.h" - -#include - #include "open_spiel/abseil-cpp/absl/strings/str_cat.h" #include "open_spiel/game_parameters.h" #include "open_spiel/observer.h" #include "open_spiel/policy.h" #include "open_spiel/spiel.h" #include "open_spiel/spiel_utils.h" - // define BMI2 only if your system supports BMI2 intrinsics, modify compiler // flags so that bmi2 instructions are compiled// #define __BMI2__ #ifdef __BMI2__ From b3490d08a9c82001d25caaca91d804b5092b5d12 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Tue, 6 Aug 2024 14:35:13 +0100 Subject: [PATCH 29/30] removed std::thread dependency --- .../german_whist_foregame/german_whist_endgame.cc | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index 9d1163654f..ab3403dee7 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -585,7 +585,7 @@ void ThreadSolver(int size_endgames, vectorNa* outTTable, } } vectorNa RetroSolver(int size_endgames, vectorNa* TTable, - const std::vector>& bin_coeffs) { + const std::vector>& bin_coeffs,const uint32_t hard_threads) { // takes endgames solved to depth d-1 and returns endgames solved to depth d // // vectorNa outTTable = InitialiseTTable(size_endgames, bin_coeffs); @@ -599,7 +599,6 @@ vectorNa RetroSolver(int size_endgames, vectorNa* TTable, } uint32_t v_length = (suit_splits.size() >> 1) + 1; uint32_t min_block_size = 256; - uint32_t hard_threads = std::thread::hardware_concurrency(); uint32_t num_threads = 1; uint32_t num_outers = outTTable.GetOuterSize(); // a haphazard attempt to mitigate false sharing// @@ -637,12 +636,12 @@ vectorNa RetroSolver(int size_endgames, vectorNa* TTable, } bool TestRetroSolve(int samples, int depth, uint32_t seed, - const std::vector>& bin_coeffs) { + const std::vector>& bin_coeffs,const uint32_t hard_threads) { // Tests endgame solution with TTable vs raw seach std::vector nodes = GWhistGenerator(samples, seed); vectorNa v; for (int i = 1; i <= depth; ++i) { - v = RetroSolver(i, &v, bin_coeffs); + v = RetroSolver(i, &v, bin_coeffs,hard_threads); } std::unordered_map SuitRanks; GenSuitRankingsRel(depth, &SuitRanks); @@ -657,12 +656,12 @@ bool TestRetroSolve(int samples, int depth, uint32_t seed, } return true; } -vectorNa BuildTablebase(const std::vector>& bin_coeffs) { +vectorNa BuildTablebase(const std::vector>& bin_coeffs,const uint32_t hard_threads) { vectorNa v; std::cout << "Building Tablebase" << "\n"; for (int i = 1; i <= kNumRanks; ++i) { - v = RetroSolver(i, &v, bin_coeffs); + v = RetroSolver(i, &v, bin_coeffs,hard_threads); std::cout << "Done " << i << "\n"; } std::cout << "Built Tablebase" @@ -717,8 +716,9 @@ int main() { std::vector> bin_coeffs = open_spiel::german_whist_foregame::BinCoeffs( 2 * open_spiel::german_whist_foregame::kNumRanks); + const uint32_t hard_threads = 8//set this to take advantage of more cores on your machine// open_spiel::german_whist_foregame::vectorNa tablebase = - open_spiel::german_whist_foregame::BuildTablebase(bin_coeffs); + open_spiel::german_whist_foregame::BuildTablebase(bin_coeffs,hard_threads); std::random_device rd; int num_samples = 100; if (open_spiel::german_whist_foregame::TestTablebase(num_samples, rd(), From 192c9f67a1d568706fe685f4496b359287c25d03 Mon Sep 17 00:00:00 2001 From: willmcgowan Date: Wed, 7 Aug 2024 13:52:46 +0100 Subject: [PATCH 30/30] Semicolon --- open_spiel/games/german_whist_foregame/german_whist_endgame.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc index ab3403dee7..fb6ff3d598 100644 --- a/open_spiel/games/german_whist_foregame/german_whist_endgame.cc +++ b/open_spiel/games/german_whist_foregame/german_whist_endgame.cc @@ -716,7 +716,7 @@ int main() { std::vector> bin_coeffs = open_spiel::german_whist_foregame::BinCoeffs( 2 * open_spiel::german_whist_foregame::kNumRanks); - const uint32_t hard_threads = 8//set this to take advantage of more cores on your machine// + const uint32_t hard_threads = 8;//set this to take advantage of more cores on your machine// open_spiel::german_whist_foregame::vectorNa tablebase = open_spiel::german_whist_foregame::BuildTablebase(bin_coeffs,hard_threads); std::random_device rd;