Skip to content
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
2 changes: 2 additions & 0 deletions src/games/game.h
Original file line number Diff line number Diff line change
Expand Up @@ -634,6 +634,8 @@ class GameRep : public BaseGameRep {
virtual GameNode GetRoot() const = 0;
/// Returns the number of nodes in the game
virtual size_t NumNodes() const = 0;
/// Returns the number of non-terminal nodes in the game
virtual size_t NumNonterminalNodes() const = 0;
//@}

/// @name Modification
Expand Down
2 changes: 2 additions & 0 deletions src/games/gameagg.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,8 @@ class GameAGGRep : public GameRep {
GameNode GetRoot() const override { throw UndefinedException(); }
/// Returns the number of nodes in the game
size_t NumNodes() const override { throw UndefinedException(); }
/// Returns the number of non-terminal nodes in the game
size_t NumNonterminalNodes() const override { throw UndefinedException(); }
//@}

/// @name General data access
Expand Down
2 changes: 2 additions & 0 deletions src/games/gamebagg.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ class GameBAGGRep : public GameRep {
GameNode GetRoot() const override { throw UndefinedException(); }
/// Returns the number of nodes in the game
size_t NumNodes() const override { throw UndefinedException(); }
/// Returns the number of non-terminal nodes in the game
size_t NumNonterminalNodes() const override { throw UndefinedException(); }
//@}

/// @name General data access
Expand Down
2 changes: 2 additions & 0 deletions src/games/gametable.h
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ class GameTableRep : public GameExplicitRep {
GameNode GetRoot() const override { throw UndefinedException(); }
/// Returns the number of nodes in the game
size_t NumNodes() const override { throw UndefinedException(); }
/// Returns the number of non-terminal nodes in the game
size_t NumNonterminalNodes() const override { throw UndefinedException(); }
//@}

/// @name Outcomes
Expand Down
10 changes: 9 additions & 1 deletion src/games/gametree.cc
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ void GameTreeRep::DeleteAction(GameAction p_action)

for (auto member : infoset->m_members) {
DeleteTree(member->m_children[where]);
m_numNodes--;
member->m_children[where]->Invalidate();
erase_atindex(member->m_children, where);
}
Expand Down Expand Up @@ -241,6 +242,7 @@ GameAction GameTreeRep::InsertAction(GameInfoset p_infoset, GameAction p_action
}

m_numNodes += p_infoset->m_members.size();
// m_numNonterminalNodes stays unchanged when an action is appended to an information set
ClearComputedValues();
Canonicalize();
return action;
Expand Down Expand Up @@ -434,6 +436,7 @@ void GameTreeRep::DeleteParent(GameNode p_node)
std::find(oldParent->m_children.begin(), oldParent->m_children.end(), node));
DeleteTree(oldParent);
node->m_parent = oldParent->m_parent;
m_numNodes--;
if (node->m_parent) {
std::replace(node->m_parent->m_children.begin(), node->m_parent->m_children.end(), oldParent,
node);
Expand All @@ -453,17 +456,20 @@ void GameTreeRep::DeleteTree(GameNode p_node)
throw MismatchException();
}
GameNodeRep *node = p_node;
if (!node->IsTerminal()) {
m_numNonterminalNodes--;
}
IncrementVersion();
while (!node->m_children.empty()) {
DeleteTree(node->m_children.front());
m_numNodes--;
node->m_children.front()->Invalidate();
erase_atindex(node->m_children, 1);
}
if (node->m_infoset) {
RemoveMember(node->m_infoset, node);
node->m_infoset = nullptr;
}
m_numNodes--;
node->m_outcome = nullptr;
node->m_label = "";

Expand Down Expand Up @@ -631,6 +637,7 @@ GameInfoset GameTreeRep::AppendMove(GameNode p_node, GameInfoset p_infoset)
node->m_children.push_back(new GameNodeRep(this, node));
m_numNodes++;
});
m_numNonterminalNodes++;
ClearComputedValues();
Canonicalize();
return node->m_infoset;
Expand Down Expand Up @@ -679,6 +686,7 @@ GameInfoset GameTreeRep::InsertMove(GameNode p_node, GameInfoset p_infoset)

// Total nodes added = 1 (newNode) + (NumActions - 1) (new children of newNode) = NumActions
m_numNodes += newNode->m_infoset->m_actions.size();
m_numNonterminalNodes++;
ClearComputedValues();
Canonicalize();
return p_infoset;
Expand Down
3 changes: 3 additions & 0 deletions src/games/gametree.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ class GameTreeRep : public GameExplicitRep {
GameNodeRep *m_root;
GamePlayerRep *m_chance;
std::size_t m_numNodes = 1;
std::size_t m_numNonterminalNodes = 0;

/// @name Private auxiliary functions
//@{
Expand Down Expand Up @@ -95,6 +96,8 @@ class GameTreeRep : public GameExplicitRep {
GameNode GetRoot() const override { return m_root; }
/// Returns the number of nodes in the game
size_t NumNodes() const override { return m_numNodes; }
/// Returns the number of non-terminal nodes in the game
size_t NumNonterminalNodes() const override { return m_numNonterminalNodes; }
//@}

void DeleteOutcome(const GameOutcome &) override;
Expand Down
1 change: 1 addition & 0 deletions src/pygambit/gambit.pxd
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,7 @@ cdef extern from "games/game.h":
void DeleteOutcome(c_GameOutcome) except +

int NumNodes() except +
int NumNonterminalNodes() except +
c_GameNode GetRoot() except +

c_GameStrategy GetStrategy(int) except +IndexError
Expand Down
44 changes: 44 additions & 0 deletions src/pygambit/game.pxi
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,41 @@ class GameNodes:
yield from dfs(Node.wrap(self.game.deref().GetRoot()))


@cython.cclass
class GameNonterminalNodes:
"""Represents the set of nodes in a game."""
game = cython.declare(c_Game)

def __init__(self, *args, **kwargs) -> None:
raise ValueError("Cannot create GameNonterminalNodes outside a Game.")

@staticmethod
@cython.cfunc
def wrap(game: c_Game) -> GameNonterminalNodes:
obj: GameNonterminalNodes = GameNonterminalNodes.__new__(GameNonterminalNodes)
obj.game = game
return obj

def __repr__(self) -> str:
return f"GameNonterminalNodes(game={Game.wrap(self.game)})"

def __len__(self) -> int:
"""The number of non-terminal nodes in the game."""
if not self.game.deref().IsTree():
return 0
return self.game.deref().NumNonterminalNodes()

def __iter__(self) -> typing.Iterator[Node]:
def dfs(node):
if not node.is_terminal:
yield node
for child in node.children:
yield from dfs(child)
if not self.game.deref().IsTree():
return
yield from dfs(Node.wrap(self.game.deref().GetRoot()))


@cython.cclass
class GameOutcomes:
"""Represents the set of outcomes in a game."""
Expand Down Expand Up @@ -713,6 +748,15 @@ class Game:
"""
return GameNodes.wrap(self.game)

@property
def _nonterminal_nodes(self) -> GameNonterminalNodes:
"""The set of non-terminal nodes in the game.

Iteration over this property yields the non-terminal nodes in the order of depth-first
search.
"""
return GameNonterminalNodes.wrap(self.game)

@property
def contingencies(self) -> pygambit.gameiter.Contingencies:
"""An iterator over the contingencies in the game."""
Expand Down
Loading