From 26894bd81e4613afd48c3172e6b8aeac4380d795 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 12:55:21 +0100 Subject: [PATCH 01/14] Refactor concept of an analysis workspace --- src/gui/gamedoc.cc | 190 +++++++++++++++++++++++++++++++-------------- src/gui/gamedoc.h | 61 +++++++++++---- 2 files changed, 180 insertions(+), 71 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 4d7d5b8d7..0f480dbfe 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -94,13 +94,124 @@ bool StrategyDominanceStack::PreviousLevel() } } +//========================================================================= +// class AnalysisWorkspace +//========================================================================= + +AnalysisWorkspace::AnalysisWorkspace(GameDocument *p_doc) + : m_doc(p_doc), m_stratSupports(p_doc, true), m_currentProfileList(0) +{ +} + +void AnalysisWorkspace::Clear() +{ + m_stratSupports.Reset(); + m_profiles.clear(); + m_currentProfileList = 0; +} + +void AnalysisWorkspace::ResetForGameChange() +{ + m_stratSupports.Reset(); + + // Even though modifications only to payoffs doesn't make the + // computed profiles invalid for the edited game, it does mean + // that, in general, they won't be Nash. For now, to avoid confusion, + // we will wipe them out. + m_profiles.clear(); + m_currentProfileList = 0; +} + +void AnalysisWorkspace::BuildNfg() +{ + m_stratSupports.Reset(); + std::for_each(m_profiles.begin(), m_profiles.end(), std::mem_fn(&AnalysisOutput::BuildNfg)); +} + +void AnalysisWorkspace::AddProfileList(std::shared_ptr p_profs) +{ + m_profiles.push_back(p_profs); + m_currentProfileList = m_profiles.size(); +} + +void AnalysisWorkspace::SetProfileList(int p_index) { m_currentProfileList = p_index; } + +void AnalysisWorkspace::SetCurrentProfile(int p_profile) +{ + m_profiles[m_currentProfileList]->SetCurrent(p_profile); +} + +void AnalysisWorkspace::SetStrategyElimStrength(bool p_strict) +{ + m_stratSupports.SetStrict(p_strict); +} + +bool AnalysisWorkspace::GetStrategyElimStrength() const { return m_stratSupports.GetStrict(); } + +bool AnalysisWorkspace::NextStrategyElimLevel() { return m_stratSupports.NextLevel(); } + +void AnalysisWorkspace::PreviousStrategyElimLevel() { m_stratSupports.PreviousLevel(); } + +void AnalysisWorkspace::TopStrategyElimLevel() { m_stratSupports.TopLevel(); } + +bool AnalysisWorkspace::CanStrategyElim() const { return m_stratSupports.CanEliminate(); } + +int AnalysisWorkspace::GetStrategyElimLevel() const { return m_stratSupports.GetLevel(); } + +void AnalysisWorkspace::Save(std::ostream &p_file) const +{ + std::for_each(m_profiles.begin(), m_profiles.end(), + [&p_file](std::shared_ptr a) { a->Save(p_file); }); +} + +bool AnalysisWorkspace::Load(TiXmlNode *p_game) +{ + m_stratSupports.Reset(); + + m_profiles.clear(); + + for (TiXmlNode *analysis = p_game->FirstChild("analysis"); analysis; + analysis = analysis->NextSibling()) { + const char *type = analysis->ToElement()->Attribute("type"); + // const char *rep = analysis->ToElement()->Attribute("rep"); + if (type && !strcmp(type, "list")) { + // Read in a list of profiles + // We need to try to guess whether the profiles are float or rational + bool isFloat = false; + for (TiXmlNode *profile = analysis->FirstChild("profile"); profile; + profile = profile->NextSiblingElement()) { + if (std::string(profile->FirstChild()->Value()).find('.') != std::string::npos || + std::string(profile->FirstChild()->Value()).find('e') != std::string::npos) { + isFloat = true; + break; + } + } + + if (isFloat) { + auto plist = std::make_shared>(m_doc, false); + plist->Load(analysis); + m_profiles.push_back(plist); + } + else { + auto plist = std::make_shared>(m_doc, false); + plist->Load(analysis); + m_profiles.push_back(plist); + } + } + } + + m_currentProfileList = m_profiles.size(); + + return true; +} + //========================================================================= // class GameDocument //========================================================================= GameDocument::GameDocument(Game p_game) : m_game(p_game), m_selectNode(nullptr), m_gameModified(false), m_unsavedResults(false), - m_stratSupports(this, true), m_currentProfileList(0) + m_workspace(this) { wxGetApp().AddDocument(this); @@ -158,42 +269,10 @@ bool GameDocument::LoadDocument(const wxString &p_filename) return false; } - m_stratSupports.Reset(); - - m_profiles.clear(); - - for (TiXmlNode *analysis = game->FirstChild("analysis"); analysis; - analysis = analysis->NextSibling()) { - const char *type = analysis->ToElement()->Attribute("type"); - // const char *rep = analysis->ToElement()->Attribute("rep"); - if (type && !strcmp(type, "list")) { - // Read in a list of profiles - // We need to try to guess whether the profiles are float or rational - bool isFloat = false; - for (TiXmlNode *profile = analysis->FirstChild("profile"); profile; - profile = profile->NextSiblingElement()) { - if (std::string(profile->FirstChild()->Value()).find('.') != std::string::npos || - std::string(profile->FirstChild()->Value()).find('e') != std::string::npos) { - isFloat = true; - break; - } - } - - if (isFloat) { - auto plist = std::make_shared>(this, false); - plist->Load(analysis); - m_profiles.push_back(plist); - } - else { - auto plist = std::make_shared>(this, false); - plist->Load(analysis); - m_profiles.push_back(plist); - } - } + if (!m_workspace.Load(game)) { + return false; } - m_currentProfileList = m_profiles.size(); - TiXmlNode *colors = docroot->FirstChild("colors"); if (colors) { m_style.SetColorXML(colors); @@ -249,8 +328,7 @@ void GameDocument::SaveDocument(std::ostream &p_file) const p_file << "\n"; } - std::for_each(m_profiles.begin(), m_profiles.end(), - [&p_file](std::shared_ptr a) { a->Save(p_file); }); + m_workspace.Save(p_file); p_file << "\n"; @@ -264,14 +342,7 @@ void GameDocument::UpdateViews(GameModificationType p_modifications) m_gameModified = true; } if (p_modifications == GBT_DOC_MODIFIED_GAME || p_modifications == GBT_DOC_MODIFIED_PAYOFFS) { - m_stratSupports.Reset(); - - // Even though modifications only to payoffs doesn't make the - // computed profiles invalid for the edited game, it does mean - // that, in general, they won't be Nash. For now, to avoid confusion, - // we will wipe them out. - m_profiles.clear(); - m_currentProfileList = 0; + m_workspace.ResetForGameChange(); } std::for_each(m_views.begin(), m_views.end(), std::mem_fn(&GameView::OnUpdate)); @@ -285,8 +356,7 @@ void GameDocument::PostPendingChanges() void GameDocument::BuildNfg() { if (m_game->IsTree()) { - m_stratSupports.Reset(); - std::for_each(m_profiles.begin(), m_profiles.end(), std::mem_fn(&AnalysisOutput::BuildNfg)); + m_workspace.BuildNfg(); } } @@ -313,53 +383,56 @@ void GameDocument::SetStyle(const TreeRenderConfig &p_style) void GameDocument::SetCurrentProfile(int p_profile) { - m_profiles[m_currentProfileList]->SetCurrent(p_profile); + m_workspace.SetCurrentProfile(p_profile); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } void GameDocument::AddProfileList(std::shared_ptr p_profs) { - m_profiles.push_back(p_profs); - m_currentProfileList = m_profiles.size(); + m_workspace.AddProfileList(p_profs); + m_unsavedResults = true; UpdateViews(GBT_DOC_MODIFIED_VIEWS); } void GameDocument::SetProfileList(int p_index) { - m_currentProfileList = p_index; + m_workspace.SetProfileList(p_index); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } void GameDocument::SetStrategyElimStrength(bool p_strict) { - m_stratSupports.SetStrict(p_strict); + m_workspace.SetStrategyElimStrength(p_strict); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -bool GameDocument::GetStrategyElimStrength() const { return m_stratSupports.GetStrict(); } +bool GameDocument::GetStrategyElimStrength() const +{ + return m_workspace.GetStrategyElimStrength(); +} bool GameDocument::NextStrategyElimLevel() { - const bool ret = m_stratSupports.NextLevel(); + const bool ret = m_workspace.NextStrategyElimLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); return ret; } void GameDocument::PreviousStrategyElimLevel() { - m_stratSupports.PreviousLevel(); + m_workspace.PreviousStrategyElimLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } void GameDocument::TopStrategyElimLevel() { - m_stratSupports.TopLevel(); + m_workspace.TopStrategyElimLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -bool GameDocument::CanStrategyElim() const { return m_stratSupports.CanEliminate(); } +bool GameDocument::CanStrategyElim() const { return m_workspace.CanStrategyElim(); } -int GameDocument::GetStrategyElimLevel() const { return m_stratSupports.GetLevel(); } +int GameDocument::GetStrategyElimLevel() const { return m_workspace.GetStrategyElimLevel(); } void GameDocument::SetSelectNode(GameNode p_node) { @@ -603,6 +676,7 @@ void GameDocument::DoSetPayoff(GameOutcome p_outcome, int p_player, const wxStri void GameDocument::DoAddOutput(AnalysisOutput &p_list, const wxString &p_output) { p_list.AddOutput(p_output); + m_unsavedResults = true; UpdateViews(GBT_DOC_MODIFIED_NONE); } diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 32f2e9e1e..49a5c326c 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -133,6 +133,47 @@ using GameModificationType = enum { GBT_DOC_MODIFIED_VIEWS = 0x08 }; +class AnalysisWorkspace { + GameDocument *m_doc; + + StrategyDominanceStack m_stratSupports; + + Array> m_profiles; + int m_currentProfileList; + +public: + explicit AnalysisWorkspace(GameDocument *p_doc); + + void Clear(); + void ResetForGameChange(); + void BuildNfg(); + + const AnalysisOutput &GetProfiles() const { return *m_profiles[m_currentProfileList]; } + const AnalysisOutput &GetProfiles(int p_index) const { return *m_profiles[p_index]; } + void AddProfileList(std::shared_ptr p_profs); + void SetProfileList(int p_index); + int NumProfileLists() const { return m_profiles.size(); } + int GetCurrentProfileList() const { return m_currentProfileList; } + + int GetCurrentProfile() const + { + return (m_profiles.size() == 0) ? 0 : GetProfiles().GetCurrent(); + } + void SetCurrentProfile(int p_profile); + + const StrategySupportProfile &GetNfgSupport() const { return m_stratSupports.GetCurrent(); } + void SetStrategyElimStrength(bool p_strict); + bool GetStrategyElimStrength() const; + bool NextStrategyElimLevel(); + void PreviousStrategyElimLevel(); + void TopStrategyElimLevel(); + bool CanStrategyElim() const; + int GetStrategyElimLevel() const; + + void Save(std::ostream &) const; + bool Load(TiXmlNode *p_game); +}; + class GameDocument { friend class GameView; @@ -154,10 +195,7 @@ class GameDocument { GameNode m_selectNode; bool m_gameModified, m_unsavedResults; - StrategyDominanceStack m_stratSupports; - - Array> m_profiles; - int m_currentProfileList; + AnalysisWorkspace m_workspace; void UpdateViews(GameModificationType p_modifications); @@ -199,17 +237,14 @@ class GameDocument { //! @name Handling of list of computed profiles //! //@{ - const AnalysisOutput &GetProfiles() const { return *m_profiles[m_currentProfileList]; } - const AnalysisOutput &GetProfiles(int p_index) const { return *m_profiles[p_index]; } + const AnalysisOutput &GetProfiles() const { return m_workspace.GetProfiles(); } + const AnalysisOutput &GetProfiles(int p_index) const { return m_workspace.GetProfiles(p_index); } void AddProfileList(std::shared_ptr p_profs); void SetProfileList(int p_index); - int NumProfileLists() const { return m_profiles.size(); } - int GetCurrentProfileList() const { return m_currentProfileList; } + int NumProfileLists() const { return m_workspace.NumProfileLists(); } + int GetCurrentProfileList() const { return m_workspace.GetCurrentProfileList(); } - int GetCurrentProfile() const - { - return (m_profiles.size() == 0) ? 0 : GetProfiles().GetCurrent(); - } + int GetCurrentProfile() const { return m_workspace.GetCurrentProfile(); } void SetCurrentProfile(int p_profile); //! @@ -223,7 +258,7 @@ class GameDocument { //! @name Handling of strategy supports //! //@{ - const StrategySupportProfile &GetNfgSupport() const { return m_stratSupports.GetCurrent(); } + const StrategySupportProfile &GetNfgSupport() const { return m_workspace.GetNfgSupport(); } void SetStrategyElimStrength(bool p_strict); bool GetStrategyElimStrength() const; bool NextStrategyElimLevel(); From 85add90197e725b2be707a16783ceeadbd9b648d Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 13:00:23 +0100 Subject: [PATCH 02/14] Implement separate workspace modification notification --- src/gui/gamedoc.cc | 23 ++++++++++++----------- src/gui/gamedoc.h | 15 ++++++++++----- src/gui/gameframe.cc | 6 +++--- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 0f480dbfe..7fc94edeb 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -210,7 +210,7 @@ bool AnalysisWorkspace::Load(TiXmlNode *p_game) //========================================================================= GameDocument::GameDocument(Game p_game) - : m_game(p_game), m_selectNode(nullptr), m_gameModified(false), m_unsavedResults(false), + : m_game(p_game), m_selectNode(nullptr), m_gameModified(false), m_workspaceModified(false), m_workspace(this) { wxGetApp().AddDocument(this); @@ -341,6 +341,9 @@ void GameDocument::UpdateViews(GameModificationType p_modifications) p_modifications == GBT_DOC_MODIFIED_LABELS) { m_gameModified = true; } + if (p_modifications == GBT_DOC_MODIFIED_WORKSPACE) { + m_workspaceModified = true; + } if (p_modifications == GBT_DOC_MODIFIED_GAME || p_modifications == GBT_DOC_MODIFIED_PAYOFFS) { m_workspace.ResetForGameChange(); } @@ -390,8 +393,13 @@ void GameDocument::SetCurrentProfile(int p_profile) void GameDocument::AddProfileList(std::shared_ptr p_profs) { m_workspace.AddProfileList(p_profs); - m_unsavedResults = true; - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + UpdateViews(GBT_DOC_MODIFIED_WORKSPACE); +} + +void GameDocument::DoAddOutput(AnalysisOutput &p_list, const wxString &p_output) +{ + p_list.AddOutput(p_output); + UpdateViews(GBT_DOC_MODIFIED_WORKSPACE); } void GameDocument::SetProfileList(int p_index) @@ -456,7 +464,7 @@ void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format) SaveDocument(file); m_filename = p_filename; m_gameModified = false; - m_unsavedResults = false; + m_workspaceModified = false; break; case GameSaveFormat::Efg: @@ -673,11 +681,4 @@ void GameDocument::DoSetPayoff(GameOutcome p_outcome, int p_player, const wxStri UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); } -void GameDocument::DoAddOutput(AnalysisOutput &p_list, const wxString &p_output) -{ - p_list.AddOutput(p_output); - m_unsavedResults = true; - UpdateViews(GBT_DOC_MODIFIED_NONE); -} - } // namespace Gambit::GUI diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 49a5c326c..cdeb91fa2 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -125,12 +125,17 @@ class StrategyDominanceStack { // GBT_DOC_MODIFIED_VIEWS: Information about how the document is viewed // (e.g., player colors) has changed. // +// GBT_DOC_MODIFIED_WORKSPACE: Stored analysis workspace data has changed. +// This affects workbook/workspace persistence, but does not modify the +// underlying game model. +// using GameModificationType = enum { GBT_DOC_MODIFIED_NONE = 0x00, GBT_DOC_MODIFIED_GAME = 0x01, GBT_DOC_MODIFIED_PAYOFFS = 0x02, GBT_DOC_MODIFIED_LABELS = 0x04, - GBT_DOC_MODIFIED_VIEWS = 0x08 + GBT_DOC_MODIFIED_VIEWS = 0x08, + GBT_DOC_MODIFIED_WORKSPACE = 0x10 }; class AnalysisWorkspace { @@ -193,7 +198,7 @@ class GameDocument { TreeRenderConfig m_style; GameNode m_selectNode; - bool m_gameModified, m_unsavedResults; + bool m_gameModified, m_workspaceModified; AnalysisWorkspace m_workspace; @@ -219,11 +224,11 @@ class GameDocument { const wxString &GetFilename() const { return m_filename; } void SetFilename(const wxString &p_filename) { m_filename = p_filename; } - bool IsModified() const { return m_gameModified || m_unsavedResults; } + bool IsModified() const { return m_gameModified || m_workspaceModified; } bool IsGameModified() const { return m_gameModified; } - bool AreResultsUnsaved() const { return m_unsavedResults; } + bool IsWorkspaceModified() const { return m_workspaceModified; } void SetGameModified(bool p_modified) { m_gameModified = p_modified; } - void SetUnsavedResults(bool p_unsaved) { m_unsavedResults = p_unsaved; } + void SetWorkspaceModified(bool p_unsaved) { m_workspaceModified = p_unsaved; } const TreeRenderConfig &GetStyle() const { return m_style; } void SetStyle(const TreeRenderConfig &p_style); diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index dbd4d61da..ff552eb58 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -1279,16 +1279,16 @@ namespace { wxString CloseWarningMessage(GameDocument *p_doc) { - if (p_doc->IsGameModified() && !p_doc->AreResultsUnsaved()) { + if (p_doc->IsGameModified() && !p_doc->IsWorkspaceModified()) { return _("This game has unsaved changes.\n\n" "Close without saving?"); } - if (!p_doc->IsGameModified() && p_doc->AreResultsUnsaved()) { + if (!p_doc->IsGameModified() && p_doc->IsWorkspaceModified()) { return _("There are unsaved computational results.\n\n" "These changes are not saved in ordinary game files.\n" "Close without saving?"); } - if (p_doc->IsGameModified() && p_doc->AreResultsUnsaved()) { + if (p_doc->IsGameModified() && p_doc->IsWorkspaceModified()) { return _("This game has unsaved changes, and there are unsaved computational results " "unsaved computational results or workspace changes.\n\n" "Close without saving?"); From eec032be053c032c12b150ea5448082555330443 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 13:15:36 +0100 Subject: [PATCH 03/14] Access const state of profile lists --- src/gui/efglayout.cc | 30 ++++++++++++++++++++---------- src/gui/efgpanel.cc | 14 +++++++------- src/gui/efgprofile.cc | 11 +++++++---- src/gui/gamedoc.h | 8 ++------ src/gui/gameframe.cc | 14 +++++++------- src/gui/nfgpanel.cc | 4 ++-- src/gui/nfgprofile.cc | 16 +++++++++++----- 7 files changed, 56 insertions(+), 41 deletions(-) diff --git a/src/gui/efglayout.cc b/src/gui/efglayout.cc index 145cbc22d..7b9f7c96c 100644 --- a/src/gui/efglayout.cc +++ b/src/gui/efglayout.cc @@ -442,12 +442,15 @@ wxString TreeLayout::CreateNodeLabel(const std::shared_ptr &p_entry, return _T(""); } case GBT_NODE_LABEL_REALIZPROB: - return {m_doc->GetProfiles().GetRealizProb(n).c_str(), *wxConvCurrent}; + return {m_doc->GetWorkspace().GetProfiles().GetRealizProb(n).c_str(), *wxConvCurrent}; case GBT_NODE_LABEL_BELIEFPROB: - return {m_doc->GetProfiles().GetBeliefProb(n).c_str(), *wxConvCurrent}; + return {m_doc->GetWorkspace().GetProfiles().GetBeliefProb(n).c_str(), *wxConvCurrent}; case GBT_NODE_LABEL_VALUE: if (n->GetInfoset() && n->GetPlayer()->GetNumber() > 0) { - return {m_doc->GetProfiles().GetNodeValue(n, n->GetPlayer()->GetNumber()).c_str(), + return {m_doc->GetWorkspace() + .GetProfiles() + .GetNodeValue(n, n->GetPlayer()->GetNumber()) + .c_str(), *wxConvCurrent}; } else { @@ -482,19 +485,25 @@ wxString TreeLayout::CreateBranchLabel(const std::shared_ptr &p_entry .c_str(), *wxConvCurrent}; } - else if (m_doc->NumProfileLists() == 0) { + else if (m_doc->GetWorkspace().NumProfileLists() == 0) { return wxT(""); } else { - return {m_doc->GetProfiles().GetActionProb(parent, p_entry->GetChildNumber()).c_str(), + return {m_doc->GetWorkspace() + .GetProfiles() + .GetActionProb(parent, p_entry->GetChildNumber()) + .c_str(), *wxConvCurrent}; } case GBT_BRANCH_LABEL_VALUE: - if (m_doc->NumProfileLists() == 0) { + if (m_doc->GetWorkspace().NumProfileLists() == 0) { return wxT(""); } else { - return {m_doc->GetProfiles().GetActionValue(parent, p_entry->GetChildNumber()).c_str(), + return {m_doc->GetWorkspace() + .GetProfiles() + .GetActionValue(parent, p_entry->GetChildNumber()) + .c_str(), *wxConvCurrent}; } default: @@ -673,11 +682,12 @@ void TreeLayout::GenerateLabels() const parent->GetInfoset()->GetAction(entry->GetChildNumber())))); } else { - const int profile = m_doc->GetCurrentProfile(); + const int profile = m_doc->GetWorkspace().GetCurrentProfile(); if (profile > 0) { try { - entry->SetActionProb((double)lexical_cast( - m_doc->GetProfiles().GetActionProb(parent, entry->GetChildNumber()))); + entry->SetActionProb( + (double)lexical_cast(m_doc->GetWorkspace().GetProfiles().GetActionProb( + parent, entry->GetChildNumber()))); } catch (ValueException &) { // This occurs when the probability is undefined diff --git a/src/gui/efgpanel.cc b/src/gui/efgpanel.cc index c9e1631e6..5f1bc4d87 100644 --- a/src/gui/efgpanel.cc +++ b/src/gui/efgpanel.cc @@ -197,36 +197,36 @@ void gbtTreePlayerPanel::OnUpdate() wxString(m_doc->GetGame()->GetPlayer(m_player)->GetLabel().c_str(), *wxConvCurrent)); m_payoff->SetForegroundColour(color); - if (m_doc->GetCurrentProfile() > 0) { - const std::string pay = m_doc->GetProfiles().GetPayoff(m_player); + if (m_doc->GetWorkspace().GetCurrentProfile() > 0) { + const std::string pay = m_doc->GetWorkspace().GetProfiles().GetPayoff(m_player); m_payoff->SetLabel(wxT("Payoff: ") + wxString(pay.c_str(), *wxConvCurrent)); GetSizer()->Show(m_payoff, true); if (const GameNode node = m_doc->GetSelectNode()) { m_nodeValue->SetForegroundColour(color); - std::string value = m_doc->GetProfiles().GetNodeValue(node, m_player); + std::string value = m_doc->GetWorkspace().GetProfiles().GetNodeValue(node, m_player); m_nodeValue->SetLabel(wxT("Node value: ") + wxString(value.c_str(), *wxConvCurrent)); GetSizer()->Show(m_nodeValue, true); if (node->GetInfoset() && node->GetPlayer()->GetNumber() == m_player) { m_nodeProb->SetForegroundColour(color); - value = m_doc->GetProfiles().GetRealizProb(node); + value = m_doc->GetWorkspace().GetProfiles().GetRealizProb(node); m_nodeProb->SetLabel(wxT("Node reached: ") + wxString(value.c_str(), *wxConvCurrent)); GetSizer()->Show(m_nodeProb, true); m_infosetValue->SetForegroundColour(color); - value = m_doc->GetProfiles().GetInfosetValue(node); + value = m_doc->GetWorkspace().GetProfiles().GetInfosetValue(node); m_infosetValue->SetLabel(wxT("Infoset value: ") + wxString(value.c_str(), *wxConvCurrent)); GetSizer()->Show(m_infosetValue, true); m_infosetProb->SetForegroundColour(color); - value = m_doc->GetProfiles().GetInfosetProb(node); + value = m_doc->GetWorkspace().GetProfiles().GetInfosetProb(node); m_infosetProb->SetLabel(wxT("Infoset reached: ") + wxString(value.c_str(), *wxConvCurrent)); GetSizer()->Show(m_infosetProb, true); m_belief->SetForegroundColour(color); - value = m_doc->GetProfiles().GetBeliefProb(node); + value = m_doc->GetWorkspace().GetProfiles().GetBeliefProb(node); m_belief->SetLabel(wxT("Belief: ") + wxString(value.c_str(), *wxConvCurrent)); GetSizer()->Show(m_belief, true); } diff --git a/src/gui/efgprofile.cc b/src/gui/efgprofile.cc index 73ba19805..6023bb7fc 100644 --- a/src/gui/efgprofile.cc +++ b/src/gui/efgprofile.cc @@ -88,7 +88,10 @@ wxString BehaviorProfileList::GetCellValue(const wxSheetCoords &p_coords) return wxT("#"); } - return {m_doc->GetProfiles().GetActionProb(p_coords.GetCol() + 1, p_coords.GetRow() + 1).c_str(), + return {m_doc->GetWorkspace() + .GetProfiles() + .GetActionProb(p_coords.GetCol() + 1, p_coords.GetRow() + 1) + .c_str(), *wxConvCurrent}; } @@ -100,7 +103,7 @@ static wxColour GetPlayerColor(const GameDocument *p_doc, int p_index) wxSheetCellAttr BehaviorProfileList::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const { - const int currentProfile = m_doc->GetCurrentProfile(); + const int currentProfile = m_doc->GetWorkspace().GetCurrentProfile(); if (IsRowLabelCell(p_coords)) { wxSheetCellAttr attr(GetSheetRefData()->m_defaultRowLabelAttr); @@ -161,12 +164,12 @@ wxSheetCellAttr BehaviorProfileList::GetAttr(const wxSheetCoords &p_coords, wxSh void BehaviorProfileList::OnUpdate() { - if (!m_doc->GetGame() || m_doc->NumProfileLists() == 0) { + if (!m_doc->GetGame() || m_doc->GetWorkspace().NumProfileLists() == 0) { DeleteRows(0, GetNumberRows()); return; } - const AnalysisOutput &profiles = m_doc->GetProfiles(); + const AnalysisOutput &profiles = m_doc->GetWorkspace().GetProfiles(); const int profileLength = m_doc->GetGame()->BehavProfileLength(); BeginBatch(); diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index cdeb91fa2..8658a564f 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -230,6 +230,8 @@ class GameDocument { void SetGameModified(bool p_modified) { m_gameModified = p_modified; } void SetWorkspaceModified(bool p_unsaved) { m_workspaceModified = p_unsaved; } + const AnalysisWorkspace &GetWorkspace() const { return m_workspace; } + const TreeRenderConfig &GetStyle() const { return m_style; } void SetStyle(const TreeRenderConfig &p_style); @@ -242,14 +244,8 @@ class GameDocument { //! @name Handling of list of computed profiles //! //@{ - const AnalysisOutput &GetProfiles() const { return m_workspace.GetProfiles(); } - const AnalysisOutput &GetProfiles(int p_index) const { return m_workspace.GetProfiles(p_index); } void AddProfileList(std::shared_ptr p_profs); void SetProfileList(int p_index); - int NumProfileLists() const { return m_workspace.NumProfileLists(); } - int GetCurrentProfileList() const { return m_workspace.GetCurrentProfileList(); } - - int GetCurrentProfile() const { return m_workspace.GetCurrentProfile(); } void SetCurrentProfile(int p_profile); //! diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index ff552eb58..0d59fe95f 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -166,15 +166,15 @@ void AnalysisNotebook::OnChoice(wxCommandEvent &p_event) void AnalysisNotebook::OnUpdate() { m_choices->Clear(); - for (int i = 1; i <= m_doc->NumProfileLists(); i++) { + for (int i = 1; i <= m_doc->GetWorkspace().NumProfileLists(); i++) { wxString label; label << wxT("Profiles ") << i; m_choices->Append(label); } - m_choices->SetSelection(m_doc->GetCurrentProfileList() - 1); + m_choices->SetSelection(m_doc->GetWorkspace().GetCurrentProfileList() - 1); - if (m_doc->GetCurrentProfileList() > 0) { - m_description->SetLabel(m_doc->GetProfiles().GetDescription()); + if (m_doc->GetWorkspace().GetCurrentProfileList() > 0) { + m_description->SetLabel(m_doc->GetWorkspace().GetProfiles().GetDescription()); } } @@ -333,11 +333,11 @@ void GameFrame::OnUpdate() GetToolBar()->EnableTool(GBT_MENU_EDIT_NEWPLAYER, !m_efgPanel || m_efgPanel->IsShown()); - menuBar->Enable(GBT_MENU_VIEW_PROFILES, m_doc->NumProfileLists() > 0); - GetToolBar()->EnableTool(GBT_MENU_VIEW_PROFILES, m_doc->NumProfileLists() > 0); + menuBar->Enable(GBT_MENU_VIEW_PROFILES, m_doc->GetWorkspace().NumProfileLists() > 0); + GetToolBar()->EnableTool(GBT_MENU_VIEW_PROFILES, m_doc->GetWorkspace().NumProfileLists() > 0); GetToolBar()->EnableTool(GBT_MENU_FORMAT_DECIMALS_DELETE, m_doc->GetStyle().NumDecimals() > 1); - if (m_doc->NumProfileLists() == 0 && m_splitter->IsSplit()) { + if (m_doc->GetWorkspace().NumProfileLists() == 0 && m_splitter->IsSplit()) { m_splitter->Unsplit(m_analysisPanel); } menuBar->Check(GBT_MENU_VIEW_PROFILES, m_splitter->IsSplit()); diff --git a/src/gui/nfgpanel.cc b/src/gui/nfgpanel.cc index c72df8620..0164afe80 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -172,10 +172,10 @@ void TablePlayerPanel::OnUpdate() m_playerLabel->SetValue( wxString(m_doc->GetGame()->GetPlayer(m_player)->GetLabel().c_str(), *wxConvCurrent)); - if (m_doc->GetCurrentProfile() > 0) { + if (m_doc->GetWorkspace().GetCurrentProfile() > 0) { m_payoff->SetForegroundColour(color); - const std::string pay = m_doc->GetProfiles().GetPayoff(m_player); + const std::string pay = m_doc->GetWorkspace().GetProfiles().GetPayoff(m_player); m_payoff->SetLabel(wxT("Payoff: ") + wxString(pay.c_str(), *wxConvCurrent)); GetSizer()->Show(m_payoff, true); } diff --git a/src/gui/nfgprofile.cc b/src/gui/nfgprofile.cc index 71f125afd..146711f54 100644 --- a/src/gui/nfgprofile.cc +++ b/src/gui/nfgprofile.cc @@ -108,11 +108,17 @@ wxString MixedProfileList::GetCellValue(const wxSheetCoords &p_coords) const int profile = RowToProfile(p_coords.GetRow()); if (IsProbabilityRow(p_coords.GetRow())) { - return {m_doc->GetProfiles().GetStrategyProb(p_coords.GetCol() + 1, profile).c_str(), + return {m_doc->GetWorkspace() + .GetProfiles() + .GetStrategyProb(p_coords.GetCol() + 1, profile) + .c_str(), *wxConvCurrent}; } else { - return {m_doc->GetProfiles().GetStrategyValue(p_coords.GetCol() + 1, profile).c_str(), + return {m_doc->GetWorkspace() + .GetProfiles() + .GetStrategyValue(p_coords.GetCol() + 1, profile) + .c_str(), *wxConvCurrent}; } } @@ -132,7 +138,7 @@ static wxColour GetPlayerColor(const GameDocument *p_doc, int p_index) wxSheetCellAttr MixedProfileList::GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const { - const int currentProfile = m_doc->GetCurrentProfile(); + const int currentProfile = m_doc->GetWorkspace().GetCurrentProfile(); if (IsRowLabelCell(p_coords)) { wxSheetCellAttr attr(GetSheetRefData()->m_defaultRowLabelAttr); @@ -178,12 +184,12 @@ wxSheetCellAttr MixedProfileList::GetAttr(const wxSheetCoords &p_coords, wxSheet void MixedProfileList::OnUpdate() { - if (m_doc->NumProfileLists() == 0) { + if (m_doc->GetWorkspace().NumProfileLists() == 0) { DeleteRows(0, GetNumberRows()); return; } - const AnalysisOutput &profiles = m_doc->GetProfiles(); + const AnalysisOutput &profiles = m_doc->GetWorkspace().GetProfiles(); BeginBatch(); From acf89a66ed5698a4553a83112d06df5a8216f2e9 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 13:19:40 +0100 Subject: [PATCH 04/14] Access const state of dominance stack --- src/gui/gamedoc.cc | 9 --------- src/gui/gamedoc.h | 3 --- src/gui/nfgpanel.cc | 14 +++++++------- 3 files changed, 7 insertions(+), 19 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 7fc94edeb..e58457a77 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -414,11 +414,6 @@ void GameDocument::SetStrategyElimStrength(bool p_strict) UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -bool GameDocument::GetStrategyElimStrength() const -{ - return m_workspace.GetStrategyElimStrength(); -} - bool GameDocument::NextStrategyElimLevel() { const bool ret = m_workspace.NextStrategyElimLevel(); @@ -438,10 +433,6 @@ void GameDocument::TopStrategyElimLevel() UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -bool GameDocument::CanStrategyElim() const { return m_workspace.CanStrategyElim(); } - -int GameDocument::GetStrategyElimLevel() const { return m_workspace.GetStrategyElimLevel(); } - void GameDocument::SetSelectNode(GameNode p_node) { m_selectNode = p_node; diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 8658a564f..e99d1ed6a 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -261,12 +261,9 @@ class GameDocument { //@{ const StrategySupportProfile &GetNfgSupport() const { return m_workspace.GetNfgSupport(); } void SetStrategyElimStrength(bool p_strict); - bool GetStrategyElimStrength() const; bool NextStrategyElimLevel(); void PreviousStrategyElimLevel(); void TopStrategyElimLevel(); - bool CanStrategyElim() const; - int GetStrategyElimLevel() const; //@} GameNode GetSelectNode() const { return m_selectNode; } diff --git a/src/gui/nfgpanel.cc b/src/gui/nfgpanel.cc index 0164afe80..0689a7b18 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -400,19 +400,19 @@ void StrategyDominanceToolbar::OnLastLevel(wxCommandEvent &) void StrategyDominanceToolbar::OnUpdate() { - m_topButton->Enable(m_doc->GetStrategyElimLevel() > 1); - m_prevButton->Enable(m_doc->GetStrategyElimLevel() > 1); - m_nextButton->Enable(m_doc->CanStrategyElim()); - m_allButton->Enable(m_doc->CanStrategyElim()); - if (m_doc->GetStrategyElimLevel() == 1) { + m_topButton->Enable(m_doc->GetWorkspace().GetStrategyElimLevel() > 1); + m_prevButton->Enable(m_doc->GetWorkspace().GetStrategyElimLevel() > 1); + m_nextButton->Enable(m_doc->GetWorkspace().CanStrategyElim()); + m_allButton->Enable(m_doc->GetWorkspace().CanStrategyElim()); + if (m_doc->GetWorkspace().GetStrategyElimLevel() == 1) { m_level->SetLabel(wxT("All strategies shown")); } - else if (m_doc->GetStrategyElimLevel() == 2) { + else if (m_doc->GetWorkspace().GetStrategyElimLevel() == 2) { m_level->SetLabel(wxT("Eliminated 1 level")); } else { wxString label; - label << "Eliminated " << (m_doc->GetStrategyElimLevel() - 1) << " levels"; + label << "Eliminated " << (m_doc->GetWorkspace().GetStrategyElimLevel() - 1) << " levels"; m_level->SetLabel(label); } GetSizer()->Layout(); From 37813b862c33dd1f65977623f7f172f831f1a656 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 13:23:55 +0100 Subject: [PATCH 05/14] Standardise on "Do" to name document mutating operations --- src/gui/dlnashmon.cc | 2 +- src/gui/efgprofile.cc | 4 ++-- src/gui/gamedoc.cc | 14 +++++++------- src/gui/gamedoc.h | 30 ++++++++---------------------- src/gui/gameframe.cc | 4 ++-- src/gui/nfgpanel.cc | 10 +++++----- src/gui/nfgprofile.cc | 4 ++-- 7 files changed, 27 insertions(+), 41 deletions(-) diff --git a/src/gui/dlnashmon.cc b/src/gui/dlnashmon.cc index a07ab5a16..07e385831 100644 --- a/src/gui/dlnashmon.cc +++ b/src/gui/dlnashmon.cc @@ -100,7 +100,7 @@ void NashMonitorDialog::Start(std::shared_ptr p_command) m_doc->BuildNfg(); } - m_doc->AddProfileList(p_command); + m_doc->DoAddEquilibriumOutput(p_command); m_process = new wxProcess(this, GBT_ID_PROCESS); m_process->Redirect(); diff --git a/src/gui/efgprofile.cc b/src/gui/efgprofile.cc index 6023bb7fc..df6be712e 100644 --- a/src/gui/efgprofile.cc +++ b/src/gui/efgprofile.cc @@ -56,7 +56,7 @@ BehaviorProfileList::~BehaviorProfileList() = default; void BehaviorProfileList::OnLabelClick(wxSheetEvent &p_event) { if (p_event.GetCol() == -1) { - m_doc->SetCurrentProfile(p_event.GetRow() + 1); + m_doc->DoSelectProfile(p_event.GetRow() + 1); } else { // Clicking on an action column sets the selected node to the first @@ -68,7 +68,7 @@ void BehaviorProfileList::OnLabelClick(wxSheetEvent &p_event) void BehaviorProfileList::OnCellClick(wxSheetEvent &p_event) { - m_doc->SetCurrentProfile(p_event.GetRow() + 1); + m_doc->DoSelectProfile(p_event.GetRow() + 1); } wxString BehaviorProfileList::GetCellValue(const wxSheetCoords &p_coords) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index e58457a77..e23204c0b 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -384,13 +384,13 @@ void GameDocument::SetStyle(const TreeRenderConfig &p_style) UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -void GameDocument::SetCurrentProfile(int p_profile) +void GameDocument::DoSelectProfile(int p_profile) { m_workspace.SetCurrentProfile(p_profile); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -void GameDocument::AddProfileList(std::shared_ptr p_profs) +void GameDocument::DoAddEquilibriumOutput(std::shared_ptr p_profs) { m_workspace.AddProfileList(p_profs); UpdateViews(GBT_DOC_MODIFIED_WORKSPACE); @@ -402,32 +402,32 @@ void GameDocument::DoAddOutput(AnalysisOutput &p_list, const wxString &p_output) UpdateViews(GBT_DOC_MODIFIED_WORKSPACE); } -void GameDocument::SetProfileList(int p_index) +void GameDocument::DoSelectEquilibriumOutput(int p_index) { m_workspace.SetProfileList(p_index); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -void GameDocument::SetStrategyElimStrength(bool p_strict) +void GameDocument::DoSetDominanceStrictness(bool p_strict) { m_workspace.SetStrategyElimStrength(p_strict); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -bool GameDocument::NextStrategyElimLevel() +bool GameDocument::DoNextDominanceLevel() { const bool ret = m_workspace.NextStrategyElimLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); return ret; } -void GameDocument::PreviousStrategyElimLevel() +void GameDocument::DoPreviousDominanceLevel() { m_workspace.PreviousStrategyElimLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } -void GameDocument::TopStrategyElimLevel() +void GameDocument::DoTopDominanceLevel() { m_workspace.TopStrategyElimLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index e99d1ed6a..e27fde8f6 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -240,31 +240,17 @@ class GameDocument { bool IsTree() const { return m_game->IsTree(); } GameAction GetAction(int p_index) const; - //! - //! @name Handling of list of computed profiles - //! - //@{ - void AddProfileList(std::shared_ptr p_profs); - void SetProfileList(int p_index); - void SetCurrentProfile(int p_profile); + void DoAddEquilibriumOutput(std::shared_ptr p_profs); + void DoSelectEquilibriumOutput(int p_index); + void DoSelectProfile(int p_profile); - //! - //! @name Handling of behavior supports - //! - //@{ BehaviorSupportProfile GetEfgSupport() const { return BehaviorSupportProfile(m_game); } - //@} - - //! - //! @name Handling of strategy supports - //! - //@{ const StrategySupportProfile &GetNfgSupport() const { return m_workspace.GetNfgSupport(); } - void SetStrategyElimStrength(bool p_strict); - bool NextStrategyElimLevel(); - void PreviousStrategyElimLevel(); - void TopStrategyElimLevel(); - //@} + + void DoSetDominanceStrictness(bool p_strict); + bool DoNextDominanceLevel(); + void DoPreviousDominanceLevel(); + void DoTopDominanceLevel(); GameNode GetSelectNode() const { return m_selectNode; } void SetSelectNode(GameNode); diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index 0d59fe95f..20d574712 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -160,7 +160,7 @@ void AnalysisNotebook::ShowMixed(bool p_show) { m_profiles->ShowMixed(p_show); } void AnalysisNotebook::OnChoice(wxCommandEvent &p_event) { - m_doc->SetProfileList(p_event.GetSelection() + 1); + m_doc->DoSelectEquilibriumOutput(p_event.GetSelection() + 1); } void AnalysisNotebook::OnUpdate() @@ -1189,7 +1189,7 @@ void GameFrame::OnToolsDominance(wxCommandEvent &p_event) wxPostEvent(m_nfgPanel, p_event); } if (!p_event.IsChecked()) { - m_doc->TopStrategyElimLevel(); + m_doc->DoTopDominanceLevel(); } } diff --git a/src/gui/nfgpanel.cc b/src/gui/nfgpanel.cc index 0689a7b18..5c21273da 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -380,21 +380,21 @@ StrategyDominanceToolbar::StrategyDominanceToolbar(wxWindow *p_parent, GameDocum void StrategyDominanceToolbar::OnStrength(wxCommandEvent &p_event) { - m_doc->SetStrategyElimStrength(p_event.GetSelection() == 0); + m_doc->DoSetDominanceStrictness(p_event.GetSelection() == 0); } -void StrategyDominanceToolbar::OnTopLevel(wxCommandEvent &) { m_doc->TopStrategyElimLevel(); } +void StrategyDominanceToolbar::OnTopLevel(wxCommandEvent &) { m_doc->DoTopDominanceLevel(); } void StrategyDominanceToolbar::OnPreviousLevel(wxCommandEvent &) { - m_doc->PreviousStrategyElimLevel(); + m_doc->DoPreviousDominanceLevel(); } -void StrategyDominanceToolbar::OnNextLevel(wxCommandEvent &) { m_doc->NextStrategyElimLevel(); } +void StrategyDominanceToolbar::OnNextLevel(wxCommandEvent &) { m_doc->DoNextDominanceLevel(); } void StrategyDominanceToolbar::OnLastLevel(wxCommandEvent &) { - while (m_doc->NextStrategyElimLevel()) + while (m_doc->DoNextDominanceLevel()) ; } diff --git a/src/gui/nfgprofile.cc b/src/gui/nfgprofile.cc index 146711f54..cebfac488 100644 --- a/src/gui/nfgprofile.cc +++ b/src/gui/nfgprofile.cc @@ -56,13 +56,13 @@ MixedProfileList::~MixedProfileList() = default; void MixedProfileList::OnLabelClick(wxSheetEvent &p_event) { if (p_event.GetCol() == -1) { - m_doc->SetCurrentProfile(RowToProfile(p_event.GetRow())); + m_doc->DoSelectProfile(RowToProfile(p_event.GetRow())); } } void MixedProfileList::OnCellClick(wxSheetEvent &p_event) { - m_doc->SetCurrentProfile(RowToProfile(p_event.GetRow())); + m_doc->DoSelectProfile(RowToProfile(p_event.GetRow())); } #ifdef UNUSED From b3d7860a1c0e9e15c1f87d3ac8a4a7ded1975b0f Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 13:29:32 +0100 Subject: [PATCH 06/14] Access game directly rather than forwarding some properties --- src/gui/analysis.cc | 8 ++++---- src/gui/dlgameprop.cc | 2 +- src/gui/dlinsertmove.cc | 8 ++++---- src/gui/dlnash.cc | 16 ++++++++-------- src/gui/efgdisplay.cc | 4 ++-- src/gui/efgpanel.cc | 8 ++++---- src/gui/gamedoc.h | 3 --- src/gui/gameframe.cc | 10 +++++----- src/gui/nfgpanel.cc | 8 ++++---- src/gui/nfgtable.cc | 2 +- src/gui/nfgtable.h | 21 ++++++++++++--------- 11 files changed, 45 insertions(+), 45 deletions(-) diff --git a/src/gui/analysis.cc b/src/gui/analysis.cc index 2ded2c819..84f1d3a7f 100644 --- a/src/gui/analysis.cc +++ b/src/gui/analysis.cc @@ -100,7 +100,7 @@ template void AnalysisProfileList::AddOutput(const wxString &p_outp auto profile = std::make_shared>(OutputToMixedProfile(m_doc, p_output)); m_mixedProfiles.push_back(profile); - if (m_doc->IsTree()) { + if (m_doc->GetGame()->IsTree()) { m_behavProfiles.push_back(std::make_shared>(*profile)); } m_current = m_mixedProfiles.size(); @@ -120,7 +120,7 @@ template void AnalysisProfileList::BuildNfg() template int AnalysisProfileList::NumProfiles() const { - return (m_doc->IsTree()) ? m_behavProfiles.size() : m_mixedProfiles.size(); + return (m_doc->GetGame()->IsTree()) ? m_behavProfiles.size() : m_mixedProfiles.size(); } template void AnalysisProfileList::Clear() @@ -202,7 +202,7 @@ template std::string AnalysisProfileList::GetPayoff(int pl, int p_i const int index = (p_index == -1) ? m_current : p_index; try { - if (m_doc->IsTree()) { + if (m_doc->GetGame()->IsTree()) { return lexical_cast( m_behavProfiles[index]->GetPayoff(m_doc->GetGame()->GetPlayer(pl)), m_doc->GetStyle().NumDecimals()); @@ -417,7 +417,7 @@ template void AnalysisProfileList::Save(std::ostream &p_file) const p_file << static_cast(m_description.mb_str()) << "\n"; p_file << "\n"; - if (m_doc->IsTree()) { + if (m_doc->GetGame()->IsTree()) { for (int j = 1; j <= NumProfiles(); j++) { const MixedBehaviorProfile &behav = *m_behavProfiles[j]; p_file << "\n"; diff --git a/src/gui/dlgameprop.cc b/src/gui/dlgameprop.cc index d2debad6d..8cca9d609 100644 --- a/src/gui/dlgameprop.cc +++ b/src/gui/dlgameprop.cc @@ -77,7 +77,7 @@ GamePropertiesDialog::GamePropertiesDialog(wxWindow *p_parent, GameDocument *p_d wxALL, 5); } - if (m_doc->IsTree()) { + if (game->IsTree()) { if (game->IsPerfectRecall()) { boxSizer->Add(new wxStaticText(this, wxID_STATIC, _("This is a game of perfect recall")), 0, wxALL, 5); diff --git a/src/gui/dlinsertmove.cc b/src/gui/dlinsertmove.cc index 320bbe179..71c16a9e7 100644 --- a/src/gui/dlinsertmove.cc +++ b/src/gui/dlinsertmove.cc @@ -118,7 +118,7 @@ void InsertMoveDialog::OnPlayer(wxCommandEvent &) if (playerNumber == 0) { player = m_doc->GetGame()->GetChance(); } - else if (playerNumber <= static_cast(m_doc->NumPlayers())) { + else if (playerNumber <= static_cast(m_doc->GetGame()->NumPlayers())) { player = m_doc->GetGame()->GetPlayer(playerNumber); } @@ -187,17 +187,17 @@ GamePlayer InsertMoveDialog::GetPlayer() const if (playerNumber == 0) { return m_doc->GetGame()->GetChance(); } - if (playerNumber <= static_cast(m_doc->NumPlayers())) { + if (playerNumber <= static_cast(m_doc->GetGame()->NumPlayers())) { return m_doc->GetGame()->GetPlayer(playerNumber); } const GamePlayer player = m_doc->GetGame()->NewPlayer(); - player->SetLabel("Player " + lexical_cast(m_doc->NumPlayers())); + player->SetLabel("Player " + lexical_cast(m_doc->GetGame()->NumPlayers())); return player; } GameInfoset InsertMoveDialog::GetInfoset() const { - if (m_playerItem->GetSelection() <= static_cast(m_doc->NumPlayers())) { + if (m_playerItem->GetSelection() <= static_cast(m_doc->GetGame()->NumPlayers())) { const GamePlayer player = GetPlayer(); const int infosetNumber = m_infosetItem->GetSelection(); diff --git a/src/gui/dlnash.cc b/src/gui/dlnash.cc index a5c201594..afc6b84ef 100644 --- a/src/gui/dlnash.cc +++ b/src/gui/dlnash.cc @@ -66,7 +66,7 @@ NashChoiceDialog::NashChoiceDialog(wxWindow *p_parent, GameDocument *p_doc) wxCommandEventHandler(NashChoiceDialog::OnCount)); topSizer->Add(m_countChoice, 0, wxALL | wxEXPAND, 5); - if (p_doc->NumPlayers() == 2 && m_doc->IsConstSum()) { + if (p_doc->GetGame()->NumPlayers() == 2 && m_doc->GetGame()->IsConstSum()) { wxString methodChoices[] = {s_recommended, s_lp, s_simpdiv, s_logit, s_enumpoly}; m_methodChoice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 5, methodChoices); @@ -79,7 +79,7 @@ NashChoiceDialog::NashChoiceDialog(wxWindow *p_parent, GameDocument *p_doc) m_methodChoice->SetSelection(0); topSizer->Add(m_methodChoice, 0, wxALL | wxEXPAND, 5); - if (m_doc->IsTree()) { + if (m_doc->GetGame()->IsTree()) { wxString repChoices[] = {wxT("using the extensive game"), wxT("using the strategic game")}; m_repChoice = new wxChoice(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 2, repChoices); m_repChoice->SetSelection(0); @@ -114,14 +114,14 @@ void NashChoiceDialog::OnCount(wxCommandEvent &p_event) m_methodChoice->Append(s_recommended); if (p_event.GetSelection() == 0) { - if (m_doc->NumPlayers() == 2 && m_doc->IsConstSum()) { + if (m_doc->GetGame()->NumPlayers() == 2 && m_doc->GetGame()->IsConstSum()) { m_methodChoice->Append(s_lp); } m_methodChoice->Append(s_simpdiv); m_methodChoice->Append(s_logit); } else if (p_event.GetSelection() == 1) { - if (m_doc->NumPlayers() == 2) { + if (m_doc->GetGame()->NumPlayers() == 2) { m_methodChoice->Append(s_lcp); } m_methodChoice->Append(s_enumpure); @@ -131,7 +131,7 @@ void NashChoiceDialog::OnCount(wxCommandEvent &p_event) m_methodChoice->Append(s_enumpoly); } else { - if (m_doc->NumPlayers() == 2) { + if (m_doc->GetGame()->NumPlayers() == 2) { m_methodChoice->Append(s_enummixed); } } @@ -195,7 +195,7 @@ std::shared_ptr NashChoiceDialog::GetCommand() const if (method == s_recommended) { if (m_countChoice->GetSelection() == 0) { - if (m_doc->NumPlayers() == 2 && m_doc->IsConstSum()) { + if (m_doc->GetGame()->NumPlayers() == 2 && m_doc->GetGame()->IsConstSum()) { cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("lp") + options); cmd->SetDescription(wxT("One equilibrium by solving a linear program ") + game); @@ -207,7 +207,7 @@ std::shared_ptr NashChoiceDialog::GetCommand() const } } else if (m_countChoice->GetSelection() == 1) { - if (m_doc->NumPlayers() == 2) { + if (m_doc->GetGame()->NumPlayers() == 2) { cmd = std::make_shared>(m_doc, useEfg); cmd->SetCommand(prefix + wxT("lcp") + options); cmd->SetDescription(wxT("Some equilibria by solving a linear complementarity program ") + @@ -220,7 +220,7 @@ std::shared_ptr NashChoiceDialog::GetCommand() const } } else { - if (m_doc->NumPlayers() == 2) { + if (m_doc->GetGame()->NumPlayers() == 2) { cmd = std::make_shared>(m_doc, false); cmd->SetCommand(prefix + wxT("enummixed")); cmd->SetDescription( diff --git a/src/gui/efgdisplay.cc b/src/gui/efgdisplay.cc index 27d498834..870d4a29a 100644 --- a/src/gui/efgdisplay.cc +++ b/src/gui/efgdisplay.cc @@ -465,7 +465,7 @@ void EfgDisplay::OnKeyEvent(wxKeyEvent &p_event) PrepareDC(dc); OnDraw(dc); - if (player < static_cast(m_doc->NumPlayers())) { + if (player < static_cast(m_doc->GetGame()->NumPlayers())) { auto entry = m_layout.GetNodeEntry(node); const wxRect rect = entry->GetPayoffExtent(player + 1); int xx, yy; @@ -836,7 +836,7 @@ void EfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event) // Editing an existing outcome auto entry = m_layout.GetNodeEntry(node); - for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { + for (size_t pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) { const wxRect rect = entry->GetPayoffExtent(pl); if (rect.Contains(x, y)) { int xx, yy; diff --git a/src/gui/efgpanel.cc b/src/gui/efgpanel.cc index 5f1bc4d87..0804c1c1a 100644 --- a/src/gui/efgpanel.cc +++ b/src/gui/efgpanel.cc @@ -186,7 +186,7 @@ gbtTreePlayerPanel::gbtTreePlayerPanel(wxWindow *p_parent, GameDocument *p_doc, void gbtTreePlayerPanel::OnUpdate() { - if (!m_doc->IsTree()) { + if (!m_doc->GetGame()->IsTree()) { return; } @@ -439,7 +439,7 @@ gbtTreePlayerToolbar::gbtTreePlayerToolbar(wxWindow *p_parent, GameDocument *p_d topSizer->Add(m_chancePanel, 0, wxALL | wxEXPAND, 5); - for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { + for (size_t pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) { m_playerPanels.push_back(new gbtTreePlayerPanel(this, m_doc, pl)); topSizer->Add(m_playerPanels[pl], 0, wxALL | wxEXPAND, 5); } @@ -450,13 +450,13 @@ gbtTreePlayerToolbar::gbtTreePlayerToolbar(wxWindow *p_parent, GameDocument *p_d void gbtTreePlayerToolbar::OnUpdate() { - while (m_playerPanels.size() < m_doc->NumPlayers()) { + while (m_playerPanels.size() < m_doc->GetGame()->NumPlayers()) { auto *panel = new gbtTreePlayerPanel(this, m_doc, m_playerPanels.size() + 1); m_playerPanels.push_back(panel); GetSizer()->Add(panel, 0, wxALL | wxEXPAND, 5); } - while (m_playerPanels.size() > m_doc->NumPlayers()) { + while (m_playerPanels.size() > m_doc->GetGame()->NumPlayers()) { gbtTreePlayerPanel *panel = m_playerPanels.back(); GetSizer()->Detach(panel); panel->Destroy(); diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index e27fde8f6..661698ab9 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -235,9 +235,6 @@ class GameDocument { const TreeRenderConfig &GetStyle() const { return m_style; } void SetStyle(const TreeRenderConfig &p_style); - size_t NumPlayers() const { return m_game->NumPlayers(); } - bool IsConstSum() const { return m_game->IsConstSum(); } - bool IsTree() const { return m_game->IsTree(); } GameAction GetAction(int p_index) const; void DoAddEquilibriumOutput(std::shared_ptr p_profs); diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index 20d574712..211be42b9 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -84,7 +84,7 @@ ProfileListPanel::ProfileListPanel(wxWindow *p_parent, GameDocument *p_doc) { auto *topSizer = new wxBoxSizer(wxHORIZONTAL); - if (p_doc->IsTree()) { + if (p_doc->GetGame()->IsTree()) { m_behavProfiles = new BehaviorProfileList(this, p_doc); m_behavProfiles->Show(false); topSizer->Add(m_behavProfiles, 1, wxEXPAND, 0); @@ -262,7 +262,7 @@ GameFrame::GameFrame(wxWindow *p_parent, GameDocument *p_doc) wxWindowBase::SetAcceleratorTable(accel); m_splitter = new wxSplitterWindow(this, wxID_ANY); - if (p_doc->IsTree()) { + if (p_doc->GetGame()->IsTree()) { m_efgPanel = new EfgPanel(m_splitter, p_doc); m_efgPanel->Show(true); m_splitter->Initialize(m_efgPanel); @@ -289,7 +289,7 @@ GameFrame::GameFrame(wxWindow *p_parent, GameDocument *p_doc) SetSizer(topSizer); wxTopLevelWindowBase::Layout(); - if (p_doc->IsTree()) { + if (p_doc->GetGame()->IsTree()) { m_efgPanel->SetFocus(); } else { @@ -558,7 +558,7 @@ void GameFrame::MakeToolbar() toolBar->AddTool(GBT_MENU_EDIT_NEWPLAYER, wxEmptyString, wxBitmap(newplayer_xpm), wxNullBitmap, wxITEM_NORMAL, _("Add a new player"), _("Add a new player to the game"), nullptr); - if (m_doc->IsTree()) { + if (m_doc->GetGame()->IsTree()) { toolBar->AddTool(GBT_MENU_VIEW_ZOOMIN, wxEmptyString, wxBitmap(zoomin_xpm), wxNullBitmap, wxITEM_NORMAL, _("Zoom in"), _("Increase magnification"), nullptr); toolBar->AddTool(GBT_MENU_VIEW_ZOOMOUT, wxEmptyString, wxBitmap(zoomout_xpm), wxNullBitmap, @@ -579,7 +579,7 @@ void GameFrame::MakeToolbar() toolBar->AddSeparator(); - if (m_doc->IsTree()) { + if (m_doc->GetGame()->IsTree()) { toolBar->AddTool(GBT_MENU_VIEW_STRATEGIC, wxEmptyString, wxBitmap(table_xpm), wxNullBitmap, wxITEM_CHECK, _("Display the reduced strategic representation of the game"), _("Display the reduced strategic representation of the game"), nullptr); diff --git a/src/gui/nfgpanel.cc b/src/gui/nfgpanel.cc index 5c21273da..f1919505a 100644 --- a/src/gui/nfgpanel.cc +++ b/src/gui/nfgpanel.cc @@ -123,7 +123,7 @@ TablePlayerPanel::TablePlayerPanel(wxWindow *p_parent, NfgPanel *p_nfgPanel, Gam wxStaticBitmap *playerIcon = new TablePlayerIcon(this, m_player); labelSizer->Add(playerIcon, 0, wxALL | wxALIGN_CENTER, 0); - if (!m_doc->IsTree()) { + if (!m_doc->GetGame()->IsTree()) { auto *addStrategyIcon = new wxBitmapButton(this, wxID_ANY, wxBitmap(newrow_xpm), wxDefaultPosition, wxDefaultSize, wxNO_BORDER); addStrategyIcon->SetToolTip(_("Add a strategy for this player")); @@ -264,7 +264,7 @@ TablePlayerToolbar::TablePlayerToolbar(NfgPanel *p_parent, GameDocument *p_doc) { auto *topSizer = new wxBoxSizer(wxVERTICAL); - for (size_t pl = 1; pl <= m_doc->NumPlayers(); pl++) { + for (size_t pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) { m_playerPanels.push_back(new TablePlayerPanel(this, p_parent, m_doc, pl)); topSizer->Add(m_playerPanels[pl], 0, wxALL | wxEXPAND, 5); } @@ -275,13 +275,13 @@ TablePlayerToolbar::TablePlayerToolbar(NfgPanel *p_parent, GameDocument *p_doc) void TablePlayerToolbar::OnUpdate() { - while (m_playerPanels.size() < m_doc->NumPlayers()) { + while (m_playerPanels.size() < m_doc->GetGame()->NumPlayers()) { auto *panel = new TablePlayerPanel(this, m_nfgPanel, m_doc, m_playerPanels.size() + 1); m_playerPanels.push_back(panel); GetSizer()->Add(panel, 0, wxALL | wxEXPAND, 5); } - while (m_playerPanels.size() > m_doc->NumPlayers()) { + while (m_playerPanels.size() > m_doc->GetGame()->NumPlayers()) { TablePlayerPanel *panel = m_playerPanels.back(); GetSizer()->Detach(panel); panel->Destroy(); diff --git a/src/gui/nfgtable.cc b/src/gui/nfgtable.cc index fe2cce6cf..ed35eab14 100644 --- a/src/gui/nfgtable.cc +++ b/src/gui/nfgtable.cc @@ -630,7 +630,7 @@ void PayoffsWidget::OnUpdate() Refresh(); } -bool TableWidget::IsReadOnly() const { return m_doc->IsTree(); } +bool TableWidget::IsReadOnly() const { return m_doc->GetGame()->IsTree(); } wxColour TableWidget::GetPlayerColor(int player) const { diff --git a/src/gui/nfgtable.h b/src/gui/nfgtable.h index 022996bcd..70ebad402 100644 --- a/src/gui/nfgtable.h +++ b/src/gui/nfgtable.h @@ -47,13 +47,13 @@ class StrategicTableLayout { public: explicit StrategicTableLayout(GameDocument *doc) : m_doc(doc) { - if (m_doc->NumPlayers() >= 1) { + if (m_doc->GetGame()->NumPlayers() >= 1) { m_rowPlayers.push_back(1); } - if (m_doc->NumPlayers() >= 2) { + if (m_doc->GetGame()->NumPlayers() >= 2) { m_colPlayers.push_back(2); } - for (int pl = 3; pl <= m_doc->NumPlayers(); ++pl) { + for (int pl = 3; pl <= m_doc->GetGame()->NumPlayers(); ++pl) { m_rowPlayers.push_back(pl); } } @@ -74,7 +74,7 @@ class StrategicTableLayout { void ReconcilePlayers() { - const int maxPlayer = static_cast(m_doc->NumPlayers()); + const int maxPlayer = static_cast(m_doc->GetGame()->NumPlayers()); m_rowPlayers.erase(std::remove_if(m_rowPlayers.begin(), m_rowPlayers.end(), [maxPlayer](int pl) { return pl > maxPlayer; }), @@ -175,7 +175,7 @@ class StrategicTableLayout { int ColToStrategy(int player, int col) const { - const int strat = col / m_doc->NumPlayers() / NumColsSpanned(player); + const int strat = col / m_doc->GetGame()->NumPlayers() / NumColsSpanned(player); return (strat % NumStrategies(m_doc->GetNfgSupport(), GetColPlayer(player)) + 1); } @@ -183,10 +183,13 @@ class StrategicTableLayout { int GetRowHeaderColCount() const { return NumRowPlayers(); } int GetColHeaderRowCount() const { return NumColPlayers(); } - int GetColHeaderColCount() const { return NumColContingencies() * m_doc->NumPlayers(); } + int GetColHeaderColCount() const + { + return NumColContingencies() * m_doc->GetGame()->NumPlayers(); + } int GetPayoffRowCount() const { return NumRowContingencies(); } - int GetPayoffColCount() const { return NumColContingencies() * m_doc->NumPlayers(); } + int GetPayoffColCount() const { return NumColContingencies() * m_doc->GetGame()->NumPlayers(); } int GetRowHeaderPlayer(int headerCol) const { return GetRowPlayer(headerCol + 1); } @@ -206,12 +209,12 @@ class StrategicTableLayout { int GetColHeaderColSpan(int headerRow) const { - return NumColsSpanned(headerRow + 1) * m_doc->NumPlayers(); + return NumColsSpanned(headerRow + 1) * m_doc->GetGame()->NumPlayers(); } int GetPayoffPlayerForColumn(int payoffCol) const { - const int index = payoffCol % m_doc->NumPlayers() + 1; + const int index = payoffCol % m_doc->GetGame()->NumPlayers() + 1; if (index <= NumRowPlayers()) { return GetRowPlayer(index); } From e9c176a9afa371a87100ae0e850449d960102547 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 13:46:25 +0100 Subject: [PATCH 07/14] Some renaming in workspace --- src/gui/gamedoc.cc | 28 ++++++++++++++-------------- src/gui/gamedoc.h | 16 +++++++++------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index e23204c0b..ad8a7b224 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -128,31 +128,31 @@ void AnalysisWorkspace::BuildNfg() std::for_each(m_profiles.begin(), m_profiles.end(), std::mem_fn(&AnalysisOutput::BuildNfg)); } -void AnalysisWorkspace::AddProfileList(std::shared_ptr p_profs) +void AnalysisWorkspace::AddEquilibriumOutput(std::shared_ptr p_profs) { m_profiles.push_back(p_profs); m_currentProfileList = m_profiles.size(); } -void AnalysisWorkspace::SetProfileList(int p_index) { m_currentProfileList = p_index; } +void AnalysisWorkspace::SelectEquilibriumOutput(int p_index) { m_currentProfileList = p_index; } -void AnalysisWorkspace::SetCurrentProfile(int p_profile) +void AnalysisWorkspace::SelectProfile(int p_profile) { m_profiles[m_currentProfileList]->SetCurrent(p_profile); } -void AnalysisWorkspace::SetStrategyElimStrength(bool p_strict) +void AnalysisWorkspace::SetDominanceStrictness(bool p_strict) { m_stratSupports.SetStrict(p_strict); } bool AnalysisWorkspace::GetStrategyElimStrength() const { return m_stratSupports.GetStrict(); } -bool AnalysisWorkspace::NextStrategyElimLevel() { return m_stratSupports.NextLevel(); } +bool AnalysisWorkspace::NextDominanceLevel() { return m_stratSupports.NextLevel(); } -void AnalysisWorkspace::PreviousStrategyElimLevel() { m_stratSupports.PreviousLevel(); } +void AnalysisWorkspace::PreviousDominanceLevel() { m_stratSupports.PreviousLevel(); } -void AnalysisWorkspace::TopStrategyElimLevel() { m_stratSupports.TopLevel(); } +void AnalysisWorkspace::TopDominanceLevel() { m_stratSupports.TopLevel(); } bool AnalysisWorkspace::CanStrategyElim() const { return m_stratSupports.CanEliminate(); } @@ -386,13 +386,13 @@ void GameDocument::SetStyle(const TreeRenderConfig &p_style) void GameDocument::DoSelectProfile(int p_profile) { - m_workspace.SetCurrentProfile(p_profile); + m_workspace.SelectProfile(p_profile); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } void GameDocument::DoAddEquilibriumOutput(std::shared_ptr p_profs) { - m_workspace.AddProfileList(p_profs); + m_workspace.AddEquilibriumOutput(p_profs); UpdateViews(GBT_DOC_MODIFIED_WORKSPACE); } @@ -404,32 +404,32 @@ void GameDocument::DoAddOutput(AnalysisOutput &p_list, const wxString &p_output) void GameDocument::DoSelectEquilibriumOutput(int p_index) { - m_workspace.SetProfileList(p_index); + m_workspace.SelectEquilibriumOutput(p_index); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } void GameDocument::DoSetDominanceStrictness(bool p_strict) { - m_workspace.SetStrategyElimStrength(p_strict); + m_workspace.SetDominanceStrictness(p_strict); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } bool GameDocument::DoNextDominanceLevel() { - const bool ret = m_workspace.NextStrategyElimLevel(); + const bool ret = m_workspace.NextDominanceLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); return ret; } void GameDocument::DoPreviousDominanceLevel() { - m_workspace.PreviousStrategyElimLevel(); + m_workspace.PreviousDominanceLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } void GameDocument::DoTopDominanceLevel() { - m_workspace.TopStrategyElimLevel(); + m_workspace.TopDominanceLevel(); UpdateViews(GBT_DOC_MODIFIED_VIEWS); } diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 661698ab9..029827b7a 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -27,6 +27,8 @@ #include "style.h" #include "analysis.h" +class TiXmlNode; + namespace Gambit::GUI { class GameView; @@ -155,8 +157,8 @@ class AnalysisWorkspace { const AnalysisOutput &GetProfiles() const { return *m_profiles[m_currentProfileList]; } const AnalysisOutput &GetProfiles(int p_index) const { return *m_profiles[p_index]; } - void AddProfileList(std::shared_ptr p_profs); - void SetProfileList(int p_index); + void AddEquilibriumOutput(std::shared_ptr p_profs); + void SelectEquilibriumOutput(int p_index); int NumProfileLists() const { return m_profiles.size(); } int GetCurrentProfileList() const { return m_currentProfileList; } @@ -164,14 +166,14 @@ class AnalysisWorkspace { { return (m_profiles.size() == 0) ? 0 : GetProfiles().GetCurrent(); } - void SetCurrentProfile(int p_profile); + void SelectProfile(int p_profile); const StrategySupportProfile &GetNfgSupport() const { return m_stratSupports.GetCurrent(); } - void SetStrategyElimStrength(bool p_strict); + void SetDominanceStrictness(bool p_strict); bool GetStrategyElimStrength() const; - bool NextStrategyElimLevel(); - void PreviousStrategyElimLevel(); - void TopStrategyElimLevel(); + bool NextDominanceLevel(); + void PreviousDominanceLevel(); + void TopDominanceLevel(); bool CanStrategyElim() const; int GetStrategyElimLevel() const; From c667b8801ddd5e46e7e617d85343ec96094b2256 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 14:13:00 +0100 Subject: [PATCH 08/14] Modernise change types --- src/gui/gamedoc.cc | 102 ++++++++++++++++++++++----------------------- src/gui/gamedoc.h | 46 +++++++++++++++----- 2 files changed, 86 insertions(+), 62 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index ad8a7b224..d6c87a0ca 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -23,8 +23,6 @@ #include #include -#include - #include "gambit.h" #include "core/tinyxml.h" // for XML parser for LoadDocument() @@ -335,19 +333,21 @@ void GameDocument::SaveDocument(std::ostream &p_file) const p_file << "\n"; } -void GameDocument::UpdateViews(GameModificationType p_modifications) +void GameDocument::NotifyChanged(GameModificationType p_modifications) { - if (p_modifications == GBT_DOC_MODIFIED_GAME || p_modifications == GBT_DOC_MODIFIED_PAYOFFS || - p_modifications == GBT_DOC_MODIFIED_LABELS) { - m_gameModified = true; - } - if (p_modifications == GBT_DOC_MODIFIED_WORKSPACE) { - m_workspaceModified = true; - } - if (p_modifications == GBT_DOC_MODIFIED_GAME || p_modifications == GBT_DOC_MODIFIED_PAYOFFS) { + m_gameModified |= HasModification(p_modifications, GameModificationType::GameForm | + GameModificationType::GamePayoffs | + GameModificationType::GameLabels); + m_workspaceModified |= HasModification(p_modifications, GameModificationType::Workspace); + if (HasModification(p_modifications, + GameModificationType::GameForm | GameModificationType::GamePayoffs)) { m_workspace.ResetForGameChange(); } + UpdateViews(); +} +void GameDocument::UpdateViews() +{ std::for_each(m_views.begin(), m_views.end(), std::mem_fn(&GameView::OnUpdate)); } @@ -381,62 +381,62 @@ GameAction GameDocument::GetAction(int p_index) const void GameDocument::SetStyle(const TreeRenderConfig &p_style) { m_style = p_style; - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + NotifyChanged(GameModificationType::Presentation); } void GameDocument::DoSelectProfile(int p_profile) { m_workspace.SelectProfile(p_profile); - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + UpdateViews(); } void GameDocument::DoAddEquilibriumOutput(std::shared_ptr p_profs) { m_workspace.AddEquilibriumOutput(p_profs); - UpdateViews(GBT_DOC_MODIFIED_WORKSPACE); + NotifyChanged(GameModificationType::Workspace); } void GameDocument::DoAddOutput(AnalysisOutput &p_list, const wxString &p_output) { p_list.AddOutput(p_output); - UpdateViews(GBT_DOC_MODIFIED_WORKSPACE); + NotifyChanged(GameModificationType::Workspace); } void GameDocument::DoSelectEquilibriumOutput(int p_index) { m_workspace.SelectEquilibriumOutput(p_index); - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + UpdateViews(); } void GameDocument::DoSetDominanceStrictness(bool p_strict) { m_workspace.SetDominanceStrictness(p_strict); - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + UpdateViews(); } bool GameDocument::DoNextDominanceLevel() { const bool ret = m_workspace.NextDominanceLevel(); - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + UpdateViews(); return ret; } void GameDocument::DoPreviousDominanceLevel() { m_workspace.PreviousDominanceLevel(); - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + UpdateViews(); } void GameDocument::DoTopDominanceLevel() { m_workspace.TopDominanceLevel(); - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + UpdateViews(); } void GameDocument::SetSelectNode(GameNode p_node) { m_selectNode = p_node; - UpdateViews(GBT_DOC_MODIFIED_VIEWS); + UpdateViews(); } //====================================================================== @@ -469,14 +469,14 @@ void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format) m_gameModified = false; break; } - UpdateViews(GBT_DOC_MODIFIED_NONE); + UpdateViews(); } void GameDocument::DoSetTitle(const wxString &p_title, const wxString &p_comment) { m_game->SetTitle(static_cast(p_title.mb_str())); m_game->SetDescription(static_cast(p_comment.mb_str())); - UpdateViews(GBT_DOC_MODIFIED_LABELS); + NotifyChanged(GameModificationType::GameLabels); } void GameDocument::DoNewPlayer() @@ -486,67 +486,67 @@ void GameDocument::DoNewPlayer() if (!m_game->IsTree()) { player->GetStrategy(1)->SetLabel("1"); } - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoSetPlayerLabel(GamePlayer p_player, const wxString &p_label) { p_player->SetLabel(p_label.ToStdString()); - UpdateViews(GBT_DOC_MODIFIED_LABELS); + NotifyChanged(GameModificationType::GameLabels); } void GameDocument::DoNewStrategy(GamePlayer p_player) { m_game->NewStrategy(p_player, std::to_string(p_player->GetStrategies().size() + 1)); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoDeleteStrategy(GameStrategy p_strategy) { m_game->DeleteStrategy(p_strategy); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoSetStrategyLabel(GameStrategy p_strategy, const wxString &p_label) { p_strategy->SetLabel(p_label.ToStdString()); - UpdateViews(GBT_DOC_MODIFIED_LABELS); + NotifyChanged(GameModificationType::GameLabels); } void GameDocument::DoSetInfosetLabel(GameInfoset p_infoset, const wxString &p_label) { p_infoset->SetLabel(p_label.ToStdString()); - UpdateViews(GBT_DOC_MODIFIED_LABELS); + NotifyChanged(GameModificationType::GameLabels); } void GameDocument::DoSetActionLabel(GameAction p_action, const wxString &p_label) { p_action->SetLabel(p_label.ToStdString()); - UpdateViews(GBT_DOC_MODIFIED_LABELS); + NotifyChanged(GameModificationType::GameLabels); } void GameDocument::DoSetActionProbs(GameInfoset p_infoset, const Array &p_probs) { m_game->SetChanceProbs(p_infoset, p_probs); - UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); + NotifyChanged(GameModificationType::GamePayoffs); } void GameDocument::DoSetInfoset(GameNode p_node, GameInfoset p_infoset) { m_game->SetInfoset(p_node, p_infoset); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoLeaveInfoset(GameNode p_node) { m_game->LeaveInfoset(p_node); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoRevealAction(GameInfoset p_infoset, GamePlayer p_player) { m_game->Reveal(p_infoset, p_player); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoInsertAction(GameNode p_node) @@ -556,43 +556,43 @@ void GameDocument::DoInsertAction(GameNode p_node) } const GameAction action = m_game->InsertAction(p_node->GetInfoset()); action->SetLabel(std::to_string(action->GetNumber())); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoSetNodeLabel(GameNode p_node, const wxString &p_label) { p_node->SetLabel(p_label.ToStdString()); - UpdateViews(GBT_DOC_MODIFIED_LABELS); + NotifyChanged(GameModificationType::GameLabels); } void GameDocument::DoAppendMove(GameNode p_node, GameInfoset p_infoset) { m_game->AppendMove(p_node, p_infoset); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoInsertMove(GameNode p_node, GamePlayer p_player, unsigned int p_actions) { m_game->InsertMove(p_node, p_player, p_actions, true); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoInsertMove(GameNode p_node, GameInfoset p_infoset) { m_game->InsertMove(p_node, p_infoset); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoCopyTree(GameNode p_destNode, GameNode p_srcNode) { m_game->CopyTree(p_destNode, p_srcNode); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoMoveTree(GameNode p_destNode, GameNode p_srcNode) { m_game->MoveTree(p_destNode, p_srcNode); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoDeleteParent(GameNode p_node) @@ -601,13 +601,13 @@ void GameDocument::DoDeleteParent(GameNode p_node) return; } m_game->DeleteParent(p_node); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoDeleteTree(GameNode p_node) { m_game->DeleteTree(p_node); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } void GameDocument::DoSetPlayer(GameInfoset p_infoset, GamePlayer p_player) @@ -615,7 +615,7 @@ void GameDocument::DoSetPlayer(GameInfoset p_infoset, GamePlayer p_player) if (!p_player->IsChance() && !p_infoset->GetPlayer()->IsChance()) { // Currently don't support switching nodes to/from chance player m_game->SetPlayer(p_infoset, p_player); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } } @@ -624,26 +624,26 @@ void GameDocument::DoSetPlayer(GameNode p_node, GamePlayer p_player) if (!p_player->IsChance() && !p_node->GetPlayer()->IsChance()) { // Currently don't support switching nodes to/from chance player m_game->SetPlayer(p_node->GetInfoset(), p_player); - UpdateViews(GBT_DOC_MODIFIED_GAME); + NotifyChanged(GameModificationType::GameForm); } } void GameDocument::DoNewOutcome(GameNode p_node) { m_game->SetOutcome(p_node, m_game->NewOutcome()); - UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); + NotifyChanged(GameModificationType::GamePayoffs); } void GameDocument::DoNewOutcome(const PureStrategyProfile &p_profile) { p_profile->SetOutcome(m_game->NewOutcome()); - UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); + NotifyChanged(GameModificationType::GamePayoffs); } void GameDocument::DoSetOutcome(GameNode p_node, GameOutcome p_outcome) { m_game->SetOutcome(p_node, p_outcome); - UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); + NotifyChanged(GameModificationType::GamePayoffs); } void GameDocument::DoRemoveOutcome(GameNode p_node) @@ -652,7 +652,7 @@ void GameDocument::DoRemoveOutcome(GameNode p_node) return; } m_game->SetOutcome(p_node, nullptr); - UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); + NotifyChanged(GameModificationType::GamePayoffs); } void GameDocument::DoCopyOutcome(GameNode p_node, GameOutcome p_outcome) @@ -663,13 +663,13 @@ void GameDocument::DoCopyOutcome(GameNode p_node, GameOutcome p_outcome) outcome->SetPayoff(player, p_outcome->GetPayoff(player)); } m_game->SetOutcome(p_node, outcome); - UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); + NotifyChanged(GameModificationType::GamePayoffs); } void GameDocument::DoSetPayoff(GameOutcome p_outcome, int p_player, const wxString &p_value) { p_outcome->SetPayoff(m_game->GetPlayer(p_player), Number(p_value.ToStdString())); - UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); + NotifyChanged(GameModificationType::GamePayoffs); } } // namespace Gambit::GUI diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 029827b7a..3e3dae16c 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -124,22 +124,45 @@ class StrategyDominanceStack { // GBT_DOC_MODIFIED_LABELS: Labeling of players, strategies, etc. has // changed. // -// GBT_DOC_MODIFIED_VIEWS: Information about how the document is viewed -// (e.g., player colors) has changed. -// // GBT_DOC_MODIFIED_WORKSPACE: Stored analysis workspace data has changed. // This affects workbook/workspace persistence, but does not modify the // underlying game model. // -using GameModificationType = enum { - GBT_DOC_MODIFIED_NONE = 0x00, - GBT_DOC_MODIFIED_GAME = 0x01, - GBT_DOC_MODIFIED_PAYOFFS = 0x02, - GBT_DOC_MODIFIED_LABELS = 0x04, - GBT_DOC_MODIFIED_VIEWS = 0x08, - GBT_DOC_MODIFIED_WORKSPACE = 0x10 +// GBT_DOC_MODIFIED_PRESENTATION: Settings on how information is rendered +// (for example, colors) have changed. +// +enum class GameModificationType : unsigned int { + None = 0x00, + GameForm = 0x01, + GamePayoffs = 0x02, + GameLabels = 0x04, + Workspace = 0x08, + Presentation = 0x10 }; +inline GameModificationType operator|(GameModificationType p_left, GameModificationType p_right) +{ + return static_cast(static_cast(p_left) | + static_cast(p_right)); +} + +inline GameModificationType operator&(GameModificationType p_left, GameModificationType p_right) +{ + return static_cast(static_cast(p_left) & + static_cast(p_right)); +} + +inline GameModificationType &operator|=(GameModificationType &p_left, GameModificationType p_right) +{ + p_left = p_left | p_right; + return p_left; +} + +inline bool HasModification(GameModificationType p_modifications, GameModificationType p_test) +{ + return static_cast(p_modifications & p_test) != 0; +} + class AnalysisWorkspace { GameDocument *m_doc; @@ -204,7 +227,8 @@ class GameDocument { AnalysisWorkspace m_workspace; - void UpdateViews(GameModificationType p_modifications); + void NotifyChanged(GameModificationType p_modifications); + void UpdateViews(); public: explicit GameDocument(Game p_game); From a55b000aca220b1de48e381c8ad66937a0b6e1f3 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 14:16:04 +0100 Subject: [PATCH 09/14] Refer to loading workspace document --- src/gui/app.cc | 2 +- src/gui/gamedoc.cc | 2 +- src/gui/gamedoc.h | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/gui/app.cc b/src/gui/app.cc index b4dde6baf..6c9582317 100644 --- a/src/gui/app.cc +++ b/src/gui/app.cc @@ -130,7 +130,7 @@ AppLoadResult Application::LoadFile(const wxString &p_filename, wxWindow *p_pare } auto *doc = new GameDocument(NewTree()); - if (doc->LoadDocument(p_filename)) { + if (doc->LoadWorkspace(p_filename)) { doc->SetFilename(p_filename); m_fileHistory.AddFileToHistory(p_filename); m_fileHistory.Save(*wxConfigBase::Get()); diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index d6c87a0ca..767c19087 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -219,7 +219,7 @@ GameDocument::GameDocument(Game p_game) GameDocument::~GameDocument() { wxGetApp().RemoveDocument(this); } -bool GameDocument::LoadDocument(const wxString &p_filename) +bool GameDocument::LoadWorkspace(const wxString &p_filename) { TiXmlDocument doc(p_filename.mb_str()); if (!doc.LoadFile()) { diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 3e3dae16c..4b7f719b3 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -235,12 +235,12 @@ class GameDocument { ~GameDocument(); //! - //! @name Reading and writing .gbt savefiles + //! @name Reading and writing savefiles //! //@{ - /// Load document from the specified file (which should be a .gbt file) + /// Load workspace from the specified file (which should be a .gbt file) /// Returns true if successful, false if error - bool LoadDocument(const wxString &p_filename); + bool LoadWorkspace(const wxString &p_filename); void SaveDocument(std::ostream &) const; //@} From 62f28219466556c55bf7f40931e70578af589224 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 14:18:46 +0100 Subject: [PATCH 10/14] Nips and tucks to GUI documentation on workspaces --- doc/gui.general.rst | 26 +++++--------------------- src/gui/gameframe.cc | 2 +- 2 files changed, 6 insertions(+), 22 deletions(-) diff --git a/doc/gui.general.rst b/doc/gui.general.rst index b42165f31..e96baa055 100644 --- a/doc/gui.general.rst +++ b/doc/gui.general.rst @@ -80,30 +80,14 @@ versions dating back to release 0.94 in 1995. (Users interested in the details of these file formats can consult :ref:`file-formats` for more information.) -Beginning with release 2005.12.xx, the graphical interface now reads -and writes a new file format, which is referred to as a"Gambit -workbook." This extended file format stores not only the +The graphical interface reads and writes an extended format, referred to +as a "Gambit workspace". +This stores not only the representation of the game, but also additional information, including parameters for laying out the game tree, the colors assigned to players, any equilibria or other analysis done on the game, and so -forth. So, for example, the workbook file can be used to store the +forth. So, for example, the workspace file can be used to store the analysis of a game and then return to it. These files by convention end in the extension .gbt. - The graphical interface will read files in all three formats: .gbt, -.efg, and .nfg. The "Save" and "Save as" commands, however, always -save in the Gambit workbook (.gbt) format. To save the game itself as -an extensive (.efg) or strategic (.nfg) game, use the items on the -"Export" submenu of the "File" menu. This is useful in interfacing -with older versions of Gambit, with other tools which read and write -those formats, and in using the underlying Gambit analysis command- -line tools directly, as those programs accept .efg or .nfg game files. -Users primarily interested in using Gambit solely via the graphical -interface are encouraged to use the workbook (.gbt) format. - - - -As it is a new format, the Gambit workbook format is still under -development and may change in details. It is intended that newer -versions of the graphical interface will still be able to read -workbook files written in older formats. +.efg, and .nfg. diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index 211be42b9..5e72b7c54 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -619,7 +619,7 @@ void GameFrame::OnFileOpen(wxCommandEvent &) { wxFileDialog dialog( this, _("Choose file to open"), wxGetApp().GetCurrentDir(), _T(""), - wxT("Gambit workbooks (*.gbt)|*.gbt|") wxT("Gambit extensive games (*.efg)|*.efg|") + wxT("Gambit workspaces (*.gbt)|*.gbt|") wxT("Gambit extensive games (*.efg)|*.efg|") wxT("Gambit strategic games (*.nfg)|*.nfg|") wxT("All files (*.*)|*.*")); if (dialog.ShowModal() == wxID_OK) { From 507a1d7977e61b1194433a87bb376f590a6add2e Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 14:19:10 +0100 Subject: [PATCH 11/14] workbook -> workspace --- doc/gui.efg.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/gui.efg.rst b/doc/gui.efg.rst index a863bdb1e..4878ec077 100644 --- a/doc/gui.efg.rst +++ b/doc/gui.efg.rst @@ -417,7 +417,7 @@ To select the font used to draw the labels in the tree, select :menuselection:`Format --> Font`. The standard font selection dialog for the operating system is displayed, showing the fonts available on the system. Since -available fonts vary across systems, when opening a workbook on a +available fonts vary across systems, when opening a workspace on a system different from the system on which it was saved, Gambit tries to match the font style as closely as possible when the original font is not available. From 148d38ca5ec9c7bca8f8147e100cec53db0f0d77 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 14:20:57 +0100 Subject: [PATCH 12/14] save workspace not document --- src/gui/gamedoc.cc | 6 +++--- src/gui/gamedoc.h | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 767c19087..a4b6c8f03 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -214,7 +214,7 @@ GameDocument::GameDocument(Game p_game) wxGetApp().AddDocument(this); std::ostringstream s; - SaveDocument(s); + SaveWorkspace(s); } GameDocument::~GameDocument() { wxGetApp().RemoveDocument(this); } @@ -297,7 +297,7 @@ bool GameDocument::LoadWorkspace(const wxString &p_filename) return true; } -void GameDocument::SaveDocument(std::ostream &p_file) const +void GameDocument::SaveWorkspace(std::ostream &p_file) const { p_file << "\n"; @@ -452,7 +452,7 @@ void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format) } switch (p_format) { case GameSaveFormat::Workbook: - SaveDocument(file); + SaveWorkspace(file); m_filename = p_filename; m_gameModified = false; m_workspaceModified = false; diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 4b7f719b3..775962060 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -241,7 +241,7 @@ class GameDocument { /// Load workspace from the specified file (which should be a .gbt file) /// Returns true if successful, false if error bool LoadWorkspace(const wxString &p_filename); - void SaveDocument(std::ostream &) const; + void SaveWorkspace(std::ostream &) const; //@} Game GetGame() const { return m_game; } From 00308db2f71deb1278a5cf6855fae851982ede80 Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Wed, 3 Jun 2026 14:42:32 +0100 Subject: [PATCH 13/14] Have application exit close windows one by one let each one veto if needed, rather than a general "there are modifications" --- src/gui/app.cc | 6 ------ src/gui/app.h | 1 - src/gui/gamedoc.cc | 2 +- src/gui/gamedoc.h | 4 ++-- src/gui/gameframe.cc | 27 +++++++++++++-------------- 5 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/gui/app.cc b/src/gui/app.cc index 6c9582317..ecc083302 100644 --- a/src/gui/app.cc +++ b/src/gui/app.cc @@ -162,12 +162,6 @@ void Application::SetCurrentDir(const wxString &p_dir) wxConfigBase::Get()->Write(_T("/General/CurrentDirectory"), p_dir); } -bool Application::AreDocumentsModified() const -{ - return std::any_of(m_documents.begin(), m_documents.end(), - std::mem_fn(&GameDocument::IsModified)); -} - } // namespace Gambit::GUI IMPLEMENT_APP(Gambit::GUI::Application) diff --git a/src/gui/app.h b/src/gui/app.h index 144f8472f..90824c14e 100644 --- a/src/gui/app.h +++ b/src/gui/app.h @@ -78,7 +78,6 @@ class Application final : public wxApp { { m_documents.erase(std::find(m_documents.begin(), m_documents.end(), p_doc)); } - bool AreDocumentsModified() const; //@} }; diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index a4b6c8f03..536b967c9 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -451,7 +451,7 @@ void GameDocument::DoSave(const wxString &p_filename, GameSaveFormat p_format) static_cast(p_filename.mb_str())); } switch (p_format) { - case GameSaveFormat::Workbook: + case GameSaveFormat::Workspace: SaveWorkspace(file); m_filename = p_filename; m_gameModified = false; diff --git a/src/gui/gamedoc.h b/src/gui/gamedoc.h index 775962060..904bb05a6 100644 --- a/src/gui/gamedoc.h +++ b/src/gui/gamedoc.h @@ -282,7 +282,7 @@ class GameDocument { void PostPendingChanges(); /// Operations on game model - enum class GameSaveFormat { Efg, Nfg, Workbook }; + enum class GameSaveFormat { Efg, Nfg, Workspace }; GameSaveFormat GetCurrentSaveFormat() const { if (m_filename.EndsWith(".efg")) { @@ -291,7 +291,7 @@ class GameDocument { if (m_filename.EndsWith(".nfg")) { return GameSaveFormat::Nfg; } - return GameSaveFormat::Workbook; + return GameSaveFormat::Workspace; } void DoSave(const wxString &p_filename, GameSaveFormat p_format); void DoSetTitle(const wxString &p_title, const wxString &p_comment); diff --git a/src/gui/gameframe.cc b/src/gui/gameframe.cc index 5e72b7c54..89ea8839a 100644 --- a/src/gui/gameframe.cc +++ b/src/gui/gameframe.cc @@ -653,7 +653,7 @@ void GameFrame::OnFileSave(wxCommandEvent &p_event) wxFileDialog dialog( this, _("Save game as"), wxPathOnly(currentFilename), wxFileNameFromPath(currentFilename), - wxT("Gambit workbooks (*.gbt)|*.gbt|") wxT("Gambit extensive games (*.efg)|*.efg|") + wxT("Gambit workspaces (*.gbt)|*.gbt|") wxT("Gambit extensive games (*.efg)|*.efg|") wxT("Gambit strategic games (*.nfg)|*.nfg|") wxT("All files (*.*)|*.*"), wxFD_SAVE | wxFD_OVERWRITE_PROMPT); @@ -678,7 +678,7 @@ void GameFrame::OnFileSave(wxCommandEvent &p_event) break; } } - GameDocument::GameSaveFormat format = GameDocument::GameSaveFormat::Workbook; + GameDocument::GameSaveFormat format = GameDocument::GameSaveFormat::Workspace; if (filename.GetExt() == wxT("efg")) { format = GameDocument::GameSaveFormat::Efg; } @@ -856,19 +856,19 @@ void GameFrame::OnFileExportSVG(wxCommandEvent &) } } -void GameFrame::OnFileExit(wxCommandEvent &p_event) +void GameFrame::OnFileExit(wxCommandEvent &) { - if (wxGetApp().AreDocumentsModified()) { - if (wxMessageBox(wxT("There are modified games.\n") wxT("Any unsaved changes will be lost!\n") - wxT("Close anyway?"), - _("Warning"), wxOK | wxCANCEL) == wxCANCEL) { + while (auto *topWindow = wxGetApp().GetTopWindow()) { + topWindow->Raise(); + const auto before = wxGetApp().GetTopWindow(); + topWindow->Close(); + + // The close was vetoed, or otherwise did not destroy/advance the top window. + // In that case, abort application exit. + if (wxGetApp().GetTopWindow() == before) { return; } } - - while (wxGetApp().GetTopWindow()) { - delete wxGetApp().GetTopWindow(); - } } void GameFrame::OnFileMRUFile(wxCommandEvent &p_event) @@ -1285,12 +1285,11 @@ wxString CloseWarningMessage(GameDocument *p_doc) } if (!p_doc->IsGameModified() && p_doc->IsWorkspaceModified()) { return _("There are unsaved computational results.\n\n" - "These changes are not saved in ordinary game files.\n" + "These can be saved in a Gambit workspace file.\n" "Close without saving?"); } if (p_doc->IsGameModified() && p_doc->IsWorkspaceModified()) { - return _("This game has unsaved changes, and there are unsaved computational results " - "unsaved computational results or workspace changes.\n\n" + return _("This game has unsaved changes, and there are unsaved computational results.\n\n" "Close without saving?"); } return wxEmptyString; From 611956bdbb5bb2439beed266e19ea28229e2f06e Mon Sep 17 00:00:00 2001 From: Theodore Turocy Date: Fri, 19 Jun 2026 14:28:45 +0100 Subject: [PATCH 14/14] Merge fixups --- src/gui/efgdisplay.cc | 4 ++-- src/gui/efgprofile.cc | 3 ++- src/gui/gamedoc.cc | 8 ++++---- src/gui/nfgprofile.cc | 11 ++++++----- 4 files changed, 14 insertions(+), 12 deletions(-) diff --git a/src/gui/efgdisplay.cc b/src/gui/efgdisplay.cc index 01d7689f6..cfd9222ba 100644 --- a/src/gui/efgdisplay.cc +++ b/src/gui/efgdisplay.cc @@ -126,7 +126,7 @@ void OutcomeEditorPopup::BuildControls() const Game game = m_doc->GetGame(); - for (size_t player = 1; player <= m_doc->NumPlayers(); ++player) { + for (size_t player = 1; player <= m_doc->GetGame()->NumPlayers(); ++player) { const GamePlayer gamePlayer = game->GetPlayer(player); payoffSizer->Add(new wxStaticText(m_contentPanel, wxID_ANY, @@ -1073,7 +1073,7 @@ void EfgDisplay::OnLeftDoubleClick(wxMouseEvent &p_event) if (node->GetOutcome()) { auto entry = m_layout.GetNodeEntry(node); - for (size_t player = 1; player <= m_doc->NumPlayers(); ++player) { + for (size_t player = 1; player <= m_doc->GetGame()->NumPlayers(); ++player) { if (entry->GetPayoffExtent(player).Contains(x, y)) { initialPlayer = static_cast(player); break; diff --git a/src/gui/efgprofile.cc b/src/gui/efgprofile.cc index 7fe2cb070..de034d739 100644 --- a/src/gui/efgprofile.cc +++ b/src/gui/efgprofile.cc @@ -134,7 +134,8 @@ void MixedBehaviorProfileList::UpdateCells() for (int col = 0; col < GetNumberCols(); ++col) { SetCellValue( row, col, - wxString(m_doc->GetProfiles().GetActionProb(col + 1, row + 1).c_str(), *wxConvCurrent)); + wxString(m_doc->GetWorkspace().GetProfiles().GetActionProb(col + 1, row + 1).c_str(), + *wxConvCurrent)); wxGridCellAttr *attr = new wxGridCellAttr; attr->SetFont(row + 1 == currentProfile ? boldFont : normalFont); diff --git a/src/gui/gamedoc.cc b/src/gui/gamedoc.cc index 391151f85..f971996e2 100644 --- a/src/gui/gamedoc.cc +++ b/src/gui/gamedoc.cc @@ -653,7 +653,7 @@ void GameDocument::DoSetOutcomeData(const GameNode &p_node, const wxString &p_la return; } - if (p_payoffs.size() != NumPlayers()) { + if (p_payoffs.size() != GetGame()->NumPlayers()) { throw std::invalid_argument("Incorrect number of payoff values"); } @@ -673,7 +673,7 @@ void GameDocument::DoSetOutcomeData(const GameNode &p_node, const wxString &p_la changed = outcome->GetLabel() != label; if (!changed) { - for (size_t player = 1; player <= NumPlayers(); ++player) { + for (size_t player = 1; player <= GetGame()->NumPlayers(); ++player) { if (outcome->GetPayoff(GetGame()->GetPlayer(player)) != parsedPayoffs[player - 1]) { changed = true; @@ -694,11 +694,11 @@ void GameDocument::DoSetOutcomeData(const GameNode &p_node, const wxString &p_la outcome->SetLabel(label); - for (size_t player = 1; player <= NumPlayers(); ++player) { + for (size_t player = 1; player <= GetGame()->NumPlayers(); ++player) { outcome->SetPayoff(GetGame()->GetPlayer(player), Number(p_payoffs[player - 1].ToStdString())); } - UpdateViews(GBT_DOC_MODIFIED_PAYOFFS); + NotifyChanged(GameModificationType::GamePayoffs); } void GameDocument::DoRemoveOutcome(GameNode p_node) diff --git a/src/gui/nfgprofile.cc b/src/gui/nfgprofile.cc index be17722b0..1a83908c5 100644 --- a/src/gui/nfgprofile.cc +++ b/src/gui/nfgprofile.cc @@ -59,7 +59,7 @@ MixedStrategyProfileList::~MixedStrategyProfileList() = default; void MixedStrategyProfileList::OnLabelClick(wxGridEvent &p_event) { if (p_event.GetCol() == -1) { - m_doc->DoSelectProfile(RowToProfile(p_event.GetRow())); + m_doc->DoSelectProfile(p_event.GetRow() + 1); } ClearSelection(); @@ -67,7 +67,7 @@ void MixedStrategyProfileList::OnLabelClick(wxGridEvent &p_event) void MixedStrategyProfileList::OnCellClick(wxGridEvent &p_event) { - m_doc->SetCurrentProfile(p_event.GetRow() + 1); + m_doc->DoSelectProfile(p_event.GetRow() + 1); ClearSelection(); } @@ -152,9 +152,10 @@ void MixedStrategyProfileList::UpdateCells() const int profile = row + 1; for (int col = 0; col < GetNumberCols(); ++col) { - SetCellValue(row, col, - wxString(m_doc->GetProfiles().GetStrategyProb(col + 1, profile).c_str(), - *wxConvCurrent)); + SetCellValue( + row, col, + wxString(m_doc->GetWorkspace().GetProfiles().GetStrategyProb(col + 1, profile).c_str(), + *wxConvCurrent)); wxGridCellAttr *attr = new wxGridCellAttr; attr->SetFont(profile == currentProfile ? boldFont : normalFont);