From ff2b4181f11f026e231a48b6c141ca419a5a625d Mon Sep 17 00:00:00 2001 From: drdkad Date: Sat, 18 Jul 2026 20:31:07 +0100 Subject: [PATCH 1/4] Make GameStrategyRep::m_behav a sparse map from information set to chosen action, holding only the sets a strategy specifies Render strategy labels lazily from the behaviour map --- src/games/game.cc | 15 +++++---------- src/games/game.h | 29 ++++++++++++++++++++++++++--- src/games/gametree.cc | 2 +- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/src/games/game.cc b/src/games/game.cc index f912c8179..5e2bd9a08 100644 --- a/src/games/game.cc +++ b/src/games/game.cc @@ -94,14 +94,8 @@ void GamePlayerRep::MakeStrategy(const std::map &behav) { auto strategy = std::make_shared(this, m_strategies.size() + 1, ""); strategy->m_behav = behav; - for (const auto &infoset : m_infosets) { - strategy->m_label += (contains(strategy->m_behav, infoset.get())) - ? std::to_string(strategy->m_behav[infoset.get()]) - : "*"; - } - if (strategy->m_label.empty()) { - strategy->m_label = "*"; - } + // A derived-label strategy: the text label is rendered from m_behav on the first GetLabel() + strategy->m_label.reset(); m_strategies.push_back(strategy); } @@ -305,8 +299,9 @@ MixedStrategyProfile::MixedStrategyProfile(const MixedBehaviorProfile &p_p for (const auto &strategy : player->m_strategies) { auto prob = static_cast(1); for (const auto &infoset : player->m_infosets) { - if (strategy->m_behav[infoset.get()] > 0) { - prob *= p_profile[infoset->GetAction(strategy->m_behav[infoset.get()])]; + if (contains(strategy->m_behav, infoset.get()) && + strategy->m_behav.at(infoset.get()) > 0) { + prob *= p_profile[infoset->GetAction(strategy->m_behav.at(infoset.get()))]; } } (*m_rep)[strategy] = prob; diff --git a/src/games/game.h b/src/games/game.h index bd598b723..21a5b1d2d 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -28,6 +28,7 @@ #include #include #include +#include #include "number.h" #include "gameobject.h" @@ -343,7 +344,9 @@ class GameStrategyRep : public std::enable_shared_from_this { bool m_valid{true}; GamePlayerRep *m_player; int m_number; - std::string m_label; + /// Stored label or std::nullopt for a strategy whose label is derived: strategies created by + /// GamePlayerRep::MakeStrategy() render a label from m_behav on the first call to GetLabel() + mutable std::optional m_label; std::map m_behav; public: @@ -363,7 +366,7 @@ class GameStrategyRep : public std::enable_shared_from_this { /// @name Data access //@{ /// Returns the text label associated with the strategy - const std::string &GetLabel() const { return m_label; } + const std::string &GetLabel() const; /// Sets the text label associated with the strategy void SetLabel(const std::string &p_label); @@ -1318,7 +1321,14 @@ inline GamePlayer GameStrategyRep::GetPlayer() const { return m_player->shared_f inline Game GameStrategyRep::GetGame() const { return m_player->GetGame(); } inline void GameStrategyRep::SetLabel(const std::string &p_label) { - if (p_label == m_label) { + // Compare against GetLabel(), not m_label: a derived-label strategy stores no label, + // so the raw comparison is vacuously false and setting a label + // equal to the rendered one would fall through to CheckStrategyLabel, where + // the strategy would spuriously collide with itself. (The infoset SetLabel + // excludes self with `!= this` inside its own loop; the strategy loop lives + // in CheckStrategyLabel, shared with NewStrategy where no self exists yet, + // so this early return is the self-exclusion.) + if (p_label == GetLabel()) { return; } GetPlayer()->CheckStrategyLabel(p_label); @@ -1474,6 +1484,19 @@ inline GamePlayerRep::Infosets GamePlayerRep::GetInfosets() const inline Game GameSubgameRep::GetGame() const { return m_game->shared_from_this(); } +inline const std::string &GameStrategyRep::GetLabel() const +{ + if (!m_label) { + std::string label; + for (const auto &infoset : m_player->GetInfosets()) { + const auto action = m_behav.find(infoset.get()); + label += (action != m_behav.end()) ? std::to_string(action->second) : "*"; + } + m_label = label.empty() ? "*" : std::move(label); + } + return *m_label; +} + //======================================================================= /// Factory function to create new game tree diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 535d1db19..84d6ec0fd 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -1741,7 +1741,7 @@ Rational TreePureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const for (const auto &player : m_game->GetPlayers()) { for (const auto &infoset : player->GetInfosets()) { try { - behav.SetAction(infoset->GetAction(GetStrategy(player)->m_behav[infoset.get()])); + behav.SetAction(infoset->GetAction(GetStrategy(player)->m_behav.at(infoset.get()))); } catch (std::out_of_range &) { } From a5a35de314b0d7d82ddfca686f4fb467617a69e2 Mon Sep 17 00:00:00 2001 From: drdkad Date: Mon, 20 Jul 2026 12:54:20 +0100 Subject: [PATCH 2/4] update ChangeLog --- ChangeLog | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ChangeLog b/ChangeLog index 19965ca19..d537d4f13 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,9 @@ +## [Unreleased] + +### Changed +- Labels for reduced strategies are now rendered lazily on first access rather than + eagerly during strategy generation. Strategy labels are unchanged. + ## [16.7.0] - 2026-07-11 ### Added From 640814e8c7b9a98675f1d738fbc8884056728c7e Mon Sep 17 00:00:00 2001 From: drdkad Date: Mon, 20 Jul 2026 12:57:51 +0100 Subject: [PATCH 3/4] small polish --- src/games/game.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/games/game.h b/src/games/game.h index 21a5b1d2d..f909c541b 100644 --- a/src/games/game.h +++ b/src/games/game.h @@ -1321,13 +1321,6 @@ inline GamePlayer GameStrategyRep::GetPlayer() const { return m_player->shared_f inline Game GameStrategyRep::GetGame() const { return m_player->GetGame(); } inline void GameStrategyRep::SetLabel(const std::string &p_label) { - // Compare against GetLabel(), not m_label: a derived-label strategy stores no label, - // so the raw comparison is vacuously false and setting a label - // equal to the rendered one would fall through to CheckStrategyLabel, where - // the strategy would spuriously collide with itself. (The infoset SetLabel - // excludes self with `!= this` inside its own loop; the strategy loop lives - // in CheckStrategyLabel, shared with NewStrategy where no self exists yet, - // so this early return is the self-exclusion.) if (p_label == GetLabel()) { return; } From 7acb3d58d79d7d9087e6dcf182d63bbac8dfbaed Mon Sep 17 00:00:00 2001 From: drdkad Date: Mon, 20 Jul 2026 13:22:56 +0100 Subject: [PATCH 4/4] fix version in ChangeLog --- ChangeLog | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ChangeLog b/ChangeLog index d537d4f13..d67989459 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,4 +1,4 @@ -## [Unreleased] +## [17.0.0] - unreleased ### Changed - Labels for reduced strategies are now rendered lazily on first access rather than