Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
7b3442d
Wrote simple layout abstraction that at least compiles...
tturocy Oct 27, 2025
cd56719
Layout of levels using new abstraction seems to work.
tturocy Oct 27, 2025
2b9e872
Remove infoset depths from GUI class.
tturocy Oct 27, 2025
7e3d2e5
Remove max level from GUI class.
tturocy Oct 27, 2025
7233788
Compute next member on demand for now
tturocy Oct 27, 2025
99faaa2
Remove unused maxlevel from new layout
tturocy Oct 27, 2025
3f400f7
Offsets now being computed from the generic layout.
tturocy Oct 27, 2025
d53c89a
Removing unused (and non-existent) includes
tturocy Oct 27, 2025
79df020
Merge branch 'master' into dev_layout
tturocy Nov 10, 2025
d448616
Remove dominance toolbar from extensive games.
tturocy Nov 17, 2025
4c00bb7
Remove dominance from BSP.
tturocy Nov 17, 2025
c69b56d
Remove unused functionality from BehaviorContingencies
tturocy Nov 17, 2025
36ec9df
Merge branch 'master' into dev_layout
tturocy Nov 19, 2025
3185b11
Move UI-independent tree layout to game
tturocy Nov 19, 2025
2cc3997
Rudimentary exposure of tree layout in Python
tturocy Nov 19, 2025
442188b
Rudimentary exposure of tree layout in Python.
tturocy Nov 19, 2025
5ea1ea7
Merge branch 'master' into bug_actiondominance
tturocy Nov 19, 2025
0d5226a
Merge branch 'dev_layout' into bug_actiondominance
tturocy Nov 19, 2025
c5b2e8c
Remove layout dependence on behavior support
tturocy Nov 19, 2025
fc3a1aa
Merge branch 'master' into bug_actiondominance
tturocy Nov 20, 2025
d6fd8ef
Merge branch 'master' into bug_actiondominance
tturocy Dec 3, 2025
4d9268b
Ad-hoc forward patch on setting state of View->Strategic
tturocy Dec 4, 2025
db95580
Disable Tools->Dominance when viewing game in extensive form.
tturocy Dec 4, 2025
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
33 changes: 8 additions & 25 deletions src/games/behavpure.cc
Original file line number Diff line number Diff line change
Expand Up @@ -98,30 +98,16 @@ MixedBehaviorProfile<Rational> PureBehaviorProfile::ToMixedBehaviorProfile() con
// class BehaviorContingencies
//========================================================================

BehaviorContingencies::BehaviorContingencies(const BehaviorSupportProfile &p_support,
const std::set<GameInfoset> &p_reachable,
const std::vector<GameAction> &p_frozen)
: m_support(p_support), m_frozen(p_frozen)
BehaviorContingencies::BehaviorContingencies(const BehaviorSupportProfile &p_support)
: m_support(p_support)
{
if (!p_reachable.empty()) {
for (const auto &infoset : p_reachable) {
m_activeInfosets.push_back(infoset);
}
}
else {
for (const auto &player : m_support.GetGame()->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
if (p_support.IsReachable(infoset)) {
m_activeInfosets.push_back(infoset);
}
for (const auto &player : m_support.GetGame()->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
if (p_support.IsReachable(infoset)) {
m_reachableInfosets.push_back(infoset);
}
}
}
for (const auto &action : m_frozen) {
m_activeInfosets.erase(std::find_if(
m_activeInfosets.begin(), m_activeInfosets.end(),
[action](const GameInfoset &infoset) { return infoset == action->GetInfoset(); }));
}
}

BehaviorContingencies::iterator::iterator(BehaviorContingencies *p_cont, bool p_end)
Expand All @@ -136,15 +122,12 @@ BehaviorContingencies::iterator::iterator(BehaviorContingencies *p_cont, bool p_
m_profile.SetAction(*m_currentBehav[infoset]);
}
}
for (const auto &action : m_cont->m_frozen) {
m_profile.SetAction(action);
}
}

BehaviorContingencies::iterator &BehaviorContingencies::iterator::operator++()
{
for (auto infoset = m_cont->m_activeInfosets.crbegin();
infoset != m_cont->m_activeInfosets.crend(); ++infoset) {
for (auto infoset = m_cont->m_reachableInfosets.crbegin();
infoset != m_cont->m_reachableInfosets.crend(); ++infoset) {
++m_currentBehav[*infoset];
if (m_currentBehav.at(*infoset) != m_cont->m_support.GetActions(*infoset).end()) {
m_profile.SetAction(*m_currentBehav[*infoset]);
Expand Down
12 changes: 3 additions & 9 deletions src/games/behavpure.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ namespace Gambit {
/// It specifies exactly one strategy for each information set in the
/// game.
class PureBehaviorProfile {
private:
Game m_efg;
std::map<GameInfoset, GameAction> m_profile;

Expand Down Expand Up @@ -86,14 +85,11 @@ template <> inline std::string PureBehaviorProfile::GetPayoff(const GamePlayer &
}

class BehaviorContingencies {
private:
BehaviorSupportProfile m_support;
std::vector<GameAction> m_frozen;
std::list<GameInfoset> m_activeInfosets;
std::list<GameInfoset> m_reachableInfosets;

public:
class iterator {
private:
BehaviorContingencies *m_cont;
bool m_atEnd;
std::map<GameInfoset, BehaviorSupportProfile::Support::const_iterator> m_currentBehav;
Expand Down Expand Up @@ -127,10 +123,8 @@ class BehaviorContingencies {
};
/// @name Lifecycle
//@{
/// Construct a new iterator on the support, holding the listed actions fixed
explicit BehaviorContingencies(const BehaviorSupportProfile &,
const std::set<GameInfoset> &p_active = {},
const std::vector<GameAction> &p_frozen = {});
/// Construct a new iterator on the support
explicit BehaviorContingencies(const BehaviorSupportProfile &);
//@}
iterator begin() { return {this, false}; }
iterator end() { return {this, true}; }
Expand Down
100 changes: 0 additions & 100 deletions src/games/behavspt.cc
Original file line number Diff line number Diff line change
Expand Up @@ -106,106 +106,6 @@ std::list<GameInfoset> BehaviorSupportProfile::GetInfosets(const GamePlayer &p_p
return answer;
}

namespace {

void ReachableInfosets(const BehaviorSupportProfile &p_support, const GameNode &p_node,
std::set<GameInfoset> &p_reached)
{
if (p_node->IsTerminal()) {
return;
}

const GameInfoset infoset = p_node->GetInfoset();
if (!infoset->GetPlayer()->IsChance()) {
p_reached.insert(infoset);
for (const auto &action : p_support.GetActions(infoset)) {
ReachableInfosets(p_support, p_node->GetChild(action), p_reached);
}
}
else {
for (const auto &child : p_node->GetChildren()) {
ReachableInfosets(p_support, child, p_reached);
}
}
}

} // end anonymous namespace

bool BehaviorSupportProfile::Dominates(const GameAction &a, const GameAction &b,
bool p_strict) const
{
const GameInfoset infoset = a->GetInfoset();
if (infoset != b->GetInfoset()) {
throw UndefinedException();
}

GamePlayer player = infoset->GetPlayer();
int thesign = 0;

auto nodelist = GetMembers(infoset);
for (const auto &node : GetMembers(infoset)) {
std::set<GameInfoset> reachable;
ReachableInfosets(*this, node->GetChild(a), reachable);
ReachableInfosets(*this, node->GetChild(b), reachable);

auto contingencies = BehaviorContingencies(*this, reachable);
if (p_strict) {
if (!std::all_of(contingencies.begin(), contingencies.end(),
[&](const PureBehaviorProfile &profile) {
return profile.GetPayoff<Rational>(node->GetChild(a), player) >
profile.GetPayoff<Rational>(node->GetChild(b), player);
})) {
return false;
}
}
else {
for (const auto &iter : contingencies) {
auto newsign = sign(iter.GetPayoff<Rational>(node->GetChild(a), player) -
iter.GetPayoff<Rational>(node->GetChild(b), player));
if (newsign < 0) {
return false;
}
thesign = std::max(thesign, newsign);
}
}
}
return p_strict || thesign > 0;
}

bool BehaviorSupportProfile::IsDominated(const GameAction &p_action, const bool p_strict) const
{
const auto &actions = GetActions(p_action->GetInfoset());
return std::any_of(actions.begin(), actions.end(), [&](const GameAction &a) {
return a != p_action && Dominates(a, p_action, p_strict);
});
}

BehaviorSupportProfile BehaviorSupportProfile::Undominated(const bool p_strict) const
{
BehaviorSupportProfile result(*this);
for (const auto &player : m_efg->GetPlayers()) {
for (const auto &infoset : player->GetInfosets()) {
const auto &actions = GetActions(infoset);
std::set<GameAction> dominated;
for (const auto &action1 : actions) {
if (contains(dominated, action1)) {
continue;
}
for (const auto &action2 : actions) {
if (action1 == action2 || contains(dominated, action2)) {
continue;
}
if (Dominates(action1, action2, p_strict)) {
dominated.insert(action2);
result.RemoveAction(action2);
}
}
}
}
}
return result;
}

bool BehaviorSupportProfile::HasReachableMembers(const GameInfoset &p_infoset) const
{
const auto &members = p_infoset->GetMembers();
Expand Down
15 changes: 7 additions & 8 deletions src/games/layout.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,7 @@

namespace Gambit {

void Layout::LayoutSubtree(const GameNode &p_node, const BehaviorSupportProfile &p_support,
int p_level, double &p_offset)
void Layout::LayoutSubtree(const GameNode &p_node, int p_level, double &p_offset)
{
const auto entry = std::make_shared<LayoutEntry>(p_level);
m_nodeMap[p_node] = entry;
Expand All @@ -46,32 +45,32 @@ void Layout::LayoutSubtree(const GameNode &p_node, const BehaviorSupportProfile
return;
}
if (p_node->GetInfoset() && !p_node->GetInfoset()->GetPlayer()->IsChance()) {
const auto actions = p_support.GetActions(p_node->GetInfoset());
for (const auto &action : actions) {
LayoutSubtree(p_node->GetChild(action), p_support, p_level + 1, p_offset);
const auto actions = p_node->GetInfoset()->GetActions();
for (const auto &action : p_node->GetInfoset()->GetActions()) {
LayoutSubtree(p_node->GetChild(action), p_level + 1, p_offset);
}
entry->m_offset = (m_nodeMap.at(p_node->GetChild(actions.front()))->m_offset +
m_nodeMap.at(p_node->GetChild(actions.back()))->m_offset) /
2;
}
else {
for (const auto &child : p_node->GetChildren()) {
LayoutSubtree(child, p_support, p_level + 1, p_offset);
LayoutSubtree(child, p_level + 1, p_offset);
}
entry->m_offset = (m_nodeMap.at(p_node->GetChildren().front())->m_offset +
m_nodeMap.at(p_node->GetChildren().back())->m_offset) /
2;
}
}

void Layout::LayoutTree(const BehaviorSupportProfile &p_support)
void Layout::LayoutTree(const Game &p_game)
{
m_nodeMap.clear();
m_numSublevels.clear();
m_infosetSublevels.clear();

m_maxOffset = 0;
LayoutSubtree(m_game->GetRoot(), p_support, 0, m_maxOffset);
LayoutSubtree(p_game->GetRoot(), 0, m_maxOffset);
}

} // namespace Gambit
6 changes: 3 additions & 3 deletions src/games/layout.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,13 @@ class Layout {

double m_maxOffset{0};

void LayoutSubtree(const GameNode &, const BehaviorSupportProfile &, int, double &);
void LayoutSubtree(const GameNode &, int, double &);

public:
explicit Layout(const Game &p_game) : m_game(p_game) {}
~Layout() = default;

void LayoutTree(const BehaviorSupportProfile &);
void LayoutTree(const Game &);

const std::map<GameNode, std::shared_ptr<LayoutEntry>> &GetNodeMap() const { return m_nodeMap; }
int GetNodeLevel(const GameNode &p_node) const { return m_nodeMap.at(p_node)->m_level; }
Expand All @@ -68,7 +68,7 @@ class Layout {
inline std::shared_ptr<Layout> CreateLayout(const Game &p_game)
{
auto layout = std::make_shared<Layout>(p_game);
layout->LayoutTree(BehaviorSupportProfile(p_game));
layout->LayoutTree(p_game);
return layout;
}

Expand Down
3 changes: 1 addition & 2 deletions src/gui/efgdisplay.cc
Original file line number Diff line number Diff line change
Expand Up @@ -592,8 +592,7 @@ void EfgDisplay::OnUpdate()

void EfgDisplay::RefreshTree()
{
m_layout.BuildNodeList(m_doc->GetEfgSupport());
m_layout.Layout(m_doc->GetEfgSupport());
m_layout.Layout(m_doc->GetGame());
Refresh();
}

Expand Down
32 changes: 8 additions & 24 deletions src/gui/efglayout.cc
Original file line number Diff line number Diff line change
Expand Up @@ -608,53 +608,37 @@ void TreeLayout::ComputeRenderedParents() const
}
}

void TreeLayout::BuildNodeList(const GameNode &p_node, const BehaviorSupportProfile &p_support)
void TreeLayout::BuildNodeList(const GameNode &p_node)
{
const auto entry = std::make_shared<NodeEntry>(p_node);
m_nodeList.push_back(entry);
m_nodeMap[p_node] = entry;
entry->m_size = m_doc->GetStyle().GetNodeSize();
entry->m_branchLength = m_doc->GetStyle().GetBranchLength();
if (m_doc->GetStyle().RootReachable()) {
if (const GameInfoset infoset = p_node->GetInfoset()) {
if (infoset->GetPlayer()->IsChance()) {
for (const auto &child : p_node->GetChildren()) {
BuildNodeList(child, p_support);
}
}
else {
for (const auto &action : p_support.GetActions(infoset)) {
BuildNodeList(p_node->GetChild(action), p_support);
}
}
}
}
else {
for (const auto &child : p_node->GetChildren()) {
BuildNodeList(child, p_support);
}
for (const auto &child : p_node->GetChildren()) {
BuildNodeList(child);
}
}

void TreeLayout::BuildNodeList(const BehaviorSupportProfile &p_support)
void TreeLayout::BuildNodeList(const Game &p_game)
{
m_nodeList.clear();
m_nodeMap.clear();
BuildNodeList(m_doc->GetGame()->GetRoot(), p_support);
BuildNodeList(p_game->GetRoot());
}

void TreeLayout::Layout(const BehaviorSupportProfile &p_support)
void TreeLayout::Layout(const Game &p_game)
{
m_infosetSpacing = (m_doc->GetStyle().GetInfosetJoin() == GBT_INFOSET_JOIN_LINES) ? 10 : 40;

if (m_nodeList.size() != m_doc->GetGame()->NumNodes()) {
// We only rebuild the node list if the number of nodes changes. If we only have
// information set changes this can be handled just by the traversal below
BuildNodeList(p_support);
BuildNodeList(p_game);
}

auto layout = Gambit::Layout(m_doc->GetGame());
layout.LayoutTree(p_support);
layout.LayoutTree(p_game);

const auto spacing = m_doc->GetStyle().GetTerminalSpacing();
for (auto [node, entry] : layout.GetNodeMap()) {
Expand Down
6 changes: 3 additions & 3 deletions src/gui/efglayout.h
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class TreeLayout final : public GameView {

std::shared_ptr<NodeEntry> ComputeNextInInfoset(const std::shared_ptr<NodeEntry> &) const;

void BuildNodeList(const GameNode &, const BehaviorSupportProfile &);
void BuildNodeList(const GameNode &);

/// Based on node levels and information set sublevels, compute the depth
/// (X coordinate) of all nodes
Expand All @@ -135,6 +135,7 @@ class TreeLayout final : public GameView {
void DrawOutcome(wxDC &, const std::shared_ptr<NodeEntry> &, bool p_noHints) const;

bool NodeHitTest(const std::shared_ptr<NodeEntry> &p_entry, int p_x, int p_y) const;
void BuildNodeList(const Game &);

public:
explicit TreeLayout(GameDocument *p_doc) : GameView(p_doc) {}
Expand All @@ -143,8 +144,7 @@ class TreeLayout final : public GameView {
GameNode PriorSameLevel(const GameNode &) const;
GameNode NextSameLevel(const GameNode &) const;

void BuildNodeList(const BehaviorSupportProfile &);
void Layout(const BehaviorSupportProfile &);
void Layout(const Game &);
void GenerateLabels() const;

std::shared_ptr<NodeEntry> GetNodeEntry(const GameNode &p_node) const
Expand Down
Loading
Loading