diff --git a/ChangeLog b/ChangeLog index f49d13a9f..6ee63ffe9 100644 --- a/ChangeLog +++ b/ChangeLog @@ -10,8 +10,9 @@ been removed as planned. (#357) ### Added -- Implement `GetPlays()` (C++) and `get_plays` (Python) to compute the set of terminal nodes consistent - with a node, information set, or action (#517) +- Implement `GameRep::GetPlays` (C++) and `Node.plays`, `Infoset.plays`, and `Action.plays` (Python) + to compute the set of terminal nodes consistent with a node, information set, or action (#517) +- Implement `GameRep::GetVeto` (C++) and `Action.veto` (Python) to compute the veto of an action (#522). ## [16.3.1] - unreleased diff --git a/doc/pygambit.api.rst b/doc/pygambit.api.rst index 50052a873..cc13a89ac 100644 --- a/doc/pygambit.api.rst +++ b/doc/pygambit.api.rst @@ -169,6 +169,7 @@ Information about the game Action.precedes Action.prob Action.plays + Action.power .. autosummary:: diff --git a/src/games/game.h b/src/games/game.h index cd17b2789..a21838d4f 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -558,6 +558,9 @@ class GameRep : public BaseGameRep { /// Returns the set of terminal nodes which are descendants of members of an action virtual std::vector GetPlays(GameAction action) const { throw UndefinedException(); } + /// Returns the power of an action + virtual std::vector GetVeto(GameAction action) const { throw UndefinedException(); } + /// Returns true if the game is perfect recall. If not, /// a pair of violating information sets is returned in the parameters. virtual bool IsPerfectRecall(GameInfoset &, GameInfoset &) const = 0; diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 7bb28e3f8..411064b9b 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -1058,6 +1058,19 @@ std::vector GameTreeRep::GetPlays(GameAction action) const return plays; } +std::vector GameTreeRep::GetVeto(GameAction action) const +{ + std::vector veto; + const std::vector aplays = GetPlays(action); + + for (const auto &node : GetPlays(action->GetInfoset())) { + if (!contains(aplays, node)) { + veto.push_back(node); + } + } + return veto; +} + void GameTreeRep::DeleteOutcome(const GameOutcome &p_outcome) { IncrementVersion(); diff --git a/src/games/gametree.h b/src/games/gametree.h index f6c3f04b7..2dd2e6309 100644 --- a/src/games/gametree.h +++ b/src/games/gametree.h @@ -147,6 +147,8 @@ class GameTreeRep : public GameExplicitRep { std::vector GetPlays(GameInfoset infoset) const override; std::vector GetPlays(GameAction action) const override; + std::vector GetVeto(GameAction action) const override; + Game CopySubgame(GameNode) const override; //@} diff --git a/src/pygambit/action.pxi b/src/pygambit/action.pxi index 576834b54..6eb4e8ed7 100644 --- a/src/pygambit/action.pxi +++ b/src/pygambit/action.pxi @@ -109,9 +109,18 @@ class Action: @property def plays(self) -> typing.List[Node]: - """Returns a list of all terminal `Node` objects consistent with it. + """Returns a list of all terminal `Node` objects consistent with the action. """ return [ Node.wrap(n) for n in self.action.deref().GetInfoset().deref().GetGame().deref().GetPlays(self.action) ] + + @property + def veto(self) -> typing.List[Node]: + """Returns power of the action. + """ + return [ + Node.wrap(n) for n in + self.action.deref().GetInfoset().deref().GetGame().deref().GetVeto(self.action) + ] diff --git a/src/pygambit/gambit.pxd b/src/pygambit/gambit.pxd index 4f518f3ad..91db76a22 100644 --- a/src/pygambit/gambit.pxd +++ b/src/pygambit/gambit.pxd @@ -282,6 +282,7 @@ cdef extern from "games/game.h": stdvector[c_GameNode] GetPlays(c_GameNode) except + stdvector[c_GameNode] GetPlays(c_GameInfoset) except + stdvector[c_GameNode] GetPlays(c_GameAction) except + + stdvector[c_GameNode] GetVeto(c_GameAction) except + bool IsPerfectRecall() except + c_GameInfoset AppendMove(c_GameNode, c_GamePlayer, int) except +ValueError diff --git a/tests/test_actions.py b/tests/test_actions.py index 9b46e7d3d..a4600e1ac 100644 --- a/tests/test_actions.py +++ b/tests/test_actions.py @@ -143,3 +143,18 @@ def test_action_plays(): } # paths=[0, 1, 0], [0, 1] assert set(test_action.plays) == expected_set_of_plays + + +def test_action_veto(): + """Verify `action.veto` returns the action's veto + (terminal `Node` objects contained in the information set associated with this action + that cannot be reached from this action). + """ + game = games.read_from_file("e01.efg") + list_infosets = list(game.infosets) + + test_action = list_infosets[2].actions[0] # members' paths=[0, 1, 0], [0, 1] + + expected_set_of_plays = set(list_infosets[2].plays) - set(test_action.plays) + + assert set(test_action.veto) == expected_set_of_plays