Skip to content
Closed
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
9 changes: 9 additions & 0 deletions src/games/game.cc
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,15 @@ GameOutcomeRep::GameOutcomeRep(GameRep *p_game, int p_number)
// class GameStrategyRep
//========================================================================

GameAction GameStrategyRep::GetAction(const GameInfoset &p_infoset) const
{
if (p_infoset->GetPlayer() != m_player) {
throw MismatchException();
}
int action = m_behav[p_infoset->GetNumber()];
return (action) ? p_infoset->GetActions()[action] : nullptr;
}

void GameStrategyRep::DeleteStrategy()
{
if (m_player->GetGame()->IsTree()) {
Expand Down
3 changes: 3 additions & 0 deletions src/games/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,9 @@ class GameStrategyRep : public GameObject {
/// Returns the index of the strategy for its player
int GetNumber() const { return m_number; }

/// Returns the action specified by the strategy at the information set
GameAction GetAction(const GameInfoset &) const;

/// Remove this strategy from the game
void DeleteStrategy();
//@}
Expand Down
1 change: 1 addition & 0 deletions src/pygambit/gambit.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ cdef extern from "games/game.h":
c_GamePlayer GetPlayer() except +
string GetLabel() except +
void SetLabel(string) except +
c_GameAction GetAction(c_GameInfoset) except +
void DeleteStrategy() except +

cdef cppclass c_GameActionRep "GameActionRep":
Expand Down
36 changes: 36 additions & 0 deletions src/pygambit/game.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
from collections import deque
import io
import itertools
import pathlib
Expand Down Expand Up @@ -1947,3 +1948,38 @@ class Game:
if len(resolved_strategy.player.strategies) == 1:
raise UndefinedOperationError("Cannot delete the only strategy for a player")
resolved_strategy.strategy.deref().DeleteStrategy()

def compute_images(self) -> dict[Node, set[Outcome]]:
"""Recursively compute images in outcomes for each node in the game tree.

For each node in the tree, calculates the set of outcomes that can be reached from it.

Returns
-------
dict[Node, set[Outcome]]
A dictionary mapping each node to the set of outcomes that can be reached from it:
- For terminal nodes, this is either {node.outcome} or an empty set
- For non-terminal nodes, this is the union of all images in outcomes of children

Notes
-----
This traverses the game tree using depth-first search, building the sets of outcomes
from the bottom up.
"""
images_in_outcomes = {}

def dfs(node) -> set[Outcome]:
if node.is_terminal:
if node.outcome is None:
images_in_outcomes[node] = set()
else:
images_in_outcomes[node] = {node.outcome}
else:
union = set()
for child in node.children:
union |= dfs(child)
images_in_outcomes[node] = union
return images_in_outcomes[node]

dfs(self.root)
return images_in_outcomes
3 changes: 3 additions & 0 deletions src/pygambit/strategy.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -73,3 +73,6 @@ class Strategy:
def number(self) -> int:
"""The number of the strategy."""
return self.strategy.deref().GetNumber() - 1

def action(self, infoset: Infoset) -> Action:
return Action.wrap(self.strategy.deref().GetAction(infoset.infoset))
18 changes: 18 additions & 0 deletions tests/test_reduced_strategic_forms.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pygambit as gbt


def test_build_images_of_nodes_in_outcomes():
"""Generate images in outcome of the nodes of a simple centipede game with 4 terminal nodes
"""
g = gbt.read_efg("tests/test_games/e02.efg")
z1, z2, z3, z4 = g.outcomes
expected_images = {
g.root: {z1, z2, z3, z4},
g.root.children[0]: {z1},
g.root.children[1]: {z2, z3, z4},
g.root.children[1].children[0]: {z2},
g.root.children[1].children[1]: {z3, z4},
g.root.children[1].children[1].children[0]: {z3},
g.root.children[1].children[1].children[1]: {z4}
}
assert expected_images == g.compute_images()
Loading