1 /*
2   Copyright (C) 2005 Dan Alcantara (dfalcantara@ucdavis.edu, http://wwwcsif.cs.ucdavis.edu/~alcantar)
3   Based on the board game "Blokus" by Educational Insights, Inc.
4 
5   This program is free software; you can redistribute
6   it and/or modify it under the terms of the GNU General
7   Public License as published by the Free Software Foundation;
8   either version 2 of the License, or (at your option) any later version.
9 
10   This program is distributed in the hope that it will be useful, but
11   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12   or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13   more details.
14 
15   You should have received a copy of the GNU General Public License along
16   with this program; if not, write to the Free Software Foundation, Inc.,
17   59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 
19   Full license visible at http://www.opensource.org/licenses/gpl-license.php
20 */
21 
22 #include <iostream>
23 #include <fstream>
24 
25 #include <wx/wx.h>
26 #include <wx/glcanvas.h>
27 #include <wx/colordlg.h>
28 #include <wx/filedlg.h>
29 #include <wx/utils.h>
30 
31 #include "game.h"
32 #include "options.h"
33 #include "help.h"
34 #include "docs.h"
35 #include "blokish.xpm"
36 
37 #ifndef WIN32
38 	#include <sys/types.h>
39 	#include <pwd.h>
40 #endif
41 
42 enum
43 {
44   ID_NEW_GAME = 100,
45   ID_CHANGE_FIRST,
46   ID_CHANGE_SECOND,
47   ID_CHANGE_THIRD,
48   ID_CHANGE_FOURTH,
49   ID_LOAD_GAME,
50   ID_SAVE_GAME,
51   ID_CHANGE_AI,
52   ID_SHOW_HELP,
53   ID_SHOW_ABOUT
54 };
55 
56 class BlokishAIThread:public wxThread
57 {
58 public:
BlokishAIThread(BlokishGame * game)59   BlokishAIThread(BlokishGame *game):wxThread(wxTHREAD_JOINABLE)
60   {
61     mpGame = game;
62     mpGame->SetOutcome(BlokishGame::WaitForAI);
63     Create();
64   }
65 
Entry()66   virtual ExitCode Entry()
67   {
68     mpGame->ResetAI();
69     mpGame->TakeAIStep();
70     return 0;
71   }
72 
73 protected:
74   BlokishGame *mpGame;
75 };
76 BlokishAIThread	*gAIThread;
77 bool gbAllowNewThreadToStart = true;;
78 
79 class BlokishCanvas:public wxGLCanvas
80 {
81   struct Color
82   {
ColorBlokishCanvas::Color83     Color(float r = 0, float g = 0, float b = 0, float a = 0)
84     {
85       values[0] = r;
86       values[1] = g;
87       values[2] = b;
88       values[3] = a;
89     }
90 
operator []BlokishCanvas::Color91     float& operator[](int which)
92     {
93       return values[which];
94     }
95 
96     float values[4];
97   };
98 
99 public:
BlokishCanvas(wxWindow * parent,wxWindowID id,wxPoint pos,wxSize size,int attribList[],BlokishGame & game)100   BlokishCanvas(wxWindow *parent, wxWindowID id, wxPoint pos, wxSize size, int attribList[], BlokishGame &game)
101     :wxGLCanvas(parent, id, pos, size, 0, wxString::FromAscii(""), attribList), mpGame(&game)
102   {
103     mnNumPiecesPerRow = 7;
104 
105     mvColor.clear();
106     mvColor.reserve(4);
107 
108     #ifdef WIN32
109       std::ifstream settingsFile(".blokishrc", std::ios::binary);
110     #else
111 			wxString filename = wxGetHomeDir();
112       filename += wxString::FromAscii("/.blokishrc");
113       std::ifstream settingsFile(filename.fn_str(), std::ios::binary);
114     #endif
115 
116     if(false == settingsFile.is_open())
117     {
118       mvColor.push_back(Color(0.25, 0.25, 1.00));
119       mvColor.push_back(Color(1.00, 1.00, 0.25));
120       mvColor.push_back(Color(1.00, 0.25, 0.25));
121       mvColor.push_back(Color(0.25, 1.00, 0.25));
122     }
123     else
124     {
125       AI::AIOptions *options = mpGame->GetOptions();
126 
127       for(unsigned int nPlayer = 0; nPlayer < 4; nPlayer++)
128       {
129         float color[3];
130         for(unsigned int nColor = 0; nColor < 3; nColor++)
131         {
132           settingsFile.read((char*) &(color[nColor]), sizeof(color[nColor]));
133         }
134         mvColor.push_back(Color(color[0], color[1], color[2]));
135 
136         settingsFile.read((char*) &(options[nPlayer]), sizeof(options[nPlayer]));
137 
138       }
139     }
140   }
141 
WriteSettings()142   void WriteSettings()
143   {
144     AI::AIOptions *options = mpGame->GetOptions();
145 
146     #ifdef WIN32
147       std::cout << "Saving to .blokishrc.\n";
148       std::ofstream settingsFile(".blokishrc", std::ios::binary);
149     #else
150 			wxString filename = wxGetHomeDir();
151       filename += wxString::FromAscii("/.blokishrc");
152       std::ofstream settingsFile(filename.fn_str(), std::ios::binary);
153 
154       if(false == settingsFile.is_open())
155       {
156 				std::cerr << "Failed to open file for writing.\n";
157 				exit(1);
158       }
159     #endif
160 
161     for(unsigned int nPlayer = 0; nPlayer < 4; nPlayer++)
162     {
163       for(unsigned int nColor = 0; nColor < 3; nColor++)
164       {
165         settingsFile.write((char *) &(mvColor[nPlayer][nColor]), sizeof(mvColor[nPlayer][nColor]));
166       }
167 
168       settingsFile.write((char*) &(options[nPlayer]), sizeof(options[nPlayer]));
169     }
170   }
171 
OnTimer()172   void OnTimer()
173   {
174     if(gbAllowNewThreadToStart && mpGame->GetOutcome() == BlokishGame::BeginAIThread)
175     {
176       gAIThread = new BlokishAIThread(mpGame);
177       gAIThread->Run();
178 
179       Refresh(false);
180       Update();
181     }
182     else if(mpGame->GetOutcome() == BlokishGame::WaitForAI)
183     {
184 			Refresh(false);
185     }
186     else if(mpGame->GetOutcome() == BlokishGame::FinishAIThread)
187     {
188       if(gAIThread != NULL)
189       {
190         if(gAIThread->IsRunning())
191         {
192 			    gAIThread->Delete();
193         }
194 			  delete gAIThread;
195         gAIThread = NULL;
196       }
197 
198       if(mpGame->FinishTurn() == BlokishGame::GameOver)
199       {
200         wxMessageBox(wxString::FromAscii("Game over!"));
201       }
202 
203       Refresh(false);
204       Update();
205     }
206     else if(mpGame->GetOutcome() == BlokishGame::GetPlayerInput)
207     {
208       Refresh(false);
209     }
210   }
211 
OnDraw(wxPaintEvent & event)212   void OnDraw(wxPaintEvent &event)
213   {
214     wxPaintDC(this);
215     SetCurrent();
216 
217     glEnable(GL_BLEND);
218     glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
219 
220     wxSize size = GetClientSize();
221 
222     // Reset
223     glViewport(0, 0, size.GetWidth() / 2, size.GetHeight());
224     glMatrixMode(GL_PROJECTION);
225     glLoadIdentity();
226     glOrtho(-.01, mpGame->GetNumCols()+.01, -.01, mpGame->GetNumRows()+.01, -1, 1);
227 
228     glClear(GL_COLOR_BUFFER_BIT);
229 
230     // Draw the board
231     glBegin(GL_QUADS);
232       glColor4f(1, 1, 1, 0.9);
233       glVertex2f(0, 0);
234       glColor4f(1, 1, 1, 0.95);
235       glVertex2f(0, mpGame->GetNumRows());
236       glColor4f(1, 1, 1, 1.0);
237       glVertex2f(mpGame->GetNumCols(), mpGame->GetNumRows());
238       glColor4f(1, 1, 1, 0.95);
239       glVertex2f(mpGame->GetNumCols(), 0);
240     glEnd();
241 
242     glBegin(GL_LINES);
243       glColor4f(0, 0, 0, 1);
244       glVertex2f(mpGame->GetNumCols(), 0);
245       glVertex2f(mpGame->GetNumCols(), mpGame->GetNumRows());
246     glEnd();
247 
248     float round = mpGame->GetRound();
249 
250     for(int nRow = int(mpGame->GetNumRows()-1); nRow >= 0; nRow--)
251     {
252       for(int nCol = 0; nCol < mpGame->GetNumCols(); nCol++)
253       {
254         glPushMatrix();
255         glTranslatef(nCol, nRow, 0);
256 
257         int age = int(round) - mpGame->GetAge(nRow, nCol);
258 
259         float factor = 0.0;
260         if(age < 4)
261         {
262           age = 4 - age;
263           factor = age / 4.0;
264         }
265 
266         DrawSquare(mpGame->GetSpaceID(nRow, nCol), 1.0f);
267 
268 				// Draws dots on the more recent pieces.
269         if(mpGame->GetSpaceID(nRow, nCol) < MAX_PLAYER_ID && factor != 0.0f)
270           DrawSquare(usable, factor, false);
271 
272 				// Draws dots where pieces could fit
273         if(mpGame->IsUsableSpace(nRow, nCol))
274         {
275           DrawSquare(usable, 1.0, false);
276         }
277 
278         glPopMatrix();
279       }
280     }
281 
282     glViewport(size.GetWidth() / 2, 0, size.GetWidth() / 2, size.GetHeight());
283     glLoadIdentity();
284     glOrtho(0, mnNumPiecesPerRow*6, 0, mnNumPiecesPerRow*6, -1, 1);
285 
286     glBegin(GL_QUADS);
287       glColor4f(.75, .75, .75, 1);
288       glVertex2f(0, 0);
289       glColor4f(.875, .875, .875, 1);
290       glVertex2f(0, mnNumPiecesPerRow*6);
291       glColor4f(1, 1, 1, 1);
292       glVertex2f(mnNumPiecesPerRow*6, mnNumPiecesPerRow*6);
293       glColor4f(.875, .875, .875, 1);
294       glVertex2f(mnNumPiecesPerRow*6, 0);
295     glEnd();
296 
297     // Draw the unused pieces
298     if(false == mpGame->IsGameOver())
299     { // Draw the current player's unused pieces
300       DrawUnusedPlayerPieces();
301 
302       // Draw the players' pieces in the top
303       for(int player = 0; player < 4; player++)
304       {
305         mnNumPiecesPerRow = int(sqrt(mpGame->GetUnplayedPlayerPieces(BlokishID(player)).size()) + 1);
306         glLoadIdentity();
307         glOrtho(-1, mnNumPiecesPerRow*6 + 1, -1, mnNumPiecesPerRow*6 + 1, -1, 1);
308 
309         int boxDimension = size.GetWidth() / 8;
310         int boxBorder = 0;
311 
312         glViewport( int((size.GetWidth()*(5.00 + player * 1.25))/ 10) + boxBorder,
313                     size.GetHeight() - boxDimension - boxBorder - 1,
314                     boxDimension - 2*boxBorder,
315                     boxDimension - 2*boxBorder);
316 
317         // Draw the box surrounding the pieces
318         if(mpGame->IsDead(BlokishID(player), true))
319           glColor4f(.35, .35, .35, .75);
320         else
321           glColor4f(mvColor[player][0], mvColor[player][1], mvColor[player][2], 1);
322 
323         glPushMatrix();
324         glLoadIdentity();
325         glOrtho(0, 1, 0, 1, -1, 1);
326         DrawSquare(BlokishID(player), 1.0f, true, mpGame->IsDead(BlokishID(player), true));
327         glPopMatrix();
328 
329         DrawUnusedPlayerPieces(BlokishID(player));
330       }
331 
332       mnNumPiecesPerRow = 7;
333     }
334     else
335     { // Draw all the players' unused pieces evenly
336       int maxPieces = 0;
337       maxPieces = std::max(maxPieces, int(mpGame->GetUnplayedPlayerPieces(first).size()));
338       maxPieces = std::max(maxPieces, int(mpGame->GetUnplayedPlayerPieces(second).size()));
339       maxPieces = std::max(maxPieces, int(mpGame->GetUnplayedPlayerPieces(third).size()));
340       maxPieces = std::max(maxPieces, int(mpGame->GetUnplayedPlayerPieces(fourth).size()));
341 
342       mnNumPiecesPerRow = int(sqrt(float(maxPieces)) + 1);
343 
344       glLoadIdentity();
345       glOrtho(0, mnNumPiecesPerRow*6, 0, mnNumPiecesPerRow*6, -1, 1);
346 
347       glViewport(size.GetWidth() / 2, size.GetHeight()/2, size.GetWidth() / 4, size.GetHeight()/2);
348       glPushMatrix();
349       glLoadIdentity();
350       glOrtho(0, 1, 0, 1, -1, 1);
351       DrawSquare(first);
352       glPopMatrix();
353       DrawUnusedPlayerPieces(first);
354 
355       glViewport(size.GetWidth() * 3 / 4, size.GetHeight()/2, size.GetWidth() / 4, size.GetHeight()/2);
356       glPushMatrix();
357       glLoadIdentity();
358       glOrtho(0, 1, 0, 1, -1, 1);
359       DrawSquare(second);
360       glPopMatrix();
361       DrawUnusedPlayerPieces(second);
362 
363       glViewport(size.GetWidth() / 2, 0, size.GetWidth() / 4, size.GetHeight()/2);
364       glPushMatrix();
365       glLoadIdentity();
366       glOrtho(0, 1, 0, 1, -1, 1);
367       DrawSquare(third);
368       glPopMatrix();
369       DrawUnusedPlayerPieces(third);
370 
371       glViewport(size.GetWidth() * 3 / 4, 0, size.GetWidth() / 4, size.GetHeight()/2);
372       glPushMatrix();
373       glLoadIdentity();
374       glOrtho(0, 1, 0, 1, -1, 1);
375       DrawSquare(fourth);
376       glPopMatrix();
377       DrawUnusedPlayerPieces(fourth);
378 
379       mnNumPiecesPerRow = 7;
380     }
381 
382     // Draw the selected piece
383     if(mpGame->GetOutcome() < BlokishGame::DOING_AI)
384     {
385       BlokishPiece *selectedPiece = mpGame->GetSelectedPiece();
386       if(selectedPiece != NULL)
387       {
388         glViewport(0, 0, size.GetWidth(), size.GetHeight());
389         glLoadIdentity();
390         glOrtho(0, mpGame->GetNumCols() * 2, 0, mpGame->GetNumRows(), -1, 1);
391 
392         float xCoordinate = mpGame->GetNumCols() * 2.0 * mMouseX / float(size.GetWidth());
393         float yCoordinate = mpGame->GetNumRows() * (size.GetHeight() - mMouseY) / float(size.GetHeight());
394 
395         glTranslatef(xCoordinate - .5, yCoordinate - .5, 0);
396 
397         // Translate the window position into a board position
398         int row = int(mpGame->GetNumRows() * (size.GetHeight() - mMouseY) / float(size.GetHeight()));
399         int col = int(mpGame->GetNumCols() * mMouseX / float(size.GetWidth() / 2.0));
400         if(mpGame->PieceCanBePlaced(selectedPiece, row, col))
401           DrawPiece(selectedPiece, 1.0f);
402         else
403           DrawPiece(selectedPiece, 0.5f);
404       }
405     }
406 
407     SwapBuffers();
408   }
409 
DrawUnusedPlayerPieces(BlokishID player=none)410   void DrawUnusedPlayerPieces(BlokishID player = none)
411   {
412     glPushMatrix();
413 
414     std::vector<BlokishPiece*> &pieces = mpGame->GetUnplayedPlayerPieces(player);
415 
416     glTranslatef(3, 3, 0);
417     int piecesDrawn = 0;
418     for(std::vector<BlokishPiece*>::iterator itr = pieces.begin(); itr != pieces.end(); itr++)
419     {
420       DrawPiece(*itr, 1.0f);
421 
422       piecesDrawn++;
423       if(piecesDrawn % mnNumPiecesPerRow == 0)
424       {
425         piecesDrawn = 0;
426         glTranslatef(-6 * (mnNumPiecesPerRow - 1), 6, 0);
427       }
428       else
429       {
430         glTranslatef(6, 0, 0);
431       }
432     }
433     glPopMatrix();
434   }
435 
DrawPiece(BlokishPiece * piece,float alpha=1.0f)436   void DrawPiece(BlokishPiece *piece, float alpha = 1.0f)
437   {
438     const std::vector<Square> &squares = piece->GetSquares();
439 
440     for(std::vector<Square>::const_iterator itr = squares.begin(); itr != squares.end(); itr++)
441     {
442       glPushMatrix();
443       glTranslatef(itr->mX, itr->mY, 0);
444       DrawSquare(piece->GetOwner(), alpha);
445       glPopMatrix();
446     }
447   }
448 
DrawSquare(const BlokishID id,float alpha=1.0,bool drawBox=true,bool gray=false)449   void DrawSquare(const BlokishID id, float alpha = 1.0, bool drawBox = true, bool gray = false)
450   {
451     const float bounds[] = {.15, .85};
452 
453     if(drawBox)
454     {
455       glBegin(GL_LINE_LOOP);
456         glColor4f(0,0,0,.1);
457         glVertex2f(0,0);
458         glVertex2f(0,1);
459         glVertex2f(1,1);
460         glVertex2f(1,0);
461       glEnd();
462     }
463 
464     if(id == usable)
465     {
466       glColor4f(0, 0, 0, .25 * alpha);
467 
468       glBegin(GL_TRIANGLE_FAN);
469         glVertex2f(.5, .5);
470 
471         for(float i = 0; i <= M_PI * 2; i += .157)
472         {
473           glVertex2f(.5 + .1*cos(i), .5 + .1*sin(i));
474         }
475 
476       glEnd();
477     }
478     else if(id == first || id == second || id == third || id == fourth)
479     {
480       glBegin(GL_QUADS);
481         if(gray)
482           glColor4f(.75, .75, .75, alpha * .75);
483         else
484           glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha * .75);
485         glVertex2f(bounds[0], bounds[0]);
486         glVertex2f(bounds[0], bounds[1]);
487         glVertex2f(bounds[1], bounds[1]);
488         glVertex2f(bounds[1], bounds[0]);
489       glEnd();
490 
491       glBegin(GL_QUADS);
492         if(gray)
493           glColor4f(.75, .75, .75, alpha);
494         else
495           glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha);
496         glVertex2f( 0,  0);
497         glVertex2f( 0,  1);
498         glVertex2f(bounds[0],  bounds[1]);
499         glVertex2f(bounds[0],  bounds[0]);
500 
501         if(gray)
502           glColor4f(.75, .75, .75, alpha * .80);
503         else
504           glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha * .80);
505         glVertex2f(0, 0);
506         glVertex2f(1, 0);
507         glVertex2f(bounds[1], bounds[0]);
508         glVertex2f(bounds[0], bounds[0]);
509 
510         if(gray)
511           glColor4f(.75, .75, .75, alpha * .60);
512         else
513           glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha * .60);
514         glVertex2f(1, 0);
515         glVertex2f(1, 1);
516         glVertex2f(bounds[1], bounds[1]);
517         glVertex2f(bounds[1], bounds[0]);
518 
519         if(gray)
520           glColor4f(.75, .75, .75, alpha * .40);
521         else
522           glColor4f(mvColor[id][0] * 1.0, mvColor[id][1] * 1.0, mvColor[id][2] * 1.0, alpha * .40);
523         glVertex2f(0, 1);
524         glVertex2f(1, 1);
525         glVertex2f(bounds[1], bounds[1]);
526         glVertex2f(bounds[0], bounds[1]);
527       glEnd();
528 
529       glBegin(GL_LINE_LOOP);
530         if(gray)
531           glColor4f(.25, .25, .25, 1.0);
532         else
533           glColor4f(mvColor[id][0] * .25, mvColor[id][1] * .25, mvColor[id][2] * .25, 1.0);
534         glVertex2f(0,0);
535         glVertex2f(0,1);
536         glVertex2f(1,1);
537         glVertex2f(1,0);
538       glEnd();
539     }
540   }
541 
OnErase(wxEraseEvent & event)542   void OnErase(wxEraseEvent &event)
543   {
544     event.Skip();
545   }
546 
OnRightDown(wxMouseEvent & event)547   void OnRightDown(wxMouseEvent &event)
548   {
549     ReflectHorizontal();
550   }
551 
ReflectHorizontal()552   void ReflectHorizontal()
553   {
554     BlokishGame::TurnOutcome outcome = mpGame->GetOutcome();
555     if(outcome == BlokishGame::BeginAIThread || outcome == BlokishGame::WaitForAI || outcome == BlokishGame::FinishAIThread)
556     { // Don't interfere.
557       return;
558     }
559 
560     if(mpGame->GetSelectedPiece() != NULL)
561     {
562       mpGame->GetSelectedPiece()->ReflectHorizontal();
563     }
564     else
565     {
566       PieceVector &pieces = mpGame->GetUnplayedPlayerPieces();
567 
568       for(PieceVector::iterator itr = pieces.begin(); itr != pieces.end(); itr++)
569       {
570         (*itr)->ReflectHorizontal();
571       }
572     }
573   }
574 
OnLeftDown(wxMouseEvent & event)575   void OnLeftDown(wxMouseEvent &event)
576   {
577     SetFocus();
578 
579     BlokishGame::TurnOutcome outcome = mpGame->GetOutcome();
580     if(outcome > BlokishGame::DOING_AI)
581     { // Don't interfere.
582       return;
583     }
584 
585     wxPaintDC(this);
586     SetCurrent();
587 
588     wxSize size = GetClientSize();
589 
590     if(event.GetX() >= size.GetWidth() / 2)
591     { // Picking a new piece
592       float pixel[3];
593       glReadBuffer(GL_FRONT);
594       glReadPixels(event.GetX(), size.GetHeight() - event.GetY(), 1, 1, GL_RGB, GL_FLOAT, pixel);
595 
596       int displayWidth = size.GetWidth() / 2;
597       int displayHeight = size.GetHeight();
598 
599       int displayPieceWidth = displayWidth / mnNumPiecesPerRow;
600       int displayPieceHeight = displayHeight / mnNumPiecesPerRow;
601 
602       int x = event.GetX() - size.GetWidth() / 2;
603       int y = size.GetHeight() - event.GetY();
604 
605       int row = y / displayPieceHeight;
606       int col = x / displayPieceWidth;
607       int nPiece = row * mnNumPiecesPerRow + col;
608 
609       mpGame->SetSelectedPiece(nPiece);
610     }
611     else
612     { // Clicking on the board
613       if(mpGame->GetSelectedPiece() != NULL)
614       {
615         // Translate the window position into a board position
616         int row = int(mpGame->GetNumRows() * (size.GetHeight() - event.GetY()) / float(size.GetHeight()));
617         int col = int(mpGame->GetNumCols() * event.GetX() / float(size.GetWidth() / 2.0));
618 
619         if(mpGame->PlacePiece(mpGame->GetSelectedPiece(), row, col))
620         {
621           if(BlokishGame::GameOver == mpGame->FinishTurn())
622           {
623             Refresh(false);
624             Update();
625             wxMessageBox(wxString::FromAscii("Game over!"));
626           }
627         }
628       }
629     }
630   }
631 
OnMotion(wxMouseEvent & event)632   void OnMotion(wxMouseEvent &event)
633   {
634     mMouseX = event.GetX();
635     mMouseY = event.GetY();
636     event.Skip();
637     Refresh(false);
638   }
639 
ChangeColor(int which)640   void ChangeColor(int which)
641   {
642     wxColour newColour = wxGetColourFromUser(this);
643     if(newColour.Ok())
644     {
645       mvColor[which][0] = newColour.Red() / 255.0f;
646       mvColor[which][1] = newColour.Green() / 255.0f;
647       mvColor[which][2] = newColour.Blue() / 255.0f;
648     }
649 
650     WriteSettings();
651   }
652 
OnMousewheel(wxMouseEvent & event)653   void OnMousewheel(wxMouseEvent &event)
654   {
655     if(event.GetWheelRotation() < 0)
656     {
657       RotateClockwise();
658     }
659     else
660     {
661       RotateCounterClockwise();
662     }
663     Refresh(false);
664   }
665 
OnKeyDown(wxKeyEvent & event)666   void OnKeyDown(wxKeyEvent &event)
667   {
668     if(event.GetKeyCode() == WXK_LEFT)
669     {
670       RotateCounterClockwise();
671     }
672     else if(event.GetKeyCode() == WXK_RIGHT)
673     {
674       RotateClockwise();
675     }
676     Refresh(false);
677   }
678 
RotateCounterClockwise()679   void RotateCounterClockwise()
680   {
681     BlokishGame::TurnOutcome outcome = mpGame->GetOutcome();
682     if(outcome == BlokishGame::BeginAIThread || outcome == BlokishGame::WaitForAI || outcome == BlokishGame::FinishAIThread)
683     { // Don't interfere.
684       return;
685     }
686 
687     if(mpGame->GetSelectedPiece() != NULL)
688     {
689       mpGame->GetSelectedPiece()->RotateCounterClockwise();
690     }
691     else
692     {
693       PieceVector &pieces = mpGame->GetUnplayedPlayerPieces();
694 
695       for(PieceVector::iterator itr = pieces.begin(); itr != pieces.end(); itr++)
696       {
697         (*itr)->RotateCounterClockwise();
698       }
699     }
700   }
701 
RotateClockwise()702   void RotateClockwise()
703   {
704     BlokishGame::TurnOutcome outcome = mpGame->GetOutcome();
705     if(outcome == BlokishGame::BeginAIThread || outcome == BlokishGame::WaitForAI || outcome == BlokishGame::FinishAIThread)
706     { // Don't interfere.
707       return;
708     }
709 
710     if(mpGame->GetSelectedPiece() != NULL)
711     {
712       mpGame->GetSelectedPiece()->RotateClockwise();
713     }
714     else
715     {
716       PieceVector &pieces = mpGame->GetUnplayedPlayerPieces();
717 
718       for(PieceVector::iterator itr = pieces.begin(); itr != pieces.end(); itr++)
719       {
720         (*itr)->RotateClockwise();
721       }
722     }
723   }
724 
OnChangeAI()725   void OnChangeAI()
726   {
727     std::vector<bool> aiStatus;
728     aiStatus.push_back(mpGame->IsComputerControlled(0));
729     aiStatus.push_back(mpGame->IsComputerControlled(1));
730     aiStatus.push_back(mpGame->IsComputerControlled(2));
731     aiStatus.push_back(mpGame->IsComputerControlled(3));
732 
733     AI::AIOptions *optionsVector = mpGame->GetOptions();
734 
735     OptionsDialog options(this, -1, wxString::FromAscii("Options"), aiStatus, optionsVector);
736 
737     for(int i = 0; i < 4; i++)
738     {
739       mpGame->SetComputerControlled(i, options.IsAI(i));
740       optionsVector[i].weightSize = options.GetSizeWeight(i);
741       optionsVector[i].weightFriendlySpaces = options.GetFriendlySpaceWeight(i);
742       optionsVector[i].weightEnemySpaces = options.GetEnemySpaceWeight(i);
743       optionsVector[i].numFriendlyPiecesToCheck = options.GetNumFriendlyPieces(i);
744       optionsVector[i].numEnemyPiecesToCheck = options.GetNumEnemyPieces(i);
745     }
746 
747     if(mpGame->GetOutcome() != BlokishGame::GameOver)
748     {
749       if(mpGame->GetOutcome() < BlokishGame::DOING_AI)
750       {
751         if(options.IsAI(int(mpGame->GetCurrentPlayer())))
752         {
753           mpGame->SetOutcome(BlokishGame::BeginAIThread);
754         }
755         else
756         {
757           mpGame->SetOutcome(BlokishGame::GetPlayerInput);
758         }
759       }
760     }
761 
762     WriteSettings();
763   }
764 
765 protected:
766   BlokishGame              *mpGame;
767   int                     mMouseX;
768   int                     mMouseY;
769   int                     mnNumPiecesPerRow;
770   std::vector<Color>      mvColor;
771 
772 DECLARE_EVENT_TABLE()
773 };
774 
775 BEGIN_EVENT_TABLE(BlokishCanvas, wxGLCanvas)
776   EVT_KEY_DOWN(BlokishCanvas::OnKeyDown)
777   EVT_MOTION(BlokishCanvas::OnMotion)
778   EVT_PAINT(BlokishCanvas::OnDraw)
779   EVT_ERASE_BACKGROUND(BlokishCanvas::OnErase)
780   EVT_LEFT_DOWN(BlokishCanvas::OnLeftDown)
781   EVT_RIGHT_DOWN(BlokishCanvas::OnRightDown)
782   EVT_MOUSEWHEEL(BlokishCanvas::OnMousewheel)
783 END_EVENT_TABLE()
784 
785 class BlokishFrame:public wxFrame
786 {
787 public:
BlokishFrame(wxWindow * parent,wxWindowID id,wxString title)788   BlokishFrame(wxWindow *parent, wxWindowID id, wxString title):wxFrame(parent, id, title, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxMINIMIZE_BOX | wxCLOSE_BOX | wxSYSTEM_MENU), mGauge(NULL), mTimer(this)
789   {
790     wxIcon BlokishIcon(blokish_xpm);
791     SetIcon(BlokishIcon);
792     if(BlokishIcon.Ok() == false)
793     {
794       std::cerr << "Nope.\n";
795       exit(1);
796     }
797 
798     CreateMenuBar();
799     CreateStatusBar();
800 
801     int attribList[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER};
802     mCanvas = new BlokishCanvas(this, -1, wxDefaultPosition, wxSize(800, 400), attribList, mGame);
803 
804     Connect(mTimer.GetId(), wxEVT_TIMER, wxCommandEventHandler(BlokishFrame::OnTimer));
805     mTimer.Start(100);
806 
807     wxBoxSizer *mainSizer = new wxBoxSizer(wxVERTICAL);
808     mainSizer->Add(mCanvas, 0, 0);
809     mainSizer->SetSizeHints(this);
810     SetSizer(mainSizer);
811     CenterOnScreen();
812   }
813 
814 protected:
CreateMenuBar()815   void CreateMenuBar()
816   {
817     menubar = new wxMenuBar();
818 
819     wxMenu *file = new wxMenu(wxString::FromAscii(""));
820     file->Append(ID_NEW_GAME, wxString::FromAscii("&New game"));
821     file->Append(ID_SAVE_GAME, wxString::FromAscii("&Save game"));
822     file->Append(ID_LOAD_GAME, wxString::FromAscii("&Load game"));
823     file->AppendSeparator();
824     file->Append(wxID_EXIT, wxString::FromAscii("E&xit"));
825 
826     wxMenu *options = new wxMenu(wxString::FromAscii(""));
827     options->Append(ID_CHANGE_AI, wxString::FromAscii("Artificial intelligence"));
828     options->AppendSeparator();
829     options->Append(ID_CHANGE_FIRST,  wxString::FromAscii("Change player &1's color"));
830     options->Append(ID_CHANGE_SECOND, wxString::FromAscii("Change player &2's color"));
831     options->Append(ID_CHANGE_THIRD,  wxString::FromAscii("Change player &3's color"));
832     options->Append(ID_CHANGE_FOURTH, wxString::FromAscii("Change player &4's color"));
833 
834     wxMenu *help = new wxMenu(wxString::FromAscii(""));
835     help->Append(ID_SHOW_HELP, wxString::FromAscii("&Help pages"));
836     help->AppendSeparator();
837     help->Append(ID_SHOW_ABOUT, wxString::FromAscii("&About"));
838 
839     menubar->Append(file, wxString::FromAscii("&File"));
840     menubar->Append(options, wxString::FromAscii("&Options"));
841     menubar->Append(help, wxString::FromAscii("&Help"));
842 
843     SetMenuBar(menubar);
844   }
845 
OnHelp(wxCommandEvent & event)846   void OnHelp(wxCommandEvent &event)
847   {
848     HelpDialog dialog(help_str, this, -1, wxString::FromAscii("Blokish help"));
849   }
850 
OnAbout(wxCommandEvent & event)851   void OnAbout(wxCommandEvent &event)
852   {
853     HelpDialog dialog(about_str, this, -1, wxString::FromAscii("About"));
854   }
855 
CreateStatusBar()856   void CreateStatusBar()
857   {
858     statusBar = new wxStatusBar(this, -1);
859     statusBar->SetFieldsCount(5);
860     mGauge = new wxGauge(statusBar, -1, mGame.GetNumCols() * mGame.GetNumRows(), wxDefaultPosition);
861     SetStatusBar(statusBar);
862   }
863 
OnClose(wxCloseEvent & event)864 	void OnClose(wxCloseEvent &event)
865 	{
866 		if(gAIThread != NULL)
867 		{
868 			std::cout << "Waiting for thread to complete... " << std::flush;
869       gbAllowNewThreadToStart = false;
870 			gAIThread->Delete();
871 			delete gAIThread;
872 			gAIThread = NULL;
873 			std::cout << "done.\n" << std::flush;
874 		}
875 		Destroy();
876 	}
877 
OnExit(wxCommandEvent & event)878   void OnExit(wxCommandEvent &event)
879   {
880     Close();
881   }
882 
OnNewGame(wxCommandEvent & event)883   void OnNewGame(wxCommandEvent &event)
884   {
885 		WaitForAIToFinish();
886     mGame.Reset();
887 		gbAllowNewThreadToStart = true;
888   }
889 
WaitForAIToFinish()890 	void WaitForAIToFinish()
891 	{
892 		if(gAIThread != NULL)
893 		{
894 			gbAllowNewThreadToStart = false;
895 
896       if(gAIThread->IsRunning())
897       {
898 			  gAIThread->Wait();
899       }
900 		}
901 	}
902 
OnSaveGame(wxCommandEvent & event)903   void OnSaveGame(wxCommandEvent &event)
904   {
905 		WaitForAIToFinish();
906     wxString filename = wxFileSelector(wxString::FromAscii("Select a filename to save as"), wxString::FromAscii(""), wxString::FromAscii("default.sav"), wxString::FromAscii(".sav"), wxString::FromAscii("*.sav"), wxSAVE);
907     if(!filename.empty())
908     {
909       std::ofstream outFile(filename.fn_str(), std::ios::binary);
910       mGame.Write(outFile);
911     }
912 		gbAllowNewThreadToStart = true;
913   }
914 
OnLoadGame(wxCommandEvent & event)915   void OnLoadGame(wxCommandEvent &event)
916   {
917 		WaitForAIToFinish();
918     wxString filename = wxFileSelector(wxString::FromAscii("Select a game to load"), wxString::FromAscii(""), wxString::FromAscii(""), wxString::FromAscii(".sav"), wxString::FromAscii("*.sav"), wxOPEN | wxFILE_MUST_EXIST);
919     if(!filename.empty())
920     {
921       std::ifstream inFile(filename.fn_str(), std::ios::binary);
922       if(false == mGame.Read(inFile))
923       {
924         wxMessageBox(wxString::FromAscii("File given was not a blokish save game."));
925       }
926 			inFile.close();
927     }
928 		gbAllowNewThreadToStart = true;
929   }
930 
OnTimer(wxCommandEvent & event)931   void OnTimer(wxCommandEvent &event)
932   {
933     char buffer[256];
934 
935 		if(manPrevScores[0] != mGame.GetScore(first))
936 		{
937     	sprintf(buffer, "Player 1: %d", mGame.GetScore(first));
938     	statusBar->SetStatusText(wxString::FromAscii(buffer), 0);
939 		}
940 
941 		if(manPrevScores[1] != mGame.GetScore(second))
942 		{
943     	sprintf(buffer, "Player 2: %d", mGame.GetScore(second));
944     	statusBar->SetStatusText(wxString::FromAscii(buffer), 1);
945 		}
946 
947 		if(manPrevScores[2] != mGame.GetScore(third))
948 		{
949     	sprintf(buffer, "Player 3: %d", mGame.GetScore(third));
950     	statusBar->SetStatusText(wxString::FromAscii(buffer), 3);
951 		}
952 
953 		if(manPrevScores[3] != mGame.GetScore(fourth))
954 		{
955     	sprintf(buffer, "Player 4: %d", mGame.GetScore(fourth));
956     	statusBar->SetStatusText(wxString::FromAscii(buffer), 4);
957 		}
958 
959     if(mGame.GetOutcome() == BlokishGame::WaitForAI)
960     {
961       statusBar->SetStatusText(wxString::FromAscii(""), 2);
962       mGauge->Show();
963       mGauge->SetValue(mGame.GetNumDone());
964     }
965     else if(mGame.GetOutcome() == BlokishGame::GameOver)
966     {
967       mGauge->Hide();
968       statusBar->SetStatusText(wxString::FromAscii("Game is over"), 2);
969     }
970     else if(mGame.GetOutcome() == BlokishGame::BeginAIThread)
971     {
972       mGauge->Hide();
973       statusBar->SetStatusText(wxString::FromAscii("Starting AI thread"), 2);
974     }
975     else if(mGame.GetOutcome() == BlokishGame::FinishAIThread)
976     {
977       mGauge->Hide();
978       statusBar->SetStatusText(wxString::FromAscii("Finishing AI thread"), 2);
979     }
980     else
981     {
982       mGauge->Hide();
983       statusBar->SetStatusText(wxString::FromAscii("Awaiting player input..."), 2);
984     }
985 
986 		manPrevScores[0] = mGame.GetScore(first);
987 		manPrevScores[1] = mGame.GetScore(second);
988 		manPrevScores[2] = mGame.GetScore(third);
989 		manPrevScores[3] = mGame.GetScore(fourth);
990 
991     mTimer.Start(100);
992     mCanvas->OnTimer();
993     event.Skip();
994   }
995 
OnChangeAI(wxCommandEvent & event)996   void OnChangeAI(wxCommandEvent &event)
997   {
998     mCanvas->OnChangeAI();
999   }
1000 
OnChangeFirst(wxCommandEvent & event)1001   void OnChangeFirst(wxCommandEvent &event)   { mCanvas->ChangeColor(0); }
OnChangeSecond(wxCommandEvent & event)1002   void OnChangeSecond(wxCommandEvent &event)  { mCanvas->ChangeColor(1); }
OnChangeThird(wxCommandEvent & event)1003   void OnChangeThird(wxCommandEvent &event)   { mCanvas->ChangeColor(2); }
OnChangeFourth(wxCommandEvent & event)1004   void OnChangeFourth(wxCommandEvent &event)  { mCanvas->ChangeColor(3); }
1005 
OnSize(wxSizeEvent & event)1006   void OnSize(wxSizeEvent &event)
1007   {
1008     if(mGauge != NULL)
1009     {
1010       wxRect rect;
1011       statusBar->GetFieldRect(2, rect);
1012       mGauge->SetSize(rect.x + 2, rect.y + 2, rect.width - 4, rect.height - 4);
1013     }
1014   }
1015 
1016 	int												 manPrevScores[4];
1017   BlokishGame::TurnOutcome   mLastIdleOutcome;
1018   BlokishCanvas            	*mCanvas;
1019   BlokishGame                mGame;
1020   wxMenuBar               	*menubar;
1021   wxStatusBar             	*statusBar;
1022   wxGauge                 	*mGauge;
1023   wxTimer                    mTimer;
1024 
1025 DECLARE_EVENT_TABLE()
1026 };
1027 
1028 BEGIN_EVENT_TABLE(BlokishFrame, wxFrame)
1029   EVT_SIZE(BlokishFrame::OnSize)
1030   EVT_MENU(ID_SHOW_HELP, BlokishFrame::OnHelp)
1031   EVT_MENU(ID_SHOW_ABOUT, BlokishFrame::OnAbout)
1032   EVT_MENU(ID_CHANGE_AI, BlokishFrame::OnChangeAI)
1033   EVT_MENU(ID_NEW_GAME, BlokishFrame::OnNewGame)
1034   EVT_MENU(ID_SAVE_GAME, BlokishFrame::OnSaveGame)
1035   EVT_MENU(ID_LOAD_GAME, BlokishFrame::OnLoadGame)
1036   EVT_MENU(wxID_EXIT, BlokishFrame::OnExit)
1037 	EVT_CLOSE(BlokishFrame::OnClose)
1038 
1039   EVT_MENU(ID_CHANGE_FIRST, BlokishFrame::OnChangeFirst)
1040   EVT_MENU(ID_CHANGE_SECOND, BlokishFrame::OnChangeSecond)
1041   EVT_MENU(ID_CHANGE_THIRD, BlokishFrame::OnChangeThird)
1042   EVT_MENU(ID_CHANGE_FOURTH, BlokishFrame::OnChangeFourth)
1043 END_EVENT_TABLE()
1044 
1045 class BlokishApp:public wxApp
1046 {
1047 public:
OnInit()1048   bool OnInit()
1049   {
1050 		CreateDocStrs();
1051     BlokishFrame *frame = new BlokishFrame(NULL, -1, wxString::FromAscii("Blokish"));
1052     frame->Show();
1053     return true;
1054   }
1055 };
1056 
1057 IMPLEMENT_APP(BlokishApp)
1058