diff --git a/ChangeLog b/ChangeLog index 890bc5a63b..fe2989fded 100644 --- a/ChangeLog +++ b/ChangeLog @@ -18,6 +18,8 @@ ### Changed - All long-running calculations are now done in a thread in the GUI process, rather than calling the command-line executables as external processes. (#990) +- When displaying a strategic form, the graphical interface preserves decimal payoffs when those are + what are specified in the game, whenever possible. (#867) ### Removed - Assigning to `Strategy.label` has been removed; use `Game.relabel_strategies`, which enforces diff --git a/src/games/behavpure.cc b/src/games/behavpure.cc index f486f179ee..e013dc0d42 100644 --- a/src/games/behavpure.cc +++ b/src/games/behavpure.cc @@ -73,6 +73,29 @@ T PureBehaviorProfile::GetPayoff(const GameNode &p_node, const GamePlayer &p_pla template double PureBehaviorProfile::GetPayoff(const GameNode &, const GamePlayer &) const; template Rational PureBehaviorProfile::GetPayoff(const GameNode &, const GamePlayer &) const; +GameOutcome PureBehaviorProfile::GetOutcome(const GameNode &p_start) const +{ + GameOutcome outcome; + GameNode node = p_start; + while (true) { + if (node->GetOutcome()) { + if (outcome) { + // More than one outcome-bearing node along this path; the combined + // payoff cannot be attributed to a single outcome's preserved text. + throw UndefinedException(); + } + outcome = node->GetOutcome(); + } + if (node->IsTerminal()) { + return outcome; + } + if (node->GetInfoset()->IsChanceInfoset()) { + throw UndefinedException(); + } + node = node->GetChild(m_profile.at(node->GetInfoset())); + } +} + MixedBehaviorProfile PureBehaviorProfile::ToMixedBehaviorProfile() const { MixedBehaviorProfile temp(m_efg); diff --git a/src/games/behavpure.h b/src/games/behavpure.h index 83dda90920..fbbb706a6a 100644 --- a/src/games/behavpure.h +++ b/src/games/behavpure.h @@ -62,6 +62,13 @@ class PureBehaviorProfile { /// Get the payoff to the player, conditional on reaching a node template T GetPayoff(const GameNode &, const GamePlayer &) const; + /// Get the outcome that results from the profile, provided this can be + /// determined without resolving a chance move and without combining more + /// than one outcome along the induced path; throws UndefinedException otherwise. + GameOutcome GetOutcome() const { return GetOutcome(m_efg->GetRoot()); } + /// Get the outcome that results from the profile, conditional on reaching a node + GameOutcome GetOutcome(const GameNode &) const; + /// Convert to a mixed behavior representation MixedBehaviorProfile ToMixedBehaviorProfile() const; //@} diff --git a/src/games/gametree.cc b/src/games/gametree.cc index 419ed9c495..4ad5813de3 100644 --- a/src/games/gametree.cc +++ b/src/games/gametree.cc @@ -1921,9 +1921,25 @@ class TreePureStrategyProfileRep : public PureStrategyProfileRep { return std::make_shared(*this); } + /// Build the behavior profile induced by this pure strategy profile + PureBehaviorProfile GetBehaviorProfile() const + { + PureBehaviorProfile behav(m_game); + for (const auto &player : m_game->GetPlayers()) { + for (const auto &infoset : player->GetInfosets()) { + try { + behav.SetAction(infoset->GetAction(GetStrategy(player)->m_behav.at(infoset.get()))); + } + catch (std::out_of_range &) { + } + } + } + return behav; + } + public: TreePureStrategyProfileRep(const Game &p_game) : PureStrategyProfileRep(p_game) {} - GameOutcome GetOutcome() const override { throw UndefinedException(); } + GameOutcome GetOutcome() const override; void SetOutcome(GameOutcome p_outcome) override { throw UndefinedException(); } Rational GetPayoff(const GamePlayer &) const override; Rational GetStrategyValue(const GameStrategy &) const override; @@ -1944,19 +1960,14 @@ PureStrategyProfile GameTreeRep::NewPureStrategyProfile() const // TreePureStrategyProfileRep: Data access and manipulation //------------------------------------------------------------------------ +GameOutcome TreePureStrategyProfileRep::GetOutcome() const +{ + return GetBehaviorProfile().GetOutcome(); +} + Rational TreePureStrategyProfileRep::GetPayoff(const GamePlayer &p_player) const { - PureBehaviorProfile behav(m_game); - for (const auto &player : m_game->GetPlayers()) { - for (const auto &infoset : player->GetInfosets()) { - try { - behav.SetAction(infoset->GetAction(GetStrategy(player)->m_behav.at(infoset.get()))); - } - catch (std::out_of_range &) { - } - } - } - return behav.GetPayoff(p_player); + return GetBehaviorProfile().GetPayoff(p_player); } Rational TreePureStrategyProfileRep::GetStrategyValue(const GameStrategy &p_strategy) const diff --git a/src/gui/nfgtable.cc b/src/gui/nfgtable.cc index 5c4502c829..8533927d62 100644 --- a/src/gui/nfgtable.cc +++ b/src/gui/nfgtable.cc @@ -1010,8 +1010,16 @@ class PayoffTable final : public wxGridTableBase { wxString PayoffTable::GetValue(int row, int col) { const PureStrategyProfile profile = m_table->GetPayoffProfile(row, col); - auto player = m_table->GetPayoffPlayer(col); - return wxString::FromUTF8(lexical_cast(profile->GetPayoff(player))); + const auto player = m_table->GetPayoffPlayer(col); + try { + if (const auto outcome = profile->GetOutcome()) { + return wxString::FromUTF8(outcome->GetPayoff(player)); + } + return wxString::FromUTF8("0"); + } + catch (const UndefinedException &) { + return wxString::FromUTF8(lexical_cast(profile->GetPayoff(player))); + } } void PayoffTable::SetValue(int row, int col, const wxString &value)