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

Updates to Spades #1242

Merged
merged 6 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 44 additions & 39 deletions open_spiel/games/spades/spades.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,49 +33,40 @@
#include "open_spiel/spiel_globals.h"
#include "open_spiel/spiel_utils.h"

// Our preferred version of the double_dummy_solver defines a DDS_EXTERNAL
// macro to add a prefix to the exported symbols to avoid name clashes.
// In order to compile with versions of the double_dummy_solver which do not
// do this, we define DDS_EXTERNAL as an identity if it isn't already defined.
#ifndef DDS_EXTERNAL
#define DDS_EXTERNAL(x) x
#endif

namespace open_spiel {
namespace spades {
namespace {

enum Seat { kNorth, kEast, kSouth, kWest };

const GameType kGameType{
/*short_name=*/"spades",
/*long_name=*/"Partnership Spades",
GameType::Dynamics::kSequential,
GameType::ChanceMode::kExplicitStochastic,
GameType::Information::kImperfectInformation,
GameType::Utility::kGeneralSum,
GameType::RewardModel::kTerminal,
/*max_num_players=*/kNumPlayers,
/*min_num_players=*/kNumPlayers,
/*provides_information_state_string=*/false,
/*provides_information_state_tensor=*/false,
/*provides_observation_string=*/true,
/*provides_observation_tensor=*/true,
/*parameter_specification=*/
{
// Whether to end the game early if score gets too low
{"use_mercy_rule", GameParameter(true)},
// If using mercy rule, the threshold of negative points
{"mercy_threshold", GameParameter(-350)},
// Amount of points needed to win the game
{"win_threshold", GameParameter(500)},
// Partnership's current scores
// (can infer bags from last digit)
{"score_partnership_0", GameParameter(0)},
{"score_partnership_1", GameParameter(0)},
// Number of played tricks in observation tensor
{"num_tricks", GameParameter(2)},
}};
/*short_name=*/"spades",
/*long_name=*/"Partnership Spades",
GameType::Dynamics::kSequential,
GameType::ChanceMode::kExplicitStochastic,
GameType::Information::kImperfectInformation,
GameType::Utility::kGeneralSum,
GameType::RewardModel::kTerminal,
/*max_num_players=*/kNumPlayers,
/*min_num_players=*/kNumPlayers,
/*provides_information_state_string=*/false,
/*provides_information_state_tensor=*/false,
/*provides_observation_string=*/true,
/*provides_observation_tensor=*/true,
/*parameter_specification=*/
{
// Whether to end the game early if score gets too low
{"use_mercy_rule", GameParameter(true)},
// If using mercy rule, the threshold of negative points
{"mercy_threshold", GameParameter(-350)},
// Amount of points needed to win the game
{"win_threshold", GameParameter(500)},
// The amount to add to reward return for winning
// (Will subtract for losing by mercy rule)
{"win_or_loss_bonus", GameParameter(200)},
// Number of played tricks in observation tensor
{"num_tricks", GameParameter(2)},
}};

std::shared_ptr<const Game> Factory(const GameParameters& params) {
return std::shared_ptr<const Game>(new SpadesGame(params));
Expand Down Expand Up @@ -116,13 +107,12 @@ SpadesGame::SpadesGame(const GameParameters& params)

SpadesState::SpadesState(std::shared_ptr<const Game> game, bool use_mercy_rule,
int mercy_threshold, int win_threshold,
int score_partnership_0, int score_partnership_1,
int num_tricks)
int win_or_loss_bonus, int num_tricks)
: State(game),
use_mercy_rule_(use_mercy_rule),
mercy_threshold_(mercy_threshold),
win_threshold_(win_threshold),
current_scores_{score_partnership_0, score_partnership_1},
win_or_loss_bonus_(win_or_loss_bonus),
num_tricks_(num_tricks) {
possible_contracts_.fill(true);
}
Expand Down Expand Up @@ -559,6 +549,21 @@ Player SpadesState::CurrentPlayer() const {
void SpadesState::ScoreUp() {
std::array<int, kNumPartnerships> scores =
Score(contracts_, num_player_tricks_, current_scores_);
// Check for if bonus reward should be applied for winning (or losing by mercy rule)
for (int pship = 0; pship < kNumPartnerships; ++pship){
// Update overall scores
current_scores_[pship] += scores[pship];
// Check for bonus/penalty to returns and if overall game is over
if (scores[pship] >= win_threshold_ && scores[pship] > scores[pship^1]){
scores[pship] += win_or_loss_bonus_; // Add bonus reward for winning
is_game_over_ = true;
}
else if (mercy_threshold_ && scores[pship] <= mercy_threshold_ && scores[pship] < scores[pship^1]){
scores[pship] -= win_or_loss_bonus_; // Subtract penalty reward for losing by mercy rule
is_game_over_ = true;
}
}
// Apply the partnership scores (with bonus/penalty applied) to corresponding players' returns
for (int pl = 0; pl < kNumPlayers; ++pl) {
returns_[pl] = scores[Partnership(pl)];
}
Expand Down
36 changes: 24 additions & 12 deletions open_spiel/games/spades/spades.h
Original file line number Diff line number Diff line change
Expand Up @@ -97,8 +97,8 @@ class Trick {
class SpadesState : public State {
public:
SpadesState(std::shared_ptr<const Game> game, bool use_mercy_rule,
int mercy_threshold, int win_threshold, int score_partnership_0,
int score_partnership_1, int num_tricks);
int mercy_threshold, int win_threshold,
int win_or_loss_bonus, int num_tricks);
Player CurrentPlayer() const override;
std::string ActionToString(Player player, Action action) const override;
std::string ToString() const override;
Expand Down Expand Up @@ -133,6 +133,20 @@ class SpadesState : public State {
// Current phase.
int CurrentPhase() const { return static_cast<int>(phase_); }

// Current overall partnership scores
std::array<int, kNumPartnerships> GetCurrentScores() const { return current_scores_; }

// Set partnership scores
void SetCurrentScores(const std::array<int, kNumPartnerships>& new_scores) {
current_scores_ = new_scores;
}

// Indicates if overall game is over (did a partnership meet win/lose condition)
bool IsGameOver() const { return is_game_over_; }

// Manually set the current player (used to specify starting player)
void SetCurrentPlayer(const int current_player) { current_player_ = current_player; }

protected:
void DoApplyAction(Action action) override;

Expand All @@ -146,7 +160,6 @@ class SpadesState : public State {
void ApplyBiddingAction(int bid);
void ApplyPlayAction(int card);

void ComputeScoreByContract() const;
void ScoreUp();
Trick& CurrentTrick() { return tricks_[num_cards_played_ / kNumPlayers]; }
const Trick& CurrentTrick() const {
Expand All @@ -161,9 +174,11 @@ class SpadesState : public State {
const bool use_mercy_rule_;
const int mercy_threshold_;
const int win_threshold_;
const std::array<int, kNumPartnerships> current_scores_;
const int win_or_loss_bonus_;
const int num_tricks_;

std::array<int, kNumPartnerships> current_scores_ = {0, 0};
bool is_game_over_ = false;
std::array<int, kNumPlayers> num_player_tricks_ = {0, 0, 0, 0};
int num_cards_played_ = 0;
Player current_player_ = 0; // During the play phase, the hand to play.
Expand All @@ -186,12 +201,12 @@ class SpadesGame : public Game {
int MaxChanceOutcomes() const override { return kNumCards; }
std::unique_ptr<State> NewInitialState() const override {
return std::unique_ptr<State>(new SpadesState(
shared_from_this(), UseMercyRule(), MercyThreshold(), WinThreshold(),
PartnershipScore(0), PartnershipScore(1), NumTricks()));
shared_from_this(), UseMercyRule(), MercyThreshold(),
WinThreshold(), WinOrLossBonus(), NumTricks()));
}
int NumPlayers() const override { return kNumPlayers; }
double MinUtility() const override { return -kMaxScore; }
double MaxUtility() const override { return kMaxScore; }
double MinUtility() const override { return -(kMaxScore + WinOrLossBonus()); }
double MaxUtility() const override { return kMaxScore + WinOrLossBonus(); }

static int GetPlayTensorSize(int num_tricks) {
return kNumBids * kNumPlayers // What each player's contract is
Expand Down Expand Up @@ -232,10 +247,7 @@ class SpadesGame : public Game {

int WinThreshold() const { return ParameterValue<int>("win_threshold", 500); }

int PartnershipScore(int partnership) const {
return partnership ? ParameterValue<int>("score_partnership_1", 0)
: ParameterValue<int>("score_partnership_0", 0);
}
int WinOrLossBonus() const { return ParameterValue<int>("win_or_loss_bonus", 200); }

int NumTricks() const { return ParameterValue<int>("num_tricks", 2); }
};
Expand Down
2 changes: 1 addition & 1 deletion open_spiel/games/spades/spades_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ void BasicGameTests() {
testing::LoadGameTest("spades");
testing::RandomSimTest(*LoadGame("spades"), 3);
testing::RandomSimTest(
*LoadGame("spades(score_partnership_0=59,score_partnership_1=99)"), 3);
*LoadGame("spades(use_mercy_rule=false,win_threshold=250,win_or_loss_bonus=1000)"), 3);
}

} // namespace
Expand Down
Loading
Loading