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
5 changes: 5 additions & 0 deletions src/pygambit/action.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,8 @@ class Action:
return decimal.Decimal(py_string.decode("ascii"))
else:
return Rational(py_string.decode("ascii"))

@property
def members(self) -> set[Node]:
"""Get the set of nodes resulting from taking this action."""
return {node.children[self.number] for node in self.infoset.members}
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
1 change: 1 addition & 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
54 changes: 54 additions & 0 deletions src/pygambit/image.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import typing

from pygambit import Action, Game, Infoset, Node, Outcome

ImageDict = dict[Node, set[Outcome]]


def build_node_images(game: Game) -> ImageDict:
"""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.
"""
node_images = {}

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

dfs(game.root)
return node_images


def build_set_image(
infoset_or_action: typing.Union[Infoset, Action],
node_images: ImageDict) -> set[Outcome]:
# Get the set of nodes constituting a given infoset or action
image_in_nodes = infoset_or_action.members

# Calculate the union of images of the member nodes
union = set()
for node in image_in_nodes:
union |= node_images[node]
return union
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))
54 changes: 54 additions & 0 deletions tests/test_images.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import pygambit as gbt
from pygambit.image import build_node_images, build_set_image


def test_build_images_of_nodes_in_outcomes():
"""Generate images in outcomes 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 == build_node_images(g)


def test_build_images_of_infosets_in_outcomes():
"""Generate images in outcomes of the infosets of a simple centipede game with 4 terminal nodes
"""
g = gbt.read_efg("tests/test_games/e02.efg")
node_images = build_node_images(g)
z1, z2, z3, z4 = g.outcomes
infosets = g.infosets
expected_images = {
infosets[0]: {z1, z2, z3, z4},
infosets[1]: {z3, z4},
infosets[2]: {z2, z3, z4}
}
built_images = {infoset: build_set_image(infoset, node_images) for infoset in infosets}
assert expected_images == built_images


def test_build_images_of_actions_in_outcomes():
"""Generate images in outcomes of the actions of a simple centipede game with 4 terminal nodes
"""
g = gbt.read_efg("tests/test_games/e02.efg")
node_images = build_node_images(g)
z1, z2, z3, z4 = g.outcomes
actions = g.actions
expected_images = {
actions[0]: {z1},
actions[1]: {z2, z3, z4},
actions[2]: {z3},
actions[3]: {z4},
actions[4]: {z2},
actions[5]: {z3, z4}
}
built_images = {action: build_set_image(action, node_images) for action in g.actions}
assert expected_images == built_images