diff --git a/src/games/game.cc b/src/games/game.cc index c25130bb1..5c0205b4b 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -66,6 +66,9 @@ GamePlayerRep::~GamePlayerRep() for (auto strategy : m_strategies) { strategy->Invalidate(); } + for (auto strategy : m_nonReducedStrategies) { + strategy->Invalidate(); + } } void GamePlayerRep::MakeStrategy() @@ -166,6 +169,42 @@ void GamePlayerRep::MakeReducedStrats(GameNodeRep *n, GameNodeRep *nn) } } +void GamePlayerRep::MakeNonReducedStratsRecursiveImpl(GameStrategyRep *p_strat, + Infosets::iterator it_infoset) +{ + if (it_infoset == GetInfosets().end()) { + auto p_strat_copy(new GameStrategyRep(*p_strat)); + p_strat_copy->m_number = m_nonReducedStrategies.size() + 1; + m_nonReducedStrategies.push_back(p_strat_copy); + return; + } + + GameInfosetRep *current_infoset = (*it_infoset); + + for (const auto &action : current_infoset->GetActions()) { + auto p_strat_copy(new GameStrategyRep(*p_strat)); + + p_strat_copy->m_behav[current_infoset->GetNumber()] = action->GetNumber(); + p_strat_copy->SetLabel(p_strat_copy->GetLabel() + action->GetLabel()); + MakeNonReducedStratsRecursiveImpl(p_strat_copy, std::next(it_infoset)); + delete p_strat_copy; + } +} + +void GamePlayerRep::MakeNonReducedStrats() +{ + for (GameStrategyRep *strat : m_nonReducedStrategies) { + delete strat; + } + m_nonReducedStrategies.clear(); + + auto starting_strategy = new GameStrategyRep(this, 0, ""); + starting_strategy->m_behav = Array(GetInfosets().size()); + + MakeNonReducedStratsRecursiveImpl(starting_strategy, GetInfosets().begin()); + delete starting_strategy; +} + size_t GamePlayerRep::NumSequences() const { if (!m_game->IsTree()) { diff --git a/src/games/game.h b/src/games/game.h index cd17b2789..3e36caaeb 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -375,10 +375,17 @@ class GamePlayerRep : public GameObject { template friend class MixedBehaviorProfile; template friend class MixedStrategyProfile; +public: + using Infosets = ElementCollection; + using Strategies = ElementCollection; + +private: /// @name Building reduced form strategies //@{ void MakeStrategy(); void MakeReducedStrats(class GameNodeRep *, class GameNodeRep *); + void MakeNonReducedStrats(); + void MakeNonReducedStratsRecursiveImpl(GameStrategyRep *, Infosets::iterator); //@} GameRep *m_game; @@ -386,15 +393,13 @@ class GamePlayerRep : public GameObject { std::string m_label; std::vector m_infosets; std::vector m_strategies; + std::vector m_nonReducedStrategies; GamePlayerRep(GameRep *p_game, int p_id) : m_game(p_game), m_number(p_id) {} GamePlayerRep(GameRep *p_game, int p_id, int m_strats); ~GamePlayerRep() override; public: - using Infosets = ElementCollection; - using Strategies = ElementCollection; - int GetNumber() const { return m_number; } Game GetGame() const; @@ -416,6 +421,10 @@ class GamePlayerRep : public GameObject { GameStrategy GetStrategy(int st) const; /// Returns the array of strategies available to the player Strategies GetStrategies() const; + /// Returns the st'th non-reduced strategy for the player + GameStrategy GetNonReducedStrategy(int st) const; + /// Returns the array of non-reduced strategies available to the player + Strategies GetNonReducedStrategies() const; //@} /// @name Sequences @@ -792,6 +801,16 @@ inline GamePlayerRep::Strategies GamePlayerRep::GetStrategies() const m_game->BuildComputedValues(); return Strategies(this, &m_strategies); } +inline GameStrategy GamePlayerRep::GetNonReducedStrategy(int st) const +{ + m_game->BuildComputedValues(); + return m_nonReducedStrategies.at(st - 1); +} +inline GamePlayerRep::Strategies GamePlayerRep::GetNonReducedStrategies() const +{ + m_game->BuildComputedValues(); + return Strategies(this, &m_nonReducedStrategies); +} inline Game GameNodeRep::GetGame() const { return m_game; } diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 7bb28e3f8..00d496fda 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -853,6 +853,7 @@ void GameTreeRep::ClearComputedValues() const strategy->Invalidate(); } player->m_strategies.clear(); + player->m_nonReducedStrategies.clear(); } const_cast(this)->m_nodePlays.clear(); m_computedValues = false; @@ -866,6 +867,7 @@ void GameTreeRep::BuildComputedValues() const const_cast(this)->Canonicalize(); for (const auto &player : m_players) { player->MakeReducedStrats(m_root, nullptr); + player->MakeNonReducedStrats(); } m_computedValues = true; } diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 4f518f3ad..92712d6ac 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -174,6 +174,8 @@ cdef extern from "games/game.h": c_GameStrategy GetStrategy(int) except +IndexError Strategies GetStrategies() except + + c_GameStrategy GetNonReducedStrategy(int) except +IndexError + Strategies GetNonReducedStrategies() except + c_GameInfoset GetInfoset(int) except +IndexError Infosets GetInfosets() except + diff --git a/src/pygambit/player.pxi b/src/pygambit/player.pxi index 083578471..ca4344b4c 100644 --- a/src/pygambit/player.pxi +++ b/src/pygambit/player.pxi @@ -148,6 +148,49 @@ class PlayerStrategies: raise TypeError(f"Strategy index must be int or str, not {index.__class__.__name__}") +@cython.cclass +class PlayerNonReducedStrategies: + """The set of non-reduced strategies available to a player.""" + player = cython.declare(c_GamePlayer) + + def __init__(self, *args, **kwargs) -> None: + raise ValueError("Cannot create PlayerNonReducedStrategies outside a Game.") + + @staticmethod + @cython.cfunc + def wrap(player: c_GamePlayer) -> PlayerNonReducedStrategies: + obj: PlayerNonReducedStrategies = PlayerNonReducedStrategies.__new__( + PlayerNonReducedStrategies + ) + obj.player = player + return obj + + def __repr__(self) -> str: + return f"PlayerNonReducedStrategies(player={Player.wrap(self.player)})" + + def __len__(self): + """The number of non-reduced strategies for the player in the game.""" + return self.player.deref().GetNonReducedStrategies().size() + + def __iter__(self) -> typing.Iterator[Strategy]: + for strategy in self.player.deref().GetNonReducedStrategies(): + yield Strategy.wrap(strategy) + + def __getitem__(self, index: typing.Union[int, str]) -> Strategy: + if isinstance(index, str): + if not index.strip(): + raise ValueError("Strategy label cannot be empty or all whitespace") + matches = [x for x in self if x.label == index.strip()] + if not matches: + raise KeyError(f"Player has no strategy with label '{index}'") + if len(matches) > 1: + raise ValueError(f"Player has multiple strategies with label '{index}'") + return matches[0] + if isinstance(index, int): + return Strategy.wrap(self.player.deref().GetNonReducedStrategy(index + 1)) + raise TypeError(f"Strategy index must be int or str, not {index.__class__.__name__}") + + @cython.cclass class Player: """A player in a ``Game``.""" @@ -214,6 +257,11 @@ class Player: """Returns the set of strategies belonging to the player.""" return PlayerStrategies.wrap(self.player) + @property + def non_reduced_strategies(self) -> PlayerNonReducedStrategies: + """Returns the set of nonreduced strategies belonging to the player.""" + return PlayerNonReducedStrategies.wrap(self.player) + @property def infosets(self) -> PlayerInfosets: """Returns the set of information sets at which the player has the decision."""