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/dlnfglogit.cc
6 // Dialog for monitoring progress of logit equilibrium computation
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 <fstream>
24 
25 #include <wx/wxprec.h>
26 #ifndef WX_PRECOMP
27 #include <wx/wx.h>
28 #endif  // WX_PRECOMP
29 #include <wx/stdpaths.h>
30 #include <wx/txtstrm.h>
31 #include <wx/tokenzr.h>
32 #include <wx/process.h>
33 #include <wx/print.h>
34 
35 #include "wx/sheet/sheet.h"
36 #include "wx/plotctrl/plotctrl.h"
37 #include "wx/wxthings/spinctld.h"   // for wxSpinCtrlDbl
38 
39 #include "gamedoc.h"
40 #include "menuconst.h"            // for tool IDs
41 
42 using namespace Gambit;
43 
44 
45 // Use an anonymous namespace to encapsulate the helper classes
46 
47 namespace {
48 
49 //========================================================================
50 //                    class LogitMixedBranch
51 //========================================================================
52 
53 /// Represents one branch of a logit equilibrium correspondence
54 class LogitMixedBranch {
55 private:
56   gbtGameDocument *m_doc;
57   List<double> m_lambdas;
58   List<MixedStrategyProfile<double> > m_profiles;
59 
60 public:
LogitMixedBranch(gbtGameDocument * p_doc)61   LogitMixedBranch(gbtGameDocument *p_doc) : m_doc(p_doc) { }
62 
63   void AddProfile(const wxString &p_text);
64 
NumPoints(void) const65   int NumPoints(void) const { return m_lambdas.Length(); }
GetLambda(int p_index) const66   double GetLambda(int p_index) const { return m_lambdas[p_index]; }
GetLambdas(void) const67   const List<double> &GetLambdas(void) const { return m_lambdas; }
GetProfile(int p_index)68   const MixedStrategyProfile<double> &GetProfile(int p_index)
69   { return m_profiles[p_index]; }
GetProfiles(void) const70   const List<MixedStrategyProfile<double> > &GetProfiles(void) const
71   { return m_profiles; }
72 };
73 
AddProfile(const wxString & p_text)74 void LogitMixedBranch::AddProfile(const wxString &p_text)
75 {
76   MixedStrategyProfile<double> profile(m_doc->GetGame()->NewMixedStrategyProfile(0.0));
77 
78   wxStringTokenizer tok(p_text, wxT(","));
79 
80   m_lambdas.Append((double) lexical_cast<Rational>(std::string((const char *) tok.GetNextToken().mb_str())));
81 
82   for (int i = 1; i <= profile.MixedProfileLength(); i++) {
83     profile[i] = lexical_cast<Rational>(std::string((const char *) tok.GetNextToken().mb_str()));
84   }
85 
86   m_profiles.Append(profile);
87 }
88 
89 //========================================================================
90 //                      class LogitMixedSheet
91 //========================================================================
92 
93 class LogitMixedSheet : public wxSheet {
94 private:
95   gbtGameDocument *m_doc;
96   LogitMixedBranch &m_branch;
97 
98   // Overriding wxSheet members for data access
99   wxString GetCellValue(const wxSheetCoords &);
100   wxSheetCellAttr GetAttr(const wxSheetCoords &p_coords, wxSheetAttr_Type) const;
101 
102   // Overriding wxSheet members to disable selection behavior
SelectRow(int,bool=false,bool=false)103   bool SelectRow(int, bool = false, bool = false)
104     { return false; }
SelectRows(int,int,bool=false,bool=false)105   bool SelectRows(int, int, bool = false, bool = false)
106     { return false; }
SelectCol(int,bool=false,bool=false)107   bool SelectCol(int, bool = false, bool = false)
108     { return false; }
SelectCols(int,int,bool=false,bool=false)109   bool SelectCols(int, int, bool = false, bool = false)
110     { return false; }
SelectCell(const wxSheetCoords &,bool=false,bool=false)111   bool SelectCell(const wxSheetCoords&, bool = false, bool = false)
112     { return false; }
SelectBlock(const wxSheetBlock &,bool=false,bool=false)113   bool SelectBlock(const wxSheetBlock&, bool = false, bool = false)
114     { return false; }
SelectAll(bool=false)115   bool SelectAll(bool = false) { return false; }
116 
117   // Overriding wxSheet member to suppress drawing of cursor
DrawCursorCellHighlight(wxDC &,const wxSheetCellAttr &)118   void DrawCursorCellHighlight(wxDC&, const wxSheetCellAttr &) { }
119 
120 public:
121   LogitMixedSheet(wxWindow *p_parent, gbtGameDocument *p_doc,
122 		     LogitMixedBranch &p_branch);
123   virtual ~LogitMixedSheet();
124 };
125 
LogitMixedSheet(wxWindow * p_parent,gbtGameDocument * p_doc,LogitMixedBranch & p_branch)126 LogitMixedSheet::LogitMixedSheet(wxWindow *p_parent,
127 				       gbtGameDocument *p_doc,
128 				       LogitMixedBranch &p_branch)
129   : wxSheet(p_parent, -1), m_doc(p_doc), m_branch(p_branch)
130 {
131   CreateGrid(p_branch.NumPoints(), p_doc->GetGame()->MixedProfileLength()+1);
132   SetRowLabelWidth(40);
133   SetColLabelHeight(25);
134 }
135 
~LogitMixedSheet()136 LogitMixedSheet::~LogitMixedSheet()
137 { }
138 
GetCellValue(const wxSheetCoords & p_coords)139 wxString LogitMixedSheet::GetCellValue(const wxSheetCoords &p_coords)
140 {
141   if (!m_doc->GetGame())  return wxT("");
142 
143   if (IsRowLabelCell(p_coords)) {
144     return wxString::Format(wxT("%d"), p_coords.GetRow() + 1);
145   }
146   else if (IsColLabelCell(p_coords)) {
147     if (p_coords.GetCol() == 0) {
148       return wxT("Lambda");
149     }
150     else {
151       int index = 1;
152       for (int pl = 1; pl <= m_doc->GetGame()->NumPlayers(); pl++) {
153 	GamePlayer player = m_doc->GetGame()->GetPlayer(pl);
154 	for (int st = 1; st <= player->NumStrategies(); st++) {
155 	  if (index++ == p_coords.GetCol()) {
156 	    return (wxString::Format(wxT("%d: "), pl) +
157 		    wxString(player->GetStrategy(st)->GetLabel().c_str(),
158 			    *wxConvCurrent));
159 	  }
160 	}
161       }
162       return wxT("");
163     }
164   }
165   else if (IsCornerLabelCell(p_coords)) {
166     return wxT("#");
167   }
168 
169   if (p_coords.GetCol() == 0) {
170     return wxString(lexical_cast<std::string>(m_branch.GetLambda(p_coords.GetRow()+1),
171 			   m_doc->GetStyle().NumDecimals()).c_str(),
172 		    *wxConvCurrent);
173   }
174   else {
175     const MixedStrategyProfile<double> &profile =
176       m_branch.GetProfile(p_coords.GetRow()+1);
177     return wxString(lexical_cast<std::string>(profile[p_coords.GetCol()],
178 				   m_doc->GetStyle().NumDecimals()).c_str(),
179 		    *wxConvCurrent);
180   }
181 }
182 
183 
GetPlayerColor(gbtGameDocument * p_doc,int p_index)184 static wxColour GetPlayerColor(gbtGameDocument *p_doc, int p_index)
185 {
186   if (!p_doc->GetGame())  return *wxBLACK;
187 
188   int index = 1;
189   for (int pl = 1; pl <= p_doc->GetGame()->NumPlayers(); pl++) {
190     GamePlayer player = p_doc->GetGame()->GetPlayer(pl);
191     for (int st = 1; st <= player->NumStrategies(); st++) {
192       if (index++ == p_index) {
193 	return p_doc->GetStyle().GetPlayerColor(pl);
194       }
195     }
196   }
197   return *wxBLACK;
198 }
199 
GetAttr(const wxSheetCoords & p_coords,wxSheetAttr_Type) const200 wxSheetCellAttr LogitMixedSheet::GetAttr(const wxSheetCoords &p_coords,
201 					 wxSheetAttr_Type) const
202 {
203   if (IsRowLabelCell(p_coords)) {
204     wxSheetCellAttr attr(GetSheetRefData()->m_defaultRowLabelAttr);
205     attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD));
206     attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
207     attr.SetOrientation(wxHORIZONTAL);
208     attr.SetReadOnly(true);
209     return attr;
210   }
211   else if (IsColLabelCell(p_coords)) {
212     wxSheetCellAttr attr(GetSheetRefData()->m_defaultColLabelAttr);
213     attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxBOLD));
214     attr.SetAlignment(wxALIGN_CENTER, wxALIGN_CENTER);
215     attr.SetOrientation(wxHORIZONTAL);
216     attr.SetReadOnly(true);
217     attr.SetForegroundColour(GetPlayerColor(m_doc, p_coords.GetCol()));
218     return attr;
219   }
220   else if (IsCornerLabelCell(p_coords)) {
221     return GetSheetRefData()->m_defaultCornerLabelAttr;
222   }
223 
224   wxSheetCellAttr attr(GetSheetRefData()->m_defaultGridCellAttr);
225   attr.SetFont(wxFont(10, wxSWISS, wxNORMAL, wxNORMAL));
226   attr.SetAlignment(wxALIGN_RIGHT, wxALIGN_CENTER);
227   attr.SetOrientation(wxHORIZONTAL);
228   attr.SetForegroundColour(GetPlayerColor(m_doc, p_coords.GetCol()));
229   attr.SetReadOnly(true);
230   return attr;
231 }
232 
233 class LogitBranchDialog : public wxDialog {
234 private:
235   LogitMixedSheet *m_sheet;
236 
237 public:
238   LogitBranchDialog(wxWindow *p_parent,
239 		       gbtGameDocument *p_doc,
240 		       LogitMixedBranch &p_branch);
241 };
242 
LogitBranchDialog(wxWindow * p_parent,gbtGameDocument * p_doc,LogitMixedBranch & p_branch)243 LogitBranchDialog::LogitBranchDialog(wxWindow *p_parent,
244 					   gbtGameDocument *p_doc,
245 					   LogitMixedBranch &p_branch)
246   : wxDialog(p_parent, wxID_ANY, wxT("Logit equilibrium correspondence"),
247 	     wxDefaultPosition)
248 {
249   m_sheet = new LogitMixedSheet(this, p_doc, p_branch);
250   m_sheet->AutoSizeCol(0);
251 
252   wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
253   sizer->Add(m_sheet, 0, wxALL, 5);
254   sizer->Add(CreateButtonSizer(wxOK), 0, wxALL | wxEXPAND, 5);
255 
256   SetSizer(sizer);
257   sizer->Fit(this);
258   sizer->SetSizeHints(this);
259   Layout();
260   CenterOnParent();
261 }
262 
263 
264 //========================================================================
265 //                      class gbtLogitPlotCtrl
266 //========================================================================
267 
268 class gbtLogitPlotCtrl : public wxPlotCtrl {
269 private:
270   // gbtGameDocument *m_doc;
271   double m_scaleFactor;
272 
273   /// Overriding x (lambda) axis labeling
274   void CalcXAxisTickPositions(void);
275 
276 public:
277   gbtLogitPlotCtrl(wxWindow *p_parent, gbtGameDocument *p_doc);
278 
LambdaToX(double p_lambda)279   double LambdaToX(double p_lambda)
280   { return m_scaleFactor*p_lambda / (1.0 + m_scaleFactor*p_lambda); }
XToLambda(double p_x)281   double XToLambda(double p_x)
282   { return p_x / (m_scaleFactor * (1.0 - p_x)); }
283 
SetScaleFactor(double p_scale)284   void SetScaleFactor(double p_scale) { m_scaleFactor = p_scale; }
285 };
286 
gbtLogitPlotCtrl(wxWindow * p_parent,gbtGameDocument * p_doc)287 gbtLogitPlotCtrl::gbtLogitPlotCtrl(wxWindow *p_parent,
288 				   gbtGameDocument *p_doc)
289   : wxPlotCtrl(p_parent), /* m_doc(p_doc), */ m_scaleFactor(1.0)
290 {
291   SetAxisLabelColour(*wxBLUE);
292   wxFont labelFont(8, wxSWISS, wxNORMAL, wxBOLD);
293   SetAxisLabelFont(labelFont);
294   SetAxisColour(*wxBLUE);
295   SetAxisFont(labelFont);
296   SetDrawSymbols(false);
297 
298   // SetAxisFont resets the width of the y axis labels, assuming
299   // a fairly long label.
300   int x=6, y=12, descent=0, leading=0;
301   GetTextExtent(wxT("0.88"), &x, &y, &descent, &leading, &labelFont);
302   m_y_axis_text_width = x + leading;
303 
304   SetXAxisLabel(wxT("Lambda"));
305   SetShowXAxisLabel(true);
306   SetYAxisLabel(wxT("Probability"));
307   SetShowYAxisLabel(true);
308 
309   SetShowKey(true);
310 
311   m_xAxisTick_step = 0.2;
312   SetViewRect(wxRect2DDouble(0, 0, 1, 1));
313 }
314 
315 //
316 // This differs from the wxPlotWindow original only by the use of
317 // XToLambda() to construct the tick labels.
318 //
CalcXAxisTickPositions(void)319 void gbtLogitPlotCtrl::CalcXAxisTickPositions(void)
320 {
321   double current = ceil(m_viewRect.GetLeft() / m_xAxisTick_step) * m_xAxisTick_step;
322   m_xAxisTicks.Clear();
323   m_xAxisTickLabels.Clear();
324   int i, x, windowWidth = GetPlotAreaRect().width;
325   for (i=0; i<m_xAxisTick_count; i++) {
326     if (!IsFinite(current, wxT("axis label is not finite"))) return;
327 
328     x = GetClientCoordFromPlotX( current );
329 
330     if ((x >= -1) && (x < windowWidth+2)) {
331       m_xAxisTicks.Add(x);
332       m_xAxisTickLabels.Add(wxString::Format(m_xAxisTickFormat.c_str(),
333 					     XToLambda(current)));
334     }
335 
336     current += m_xAxisTick_step;
337   }
338 }
339 
340 
341 //========================================================================
342 //                     class gbtLogitPlotStrategyList
343 //========================================================================
344 
345 class gbtLogitPlotStrategyList : public wxSheet {
346 private:
347   gbtGameDocument *m_doc;
348 
349   //!
350   //! @name Overriding wxSheet members to disable selection behavior
351   //!
352   //@{
SelectRow(int,bool=false,bool=false)353   bool SelectRow(int, bool = false, bool = false)
354     { return false; }
SelectRows(int,int,bool=false,bool=false)355   bool SelectRows(int, int, bool = false, bool = false)
356     { return false; }
SelectCol(int,bool=false,bool=false)357   bool SelectCol(int, bool = false, bool = false)
358     { return false; }
SelectCols(int,int,bool=false,bool=false)359   bool SelectCols(int, int, bool = false, bool = false)
360     { return false; }
SelectCell(const wxSheetCoords &,bool=false,bool=false)361   bool SelectCell(const wxSheetCoords&, bool = false, bool = false)
362     { return false; }
SelectBlock(const wxSheetBlock &,bool=false,bool=false)363   bool SelectBlock(const wxSheetBlock&, bool = false, bool = false)
364     { return false; }
SelectAll(bool=false)365   bool SelectAll(bool = false) { return false; }
366 
HasSelection(bool=true) const367   bool HasSelection(bool = true) const { return false; }
IsCellSelected(const wxSheetCoords &) const368   bool IsCellSelected(const wxSheetCoords &) const { return false; }
IsRowSelected(int) const369   bool IsRowSelected(int) const { return false; }
IsColSelected(int) const370   bool IsColSelected(int) const { return false; }
DeselectBlock(const wxSheetBlock &,bool=false)371   bool DeselectBlock(const wxSheetBlock &, bool = false) { return false; }
ClearSelection(bool=false)372   bool ClearSelection(bool = false) { return false; }
373   //@}
374 
375   /// Overriding wxSheet member to suppress drawing of cursor
DrawCursorCellHighlight(wxDC &,const wxSheetCellAttr &)376   void DrawCursorCellHighlight(wxDC&, const wxSheetCellAttr &) { }
377 
378   // Event handlers
379   // This disables moving the (unseen) cursor
OnLeftDown(wxSheetEvent &)380   void OnLeftDown(wxSheetEvent &) { }
381   void OnLeftUp(wxSheetEvent &);
382 
383 public:
384   gbtLogitPlotStrategyList(wxWindow *p_parent, gbtGameDocument *p_doc);
385 
IsStrategyShown(int p_index)386   bool IsStrategyShown(int p_index)
387   { return GetCellValue(wxSheetCoords(p_index-1, 2)) == wxT("1"); }
388 };
389 
gbtLogitPlotStrategyList(wxWindow * p_parent,gbtGameDocument * p_doc)390 gbtLogitPlotStrategyList::gbtLogitPlotStrategyList(wxWindow *p_parent,
391 						   gbtGameDocument *p_doc)
392   : wxSheet(p_parent, wxID_ANY), m_doc(p_doc)
393 {
394   CreateGrid(m_doc->GetGame()->MixedProfileLength(), 3);
395 
396   SetRowLabelWidth(0);
397   SetColLabelHeight(0);
398   SetGridLineColour(*wxWHITE);
399 
400   for (int st = 1; st <= m_doc->GetGame()->MixedProfileLength(); st++) {
401     GameStrategy strategy = m_doc->GetGame()->GetStrategy(st);
402     GamePlayer player = strategy->GetPlayer();
403     wxColour color = m_doc->GetStyle().GetPlayerColor(player->GetNumber());
404 
405     if (strategy->GetNumber() == 1) {
406       SetCellSpan(wxSheetCoords(st-1, 0),
407 		  wxSheetCoords(player->NumStrategies(), 1));
408       SetCellValue(wxSheetCoords(st-1, 0),
409 		   wxString(player->GetLabel().c_str(), *wxConvCurrent));
410       SetAttrForegroundColour(wxSheetCoords(st-1, 0), color);
411       SetAttrAlignment(wxSheetCoords(st-1, 0),
412 		       wxALIGN_CENTER | wxALIGN_CENTER_VERTICAL);
413     }
414 
415 
416     SetCellValue(wxSheetCoords(st-1, 1),
417 		 wxString(strategy->GetLabel().c_str(), *wxConvCurrent));
418     SetAttrForegroundColour(wxSheetCoords(st-1, 1), color);
419 
420     SetCellValue(wxSheetCoords(st-1, 2), wxT("1"));
421     SetAttrForegroundColour(wxSheetCoords(st-1, 2), color);
422     SetAttrRenderer(wxSheetCoords(st-1, 2),
423 		    wxSheetCellRenderer(new wxSheetCellBoolRendererRefData()));
424     SetAttrEditor(wxSheetCoords(st-1, 2),
425 		  wxSheetCellEditor(new wxSheetCellBoolEditorRefData()));
426   }
427 
428   AutoSizeCols();
429 
430   Connect(GetId(), wxEVT_SHEET_CELL_LEFT_DOWN,
431 	  (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtLogitPlotStrategyList::OnLeftDown)));
432 
433   Connect(GetId(), wxEVT_SHEET_CELL_LEFT_UP,
434 	  (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&gbtLogitPlotStrategyList::OnLeftUp)));
435 }
436 
OnLeftUp(wxSheetEvent & p_event)437 void gbtLogitPlotStrategyList::OnLeftUp(wxSheetEvent &p_event)
438 {
439   if (p_event.GetCoords().GetCol() != 2) {
440     return;
441   }
442 
443   if (GetCellValue(p_event.GetCoords()) == wxT("1")) {
444     SetCellValue(p_event.GetCoords(), wxT("0"));
445   }
446   else {
447     SetCellValue(p_event.GetCoords(), wxT("1"));
448   }
449 
450   // Allow normal processing -- parent window will want to update
451   p_event.Skip();
452 }
453 
454 
455 //========================================================================
456 //                       class LogitPlotPanel
457 //========================================================================
458 
459 class LogitPlotPanel : public wxPanel {
460 private:
461   gbtGameDocument *m_doc;
462   LogitMixedBranch m_branch;
463   gbtLogitPlotStrategyList *m_plotStrategies;
464   gbtLogitPlotCtrl *m_plotCtrl;
465 
466   // Event handlers
OnChangeStrategies(wxSheetEvent &)467   void OnChangeStrategies(wxSheetEvent &) { Plot(); }
468 
469 public:
470   LogitPlotPanel(wxWindow *p_parent, gbtGameDocument *p_doc);
471 
AddProfile(const wxString & p_text)472   void AddProfile(const wxString &p_text)
473   { m_branch.AddProfile(p_text); }
474 
475   void SetScaleFactor(double p_scale);
476   void FitZoom(void);
477   void Plot(void);
478 
GetBranch(void)479   LogitMixedBranch &GetBranch(void) { return m_branch; }
GetPlotCtrl(void) const480   wxPlotCtrl *GetPlotCtrl(void) const { return m_plotCtrl; }
481 };
482 
LogitPlotPanel(wxWindow * p_parent,gbtGameDocument * p_doc)483 LogitPlotPanel::LogitPlotPanel(wxWindow *p_parent,
484 				     gbtGameDocument *p_doc)
485   : wxPanel(p_parent, wxID_ANY), m_doc(p_doc), m_branch(p_doc)
486 {
487   m_plotCtrl = new gbtLogitPlotCtrl(this, p_doc);
488   m_plotCtrl->SetSizeHints(wxSize(600, 400));
489 
490   m_plotStrategies = new gbtLogitPlotStrategyList(this, p_doc);
491   wxStaticBoxSizer *playerSizer =
492     new wxStaticBoxSizer(wxHORIZONTAL, this, wxT("Show strategies"));
493   playerSizer->Add(m_plotStrategies, 1, wxALL | wxEXPAND, 5);
494 
495   wxBoxSizer *sizer = new wxBoxSizer(wxHORIZONTAL);
496   sizer->Add(playerSizer, 0, wxALL | wxEXPAND, 5);
497   sizer->Add(m_plotCtrl, 0, wxALL, 5);
498 
499   Connect(m_plotStrategies->GetId(), wxEVT_SHEET_CELL_LEFT_UP,
500 	  (wxObjectEventFunction) (wxEventFunction) wxStaticCastEvent(wxSheetEventFunction, wxSheetEventFunction(&LogitPlotPanel::OnChangeStrategies)));
501 
502   SetSizer(sizer);
503   Layout();
504 }
505 
Plot(void)506 void LogitPlotPanel::Plot(void)
507 {
508   if (m_branch.NumPoints() == 0)  return;
509 
510   m_plotCtrl->DeleteCurve(-1);
511 
512   for (int st = 1; st <= m_doc->GetGame()->MixedProfileLength(); st++) {
513     if (!m_plotStrategies->IsStrategyShown(st)) continue;
514 
515     wxPlotData *curve = new wxPlotData(m_branch.NumPoints());
516 
517     GameStrategy strategy = m_doc->GetGame()->GetStrategy(st);
518     GamePlayer player = strategy->GetPlayer();
519 
520     curve->SetFilename(wxString(player->GetLabel().c_str(), *wxConvCurrent) +
521 		       wxT(":") +
522 		       wxString(strategy->GetLabel().c_str(), *wxConvCurrent));
523 
524     for (int i = 0; i < m_branch.NumPoints(); i++) {
525       curve->SetValue(i, m_plotCtrl->LambdaToX(m_branch.GetLambda(i+1)),
526 		      m_branch.GetProfile(i+1)[st]);
527     }
528 
529     curve->SetPen(wxPLOTPEN_NORMAL,
530 		  wxPen(m_doc->GetStyle().GetPlayerColor(player->GetNumber()),
531 			1, wxSOLID));
532 
533     m_plotCtrl->AddCurve(curve, false);
534   }
535 }
536 
SetScaleFactor(double p_scale)537 void LogitPlotPanel::SetScaleFactor(double p_scale)
538 {
539   m_plotCtrl->SetScaleFactor(p_scale);
540   Plot();
541 }
542 
FitZoom(void)543 void LogitPlotPanel::FitZoom(void)
544 {
545   m_plotCtrl->MakeCurveVisible(-1);
546 }
547 
548 //========================================================================
549 //                       class LogitPrintout
550 //========================================================================
551 
552 class LogitPrintout : public wxPrintout {
553 private:
554   wxPlotCtrl *m_plot;
555 
556 public:
LogitPrintout(wxPlotCtrl * p_plot,const wxString & p_label)557   LogitPrintout(wxPlotCtrl *p_plot, const wxString &p_label)
558     : wxPrintout(p_label), m_plot(p_plot) { }
~LogitPrintout()559   virtual ~LogitPrintout() { }
560 
OnPrintPage(int)561   bool OnPrintPage(int)
562   { wxSize size = GetDC()->GetSize();
563     m_plot->DrawWholePlot(GetDC(), wxRect(50, 50,
564 					  size.GetWidth() - 100,
565 					  size.GetHeight() - 100));
566     return true;
567   }
HasPage(int page)568   bool HasPage(int page) { return (page <= 1); }
GetPageInfo(int * minPage,int * maxPage,int * selPageFrom,int * selPageTo)569   void GetPageInfo(int *minPage, int *maxPage,
570 		   int *selPageFrom, int *selPageTo)
571   { *minPage = 1; *maxPage = 1; *selPageFrom = 1; *selPageTo = 1; }
572 };
573 
574 
575 //========================================================================
576 //                      class LogitMixedDialog
577 //========================================================================
578 
579 class LogitMixedDialog : public wxDialog {
580 private:
581   gbtGameDocument *m_doc;
582   int m_pid;
583   wxProcess *m_process;
584   LogitPlotPanel *m_plot;
585   wxSpinCtrlDbl *m_scaler;
586   wxToolBar *m_toolBar;
587   wxStaticText *m_statusText;
588   wxButton *m_stopButton, *m_okButton;
589   wxTimer m_timer;
590   wxString m_output;
591 
592   void OnStop(wxCommandEvent &);
593   void OnTimer(wxTimerEvent &);
594   void OnIdle(wxIdleEvent &);
595   void OnEndProcess(wxProcessEvent &);
596   void OnSave(wxCommandEvent &);
597   void OnPrint(wxCommandEvent &);
598 
599   void OnChangeScale(wxSpinEvent &);
600   void OnZoomFit(wxCommandEvent &);
601   void OnViewData(wxCommandEvent &);
602 
603   void Start(void);
604 
605 public:
606   LogitMixedDialog(wxWindow *p_parent, gbtGameDocument *p_doc);
607 
608   DECLARE_EVENT_TABLE()
609 };
610 
611 const int GBT_ID_TIMER = 2000;
612 const int GBT_ID_PROCESS = 2001;
613 const int GBT_MENU_VIEW_DATA = 2002;
614 
BEGIN_EVENT_TABLE(LogitMixedDialog,wxDialog)615 BEGIN_EVENT_TABLE(LogitMixedDialog, wxDialog)
616   EVT_END_PROCESS(GBT_ID_PROCESS, LogitMixedDialog::OnEndProcess)
617   EVT_IDLE(LogitMixedDialog::OnIdle)
618   EVT_TIMER(GBT_ID_TIMER, LogitMixedDialog::OnTimer)
619   EVT_MENU(wxID_SAVE, LogitMixedDialog::OnSave)
620   EVT_MENU(wxID_PRINT, LogitMixedDialog::OnPrint)
621   EVT_MENU(GBT_MENU_VIEW_ZOOMFIT, LogitMixedDialog::OnZoomFit)
622   EVT_MENU(GBT_MENU_VIEW_DATA, LogitMixedDialog::OnViewData)
623 END_EVENT_TABLE()
624 
625 #include "bitmaps/stop.xpm"
626 #include "bitmaps/print.xpm"
627 #include "bitmaps/savedata.xpm"
628 #include "bitmaps/datasrc.xpm"
629 #include "bitmaps/zoomfit.xpm"
630 
631 LogitMixedDialog::LogitMixedDialog(wxWindow *p_parent,
632 				   gbtGameDocument *p_doc)
633   : wxDialog(p_parent, -1, wxT("Compute quantal response equilibria"),
634 	     wxDefaultPosition),
635     m_doc(p_doc), m_process(0), m_timer(this, GBT_ID_TIMER)
636 {
637   wxBoxSizer *sizer = new wxBoxSizer(wxVERTICAL);
638 
639   wxBoxSizer *startSizer = new wxBoxSizer(wxHORIZONTAL);
640 
641   m_statusText = new wxStaticText(this, wxID_STATIC,
642 				  wxT("The computation is currently in progress."));
643   m_statusText->SetForegroundColour(*wxBLUE);
644   startSizer->Add(m_statusText, 0, wxALL | wxALIGN_CENTER, 5);
645 
646   m_stopButton = new wxBitmapButton(this, wxID_CANCEL, wxBitmap(stop_xpm));
647   m_stopButton->SetToolTip(_("Stop the computation"));
648   startSizer->Add(m_stopButton, 0, wxALL | wxALIGN_CENTER, 5);
649   Connect(wxID_CANCEL, wxEVT_COMMAND_BUTTON_CLICKED,
650 	  wxCommandEventHandler(LogitMixedDialog::OnStop));
651 
652   sizer->Add(startSizer, 0, wxALL | wxALIGN_CENTER, 5);
653 
654   m_toolBar = new wxToolBar(this, wxID_ANY);
655   m_toolBar->SetWindowStyle(wxTB_HORIZONTAL | wxTB_FLAT);
656   m_toolBar->SetMargins(4, 4);
657   m_toolBar->SetToolBitmapSize(wxSize(24, 24));
658 
659   m_toolBar->AddTool(wxID_SAVE,
660                      wxEmptyString,
661                      wxBitmap(savedata_xpm),
662                      wxNullBitmap,
663                      wxITEM_NORMAL,
664 		                 _("Save the correspondence to a CSV file"),
665 		                 _("Save the correspondence to a CSV file"));
666 
667   m_toolBar->EnableTool(wxID_SAVE, false);
668 
669   m_toolBar->AddTool(GBT_MENU_VIEW_DATA,
670                      wxEmptyString,
671                      wxBitmap(datasrc_xpm),
672                      wxNullBitmap,
673                      wxITEM_NORMAL,
674 		                 _("View the points in the correspondence"),
675 		                 _("View the points in the correspondence"));
676 
677   m_toolBar->EnableTool(GBT_MENU_VIEW_DATA, false);
678 
679   m_toolBar->AddTool(wxID_PRINT,
680                      wxEmptyString,
681                      wxBitmap(print_xpm),
682                      wxNullBitmap,
683                      wxITEM_NORMAL,
684                      _("Print the graph"),
685                      _("Print the graph"));
686 
687   m_toolBar->AddSeparator();
688 
689   m_toolBar->AddTool(GBT_MENU_VIEW_ZOOMFIT,
690                      wxEmptyString,
691                      wxBitmap(zoomfit_xpm),
692                      wxNullBitmap,
693                      wxITEM_NORMAL,
694                      _("Show the whole graph"),
695                      _("Show the whole graph"));
696 
697   m_toolBar->AddControl(new wxStaticText(m_toolBar, wxID_STATIC,
698 					 wxT("Graph scaling:")));
699   m_scaler = new wxSpinCtrlDbl(*m_toolBar, wxID_ANY,
700 			       wxT(""),
701 			       wxDefaultPosition, wxDefaultSize,
702 			       0, 0.1, 10.0, 1.0, 0.1);
703   m_toolBar->AddControl(m_scaler);
704   Connect(m_scaler->GetId(), wxEVT_COMMAND_SPINCTRL_UPDATED,
705 	  wxSpinEventHandler(LogitMixedDialog::OnChangeScale));
706 
707   m_toolBar->Realize();
708   sizer->Add(m_toolBar, 0, wxALL | wxEXPAND, 5);
709 
710 
711   wxBoxSizer *midSizer = new wxBoxSizer(wxHORIZONTAL);
712   m_plot = new LogitPlotPanel(this, m_doc);
713   midSizer->Add(m_plot, 0, wxALL | wxALIGN_CENTER, 5);
714 
715   sizer->Add(midSizer, 0, wxALL | wxALIGN_CENTER, 5);
716 
717   wxBoxSizer *buttonSizer = new wxBoxSizer(wxHORIZONTAL);
718   m_okButton = new wxButton(this, wxID_OK, wxT("OK"));
719   buttonSizer->Add(m_okButton, 0, wxALL | wxALIGN_CENTER, 5);
720   m_okButton->Enable(false);
721 
722   sizer->Add(buttonSizer, 0, wxALL | wxALIGN_RIGHT, 5);
723 
724   SetSizer(sizer);
725   sizer->Fit(this);
726   sizer->SetSizeHints(this);
727   Layout();
728   CenterOnParent();
729   Start();
730 }
731 
Start(void)732 void LogitMixedDialog::Start(void)
733 {
734   m_doc->BuildNfg();
735 
736   m_process = new wxProcess(this, GBT_ID_PROCESS);
737   m_process->Redirect();
738 
739 #ifdef __WXMAC__
740   m_pid = wxExecute(wxStandardPaths::Get().GetExecutablePath() +
741 		    wxT("-logit -S"),
742                     wxEXEC_ASYNC, m_process);
743 #else
744   m_pid = wxExecute(wxT("gambit-logit -S"), wxEXEC_ASYNC, m_process);
745 #endif // __WXMAC__
746 
747   std::ostringstream s;
748   m_doc->GetGame()->Write(s, "nfg");
749   wxString str(wxString(s.str().c_str(), *wxConvCurrent));
750 
751   // It is possible that the whole string won't write on one go, so
752   // we should take this possibility into account.  If the write doesn't
753   // complete the whole way, we take a 100-millisecond siesta and try
754   // again.  (This seems to primarily be an issue with -- you guessed it --
755   // Windows!)
756   while (str.length() > 0) {
757     wxTextOutputStream os(*m_process->GetOutputStream());
758 
759     // It appears that (at least with mingw) the string itself contains
760     // only '\n' for newlines.  If we don't SetMode here, these get
761     // converted to '\r\n' sequences, and so the number of characters
762     // LastWrite() returns does not match the number of characters in
763     // our string.  Setting this explicitly solves this problem.
764     os.SetMode(wxEOL_UNIX);
765     os.WriteString(str);
766     str.Remove(0, m_process->GetOutputStream()->LastWrite());
767     wxMilliSleep(100);
768   }
769   m_process->CloseOutput();
770 
771   m_timer.Start(1000, false);
772 }
773 
OnIdle(wxIdleEvent & p_event)774 void LogitMixedDialog::OnIdle(wxIdleEvent &p_event)
775 {
776   if (!m_process)  return;
777 
778   if (m_process->IsInputAvailable()) {
779     wxTextInputStream tis(*m_process->GetInputStream());
780 
781     wxString msg;
782     msg << tis.ReadLine();
783     m_plot->AddProfile(msg);
784     //m_mixedList->AddProfile(msg, false);
785     m_output += msg;
786     m_output += wxT("\n");
787 
788     p_event.RequestMore();
789   }
790   else {
791     m_timer.Start(1000, false);
792   }
793 }
794 
OnTimer(wxTimerEvent & p_event)795 void LogitMixedDialog::OnTimer(wxTimerEvent &p_event)
796 {
797   wxWakeUpIdle();
798 }
799 
OnEndProcess(wxProcessEvent & p_event)800 void LogitMixedDialog::OnEndProcess(wxProcessEvent &p_event)
801 {
802   m_stopButton->Enable(false);
803   m_timer.Stop();
804 
805   while (m_process->IsInputAvailable()) {
806     wxTextInputStream tis(*m_process->GetInputStream());
807 
808     wxString msg;
809     msg << tis.ReadLine();
810 
811     if (msg != wxT("")) {
812       m_plot->AddProfile(msg);
813       //m_mixedList->AddProfile(msg, true);
814       m_output += msg;
815       m_output += wxT("\n");
816     }
817   }
818 
819   if (p_event.GetExitCode() == 0) {
820     m_statusText->SetLabel(wxT("The computation has completed."));
821     m_statusText->SetForegroundColour(wxColour(0, 192, 0));
822   }
823   else {
824     m_statusText->SetLabel(wxT("The computation ended abnormally."));
825     m_statusText->SetForegroundColour(*wxRED);
826   }
827 
828   m_okButton->Enable(true);
829   m_toolBar->EnableTool(wxID_SAVE, true);
830   m_toolBar->EnableTool(GBT_MENU_VIEW_DATA, true);
831   m_plot->Plot();
832 }
833 
OnStop(wxCommandEvent &)834 void LogitMixedDialog::OnStop(wxCommandEvent &)
835 {
836   // Per the wxWidgets wiki, under Windows, programs that run
837   // without a console window don't respond to the more polite
838   // SIGTERM, so instead we must be rude and SIGKILL it.
839   m_stopButton->Enable(false);
840 
841 #ifdef __WXMSW__
842   wxProcess::Kill(m_pid, wxSIGKILL);
843 #else
844   wxProcess::Kill(m_pid, wxSIGTERM);
845 #endif  // __WXMSW__
846 }
847 
OnSave(wxCommandEvent &)848 void LogitMixedDialog::OnSave(wxCommandEvent &)
849 {
850   wxFileDialog dialog(this, _("Choose file"), wxT(""), wxT(""),
851 		      wxT("CSV files (*.csv)|*.csv|")
852 			  wxT("All files (*.*)|*.*"),
853 		      wxFD_SAVE | wxFD_OVERWRITE_PROMPT);
854 
855   if (dialog.ShowModal() == wxID_OK) {
856     std::ofstream file((const char *) dialog.GetPath().mb_str());
857     file << ((const char *) m_output.mb_str());
858   }
859 }
860 
OnPrint(wxCommandEvent &)861 void LogitMixedDialog::OnPrint(wxCommandEvent &)
862 {
863   wxPrintDialogData data;
864   wxPrinter printer(&data);
865 
866   wxPrintout *printout = new LogitPrintout(m_plot->GetPlotCtrl(),
867 					   wxT("Logit correspondence"));
868 
869   if (!printer.Print(this, printout, true)) {
870     if (wxPrinter::GetLastError() == wxPRINTER_ERROR) {
871       wxMessageBox(_("There was an error in printing"), _("Error"), wxOK);
872     }
873     // Otherwise, user hit "cancel"; just be quiet and return.
874     return;
875   }
876 }
877 
OnChangeScale(wxSpinEvent &)878 void LogitMixedDialog::OnChangeScale(wxSpinEvent &)
879 {
880   m_plot->SetScaleFactor(m_scaler->GetValue());
881 }
882 
OnZoomFit(wxCommandEvent &)883 void LogitMixedDialog::OnZoomFit(wxCommandEvent &)
884 {
885   m_plot->FitZoom();
886 }
887 
OnViewData(wxCommandEvent &)888 void LogitMixedDialog::OnViewData(wxCommandEvent &)
889 {
890   LogitBranchDialog dialog(this, m_doc, m_plot->GetBranch());
891   dialog.ShowModal();
892 }
893 
894 
895 }  // end encapsulating anonymous namespace
896 
897 //========================================================================
898 //                        External interface
899 //========================================================================
900 
LogitStrategic(wxWindow * p_parent,gbtGameDocument * p_doc)901 void LogitStrategic(wxWindow *p_parent, gbtGameDocument *p_doc)
902 {
903   LogitMixedDialog(p_parent, p_doc).ShowModal();
904 }
905