|
23 | 23 | #include <iostream> |
24 | 24 | #include <algorithm> |
25 | 25 | #include <numeric> |
| 26 | +#include <stack> |
| 27 | +#include <set> |
| 28 | +#include <variant> |
26 | 29 |
|
27 | 30 | #include "gambit.h" |
28 | 31 | #include "gametree.h" |
@@ -748,54 +751,18 @@ bool GameTreeRep::IsConstSum() const |
748 | 751 | } |
749 | 752 | } |
750 | 753 |
|
751 | | -bool GameTreeRep::IsPerfectRecall(GameInfoset &s1, GameInfoset &s2) const |
| 754 | +bool GameTreeRep::IsPerfectRecall() const |
752 | 755 | { |
753 | | - for (auto player : m_players) { |
754 | | - for (size_t i = 1; i <= player->m_infosets.size(); i++) { |
755 | | - auto iset1 = player->m_infosets[i - 1]; |
756 | | - for (size_t j = 1; j <= player->m_infosets.size(); j++) { |
757 | | - auto iset2 = player->m_infosets[j - 1]; |
758 | | - |
759 | | - bool precedes = false; |
760 | | - GameAction action = nullptr; |
761 | | - |
762 | | - for (size_t m = 1; m <= iset2->m_members.size(); m++) { |
763 | | - size_t n; |
764 | | - for (n = 1; n <= iset1->m_members.size(); n++) { |
765 | | - if (iset2->GetMember(m)->IsSuccessorOf(iset1->GetMember(n)) && |
766 | | - iset1->GetMember(n) != iset2->GetMember(m)) { |
767 | | - precedes = true; |
768 | | - for (const auto &act : iset1->GetActions()) { |
769 | | - if (iset2->GetMember(m)->IsSuccessorOf(iset1->GetMember(n)->GetChild(act))) { |
770 | | - if (action != nullptr && action != act) { |
771 | | - s1 = iset1; |
772 | | - s2 = iset2; |
773 | | - return false; |
774 | | - } |
775 | | - action = act; |
776 | | - } |
777 | | - } |
778 | | - break; |
779 | | - } |
780 | | - } |
781 | | - |
782 | | - if (i == j && precedes) { |
783 | | - s1 = iset1; |
784 | | - s2 = iset2; |
785 | | - return false; |
786 | | - } |
| 756 | + if (m_infosetParents.empty() && !m_root->IsTerminal()) { |
| 757 | + const_cast<GameTreeRep *>(this)->BuildInfosetParents(); |
| 758 | + } |
787 | 759 |
|
788 | | - if (n > iset1->m_members.size() && precedes) { |
789 | | - s1 = iset1; |
790 | | - s2 = iset2; |
791 | | - return false; |
792 | | - } |
793 | | - } |
794 | | - } |
795 | | - } |
| 760 | + if (GetRoot()->IsTerminal()) { |
| 761 | + return true; |
796 | 762 | } |
797 | 763 |
|
798 | | - return true; |
| 764 | + return std::all_of(m_infosetParents.cbegin(), m_infosetParents.cend(), |
| 765 | + [](const auto &pair) { return pair.second.size() <= 1; }); |
799 | 766 | } |
800 | 767 |
|
801 | 768 | //------------------------------------------------------------------------ |
@@ -872,6 +839,7 @@ void GameTreeRep::ClearComputedValues() const |
872 | 839 | player->m_strategies.clear(); |
873 | 840 | } |
874 | 841 | const_cast<GameTreeRep *>(this)->m_nodePlays.clear(); |
| 842 | + const_cast<GameTreeRep *>(this)->m_infosetParents.clear(); |
875 | 843 | m_computedValues = false; |
876 | 844 | } |
877 | 845 |
|
@@ -912,6 +880,77 @@ std::vector<GameNodeRep *> GameTreeRep::BuildConsistentPlaysRecursiveImpl(GameNo |
912 | 880 | return consistent_plays; |
913 | 881 | } |
914 | 882 |
|
| 883 | +void GameTreeRep::BuildInfosetParents() |
| 884 | +{ |
| 885 | + if (m_root->IsTerminal()) { |
| 886 | + m_infosetParents[m_root->m_infoset].insert(nullptr); |
| 887 | + return; |
| 888 | + } |
| 889 | + |
| 890 | + using AbsentMindedEdge = std::pair<GameAction, GameNode>; |
| 891 | + using ActiveEdge = std::variant<GameNodeRep::Actions::iterator, AbsentMindedEdge>; |
| 892 | + std::stack<ActiveEdge> position; |
| 893 | + |
| 894 | + std::map<GamePlayer, std::stack<GameAction>> prior_actions; |
| 895 | + std::map<GameInfoset, GameAction> path_choices; |
| 896 | + |
| 897 | + for (auto player_rep : m_players) { |
| 898 | + prior_actions[GamePlayer(player_rep)].emplace(nullptr); |
| 899 | + } |
| 900 | + prior_actions[GamePlayer(m_chance)].emplace(nullptr); |
| 901 | + |
| 902 | + position.emplace(m_root->GetActions().begin()); |
| 903 | + prior_actions[m_root->m_infoset->m_player->shared_from_this()].emplace(nullptr); |
| 904 | + if (m_root->m_infoset) { |
| 905 | + m_infosetParents[m_root->m_infoset].insert(nullptr); |
| 906 | + } |
| 907 | + |
| 908 | + while (!position.empty()) { |
| 909 | + ActiveEdge ¤t_edge = position.top(); |
| 910 | + GameNode child, node; |
| 911 | + GameAction action; |
| 912 | + |
| 913 | + if (std::holds_alternative<GameNodeRep::Actions::iterator>(current_edge)) { |
| 914 | + auto ¤t_it = std::get<GameNodeRep::Actions::iterator>(current_edge); |
| 915 | + node = current_it.GetOwner(); |
| 916 | + |
| 917 | + if (current_it == node->GetActions().end()) { |
| 918 | + prior_actions.at(node->m_infoset->m_player->shared_from_this()).pop(); |
| 919 | + position.pop(); |
| 920 | + path_choices.erase(node->m_infoset->shared_from_this()); |
| 921 | + continue; |
| 922 | + } |
| 923 | + else { |
| 924 | + std::tie(action, child) = *current_it; |
| 925 | + ++current_it; |
| 926 | + path_choices[node->m_infoset->shared_from_this()] = action; |
| 927 | + } |
| 928 | + } |
| 929 | + else { |
| 930 | + std::tie(action, node) = std::get<AbsentMindedEdge>(current_edge); |
| 931 | + position.pop(); |
| 932 | + child = node->GetChild(action); |
| 933 | + } |
| 934 | + |
| 935 | + prior_actions.at(node->m_infoset->m_player->shared_from_this()).top() = action; |
| 936 | + |
| 937 | + if (!child->IsTerminal()) { |
| 938 | + auto child_player = child->m_infoset->m_player->shared_from_this(); |
| 939 | + auto prior_action = prior_actions.at(child_player).top(); |
| 940 | + m_infosetParents[child->m_infoset].insert(prior_action ? prior_action.get() : nullptr); |
| 941 | + |
| 942 | + if (path_choices.find(child->m_infoset->shared_from_this()) != path_choices.end()) { |
| 943 | + const GameAction replay_action = path_choices.at(child->m_infoset->shared_from_this()); |
| 944 | + position.emplace(AbsentMindedEdge{replay_action, child}); |
| 945 | + } |
| 946 | + else { |
| 947 | + position.emplace(child->GetActions().begin()); |
| 948 | + } |
| 949 | + prior_actions.at(child_player).emplace(nullptr); |
| 950 | + } |
| 951 | + } |
| 952 | +} |
| 953 | + |
915 | 954 | //------------------------------------------------------------------------ |
916 | 955 | // GameTreeRep: Writing data files |
917 | 956 | //------------------------------------------------------------------------ |
|
0 commit comments