Skip to content

Commit

Permalink
Merge pull request #1035 from VitamintK:maxgamelengthfix2
Browse files Browse the repository at this point in the history
PiperOrigin-RevId: 517103859
Change-Id: I63ca96d9bb1c679fa566a885d8491d0cdb2de4fe
  • Loading branch information
lanctot committed Mar 20, 2023
2 parents 0165c53 + e0f464a commit 2da02cf
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 22 deletions.
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

0 comments on commit 2da02cf

Please sign in to comment.