Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UniversalPoker/ACPC fixes: fullgame+abstracted MaxGameLength, fullgame LegalActions #1035

Merged
merged 7 commits into from
Mar 20, 2023
42 changes: 35 additions & 7 deletions open_spiel/games/universal_poker.cc
Original file line number Diff line number Diff line change
Expand Up @@ -234,7 +234,7 @@ UniversalPokerState::UniversalPokerState(std::shared_ptr<const Game> game)
const std::string handReaches =
game->GetParameters().at("handReaches").string_value();
if (!handReaches.empty()) {
std::stringstream iss( handReaches );
std::stringstream iss(handReaches);
double number;
while ( iss >> number ) {
handReaches_.push_back(number);
Expand Down Expand Up @@ -755,6 +755,9 @@ std::vector<Action> UniversalPokerState::LegalActions() const {
}
return legal_actions;
} else {
if (acpc_state_.IsFinished()) {
return legal_actions;
}
if (acpc_state_.IsValidAction(
acpc_cpp::ACPCState::ACPCActionType::ACPC_FOLD, 0)) {
legal_actions.push_back(kFold);
Expand Down Expand Up @@ -972,8 +975,6 @@ UniversalPokerGame::UniversalPokerGame(const GameParameters &params)
potSize_(ParameterValue<int>("potSize")),
boardCards_(ParameterValue<std::string>("boardCards")),
handReaches_(ParameterValue<std::string>("handReaches")) {
max_game_length_ = MaxGameLength();
SPIEL_CHECK_TRUE(max_game_length_.has_value());
std::string betting_abstraction =
ParameterValue<std::string>("bettingAbstraction");
if (betting_abstraction == "fc") {
Expand All @@ -988,6 +989,8 @@ UniversalPokerGame::UniversalPokerGame(const GameParameters &params)
SpielFatalError(absl::StrFormat("bettingAbstraction: %s not supported.",
betting_abstraction));
}
max_game_length_ = MaxGameLength();
SPIEL_CHECK_TRUE(max_game_length_.has_value());
}

std::unique_ptr<State> UniversalPokerGame::NewInitialState() const {
Expand Down Expand Up @@ -1079,10 +1082,17 @@ int UniversalPokerGame::MaxGameLength() const {
length += acpc_game_.GetTotalNbBoardCards() +
acpc_game_.GetNbHoleCardsRequired() * acpc_game_.GetNbPlayers();

// The longest game (with a single betting round, for simplicity) consists of:
// n-1 players checking,
// 1 player betting, n-2 players calling,
// 1 player raising, n-2 players calling,
// etc...,
// 1 player raising, n-1 players calling

// Check Actions
length += (NumPlayers() * acpc_game_.NumRounds());

// Bet Actions
// Bet/Raise/Call Actions
double maxStack = 0;
double maxBlind = 0;
for (uint32_t p = 0; p < NumPlayers(); p++) {
Expand All @@ -1092,10 +1102,28 @@ int UniversalPokerGame::MaxGameLength() const {
acpc_game_.BlindSize(p) > maxBlind ? acpc_game_.BlindSize(p) : maxBlind;
}

while (maxStack > maxBlind) {
maxStack /= 2.0; // You have always to bet the pot size
length += NumPlayers(); // Each player has to react
int max_num_raises = 0;
if (betting_abstraction_ == BettingAbstraction::kFC) {
// no raises
} else if (betting_abstraction_ == BettingAbstraction::kFCPA) {
double pot_size = maxBlind * NumPlayers();
while (pot_size / NumPlayers() < maxStack) {
max_num_raises++;
pot_size += pot_size * NumPlayers();
}
} else if (betting_abstraction_ == BettingAbstraction::kFCHPA) {
double pot_size = maxBlind * NumPlayers();
while (pot_size / NumPlayers() < maxStack) {
max_num_raises++;
pot_size += NumPlayers() * pot_size/2;
}
} else if (betting_abstraction_ == BettingAbstraction::kFULLGAME) {
max_num_raises = (maxStack + maxBlind - 1)/maxBlind; // ceil divide
} else {
SpielFatalError("Unknown Betting Abstraction");
}
// each bet/raise is followed by n-2 calls, for a total of n-1 actions:
length += max_num_raises * (NumPlayers() - 1);
return length;
}

Expand Down
61 changes: 61 additions & 0 deletions open_spiel/games/universal_poker_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include <iostream>
#include <memory>
#include <string>
#include <set>

#include "open_spiel/abseil-cpp/absl/algorithm/container.h"
#include "open_spiel/abseil-cpp/absl/container/flat_hash_map.h"
Expand Down Expand Up @@ -436,6 +437,65 @@ void FullNLBettingTest3() {
":2c2d|2h2s|3c3d/3h3s4c/4d/4h"));
}

// Check that a max length game works and infostate tensors are all unique.
void FullNLBettingTest4() {
std::shared_ptr<const Game> game = LoadGame(
"universal_poker(betting=nolimit,"
"numPlayers=2,"
"numRounds=2,"
"blind=100 50,"
"numSuits=1,"
"numRanks=4,"
"numHoleCards=1,"
"numBoardCards=0 1,"
"stack=2000 2000,"
"bettingAbstraction=fullgame)");
std::set<std::vector<float>> information_state_tensor_set;
std::vector<float> tensor;
std::unique_ptr<State> state = game->NewInitialState();
SPIEL_CHECK_EQ(game->NumDistinctActions(), 2001);
// deal cards
while (state->IsChanceNode()) state->ApplyAction(state->LegalActions()[0]);
// check the infostate tensor and add to set
tensor = state->InformationStateTensor();
SPIEL_CHECK_FALSE(information_state_tensor_set.count(tensor));
information_state_tensor_set.insert(tensor);
state->ApplyAction(1); // check
// check the infostate tensor and add to set
tensor = state->InformationStateTensor();
SPIEL_CHECK_FALSE(information_state_tensor_set.count(tensor));
information_state_tensor_set.insert(tensor);
state->ApplyAction(200); // min bet
// check the infostate tensor and add to set
tensor = state->InformationStateTensor();
SPIEL_CHECK_FALSE(information_state_tensor_set.count(tensor));
information_state_tensor_set.insert(tensor);
state->ApplyAction(1); // call
state->ApplyAction(state->LegalActions()[0]); // deal flop
// check the infostate tensor and add to set
tensor = state->InformationStateTensor();
SPIEL_CHECK_FALSE(information_state_tensor_set.count(tensor));
information_state_tensor_set.insert(tensor);
state->ApplyAction(1); // check
// check the infostate tensor and add to set
tensor = state->InformationStateTensor();
SPIEL_CHECK_FALSE(information_state_tensor_set.count(tensor));
information_state_tensor_set.insert(tensor);
for (int i=300; i < 2000; i+=100) {
state->ApplyAction(i); // min bet/raise
// check the infostate tensor and add to set
tensor = state->InformationStateTensor();
SPIEL_CHECK_FALSE(information_state_tensor_set.count(tensor));
information_state_tensor_set.insert(tensor);
}
state->ApplyAction(1); // call
SPIEL_CHECK_EQ(state->LegalActions().size(), 0);
std::cout << state->ToString() << std::endl;
SPIEL_CHECK_TRUE(absl::StrContains(state->ToString(),
"ACPC State: STATE:0:cr200c/cr300r400r500r600r700r800r900r1000r1100"
"r1200r1300r1400r1500r1600r1700r1800r1900c:2c|3c/4c"));
}

void ChanceDealRegressionTest() {
std::shared_ptr<const Game> game = LoadGame(
"universal_poker(betting=nolimit,"
Expand Down Expand Up @@ -714,6 +774,7 @@ int main(int argc, char **argv) {
open_spiel::universal_poker::FullNLBettingTest1();
open_spiel::universal_poker::FullNLBettingTest2();
open_spiel::universal_poker::FullNLBettingTest3();
open_spiel::universal_poker::FullNLBettingTest4();
open_spiel::universal_poker::HulhMaxUtilityIsCorrect();
open_spiel::universal_poker::CanConvertActionsCorrectly();
open_spiel::universal_poker::TestFCHPA();
Expand Down
Loading