// // $Source: /cvsroot/gambit/gambit/sources/gui/efglayout.cc,v $ // $Date: 2007/01/08 19:22:07 $ // $Revision: 1.91 $ // // DESCRIPTION: // Implementation of tree layout representation // // This file is part of Gambit // Copyright (c) 2002, The Gambit Project // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. // #include #include #ifndef WX_PRECOMP #include #endif // WX_PRECOMP #include "libgambit/libgambit.h" #include "efgdisplay.h" //----------------------------------------------------------------------- // class gbtNodeEntry: Member functions //----------------------------------------------------------------------- gbtNodeEntry::gbtNodeEntry(Gambit::GameNode p_node) : m_node(p_node), m_parent(0), m_x(-1), m_y(-1), m_nextMember(0), m_inSupport(true), m_size(20), m_token(GBT_NODE_TOKEN_CIRCLE), m_branchStyle(GBT_BRANCH_STYLE_LINE), m_branchLabel(GBT_BRANCH_LABEL_HORIZONTAL), m_branchLength(0), m_sublevel(0), m_actionProb(0) { } int gbtNodeEntry::GetChildNumber(void) const { if (m_node->GetParent()) { return m_node->GetPriorAction()->GetNumber(); } else { return 0; } } // // Draws the node token itself, as well as the incoming branch // (if not the root node) // void gbtNodeEntry::Draw(wxDC &p_dc, Gambit::GameNode p_selection, bool p_noHints) const { if (m_node->GetParent() && m_inSupport) { DrawIncomingBranch(p_dc); } else { m_branchAboveRect = wxRect(); m_branchBelowRect = wxRect(); } p_dc.SetPen(*wxThePenList->FindOrCreatePen(m_color, (p_selection == m_node) ? 6 : 3, wxSOLID)); p_dc.SetTextForeground(m_color); if (m_token == GBT_NODE_TOKEN_LINE) { p_dc.DrawLine(m_x, m_y, m_x + m_size, m_y); if (m_branchStyle == GBT_BRANCH_STYLE_FORKTINE) { // "classic" Gambit style: draw a small 'token' to separate // the fork from the node p_dc.DrawEllipse(m_x - 1, m_y - 1, 3, 3); } } else if (m_token == GBT_NODE_TOKEN_BOX) { p_dc.SetBrush(*wxWHITE_BRUSH); p_dc.DrawRectangle(m_x, m_y - m_size / 2, m_size, m_size); } else if (m_token == GBT_NODE_TOKEN_DIAMOND) { wxPoint points[4] = { wxPoint(m_x + m_size / 2, m_y - m_size / 2), wxPoint(m_x, m_y), wxPoint(m_x + m_size / 2, m_y + m_size / 2), wxPoint(m_x + m_size, m_y) }; p_dc.SetBrush(*wxWHITE_BRUSH); p_dc.DrawPolygon(4, points); } else if (m_token == GBT_NODE_TOKEN_DOT) { p_dc.SetBrush(wxBrush(m_color, wxSOLID)); p_dc.DrawEllipse(m_x, m_y - m_size / 2, m_size, m_size); } else { // Default: draw circles p_dc.SetBrush(*wxWHITE_BRUSH); p_dc.DrawEllipse(m_x, m_y - m_size / 2, m_size, m_size); } int textWidth, textHeight; p_dc.SetFont(m_nodeAboveFont); p_dc.GetTextExtent(m_nodeAboveLabel, &textWidth, &textHeight); p_dc.DrawText(m_nodeAboveLabel, m_x + (m_size - textWidth) / 2, m_y - textHeight - 9); p_dc.SetFont(m_nodeBelowFont); p_dc.GetTextExtent(m_nodeBelowLabel, &textWidth, &textHeight); p_dc.DrawText(m_nodeBelowLabel, m_x + (m_size - textWidth) / 2, m_y + 9); p_dc.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD)); DrawOutcome(p_dc, p_noHints); } void gbtNodeEntry::DrawIncomingBranch(wxDC &p_dc) const { int xStart = m_parent->m_x + m_parent->m_size; int xEnd = m_x; int yStart = m_parent->m_y; int yEnd = m_y; p_dc.SetPen(*wxThePenList->FindOrCreatePen(m_parent->m_color, 4, wxSOLID)); p_dc.SetTextForeground(m_parent->m_color); if (m_branchStyle == GBT_BRANCH_STYLE_LINE) { p_dc.DrawLine(xStart, yStart, xEnd, yEnd); // Draw in the highlight indicating action probabilities if (m_actionProb >= Gambit::Rational(0)) { p_dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4, wxSOLID)); p_dc.DrawLine(xStart, yStart, xStart + (int) ((double) (xEnd - xStart) * (double) m_actionProb), yStart + (int) ((double) (yEnd - yStart) * (double) m_actionProb)); } int textWidth, textHeight; p_dc.SetFont(m_branchAboveFont); p_dc.GetTextExtent(m_branchAboveLabel, &textWidth, &textHeight); // The angle of the branch double theta = -atan((double) (yEnd - yStart) / (double) (xEnd - xStart)); // The "centerpoint" of the branch int xbar = (xStart + xEnd) / 2; int ybar = (yStart + yEnd) / 2; if (m_branchLabel == GBT_BRANCH_LABEL_HORIZONTAL) { if (yStart >= yEnd) { p_dc.DrawText(m_branchAboveLabel, xbar - textWidth / 2, ybar - textHeight + textWidth / 2 * (yEnd - yStart) / (xEnd - xStart)); m_branchAboveRect = wxRect(xbar - textWidth / 2, ybar - textHeight + textWidth / 2 * (yEnd - yStart) / (xEnd - xStart), textWidth, textHeight); } else { p_dc.DrawText(m_branchAboveLabel, xbar - textWidth / 2, ybar - textHeight - textWidth / 2 * (yEnd - yStart) / (xEnd - xStart)); m_branchAboveRect = wxRect(xbar - textWidth / 2, ybar - textHeight - textWidth / 2 * (yEnd - yStart) / (xEnd - xStart), textWidth, textHeight); } } else { // Draw the text rotated appropriately p_dc.DrawRotatedText(m_branchAboveLabel, (int) ((double) xbar - (double) textHeight * sin(theta) - (double) textWidth * cos(theta) / 2.0), (int) ((double) ybar - (double) textHeight * cos(theta) + (double) textWidth * sin(theta) / 2.0), theta * 180.0 / 3.14159); m_branchAboveRect = wxRect(); } p_dc.SetFont(m_branchBelowFont); p_dc.GetTextExtent(m_branchBelowLabel, &textWidth, &textHeight); if (m_branchLabel == GBT_BRANCH_LABEL_HORIZONTAL) { if (yStart >= yEnd) { p_dc.DrawText(m_branchBelowLabel, xbar - textWidth / 2, ybar - textWidth/2 * (yEnd - yStart) / (xEnd - xStart)); m_branchBelowRect = wxRect(xbar - textWidth / 2, ybar - textWidth/2 * (yEnd - yStart) / (xEnd - xStart), textWidth, textHeight); } else { p_dc.DrawText(m_branchBelowLabel, xbar - textWidth / 2, ybar + textWidth/2 * (yEnd - yStart) / (xEnd - xStart)); m_branchBelowRect = wxRect(xbar - textWidth / 2, ybar + textWidth/2 * (yEnd - yStart) / (xEnd - xStart), textWidth, textHeight); } } else { // Draw the text rotated appropriately p_dc.DrawRotatedText(m_branchBelowLabel, (int) ((double) xbar - (double) textWidth * cos(theta) / 2.0), (int) ((double) ybar + (double) textWidth * sin(theta) / 2.0), theta * 180.0 / 3.14159); m_branchBelowRect = wxRect(); } } else { // Old style fork-tine p_dc.DrawLine(xStart, yStart, xStart + m_branchLength, yEnd); p_dc.DrawLine(xStart + m_branchLength, yEnd, xEnd, yEnd); // Draw in the highlight indicating action probabilities if (m_actionProb >= Gambit::Rational(0)) { p_dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2, wxSOLID)); p_dc.DrawLine(xStart, yStart, xStart + (int) ((double) m_branchLength * (double) m_actionProb), yStart + (int) ((double) (yEnd - yStart) * (double) m_actionProb)); } int textWidth, textHeight; p_dc.SetFont(m_branchAboveFont); p_dc.GetTextExtent(m_branchAboveLabel, &textWidth, &textHeight); p_dc.DrawText(m_branchAboveLabel, xStart + m_branchLength + 3, yEnd - textHeight - 3); m_branchAboveRect = wxRect(xStart + m_branchLength + 3, yEnd - textHeight - 3, textWidth, textHeight); p_dc.SetFont(m_branchBelowFont); p_dc.GetTextExtent(m_branchBelowLabel, &textWidth, &textHeight); p_dc.DrawText(m_branchBelowLabel, xStart + m_branchLength + 3, yEnd + 3); m_branchBelowRect = wxRect(xStart + m_branchLength + 3, yEnd + +3, textWidth, textHeight); } } static wxPoint DrawFraction(wxDC &p_dc, wxPoint p_point, const Gambit::Rational &p_value) { p_dc.SetFont(wxFont(7, wxSWISS, wxNORMAL, wxBOLD)); int numWidth, numHeight; wxString num = wxString(ToText(p_value.numerator()).c_str(), *wxConvCurrent); p_dc.GetTextExtent(num, &numWidth, &numHeight); int denWidth, denHeight; wxString den = wxString(ToText(p_value.denominator()).c_str(), *wxConvCurrent); p_dc.GetTextExtent(den, &denWidth, &denHeight); int width = ((numWidth > denWidth) ? numWidth : denWidth); p_dc.DrawLine(p_point.x, p_point.y, p_point.x + width + 4, p_point.y); p_dc.DrawText(num, p_point.x + 2 + (width - numWidth) / 2, p_point.y - 2 - numHeight); p_dc.DrawText(den, p_point.x + 2 + (width - denWidth) / 2, p_point.y + 2); p_point.x += width + 14; return p_point; } void gbtNodeEntry::DrawOutcome(wxDC &p_dc, bool p_noHints) const { wxPoint point(m_x + m_size + 20, m_y); Gambit::GameOutcome outcome = m_node->GetOutcome(); if (!outcome) { if (p_noHints) return; int width, height; p_dc.SetFont(wxFont(9, wxSWISS, wxITALIC, wxBOLD)); p_dc.SetTextForeground(*wxLIGHT_GREY); p_dc.GetTextExtent(wxT("(u)"), &width, &height); p_dc.DrawText(wxT("(u)"), point.x, point.y - height / 2); m_outcomeRect = wxRect(point.x, point.y - height / 2, width, height); m_payoffRect = Gambit::Array(); return; } int width, height = 25; m_payoffRect = Gambit::Array(); for (int pl = 1; pl <= m_node->GetGame()->NumPlayers(); pl++) { Gambit::GamePlayer player = m_node->GetGame()->GetPlayer(pl); p_dc.SetTextForeground(m_style->GetPlayerColor(pl)); std::string payoff = outcome->GetPayoff(pl); if (payoff.find('/') != (unsigned int) -1) { p_dc.SetPen(wxPen(m_style->GetPlayerColor(pl), 1, wxSOLID)); int oldX = point.x; point = DrawFraction(p_dc, point, outcome->GetPayoff(pl)); m_payoffRect.Append(wxRect(oldX - 5, point.y - height / 2, point.x - oldX + 10, height)); } else { wxString label = wxString(payoff.c_str(), *wxConvCurrent); p_dc.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD)); p_dc.GetTextExtent(label, &width, &height); p_dc.DrawText(label, point.x, point.y - height / 2); m_payoffRect.Append(wxRect(point.x - 5, point.y - height / 2, width + 10, height)); point.x += width + 10; } } if (height == 0) { // Happens if all payoffs are fractional height = 25; } m_outcomeRect = wxRect(m_x + m_size + 20, m_y - height / 2, point.x - (m_x + m_size + 20), height); } bool gbtNodeEntry::NodeHitTest(int p_x, int p_y) const { if (p_x < m_x || p_x >= m_x + m_size) { return false; } if (m_token == GBT_NODE_TOKEN_LINE) { const int DELTA = 8; // a fudge factor for "almost" hitting the node return (p_y >= m_y - DELTA && p_y <= m_y + DELTA); } else { return (p_y >= m_y - m_size / 2 && p_y <= m_y + m_size / 2); } } //----------------------------------------------------------------------- // class gbtTreeLayout: Member functions //----------------------------------------------------------------------- gbtTreeLayout::gbtTreeLayout(gbtEfgDisplay *p_parent, gbtGameDocument *p_doc) : gbtGameView(p_doc), m_parent(p_parent), m_infosetSpacing(40), c_leftMargin(20), c_topMargin(40) { } Gambit::GameNode gbtTreeLayout::NodeHitTest(int p_x, int p_y) const { for (int i = 1; i <= m_nodeList.Length(); i++) { if (m_nodeList[i]->NodeHitTest(p_x, p_y)) { return m_nodeList[i]->GetNode(); } } return 0; } Gambit::GameNode gbtTreeLayout::OutcomeHitTest(int p_x, int p_y) const { for (int i = 1; i <= m_nodeList.Length(); i++) { if (m_nodeList[i]->OutcomeHitTest(p_x, p_y)) { return m_nodeList[i]->GetNode(); } } return 0; } Gambit::GameNode gbtTreeLayout::BranchAboveHitTest(int p_x, int p_y) const { for (int i = 1; i <= m_nodeList.Length(); i++) { if (m_nodeList[i]->BranchAboveHitTest(p_x, p_y)) { return m_nodeList[i]->GetNode()->GetParent(); } } return 0; } Gambit::GameNode gbtTreeLayout::BranchBelowHitTest(int p_x, int p_y) const { for (int i = 1; i <= m_nodeList.Length(); i++) { if (m_nodeList[i]->BranchAboveHitTest(p_x, p_y)) { return m_nodeList[i]->GetNode()->GetParent(); } } return 0; } Gambit::GameNode gbtTreeLayout::InfosetHitTest(int p_x, int p_y) const { for (int i = 1; i <= m_nodeList.Length(); i++) { gbtNodeEntry *entry = m_nodeList[i]; if (entry->GetNextMember() && entry->GetNode()->GetInfoset()) { if (p_x > entry->X() + entry->GetSublevel() * m_infosetSpacing - 2 && p_x < entry->X() + entry->GetSublevel() * m_infosetSpacing + 2) { if (p_y > entry->Y() && p_y < entry->GetNextMember()->Y()) { // next iset is below this one return entry->GetNode(); } else if (p_y > entry->GetNextMember()->Y() && p_y < entry->Y()) { // next iset is above this one return entry->GetNode(); } } } } return 0; } wxString gbtTreeLayout::CreateNodeLabel(const gbtNodeEntry *p_entry, int p_which) const { Gambit::GameNode n = p_entry->GetNode(); try { switch (p_which) { case GBT_NODE_LABEL_NOTHING: return wxT(""); case GBT_NODE_LABEL_LABEL: return wxString(n->GetLabel().c_str(), *wxConvCurrent); case GBT_NODE_LABEL_PLAYER: if (n->GetPlayer()) { return wxString(n->GetPlayer()->GetLabel().c_str(), *wxConvCurrent); } else { return wxT(""); } case GBT_NODE_LABEL_ISETLABEL: if (n->GetInfoset()) { return wxString(n->GetInfoset()->GetLabel().c_str(), *wxConvCurrent); } else { return wxT(""); } case GBT_NODE_LABEL_ISETID: if (n->GetInfoset()) { if (n->GetInfoset()->IsChanceInfoset()) { return wxString::Format(wxT("C:%d"), n->GetInfoset()->GetNumber()); } else { return wxString::Format(wxT("%d:%d"), n->GetPlayer()->GetNumber(), n->GetInfoset()->GetNumber()); } } else { return _T(""); } case GBT_NODE_LABEL_REALIZPROB: return wxString(m_doc->GetProfiles().GetRealizProb(n).c_str(), *wxConvCurrent); case GBT_NODE_LABEL_BELIEFPROB: return wxString(m_doc->GetProfiles().GetBeliefProb(n).c_str(), *wxConvCurrent); case GBT_NODE_LABEL_VALUE: if (n->GetInfoset() && n->GetPlayer()->GetNumber() > 0) { return wxString(m_doc->GetProfiles().GetNodeValue(n, n->GetPlayer()->GetNumber()).c_str(), *wxConvCurrent); } else { return wxT(""); } default: return wxT(""); } } catch (...) { return wxT(""); } } wxString gbtTreeLayout::CreateBranchLabel(const gbtNodeEntry *p_entry, int p_which) const { Gambit::GameNode parent = p_entry->GetParent()->GetNode(); try { switch (p_which) { case GBT_BRANCH_LABEL_NOTHING: return wxT(""); case GBT_BRANCH_LABEL_LABEL: return wxString(parent->GetInfoset()->GetAction(p_entry->GetChildNumber())->GetLabel().c_str(), *wxConvCurrent); case GBT_BRANCH_LABEL_PROBS: if (parent->GetPlayer() && parent->GetPlayer()->IsChance()) { return wxString(parent->GetInfoset()->GetActionProb(p_entry->GetChildNumber()).c_str(), *wxConvCurrent); } else if (m_doc->NumProfileLists() == 0) { return wxT(""); } else { return wxString(m_doc->GetProfiles().GetActionProb(parent, p_entry->GetChildNumber()).c_str(), *wxConvCurrent); } case GBT_BRANCH_LABEL_VALUE: if (m_doc->NumProfileLists() == 0) { return wxT(""); } else { return wxString(m_doc->GetProfiles().GetActionValue(parent, p_entry->GetChildNumber()).c_str(), *wxConvCurrent); } default: return wxT(""); } } catch (...) { return wxT(""); } } gbtNodeEntry *gbtTreeLayout::GetValidParent(Gambit::GameNode e) { gbtNodeEntry *n = GetNodeEntry(e->GetParent()); if (n) { return n; } else { return GetValidParent(e->GetParent()); } } gbtNodeEntry *gbtTreeLayout::GetValidChild(Gambit::GameNode e) { for (int i = 1; i <= e->NumChildren(); i++) { gbtNodeEntry *n = GetNodeEntry(e->GetChild(i)); if (n) { return n; } else { n = GetValidChild(e->GetChild(i)); if (n) return n; } } return 0; } gbtNodeEntry *gbtTreeLayout::GetEntry(Gambit::GameNode p_node) const { for (int i = 1; i <= m_nodeList.Length(); i++) { if (m_nodeList[i]->GetNode() == p_node) { return m_nodeList[i]; } } return 0; } Gambit::GameNode gbtTreeLayout::PriorSameLevel(Gambit::GameNode p_node) const { gbtNodeEntry *entry = GetEntry(p_node); if (entry) { for (int i = m_nodeList.Find(entry) - 1; i >= 1; i--) { if (m_nodeList[i]->GetLevel() == entry->GetLevel()) return m_nodeList[i]->GetNode(); } } return 0; } Gambit::GameNode gbtTreeLayout::NextSameLevel(Gambit::GameNode p_node) const { gbtNodeEntry *entry = GetEntry(p_node); if (entry) { for (int i = m_nodeList.Find(entry) + 1; i <= m_nodeList.Length(); i++) { if (m_nodeList[i]->GetLevel() == entry->GetLevel()) { return m_nodeList[i]->GetNode(); } } } return 0; } int gbtTreeLayout::LayoutSubtree(Gambit::GameNode p_node, const Gambit::BehavSupport &p_support, int &p_maxy, int &p_miny, int &p_ycoord) { int y1 = -1, yn = 0; const gbtStyle &settings = m_doc->GetStyle(); gbtNodeEntry *entry = GetEntry(p_node); entry->SetNextMember(0); if (m_doc->GetStyle().RootReachable() && p_node->GetInfoset() && !p_node->GetInfoset()->GetPlayer()->IsChance()) { Gambit::GameInfoset infoset = p_node->GetInfoset(); for (int i = 1; i <= p_support.NumActions(infoset); i++) { yn = LayoutSubtree(p_node->GetChild(p_support.GetAction(infoset, i)->GetNumber()), p_support, p_maxy, p_miny, p_ycoord); if (y1 == -1) { y1 = yn; } } entry->SetY((y1 + yn) / 2); } else { if (p_node->NumChildren() > 0) { for (int i = 1; i <= p_node->NumChildren(); i++) { yn = LayoutSubtree(p_node->GetChild(i), p_support, p_maxy, p_miny, p_ycoord); if (y1 == -1) { y1 = yn; } if (!p_node->GetPlayer()->IsChance() && !p_support.Contains(p_node->GetInfoset()->GetAction(i))) { m_nodeList[p_node->GetChild(i)->GetNumber()]->SetInSupport(false); } } entry->SetY((y1 + yn) / 2); } else { entry->SetY(p_ycoord); p_ycoord += settings.TerminalSpacing(); } } if (settings.BranchStyle() == GBT_BRANCH_STYLE_LINE) { entry->SetX(c_leftMargin + entry->GetLevel() * (settings.NodeSize() + settings.BranchLength())); } else { entry->SetX(c_leftMargin + entry->GetLevel() * (settings.NodeSize() + settings.BranchLength() + settings.TineLength())); } if (p_node->GetPlayer() && p_node->GetPlayer()->IsChance()) { entry->SetColor(settings.ChanceColor()); entry->SetToken(settings.ChanceToken()); } else if (p_node->GetPlayer()) { entry->SetColor(settings.GetPlayerColor(p_node->GetPlayer()->GetNumber())); entry->SetToken(settings.PlayerToken()); } else { entry->SetColor(settings.TerminalColor()); entry->SetToken(settings.TerminalToken()); } entry->SetSize(settings.NodeSize()); entry->SetBranchStyle(settings.BranchStyle()); if (settings.BranchStyle() == GBT_BRANCH_STYLE_LINE) { entry->SetBranchLabelStyle(settings.BranchLabels()); } entry->SetBranchLength(settings.BranchLength()); p_maxy = Gambit::max(entry->Y(), p_maxy); p_miny = Gambit::min(entry->Y(), p_miny); return entry->Y(); } // // Checks if there are any nodes in the same infoset as e that are either // on the same level (if SHOWISET_SAME) or on any level (if SHOWISET_ALL) // gbtNodeEntry *gbtTreeLayout::NextInfoset(gbtNodeEntry *e) { const gbtStyle &draw_settings = m_doc->GetStyle(); for (int pos = m_nodeList.Find(e) + 1; pos <= m_nodeList.Length(); pos++) { gbtNodeEntry *e1 = m_nodeList[pos]; // infosets are the same and the nodes are on the same level if (e->GetNode()->GetInfoset() == e1->GetNode()->GetInfoset()) { if (draw_settings.InfosetConnect() == GBT_INFOSET_CONNECT_ALL) { return e1; } else if (e->GetLevel() == e1->GetLevel()) { return e1; } } } return 0; } // // CheckInfosetEntry. Checks how many infoset lines are to be drawn at each // level, spaces them by setting each infoset's node's num to the previous // infoset node+1. Also lengthens the nodes by the amount of space taken up // by the infoset lines. // void gbtTreeLayout::CheckInfosetEntry(gbtNodeEntry *e) { int pos; gbtNodeEntry *infoset_entry, *e1; // Check if the infoset this entry belongs to (on this level) has already // been processed. If so, make this entry->num the same as the one already // processed and return infoset_entry = NextInfoset(e); for (pos = 1; pos <= m_nodeList.Length(); pos++) { e1 = m_nodeList[pos]; // if the infosets are the same and they are on the same level and e1 has been processed if (e->GetNode()->GetInfoset() == e1->GetNode()->GetInfoset() && e->GetLevel() == e1->GetLevel() && e1->GetSublevel() > 0) { e->SetSublevel(e1->GetSublevel()); if (infoset_entry) { e->SetNextMember(infoset_entry); } return; } } // If we got here, this entry does not belong to any processed infoset yet. // Check if it belongs to ANY infoset, if not just return if (!infoset_entry) return; // If we got here, then this entry is new and is connected to other entries // find the entry on the same level with the maximum num. // This entry will have num = num+1. int num = 0; for (pos = 1; pos <= m_nodeList.Length(); pos++) { e1 = m_nodeList[pos]; // Find the max num for this level if (e->GetLevel() == e1->GetLevel()) { num = Gambit::max(e1->GetSublevel(), num); } } num++; e->SetSublevel(num); e->SetNextMember(infoset_entry); } void gbtTreeLayout::FillInfosetTable(Gambit::GameNode n, const Gambit::BehavSupport &cur_sup) { const gbtStyle &draw_settings = m_doc->GetStyle(); gbtNodeEntry *entry = GetNodeEntry(n); if (n->NumChildren() > 0) { for (int i = 1; i <= n->NumChildren(); i++) { bool in_sup = true; if (n->GetPlayer()->GetNumber()) { in_sup = cur_sup.Contains(n->GetInfoset()->GetAction(i)); } if (in_sup || !draw_settings.RootReachable()) { FillInfosetTable(n->GetChild(i), cur_sup); } } } if (entry) { CheckInfosetEntry(entry); } } void gbtTreeLayout::UpdateTableInfosets(void) { // Note that levels are numbered from 0, not 1. // create an array to hold max num for each level Gambit::Array nums(0, m_maxLevel + 1); for (int i = 0; i <= m_maxLevel + 1; nums[i++] = 0); // find the max e->num for each level for (int pos = 1; pos <= m_nodeList.Length(); pos++) { gbtNodeEntry *entry = m_nodeList[pos]; nums[entry->GetLevel()] = Gambit::max(entry->GetSublevel() + 1, nums[entry->GetLevel()]); } for (int i = 0; i <= m_maxLevel; i++) { nums[i+1] += nums[i]; } // now add the needed length to each level, and set maxX accordingly m_maxX = 0; for (int pos = 1; pos <= m_nodeList.Length(); pos++) { gbtNodeEntry *entry = m_nodeList[pos]; if (entry->GetLevel() != 0) { entry->SetX(entry->X() + (nums[entry->GetLevel()-1] + entry->GetSublevel()) * m_infosetSpacing); } m_maxX = Gambit::max(m_maxX, entry->X() + entry->GetSize()); } } void gbtTreeLayout::UpdateTableParents(void) { for (int pos = 1; pos <= m_nodeList.Length(); pos++) { gbtNodeEntry *e = m_nodeList[pos]; e->SetParent((e->GetNode() == m_doc->GetGame()->GetRoot()) ? e : GetValidParent(e->GetNode())); } } void gbtTreeLayout::Layout(const Gambit::BehavSupport &p_support) { // Kinda kludgey; probably should query draw settings whenever needed. m_infosetSpacing = (m_doc->GetStyle().InfosetJoin() == GBT_INFOSET_JOIN_LINES) ? 10 : 40; if (m_nodeList.Length() != m_doc->GetGame()->NumNodes()) { // A rebuild is in order; force it BuildNodeList(p_support); } int miny = 0, maxy = 0, ycoord = c_topMargin; LayoutSubtree(m_doc->GetGame()->GetRoot(), p_support, maxy, miny, ycoord); const gbtStyle &draw_settings = m_doc->GetStyle(); if (draw_settings.InfosetConnect() != GBT_INFOSET_CONNECT_NONE) { // FIXME! This causes lines to disappear... sometimes. FillInfosetTable(m_doc->GetGame()->GetRoot(), p_support); UpdateTableInfosets(); } UpdateTableParents(); GenerateLabels(); m_maxY = maxy + 25; } void gbtTreeLayout::BuildNodeList(Gambit::GameNode p_node, const Gambit::BehavSupport &p_support, int p_level) { gbtNodeEntry *entry = new gbtNodeEntry(p_node); entry->SetStyle(&m_doc->GetStyle()); m_nodeList.Append(entry); entry->SetLevel(p_level); if (m_doc->GetStyle().RootReachable()) { Gambit::GameInfoset infoset = p_node->GetInfoset(); if (infoset) { if (infoset->GetPlayer()->IsChance()) { for (int i = 1; i <= p_node->NumChildren(); i++) { BuildNodeList(p_node->GetChild(i), p_support, p_level + 1); } } else { for (int i = 1; i <= p_support.NumActions(infoset); i++) { BuildNodeList(p_node->GetChild(p_support.GetAction(infoset, i)->GetNumber()), p_support, p_level + 1); } } } } else { for (int i = 1; i <= p_node->NumChildren(); i++) { BuildNodeList(p_node->GetChild(i), p_support, p_level + 1); } } m_maxLevel = Gambit::max(p_level, m_maxLevel); } void gbtTreeLayout::BuildNodeList(const Gambit::BehavSupport &p_support) { while (m_nodeList.Length() > 0) { delete m_nodeList.Remove(1); } m_maxLevel = 0; BuildNodeList(m_doc->GetGame()->GetRoot(), p_support, 0); } void gbtTreeLayout::GenerateLabels(void) { const gbtStyle &settings = m_doc->GetStyle(); for (int i = 1; i <= m_nodeList.Length(); i++) { gbtNodeEntry *entry = m_nodeList[i]; entry->SetNodeAboveLabel(CreateNodeLabel(entry, settings.NodeAboveLabel())); entry->SetNodeAboveFont(settings.GetFont()); entry->SetNodeBelowLabel(CreateNodeLabel(entry, settings.NodeBelowLabel())); entry->SetNodeBelowFont(settings.GetFont()); if (entry->GetChildNumber() > 0) { entry->SetBranchAboveLabel(CreateBranchLabel(entry, settings.BranchAboveLabel())); entry->SetBranchAboveFont(settings.GetFont()); entry->SetBranchBelowLabel(CreateBranchLabel(entry, settings.BranchBelowLabel())); entry->SetBranchBelowFont(settings.GetFont()); Gambit::GameNode parent = entry->GetNode()->GetParent(); if (parent->GetPlayer()->IsChance()) { entry->SetActionProb(parent->GetInfoset()->GetActionProb(entry->GetChildNumber())); } else { int profile = m_doc->GetCurrentProfile(); if (profile > 0) { entry->SetActionProb((double) Gambit::ToNumber(m_doc->GetProfiles().GetActionProb(parent, entry->GetChildNumber()))); } } } } } // // RenderSubtree: Render branches and labels // // The following speed optimizations have been added: // The algorithm now traverses the tree as a linear linked list, eliminating // expensive searches. // // There was some clipping code in here, but it didn't correctly // deal with drawing information sets while scrolling. So, the code // has temporarily been removed. It remains to be seen whether // performance will require a more sophisticated solution to the // problem. (TLT 5/2001) // void gbtTreeLayout::RenderSubtree(wxDC &p_dc, bool p_noHints) const { const gbtStyle &settings = m_doc->GetStyle(); for (int pos = 1; pos <= m_nodeList.Length(); pos++) { gbtNodeEntry *entry = m_nodeList[pos]; gbtNodeEntry *parentEntry = entry->GetParent(); if (entry->GetChildNumber() == 1) { parentEntry->Draw(p_dc, m_doc->GetSelectNode(), p_noHints); if (m_doc->GetStyle().InfosetConnect() != GBT_INFOSET_CONNECT_NONE && parentEntry->GetNextMember()) { int nextX = parentEntry->GetNextMember()->X(); int nextY = parentEntry->GetNextMember()->Y(); if ((m_doc->GetStyle().InfosetConnect() != GBT_INFOSET_CONNECT_SAMELEVEL) || parentEntry->X() == nextX) { #ifdef __WXGTK__ // A problem with using styled pens and user scaling on wxGTK p_dc.SetPen(wxPen(parentEntry->GetColor(), 1, wxSOLID)); #else p_dc.SetPen(wxPen(parentEntry->GetColor(), 1, wxDOT)); #endif // __WXGTK__ p_dc.DrawLine(parentEntry->X(), parentEntry->Y(), parentEntry->X(), nextY); if (settings.InfosetJoin() == GBT_INFOSET_JOIN_CIRCLES) { p_dc.DrawLine(parentEntry->X() + parentEntry->GetSize(), parentEntry->Y(), parentEntry->X() + parentEntry->GetSize(), nextY); } if (parentEntry->GetNextMember()->X() != parentEntry->X()) { // Draw a little arrow in the direction of the iset. int startX, endX; if (settings.InfosetJoin() == GBT_INFOSET_JOIN_LINES) { startX = parentEntry->X(); endX = (startX + m_infosetSpacing * ((parentEntry->GetNextMember()->X() > parentEntry->X()) ? 1 : -1)); } else { if (parentEntry->GetNextMember()->X() < parentEntry->X()) { // information set is continued to the left startX = parentEntry->X() + parentEntry->GetSize(); endX = parentEntry->X() - m_infosetSpacing; } else { // information set is continued to the right startX = parentEntry->X(); endX = (parentEntry->X() + parentEntry->GetSize() + m_infosetSpacing); } } p_dc.DrawLine(startX, nextY, endX, nextY); if (startX > endX) { p_dc.DrawLine(endX, nextY, endX + m_infosetSpacing / 2, nextY + m_infosetSpacing / 2); p_dc.DrawLine(endX, nextY, endX + m_infosetSpacing / 2, nextY - m_infosetSpacing / 2); } else { p_dc.DrawLine(endX, nextY, endX - m_infosetSpacing / 2, nextY + m_infosetSpacing / 2); p_dc.DrawLine(endX, nextY, endX - m_infosetSpacing / 2, nextY - m_infosetSpacing / 2); } } } } } if (entry->GetNode()->NumChildren() == 0) { entry->Draw(p_dc, m_doc->GetSelectNode(), p_noHints); } // As we draw, we determine the outcome label extents. Adjust the // overall size of the plot accordingly. if (entry->GetOutcomeExtent().GetRight() > m_maxX) { m_maxX = entry->GetOutcomeExtent().GetRight(); } } } void gbtTreeLayout::Render(wxDC &p_dc, bool p_noHints) const { RenderSubtree(p_dc, p_noHints); }