1 //
2 // This file is part of Gambit
3 // Copyright (c) 1994-2016, The Gambit Project (http://www.gambit-project.org)
4 //
5 // FILE: src/gui/efglayout.cc
6 // Implementation of tree layout representation
7 //
8 // This program is free software; you can redistribute it and/or modify
9 // it under the terms of the GNU General Public License as published by
10 // the Free Software Foundation; either version 2 of the License, or
11 // (at your option) any later version.
12 //
13 // This program is distributed in the hope that it will be useful,
14 // but WITHOUT ANY WARRANTY; without even the implied warranty of
15 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 // GNU General Public License for more details.
17 //
18 // You should have received a copy of the GNU General Public License
19 // along with this program; if not, write to the Free Software
20 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
21 //
22 
23 #include <cmath>
24 #include <algorithm>    // for std::min, std::max
25 
26 #include <wx/wxprec.h>
27 #ifndef WX_PRECOMP
28 #include <wx/wx.h>
29 #endif  // WX_PRECOMP
30 
31 #include "gambit/gambit.h"
32 #include "efgdisplay.h"
33 
34 //-----------------------------------------------------------------------
35 //                   class gbtNodeEntry: Member functions
36 //-----------------------------------------------------------------------
37 
gbtNodeEntry(Gambit::GameNode p_node)38 gbtNodeEntry::gbtNodeEntry(Gambit::GameNode p_node)
39   : m_node(p_node), m_parent(0),
40     m_x(-1), m_y(-1), m_nextMember(0), m_inSupport(true),
41     m_size(20), m_token(GBT_NODE_TOKEN_CIRCLE),
42     m_branchStyle(GBT_BRANCH_STYLE_LINE), m_branchLabel(GBT_BRANCH_LABEL_HORIZONTAL),
43     m_branchLength(0),
44     m_sublevel(0), m_actionProb(0)
45 { }
46 
GetChildNumber(void) const47 int gbtNodeEntry::GetChildNumber(void) const
48 {
49   if (m_node->GetParent()) {
50     return m_node->GetPriorAction()->GetNumber();
51   }
52   else {
53     return 0;
54   }
55 }
56 
57 //
58 // Draws the node token itself, as well as the incoming branch
59 // (if not the root node)
60 //
Draw(wxDC & p_dc,Gambit::GameNode p_selection,bool p_noHints) const61 void gbtNodeEntry::Draw(wxDC &p_dc, Gambit::GameNode p_selection,
62 			bool p_noHints) const
63 {
64   if (m_node->GetParent() && m_inSupport) {
65     DrawIncomingBranch(p_dc);
66   }
67   else {
68     m_branchAboveRect = wxRect();
69     m_branchBelowRect = wxRect();
70   }
71 
72   p_dc.SetPen(*wxThePenList->FindOrCreatePen(m_color,
73 					     (p_selection == m_node) ? 6 : 3,
74 					     wxSOLID));
75   p_dc.SetTextForeground(m_color);
76 
77   if (m_token == GBT_NODE_TOKEN_LINE) {
78     p_dc.DrawLine(m_x, m_y, m_x + m_size, m_y);
79     if (m_branchStyle == GBT_BRANCH_STYLE_FORKTINE) {
80       // "classic" Gambit style: draw a small 'token' to separate
81       // the fork from the node
82       p_dc.DrawEllipse(m_x - 1, m_y - 1, 3, 3);
83     }
84   }
85   else if (m_token == GBT_NODE_TOKEN_BOX) {
86     p_dc.SetBrush(*wxWHITE_BRUSH);
87     p_dc.DrawRectangle(m_x, m_y - m_size / 2, m_size, m_size);
88   }
89   else if (m_token == GBT_NODE_TOKEN_DIAMOND) {
90     wxPoint points[4] = { wxPoint(m_x + m_size / 2, m_y - m_size / 2),
91 			  wxPoint(m_x, m_y),
92 			  wxPoint(m_x + m_size / 2, m_y + m_size / 2),
93 			  wxPoint(m_x + m_size, m_y) };
94     p_dc.SetBrush(*wxWHITE_BRUSH);
95     p_dc.DrawPolygon(4, points);
96   }
97   else if (m_token == GBT_NODE_TOKEN_DOT) {
98     p_dc.SetBrush(wxBrush(m_color, wxSOLID));
99     p_dc.DrawEllipse(m_x, m_y - m_size / 2, m_size, m_size);
100   }
101   else {
102     // Default: draw circles
103     p_dc.SetBrush(*wxWHITE_BRUSH);
104     p_dc.DrawEllipse(m_x, m_y - m_size / 2, m_size, m_size);
105   }
106 
107   int textWidth, textHeight;
108   p_dc.SetFont(m_nodeAboveFont);
109   p_dc.GetTextExtent(m_nodeAboveLabel, &textWidth, &textHeight);
110   p_dc.DrawText(m_nodeAboveLabel,
111 		m_x + (m_size - textWidth) / 2, m_y - textHeight - 9);
112   p_dc.SetFont(m_nodeBelowFont);
113   p_dc.GetTextExtent(m_nodeBelowLabel, &textWidth, &textHeight);
114   p_dc.DrawText(m_nodeBelowLabel,
115 		m_x + (m_size - textWidth) / 2, m_y + 9);
116 
117   p_dc.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD));
118   DrawOutcome(p_dc, p_noHints);
119 }
120 
DrawIncomingBranch(wxDC & p_dc) const121 void gbtNodeEntry::DrawIncomingBranch(wxDC &p_dc) const
122 {
123   int xStart = m_parent->m_x + m_parent->m_size;
124   int xEnd = m_x;
125   int yStart = m_parent->m_y;
126   int yEnd = m_y;
127 
128   p_dc.SetPen(*wxThePenList->FindOrCreatePen(m_parent->m_color,
129 					     4, wxSOLID));
130   p_dc.SetTextForeground(m_parent->m_color);
131 
132   if (m_branchStyle == GBT_BRANCH_STYLE_LINE) {
133     p_dc.DrawLine(xStart, yStart, xEnd, yEnd);
134 
135     // Draw in the highlight indicating action probabilities
136     if (m_actionProb >= Gambit::Rational(0)) {
137       p_dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 4, wxSOLID));
138       p_dc.DrawLine(xStart, yStart,
139 		    xStart +
140 		    (int) ((double) (xEnd - xStart) * (double) m_actionProb),
141 		    yStart +
142 		    (int) ((double) (yEnd - yStart) * (double) m_actionProb));
143     }
144 
145     int textWidth, textHeight;
146     p_dc.SetFont(m_branchAboveFont);
147     p_dc.GetTextExtent(m_branchAboveLabel, &textWidth, &textHeight);
148 
149     // The angle of the branch
150     double theta = -atan((double) (yEnd - yStart) / (double) (xEnd - xStart));
151     // The "centerpoint" of the branch
152     int xbar = (xStart + xEnd) / 2;
153     int ybar = (yStart + yEnd) / 2;
154 
155     if (m_branchLabel == GBT_BRANCH_LABEL_HORIZONTAL) {
156       if (yStart >= yEnd) {
157 	p_dc.DrawText(m_branchAboveLabel, xbar - textWidth / 2,
158 		      ybar - textHeight +
159 		      textWidth / 2 * (yEnd - yStart) / (xEnd - xStart));
160 	m_branchAboveRect = wxRect(xbar - textWidth / 2,
161 				   ybar - textHeight +
162 				   textWidth / 2 * (yEnd - yStart) / (xEnd - xStart),
163 				   textWidth, textHeight);
164       }
165       else {
166 	p_dc.DrawText(m_branchAboveLabel, xbar - textWidth / 2,
167 		      ybar - textHeight -
168 		      textWidth / 2 * (yEnd - yStart) / (xEnd - xStart));
169 	m_branchAboveRect = wxRect(xbar - textWidth / 2,
170 				   ybar - textHeight -
171 				   textWidth / 2 * (yEnd - yStart) / (xEnd - xStart),
172 				   textWidth, textHeight);
173       }
174     }
175     else {
176       // Draw the text rotated appropriately
177       p_dc.DrawRotatedText(m_branchAboveLabel,
178 			   (int) ((double) xbar -
179 				  (double) textHeight * sin(theta) -
180 				  (double) textWidth * cos(theta) / 2.0),
181 			   (int) ((double) ybar -
182 				  (double) textHeight * cos(theta) +
183 				  (double) textWidth * sin(theta) / 2.0),
184 			   theta * 180.0 / 3.14159);
185       m_branchAboveRect = wxRect();
186     }
187 
188     p_dc.SetFont(m_branchBelowFont);
189     p_dc.GetTextExtent(m_branchBelowLabel, &textWidth, &textHeight);
190 
191     if (m_branchLabel == GBT_BRANCH_LABEL_HORIZONTAL) {
192       if (yStart >= yEnd) {
193 	p_dc.DrawText(m_branchBelowLabel, xbar - textWidth / 2,
194 		      ybar - textWidth/2 * (yEnd - yStart) / (xEnd - xStart));
195 	m_branchBelowRect = wxRect(xbar - textWidth / 2,
196 				   ybar - textWidth/2 * (yEnd - yStart) / (xEnd - xStart),
197 				   textWidth, textHeight);
198       }
199       else {
200 	p_dc.DrawText(m_branchBelowLabel, xbar - textWidth / 2,
201 		      ybar + textWidth/2 * (yEnd - yStart) / (xEnd - xStart));
202 	m_branchBelowRect = wxRect(xbar - textWidth / 2,
203 				   ybar + textWidth/2 * (yEnd - yStart) / (xEnd - xStart),
204 				   textWidth, textHeight);
205       }
206     }
207     else {
208       // Draw the text rotated appropriately
209       p_dc.DrawRotatedText(m_branchBelowLabel,
210 			   (int) ((double) xbar -
211 				  (double) textWidth * cos(theta) / 2.0),
212 			   (int) ((double) ybar +
213 				  (double) textWidth * sin(theta) / 2.0),
214 			   theta * 180.0 / 3.14159);
215       m_branchBelowRect = wxRect();
216     }
217   }
218   else {
219     // Old style fork-tine
220     p_dc.DrawLine(xStart, yStart, xStart + m_branchLength, yEnd);
221     p_dc.DrawLine(xStart + m_branchLength, yEnd, xEnd, yEnd);
222 
223     // Draw in the highlight indicating action probabilities
224     if (m_actionProb >= Gambit::Rational(0)) {
225       p_dc.SetPen(*wxThePenList->FindOrCreatePen(*wxBLACK, 2, wxSOLID));
226       p_dc.DrawLine(xStart, yStart,
227 		    xStart +
228 		    (int) ((double) m_branchLength * (double) m_actionProb),
229 		    yStart +
230 		    (int) ((double) (yEnd - yStart) * (double) m_actionProb));
231     }
232 
233     int textWidth, textHeight;
234     p_dc.SetFont(m_branchAboveFont);
235     p_dc.GetTextExtent(m_branchAboveLabel, &textWidth, &textHeight);
236     p_dc.DrawText(m_branchAboveLabel,
237 		  xStart + m_branchLength + 3, yEnd - textHeight - 3);
238     m_branchAboveRect = wxRect(xStart + m_branchLength + 3,
239 			       yEnd - textHeight - 3,
240 			       textWidth, textHeight);
241 
242     p_dc.SetFont(m_branchBelowFont);
243     p_dc.GetTextExtent(m_branchBelowLabel, &textWidth, &textHeight);
244     p_dc.DrawText(m_branchBelowLabel,
245 		  xStart + m_branchLength + 3, yEnd + 3);
246     m_branchBelowRect = wxRect(xStart + m_branchLength + 3,
247 			       yEnd + +3,
248 			       textWidth, textHeight);
249   }
250 }
251 
DrawFraction(wxDC & p_dc,wxPoint p_point,const Gambit::Rational & p_value)252 static wxPoint DrawFraction(wxDC &p_dc, wxPoint p_point,
253 			    const Gambit::Rational &p_value)
254 {
255   p_dc.SetFont(wxFont(7, wxSWISS, wxNORMAL, wxBOLD));
256 
257   int numWidth, numHeight;
258   wxString num = wxString(lexical_cast<std::string>(p_value.numerator()).c_str(),
259 			  *wxConvCurrent);
260   p_dc.GetTextExtent(num, &numWidth, &numHeight);
261 
262   int denWidth, denHeight;
263   wxString den = wxString(lexical_cast<std::string>(p_value.denominator()).c_str(),
264 			  *wxConvCurrent);
265   p_dc.GetTextExtent(den, &denWidth, &denHeight);
266 
267   int width = ((numWidth > denWidth) ? numWidth : denWidth);
268 
269   p_dc.DrawLine(p_point.x, p_point.y, p_point.x + width + 4, p_point.y);
270   p_dc.DrawText(num,
271 		p_point.x + 2 + (width - numWidth) / 2,
272 		p_point.y - 2 - numHeight);
273   p_dc.DrawText(den,
274 		p_point.x + 2 + (width - denWidth) / 2,
275 		p_point.y + 2);
276 
277   p_point.x += width + 14;
278   return p_point;
279 }
280 
DrawOutcome(wxDC & p_dc,bool p_noHints) const281 void gbtNodeEntry::DrawOutcome(wxDC &p_dc, bool p_noHints) const
282 {
283   wxPoint point(m_x + m_size + 20, m_y);
284 
285   Gambit::GameOutcome outcome = m_node->GetOutcome();
286   if (!outcome) {
287     if (p_noHints)  return;
288 
289     int width, height;
290     p_dc.SetFont(wxFont(9, wxSWISS, wxITALIC, wxBOLD));
291     p_dc.SetTextForeground(*wxLIGHT_GREY);
292     p_dc.GetTextExtent(wxT("(u)"), &width, &height);
293     p_dc.DrawText(wxT("(u)"), point.x, point.y - height / 2);
294     m_outcomeRect = wxRect(point.x, point.y - height / 2,
295 			   width, height);
296     m_payoffRect = Gambit::Array<wxRect>();
297     return;
298   }
299 
300   int width, height = 25;
301   m_payoffRect = Gambit::Array<wxRect>();
302   for (int pl = 1; pl <= m_node->GetGame()->NumPlayers(); pl++) {
303     Gambit::GamePlayer player = m_node->GetGame()->GetPlayer(pl);
304     p_dc.SetTextForeground(m_style->GetPlayerColor(pl));
305 
306     std::string payoff = outcome->GetPayoff<std::string>(pl);
307 
308     if (payoff.find('/') != std::string::npos) {
309       p_dc.SetPen(wxPen(m_style->GetPlayerColor(pl), 1, wxSOLID));
310       int oldX = point.x;
311       point = DrawFraction(p_dc, point, outcome->GetPayoff<Gambit::Rational>(pl));
312       m_payoffRect.Append(wxRect(oldX - 5, point.y - height / 2,
313 				 point.x - oldX + 10, height));
314     }
315     else {
316       wxString label = wxString(payoff.c_str(), *wxConvCurrent);
317       p_dc.SetFont(wxFont(9, wxSWISS, wxNORMAL, wxBOLD));
318       p_dc.GetTextExtent(label, &width, &height);
319       p_dc.DrawText(label, point.x, point.y - height / 2);
320       m_payoffRect.Append(wxRect(point.x - 5, point.y - height / 2,
321 				 width + 10, height));
322       point.x += width + 10;
323     }
324   }
325 
326   if (height == 0) {
327     // Happens if all payoffs are fractional
328     height = 25;
329   }
330 
331   m_outcomeRect = wxRect(m_x + m_size + 20, m_y - height / 2,
332 			 point.x - (m_x + m_size + 20), height);
333 }
334 
NodeHitTest(int p_x,int p_y) const335 bool gbtNodeEntry::NodeHitTest(int p_x, int p_y) const
336 {
337   if (p_x < m_x || p_x >= m_x + m_size) {
338     return false;
339   }
340 
341   if (m_token == GBT_NODE_TOKEN_LINE) {
342     const int DELTA = 8;  // a fudge factor for "almost" hitting the node
343     return (p_y >= m_y - DELTA && p_y <= m_y + DELTA);
344   }
345   else {
346     return (p_y >= m_y - m_size / 2 && p_y <= m_y + m_size / 2);
347   }
348 }
349 
350 //-----------------------------------------------------------------------
351 //                class gbtTreeLayout: Member functions
352 //-----------------------------------------------------------------------
353 
gbtTreeLayout(gbtEfgDisplay * p_parent,gbtGameDocument * p_doc)354 gbtTreeLayout::gbtTreeLayout(gbtEfgDisplay *p_parent, gbtGameDocument *p_doc)
355   : gbtGameView(p_doc),
356     /* m_parent(p_parent),*/ m_infosetSpacing(40),
357     c_leftMargin(20), c_topMargin(40)
358 { }
359 
NodeHitTest(int p_x,int p_y) const360 Gambit::GameNode gbtTreeLayout::NodeHitTest(int p_x, int p_y) const
361 {
362   for (int i = 1; i <= m_nodeList.Length(); i++) {
363     if (m_nodeList[i]->NodeHitTest(p_x, p_y)) {
364       return m_nodeList[i]->GetNode();
365     }
366   }
367   return 0;
368 }
369 
OutcomeHitTest(int p_x,int p_y) const370 Gambit::GameNode gbtTreeLayout::OutcomeHitTest(int p_x, int p_y) const
371 {
372   for (int i = 1; i <= m_nodeList.Length(); i++) {
373     if (m_nodeList[i]->OutcomeHitTest(p_x, p_y)) {
374       return m_nodeList[i]->GetNode();
375     }
376   }
377   return 0;
378 }
379 
BranchAboveHitTest(int p_x,int p_y) const380 Gambit::GameNode gbtTreeLayout::BranchAboveHitTest(int p_x, int p_y) const
381 {
382   for (int i = 1; i <= m_nodeList.Length(); i++) {
383     if (m_nodeList[i]->BranchAboveHitTest(p_x, p_y)) {
384       return m_nodeList[i]->GetNode()->GetParent();
385     }
386   }
387   return 0;
388 }
389 
BranchBelowHitTest(int p_x,int p_y) const390 Gambit::GameNode gbtTreeLayout::BranchBelowHitTest(int p_x, int p_y) const
391 {
392   for (int i = 1; i <= m_nodeList.Length(); i++) {
393     if (m_nodeList[i]->BranchAboveHitTest(p_x, p_y)) {
394       return m_nodeList[i]->GetNode()->GetParent();
395     }
396   }
397   return 0;
398 }
399 
InfosetHitTest(int p_x,int p_y) const400 Gambit::GameNode gbtTreeLayout::InfosetHitTest(int p_x, int p_y) const
401 {
402   for (int i = 1; i <= m_nodeList.Length(); i++) {
403     gbtNodeEntry *entry = m_nodeList[i];
404     if (entry->GetNextMember() && entry->GetNode()->GetInfoset()) {
405       if (p_x > entry->X() + entry->GetSublevel() * m_infosetSpacing - 2 &&
406 	  p_x < entry->X() + entry->GetSublevel() * m_infosetSpacing + 2) {
407 	if (p_y > entry->Y() && p_y < entry->GetNextMember()->Y()) {
408 	  // next iset is below this one
409 	  return entry->GetNode();
410 	}
411 	else if (p_y > entry->GetNextMember()->Y() && p_y < entry->Y()) {
412 	  // next iset is above this one
413 	  return entry->GetNode();
414 	}
415       }
416     }
417   }
418   return 0;
419 }
420 
CreateNodeLabel(const gbtNodeEntry * p_entry,int p_which) const421 wxString gbtTreeLayout::CreateNodeLabel(const gbtNodeEntry *p_entry,
422 					int p_which) const
423 {
424   Gambit::GameNode n = p_entry->GetNode();
425 
426   try {
427     switch (p_which) {
428     case GBT_NODE_LABEL_NOTHING:
429       return wxT("");
430     case GBT_NODE_LABEL_LABEL:
431       return wxString(n->GetLabel().c_str(), *wxConvCurrent);
432     case GBT_NODE_LABEL_PLAYER:
433       if (n->GetPlayer()) {
434 	return wxString(n->GetPlayer()->GetLabel().c_str(), *wxConvCurrent);
435       }
436       else {
437 	return wxT("");
438       }
439     case GBT_NODE_LABEL_ISETLABEL:
440       if (n->GetInfoset()) {
441 	return wxString(n->GetInfoset()->GetLabel().c_str(),
442 			*wxConvCurrent);
443       }
444       else {
445 	return wxT("");
446       }
447     case GBT_NODE_LABEL_ISETID:
448       if (n->GetInfoset()) {
449 	if (n->GetInfoset()->IsChanceInfoset()) {
450 	  return wxString::Format(wxT("C:%d"), n->GetInfoset()->GetNumber());
451 	}
452 	else {
453 	  return wxString::Format(wxT("%d:%d"),
454 				  n->GetPlayer()->GetNumber(),
455 				  n->GetInfoset()->GetNumber());
456 	}
457       }
458       else {
459 	return _T("");
460       }
461     case GBT_NODE_LABEL_REALIZPROB:
462       return wxString(m_doc->GetProfiles().GetRealizProb(n).c_str(),
463 		      *wxConvCurrent);
464     case GBT_NODE_LABEL_BELIEFPROB:
465       return wxString(m_doc->GetProfiles().GetBeliefProb(n).c_str(),
466 		      *wxConvCurrent);
467     case GBT_NODE_LABEL_VALUE:
468       if (n->GetInfoset() && n->GetPlayer()->GetNumber() > 0) {
469 	return wxString(m_doc->GetProfiles().GetNodeValue(n, n->GetPlayer()->GetNumber()).c_str(), *wxConvCurrent);
470       }
471       else {
472 	return wxT("");
473       }
474     default:
475       return wxT("");
476     }
477   }
478   catch (...) {
479     return wxT("");
480   }
481 }
482 
CreateBranchLabel(const gbtNodeEntry * p_entry,int p_which) const483 wxString gbtTreeLayout::CreateBranchLabel(const gbtNodeEntry *p_entry,
484 					  int p_which) const
485 {
486   Gambit::GameNode parent = p_entry->GetParent()->GetNode();
487 
488   try {
489     switch (p_which) {
490     case GBT_BRANCH_LABEL_NOTHING:
491       return wxT("");
492     case GBT_BRANCH_LABEL_LABEL:
493       return wxString(parent->GetInfoset()->GetAction(p_entry->GetChildNumber())->GetLabel().c_str(),
494 		      *wxConvCurrent);
495     case GBT_BRANCH_LABEL_PROBS:
496       if (parent->GetPlayer() && parent->GetPlayer()->IsChance()) {
497 	return wxString(parent->GetInfoset()->GetActionProb(p_entry->GetChildNumber(), "").c_str(),
498 			*wxConvCurrent);
499       }
500       else if (m_doc->NumProfileLists() == 0) {
501 	return wxT("");
502       }
503       else {
504 	return wxString(m_doc->GetProfiles().GetActionProb(parent,
505 							   p_entry->GetChildNumber()).c_str(),
506 			*wxConvCurrent);
507       }
508     case GBT_BRANCH_LABEL_VALUE:
509       if (m_doc->NumProfileLists() == 0) {
510 	return wxT("");
511       }
512       else {
513 	return wxString(m_doc->GetProfiles().GetActionValue(parent,
514 							    p_entry->GetChildNumber()).c_str(),
515 			*wxConvCurrent);
516       }
517     default:
518       return wxT("");
519     }
520   }
521   catch (...) {
522     return wxT("");
523   }
524 }
525 
GetValidParent(Gambit::GameNode e)526 gbtNodeEntry *gbtTreeLayout::GetValidParent(Gambit::GameNode e)
527 {
528   gbtNodeEntry *n = GetNodeEntry(e->GetParent());
529   if (n) {
530     return n;
531   }
532   else {
533     return GetValidParent(e->GetParent());
534   }
535 }
536 
GetValidChild(Gambit::GameNode e)537 gbtNodeEntry *gbtTreeLayout::GetValidChild(Gambit::GameNode e)
538 {
539   for (int i = 1; i <= e->NumChildren(); i++)  {
540     gbtNodeEntry *n = GetNodeEntry(e->GetChild(i));
541     if (n) {
542       return n;
543     }
544     else  {
545       n = GetValidChild(e->GetChild(i));
546       if (n) return n;
547     }
548   }
549   return 0;
550 }
551 
GetEntry(Gambit::GameNode p_node) const552 gbtNodeEntry *gbtTreeLayout::GetEntry(Gambit::GameNode p_node) const
553 {
554   for (int i = 1; i <= m_nodeList.Length(); i++) {
555     if (m_nodeList[i]->GetNode() == p_node) {
556       return m_nodeList[i];
557     }
558   }
559   return 0;
560 }
561 
PriorSameLevel(Gambit::GameNode p_node) const562 Gambit::GameNode gbtTreeLayout::PriorSameLevel(Gambit::GameNode p_node) const
563 {
564   gbtNodeEntry *entry = GetEntry(p_node);
565   if (entry) {
566     for (int i = m_nodeList.Find(entry) - 1; i >= 1; i--) {
567       if (m_nodeList[i]->GetLevel() == entry->GetLevel())
568 	return m_nodeList[i]->GetNode();
569     }
570   }
571   return 0;
572 }
573 
NextSameLevel(Gambit::GameNode p_node) const574 Gambit::GameNode gbtTreeLayout::NextSameLevel(Gambit::GameNode p_node) const
575 {
576   gbtNodeEntry *entry = GetEntry(p_node);
577   if (entry) {
578     for (int i = m_nodeList.Find(entry) + 1; i <= m_nodeList.Length(); i++) {
579       if (m_nodeList[i]->GetLevel() == entry->GetLevel()) {
580 	return m_nodeList[i]->GetNode();
581       }
582     }
583   }
584   return 0;
585 }
586 
LayoutSubtree(Gambit::GameNode p_node,const Gambit::BehaviorSupportProfile & p_support,int & p_maxy,int & p_miny,int & p_ycoord)587 int gbtTreeLayout::LayoutSubtree(Gambit::GameNode p_node, const Gambit::BehaviorSupportProfile &p_support,
588 				 int &p_maxy, int &p_miny, int &p_ycoord)
589 {
590   int y1 = -1, yn = 0;
591   const gbtStyle &settings = m_doc->GetStyle();
592 
593   gbtNodeEntry *entry = GetEntry(p_node);
594   entry->SetNextMember(0);
595   if (m_doc->GetStyle().RootReachable() &&
596       p_node->GetInfoset() && !p_node->GetInfoset()->GetPlayer()->IsChance()) {
597     Gambit::GameInfoset infoset = p_node->GetInfoset();
598     for (int i = 1; i <= p_support.NumActions(infoset); i++) {
599       yn = LayoutSubtree(p_node->GetChild(p_support.GetAction(infoset, i)->GetNumber()),
600 			 p_support, p_maxy, p_miny, p_ycoord);
601       if (y1 == -1) {
602 	y1 = yn;
603       }
604     }
605     entry->SetY((y1 + yn) / 2);
606   }
607   else {
608     if (p_node->NumChildren() > 0) {
609       for (int i = 1; i <= p_node->NumChildren(); i++) {
610 	yn = LayoutSubtree(p_node->GetChild(i), p_support,
611 			   p_maxy, p_miny, p_ycoord);
612 	if (y1 == -1) {
613 	  y1 = yn;
614 	}
615 
616 	if (!p_node->GetPlayer()->IsChance() &&
617 	    !p_support.Contains(p_node->GetInfoset()->GetAction(i))) {
618 	  m_nodeList[p_node->GetChild(i)->GetNumber()]->SetInSupport(false);
619 	}
620       }
621       entry->SetY((y1 + yn) / 2);
622     }
623     else {
624       entry->SetY(p_ycoord);
625       p_ycoord += settings.TerminalSpacing();
626     }
627   }
628 
629   if (settings.BranchStyle() == GBT_BRANCH_STYLE_LINE) {
630     entry->SetX(c_leftMargin + entry->GetLevel() * (settings.NodeSize() +
631 						    settings.BranchLength()));
632   }
633   else {
634     entry->SetX(c_leftMargin + entry->GetLevel() * (settings.NodeSize() +
635 						    settings.BranchLength() +
636 						    settings.TineLength()));
637   }
638 
639   if (p_node->GetPlayer() && p_node->GetPlayer()->IsChance()) {
640     entry->SetColor(settings.ChanceColor());
641     entry->SetToken(settings.ChanceToken());
642   }
643   else if (p_node->GetPlayer()) {
644     entry->SetColor(settings.GetPlayerColor(p_node->GetPlayer()->GetNumber()));
645     entry->SetToken(settings.PlayerToken());
646   }
647   else {
648     entry->SetColor(settings.TerminalColor());
649     entry->SetToken(settings.TerminalToken());
650   }
651 
652   entry->SetSize(settings.NodeSize());
653   entry->SetBranchStyle(settings.BranchStyle());
654   if (settings.BranchStyle() == GBT_BRANCH_STYLE_LINE) {
655     entry->SetBranchLabelStyle(settings.BranchLabels());
656   }
657   entry->SetBranchLength(settings.BranchLength());
658 
659   p_maxy = std::max(entry->Y(), p_maxy);
660   p_miny = std::min(entry->Y(), p_miny);
661 
662   return entry->Y();
663 }
664 
665 //
666 // Checks if there are any nodes in the same infoset as e that are either
667 // on the same level (if SHOWISET_SAME) or on any level (if SHOWISET_ALL)
668 //
NextInfoset(gbtNodeEntry * e)669 gbtNodeEntry *gbtTreeLayout::NextInfoset(gbtNodeEntry *e)
670 {
671   const gbtStyle &draw_settings = m_doc->GetStyle();
672 
673   for (int pos = m_nodeList.Find(e) + 1; pos <= m_nodeList.Length(); pos++) {
674     gbtNodeEntry *e1 = m_nodeList[pos];
675     // infosets are the same and the nodes are on the same level
676     if (e->GetNode()->GetInfoset() == e1->GetNode()->GetInfoset()) {
677       if (draw_settings.InfosetConnect() == GBT_INFOSET_CONNECT_ALL) {
678 	return e1;
679       }
680       else if (e->GetLevel() == e1->GetLevel()) {
681 	return e1;
682       }
683     }
684   }
685   return 0;
686 }
687 
688 //
689 // CheckInfosetEntry.  Checks how many infoset lines are to be drawn at each
690 // level, spaces them by setting each infoset's node's num to the previous
691 // infoset node+1.  Also lengthens the nodes by the amount of space taken up
692 // by the infoset lines.
693 //
CheckInfosetEntry(gbtNodeEntry * e)694 void gbtTreeLayout::CheckInfosetEntry(gbtNodeEntry *e)
695 {
696   int pos;
697   gbtNodeEntry *infoset_entry, *e1;
698   // Check if the infoset this entry belongs to (on this level) has already
699   // been processed.  If so, make this entry->num the same as the one already
700   // processed and return
701   infoset_entry = NextInfoset(e);
702   for (pos = 1; pos <= m_nodeList.Length(); pos++) {
703     e1 = m_nodeList[pos];
704     // if the infosets are the same and they are on the same level and e1 has been processed
705     if (e->GetNode()->GetInfoset() == e1->GetNode()->GetInfoset() &&
706 	e->GetLevel() == e1->GetLevel() && e1->GetSublevel() > 0) {
707       e->SetSublevel(e1->GetSublevel());
708       if (infoset_entry) {
709 	e->SetNextMember(infoset_entry);
710       }
711       return;
712     }
713   }
714 
715   // If we got here, this entry does not belong to any processed infoset yet.
716   // Check if it belongs to ANY infoset, if not just return
717   if (!infoset_entry) return;
718 
719   // If we got here, then this entry is new and is connected to other entries
720   // find the entry on the same level with the maximum num.
721   // This entry will have num = num+1.
722   int num = 0;
723   for (pos = 1; pos <= m_nodeList.Length(); pos++) {
724     e1 = m_nodeList[pos];
725     // Find the max num for this level
726     if (e->GetLevel() == e1->GetLevel())  {
727       num = std::max(e1->GetSublevel(), num);
728     }
729   }
730   num++;
731   e->SetSublevel(num);
732   e->SetNextMember(infoset_entry);
733 }
734 
FillInfosetTable(Gambit::GameNode n,const Gambit::BehaviorSupportProfile & cur_sup)735 void gbtTreeLayout::FillInfosetTable(Gambit::GameNode n, const Gambit::BehaviorSupportProfile &cur_sup)
736 {
737   const gbtStyle &draw_settings = m_doc->GetStyle();
738   gbtNodeEntry *entry = GetNodeEntry(n);
739   if (n->NumChildren() > 0) {
740     for (int i = 1; i <= n->NumChildren(); i++) {
741       bool in_sup = true;
742       if (n->GetPlayer()->GetNumber()) {
743 	in_sup = cur_sup.Contains(n->GetInfoset()->GetAction(i));
744       }
745 
746       if (in_sup || !draw_settings.RootReachable()) {
747 	FillInfosetTable(n->GetChild(i), cur_sup);
748       }
749     }
750   }
751 
752   if (entry) {
753     CheckInfosetEntry(entry);
754   }
755 }
756 
UpdateTableInfosets(void)757 void gbtTreeLayout::UpdateTableInfosets(void)
758 {
759   // Note that levels are numbered from 0, not 1.
760   // create an array to hold max num for each level
761   Gambit::Array<int> nums(0, m_maxLevel + 1);
762 
763   for (int i = 0; i <= m_maxLevel + 1; nums[i++] = 0);
764   // find the max e->num for each level
765   for (int pos = 1; pos <= m_nodeList.Length(); pos++) {
766     gbtNodeEntry *entry = m_nodeList[pos];
767     nums[entry->GetLevel()] = std::max(entry->GetSublevel() + 1,
768 					  nums[entry->GetLevel()]);
769   }
770 
771   for (int i = 0; i <= m_maxLevel; i++) {
772     nums[i+1] += nums[i];
773   }
774 
775   // now add the needed length to each level, and set maxX accordingly
776   m_maxX = 0;
777   for (int pos = 1; pos <= m_nodeList.Length(); pos++) {
778     gbtNodeEntry *entry = m_nodeList[pos];
779     if (entry->GetLevel() != 0) {
780       entry->SetX(entry->X() +
781 		  (nums[entry->GetLevel()-1] +
782 		   entry->GetSublevel()) * m_infosetSpacing);
783     }
784     m_maxX = std::max(m_maxX, entry->X() + entry->GetSize());
785   }
786 }
787 
UpdateTableParents(void)788 void gbtTreeLayout::UpdateTableParents(void)
789 {
790   for (int pos = 1; pos <= m_nodeList.Length(); pos++) {
791     gbtNodeEntry *e = m_nodeList[pos];
792     e->SetParent((e->GetNode() == m_doc->GetGame()->GetRoot()) ?
793 		 e : GetValidParent(e->GetNode()));
794   }
795 }
796 
Layout(const Gambit::BehaviorSupportProfile & p_support)797 void gbtTreeLayout::Layout(const Gambit::BehaviorSupportProfile &p_support)
798 {
799   // Kinda kludgey; probably should query draw settings whenever needed.
800   m_infosetSpacing =
801     (m_doc->GetStyle().InfosetJoin() == GBT_INFOSET_JOIN_LINES) ? 10 : 40;
802 
803   if (m_nodeList.Length() != m_doc->GetGame()->NumNodes()) {
804     // A rebuild is in order; force it
805     BuildNodeList(p_support);
806   }
807 
808   int miny = 0, maxy = 0, ycoord = c_topMargin;
809   LayoutSubtree(m_doc->GetGame()->GetRoot(), p_support, maxy, miny, ycoord);
810 
811   const gbtStyle &draw_settings = m_doc->GetStyle();
812   if (draw_settings.InfosetConnect() != GBT_INFOSET_CONNECT_NONE) {
813     // FIXME! This causes lines to disappear... sometimes.
814     FillInfosetTable(m_doc->GetGame()->GetRoot(), p_support);
815     UpdateTableInfosets();
816   }
817 
818   UpdateTableParents();
819   GenerateLabels();
820 
821   m_maxY = maxy + 25;
822 }
823 
BuildNodeList(Gambit::GameNode p_node,const Gambit::BehaviorSupportProfile & p_support,int p_level)824 void gbtTreeLayout::BuildNodeList(Gambit::GameNode p_node, const Gambit::BehaviorSupportProfile &p_support,
825 				  int p_level)
826 {
827   gbtNodeEntry *entry = new gbtNodeEntry(p_node);
828   entry->SetStyle(&m_doc->GetStyle());
829   m_nodeList.Append(entry);
830   entry->SetLevel(p_level);
831   if (m_doc->GetStyle().RootReachable()) {
832     Gambit::GameInfoset infoset = p_node->GetInfoset();
833     if (infoset) {
834       if (infoset->GetPlayer()->IsChance()) {
835 	for (int i = 1; i <= p_node->NumChildren(); i++) {
836 	  BuildNodeList(p_node->GetChild(i), p_support, p_level + 1);
837 	}
838       }
839       else {
840 	for (int i = 1; i <= p_support.NumActions(infoset); i++) {
841 	  BuildNodeList(p_node->GetChild(p_support.GetAction(infoset, i)->GetNumber()),
842 			p_support, p_level + 1);
843 	}
844       }
845     }
846   }
847   else {
848     for (int i = 1; i <= p_node->NumChildren(); i++) {
849       BuildNodeList(p_node->GetChild(i), p_support, p_level + 1);
850     }
851   }
852   m_maxLevel = std::max(p_level, m_maxLevel);
853 }
854 
BuildNodeList(const Gambit::BehaviorSupportProfile & p_support)855 void gbtTreeLayout::BuildNodeList(const Gambit::BehaviorSupportProfile &p_support)
856 {
857   while (m_nodeList.Length() > 0) {
858     delete m_nodeList.Remove(1);
859   }
860 
861   m_maxLevel = 0;
862   BuildNodeList(m_doc->GetGame()->GetRoot(), p_support, 0);
863 }
864 
865 
GenerateLabels(void)866 void gbtTreeLayout::GenerateLabels(void)
867 {
868   const gbtStyle &settings = m_doc->GetStyle();
869   for (int i = 1; i <= m_nodeList.Length(); i++) {
870     gbtNodeEntry *entry = m_nodeList[i];
871     entry->SetNodeAboveLabel(CreateNodeLabel(entry,
872 					     settings.NodeAboveLabel()));
873     entry->SetNodeAboveFont(settings.GetFont());
874     entry->SetNodeBelowLabel(CreateNodeLabel(entry,
875 					     settings.NodeBelowLabel()));
876     entry->SetNodeBelowFont(settings.GetFont());
877     if (entry->GetChildNumber() > 0) {
878       entry->SetBranchAboveLabel(CreateBranchLabel(entry,
879 						   settings.BranchAboveLabel()));
880       entry->SetBranchAboveFont(settings.GetFont());
881       entry->SetBranchBelowLabel(CreateBranchLabel(entry,
882 						   settings.BranchBelowLabel()));
883       entry->SetBranchBelowFont(settings.GetFont());
884 
885       Gambit::GameNode parent = entry->GetNode()->GetParent();
886       if (parent->GetPlayer()->IsChance()) {
887 	entry->SetActionProb(parent->GetInfoset()->GetActionProb(entry->GetChildNumber(), (double) 0));
888       }
889       else {
890 	int profile = m_doc->GetCurrentProfile();
891 	if (profile > 0) {
892 	  try {
893 	    entry->SetActionProb((double) Gambit::lexical_cast<Gambit::Rational>(m_doc->GetProfiles().GetActionProb(parent, entry->GetChildNumber())));
894 	  }
895 	  catch (ValueException &) {
896 	    // This occurs when the probability is undefined
897 	    entry->SetActionProb(0.0);
898 	  }
899 	}
900       }
901     }
902   }
903 }
904 
905 //
906 // RenderSubtree: Render branches and labels
907 //
908 // The following speed optimizations have been added:
909 // The algorithm now traverses the tree as a linear linked list, eliminating
910 // expensive searches.
911 //
912 // There was some clipping code in here, but it didn't correctly
913 // deal with drawing information sets while scrolling.  So, the code
914 // has temporarily been removed.  It remains to be seen whether
915 // performance will require a more sophisticated solution to the
916 // problem.  (TLT 5/2001)
917 //
RenderSubtree(wxDC & p_dc,bool p_noHints) const918 void gbtTreeLayout::RenderSubtree(wxDC &p_dc, bool p_noHints) const
919 {
920   const gbtStyle &settings = m_doc->GetStyle();
921 
922   for (int pos = 1; pos <= m_nodeList.Length(); pos++) {
923     gbtNodeEntry *entry = m_nodeList[pos];
924     gbtNodeEntry *parentEntry = entry->GetParent();
925 
926     if (entry->GetChildNumber() == 1) {
927       parentEntry->Draw(p_dc, m_doc->GetSelectNode(), p_noHints);
928 
929       if (m_doc->GetStyle().InfosetConnect() != GBT_INFOSET_CONNECT_NONE &&
930 	  parentEntry->GetNextMember()) {
931 	int nextX = parentEntry->GetNextMember()->X();
932 	int nextY = parentEntry->GetNextMember()->Y();
933 
934 	if ((m_doc->GetStyle().InfosetConnect() !=
935 	     GBT_INFOSET_CONNECT_SAMELEVEL) ||
936 	    parentEntry->X() == nextX) {
937 #ifdef __WXGTK__
938 	  // A problem with using styled pens and user scaling on wxGTK
939 	  p_dc.SetPen(wxPen(parentEntry->GetColor(), 1, wxSOLID));
940 #else
941 	  p_dc.SetPen(wxPen(parentEntry->GetColor(), 1, wxDOT));
942 #endif   // __WXGTK__
943 	  p_dc.DrawLine(parentEntry->X(), parentEntry->Y(),
944 			parentEntry->X(), nextY);
945 	  if (settings.InfosetJoin() == GBT_INFOSET_JOIN_CIRCLES) {
946 	    p_dc.DrawLine(parentEntry->X() + parentEntry->GetSize(),
947 			  parentEntry->Y(),
948 			  parentEntry->X() + parentEntry->GetSize(),
949 			  nextY);
950 	  }
951 
952 	  if (parentEntry->GetNextMember()->X() != parentEntry->X()) {
953 	    // Draw a little arrow in the direction of the iset.
954 	    int startX, endX;
955 	    if (settings.InfosetJoin() == GBT_INFOSET_JOIN_LINES) {
956 	      startX = parentEntry->X();
957 	      endX = (startX + m_infosetSpacing *
958 		      ((parentEntry->GetNextMember()->X() >
959 			parentEntry->X()) ? 1 : -1));
960 	    }
961 	    else {
962 	      if (parentEntry->GetNextMember()->X() < parentEntry->X()) {
963 		// information set is continued to the left
964 		startX = parentEntry->X() + parentEntry->GetSize();
965 		endX = parentEntry->X() - m_infosetSpacing;
966 	      }
967 	      else {
968 		// information set is continued to the right
969 		startX = parentEntry->X();
970 		endX = (parentEntry->X() + parentEntry->GetSize() +
971 			m_infosetSpacing);
972 	      }
973 	    }
974 	    p_dc.DrawLine(startX, nextY, endX, nextY);
975 	    if (startX > endX) {
976 	      p_dc.DrawLine(endX, nextY, endX + m_infosetSpacing / 2,
977 			    nextY + m_infosetSpacing / 2);
978 	      p_dc.DrawLine(endX, nextY, endX + m_infosetSpacing / 2,
979 			    nextY - m_infosetSpacing / 2);
980 	    }
981 	    else {
982 	      p_dc.DrawLine(endX, nextY, endX - m_infosetSpacing / 2,
983 			    nextY + m_infosetSpacing / 2);
984 	      p_dc.DrawLine(endX, nextY, endX - m_infosetSpacing / 2,
985 			    nextY - m_infosetSpacing / 2);
986 	    }
987 	  }
988 	}
989       }
990     }
991 
992     if (entry->GetNode()->NumChildren() == 0) {
993       entry->Draw(p_dc, m_doc->GetSelectNode(), p_noHints);
994     }
995 
996     // As we draw, we determine the outcome label extents.  Adjust the
997     // overall size of the plot accordingly.
998     if (entry->GetOutcomeExtent().GetRight() > m_maxX) {
999       m_maxX = entry->GetOutcomeExtent().GetRight();
1000     }
1001   }
1002 }
1003 
Render(wxDC & p_dc,bool p_noHints) const1004 void gbtTreeLayout::Render(wxDC &p_dc, bool p_noHints) const
1005 {
1006   RenderSubtree(p_dc, p_noHints);
1007 }
1008 
1009