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