Skip to content

Commit 015bce7

Browse files
committed
Add ActionsIterator class to zip actions and children; refactor PR-checker using a separate BuildInfosetParents method
1 parent 33f3b03 commit 015bce7

4 files changed

Lines changed: 147 additions & 43 deletions

File tree

src/games/game.cc

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,20 @@ size_t GamePlayerRep::NumSequences() const
188188
[](const GameInfosetRep *s) { return s->m_actions.size(); });
189189
}
190190

191+
//========================================================================
192+
// class GameNodeRep
193+
//========================================================================
194+
195+
GameNodeRep::Actions::iterator GameNodeRep::Actions::begin() const
196+
{
197+
return {m_owner->GetInfoset()->GetActions().begin(), m_owner->GetChildren().begin()};
198+
}
199+
200+
GameNodeRep::Actions::iterator GameNodeRep::Actions::end() const
201+
{
202+
return {m_owner->GetInfoset()->GetActions().end(), m_owner->GetChildren().end()};
203+
}
204+
191205
//========================================================================
192206
// class GameRep
193207
//========================================================================

src/games/game.h

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ using GameNode = GameObjectPtr<GameNodeRep>;
5555
class GameRep;
5656
using Game = GameObjectPtr<GameRep>;
5757

58+
class ActionsIterator;
59+
5860
template <class P, class T> class ElementCollection {
5961
P m_owner{nullptr};
6062
const std::vector<T *> *m_container{nullptr};
@@ -103,6 +105,7 @@ template <class P, class T> class ElementCollection {
103105
return *this;
104106
}
105107
value_type operator*() const { return m_container->at(m_index); }
108+
P GetOwner() const { return m_owner; }
106109
};
107110

108111
ElementCollection() = default;
@@ -454,6 +457,23 @@ class GameNodeRep : public GameObject {
454457
public:
455458
using Children = ElementCollection<GameNode, GameNodeRep>;
456459

460+
/// @brief A range class for iterating over a node's (action, child) pairs.
461+
class Actions {
462+
private:
463+
GameNode m_owner{nullptr};
464+
465+
public:
466+
using iterator = ActionsIterator;
467+
468+
explicit Actions(GameNode p_owner) : m_owner(p_owner) {}
469+
470+
iterator begin() const;
471+
iterator end() const;
472+
};
473+
474+
/// @brief Returns a range for iterating over this node's (action, child) pairs.
475+
Actions GetActions() const { return Actions(this); }
476+
457477
Game GetGame() const;
458478

459479
const std::string &GetLabel() const { return m_label; }
@@ -484,6 +504,76 @@ class GameNodeRep : public GameObject {
484504
bool IsSubgameRoot() const;
485505
};
486506

507+
class ActionsIterator {
508+
public:
509+
/// @name Iterator
510+
//@{
511+
using iterator_category = std::forward_iterator_tag;
512+
using value_type = std::pair<GameAction, GameNode>;
513+
using difference_type = std::ptrdiff_t;
514+
using pointer = value_type *;
515+
using reference = value_type;
516+
//@}
517+
518+
private:
519+
/// @brief An iterator to the action at the parent's information set.
520+
GameInfosetRep::Actions::iterator m_action_it;
521+
/// @brief An iterator to the child node.
522+
GameNodeRep::Children::iterator m_child_it;
523+
524+
public:
525+
/// @name Lifecycle
526+
//@{
527+
/// Default constructor. Creates an iterator in a past-the-end state.
528+
ActionsIterator() = default;
529+
530+
/// Creates a new iterator that zips an action iterator and a child iterator.
531+
ActionsIterator(GameInfosetRep::Actions::iterator p_action_it,
532+
GameNodeRep::Children::iterator p_child_it)
533+
: m_action_it(p_action_it), m_child_it(p_child_it)
534+
{
535+
}
536+
//@}
537+
538+
/// @name Iterator Operations
539+
//@{
540+
/// Returns the current action-child pair.
541+
reference operator*() const { return {*m_action_it, *m_child_it}; }
542+
543+
/// Advances the iterator to the next pair (pre-increment).
544+
ActionsIterator &operator++()
545+
{
546+
++m_action_it;
547+
++m_child_it;
548+
return *this;
549+
}
550+
551+
/// Advances the iterator to the next pair (post-increment).
552+
ActionsIterator operator++(int)
553+
{
554+
ActionsIterator tmp = *this;
555+
++(*this);
556+
return tmp;
557+
}
558+
559+
/// Compares two iterators for equality.
560+
bool operator==(const ActionsIterator &p_other) const
561+
{
562+
// Comparing one of the wrapped iterators is sufficient as they move in lockstep.
563+
return m_child_it == p_other.m_child_it;
564+
}
565+
566+
/// Compares two iterators for inequality.
567+
bool operator!=(const ActionsIterator &p_other) const { return !(*this == p_other); }
568+
//@}
569+
570+
GameNode GetOwner() const
571+
{
572+
// Delegate the request to the internal child iterator.
573+
return m_child_it.GetOwner();
574+
}
575+
};
576+
487577
/// This is the class for representing an arbitrary finite game.
488578
class GameRep : public BaseGameRep {
489579
friend class GameOutcomeRep;

src/games/gametree.cc

Lines changed: 41 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -736,61 +736,21 @@ bool GameTreeRep::IsConstSum() const
736736

737737
bool GameTreeRep::IsPerfectRecall() const
738738
{
739-
using ChildIterator = GameNodeRep::Children::iterator;
740-
using ActionIterator = GameInfosetRep::Actions::iterator;
741-
742-
std::map<GamePlayer, std::stack<GameAction>> prior_actions;
743-
std::map<GameInfoset, std::vector<GameAction>> infoset_parents;
744-
std::stack<std::tuple<GameNode, ChildIterator, ActionIterator>> position;
745-
746-
for (auto player : GetPlayers()) {
747-
prior_actions[player].emplace(nullptr);
739+
if (m_infosetParents.empty() && !GetRoot()->IsTerminal()) {
740+
const_cast<GameTreeRep *>(this)->BuildInfosetParents();
748741
}
749-
prior_actions[GetChance()].emplace(nullptr);
750-
prior_actions[GetRoot()->GetPlayer()].emplace(nullptr);
751742

752743
if (GetRoot()->IsTerminal()) {
753744
return true;
754745
}
755746

756-
position.emplace(GetRoot(), GetRoot()->GetChildren().begin(),
757-
GetRoot()->GetInfoset()->GetActions().begin());
758-
759-
infoset_parents[GetRoot()->GetInfoset()].emplace_back(nullptr);
760-
761-
while (!position.empty()) {
762-
auto &[parent, child_it, action_it] = position.top();
763-
764-
if (child_it != parent->GetChildren().end()) {
765-
const GameNode child = *child_it;
766-
const GameAction action = *action_it;
767-
768-
prior_actions[parent->GetPlayer()].top() = action;
769-
770-
if (!child->IsTerminal()) {
771-
infoset_parents[child->GetInfoset()].push_back(prior_actions[child->GetPlayer()].top());
772-
position.emplace(child, child->GetChildren().begin(),
773-
child->GetInfoset()->GetActions().begin());
774-
prior_actions[child->GetPlayer()].emplace(nullptr);
775-
}
776-
++child_it;
777-
++action_it;
778-
}
779-
780-
else {
781-
prior_actions[parent->GetPlayer()].pop();
782-
position.pop();
783-
}
784-
}
785-
786-
for (const auto &[infoset, parent_action_options] : infoset_parents) {
747+
for (const auto &[infoset, parent_action_options] : m_infosetParents) {
787748
const std::set<GameAction> unique_parents(parent_action_options.begin(),
788749
parent_action_options.end());
789750
if (unique_parents.size() > 1) {
790751
return false;
791752
}
792753
}
793-
794754
return true;
795755
}
796756

@@ -867,6 +827,7 @@ void GameTreeRep::ClearComputedValues() const
867827
player->m_strategies.clear();
868828
}
869829
const_cast<GameTreeRep *>(this)->m_nodePlays.clear();
830+
const_cast<GameTreeRep *>(this)->m_infosetParents.clear();
870831
m_computedValues = false;
871832
}
872833

@@ -905,6 +866,43 @@ std::vector<GameNodeRep *> GameTreeRep::BuildConsistentPlaysRecursiveImpl(GameNo
905866
return consistent_plays;
906867
}
907868

869+
void GameTreeRep::BuildInfosetParents()
870+
{
871+
std::map<GamePlayer, std::stack<GameAction>> prior_actions;
872+
std::stack<ActionsIterator> position;
873+
874+
for (auto player : GetPlayers()) {
875+
prior_actions[player].emplace(nullptr);
876+
}
877+
prior_actions[GetChance()].emplace(nullptr);
878+
prior_actions[GetRoot()->GetPlayer()].emplace(nullptr);
879+
880+
position.emplace(GetRoot()->GetActions().begin());
881+
m_infosetParents[GetRoot()->GetInfoset()].emplace_back(nullptr);
882+
883+
while (!position.empty()) {
884+
ActionsIterator &current_it = position.top();
885+
const GameNode parent = current_it.GetOwner();
886+
887+
if (current_it != parent->GetActions().end()) {
888+
auto [action, child] = *current_it;
889+
890+
prior_actions[parent->GetPlayer()].top() = action;
891+
892+
if (!child->IsTerminal()) {
893+
m_infosetParents[child->GetInfoset()].push_back(prior_actions[child->GetPlayer()].top());
894+
position.emplace(child->GetActions().begin());
895+
prior_actions[child->GetPlayer()].emplace(nullptr);
896+
}
897+
++current_it;
898+
}
899+
else {
900+
prior_actions[parent->GetPlayer()].pop();
901+
position.pop();
902+
}
903+
}
904+
}
905+
908906
//------------------------------------------------------------------------
909907
// GameTreeRep: Writing data files
910908
//------------------------------------------------------------------------

src/games/gametree.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ class GameTreeRep : public GameExplicitRep {
3939
std::size_t m_numNodes = 1;
4040
std::size_t m_numNonterminalNodes = 0;
4141
std::map<GameNodeRep *, std::vector<GameNodeRep *>> m_nodePlays;
42+
std::map<GameInfoset, std::vector<GameAction>> m_infosetParents;
4243

4344
/// @name Private auxiliary functions
4445
//@{
@@ -159,6 +160,7 @@ class GameTreeRep : public GameExplicitRep {
159160

160161
private:
161162
std::vector<GameNodeRep *> BuildConsistentPlaysRecursiveImpl(GameNodeRep *node);
163+
void BuildInfosetParents();
162164
};
163165

164166
template <class T> class TreeMixedStrategyProfileRep : public MixedStrategyProfileRep<T> {

0 commit comments

Comments
 (0)