1 /***********************************************************************
2 created:    05/08/2006
3 author:     Olivier Delannoy (Dalfy)
4 *************************************************************************/
5 /***************************************************************************
6 *   Copyright (C) 2004 - 2006 Paul D Turner & The CEGUI Development Team
7 *
8 *   Permission is hereby granted, free of charge, to any person obtaining
9 *   a copy of this software and associated documentation files (the
10 *   "Software"), to deal in the Software without restriction, including
11 *   without limitation the rights to use, copy, modify, merge, publish,
12 *   distribute, sublicense, and/or sell copies of the Software, and to
13 *   permit persons to whom the Software is furnished to do so, subject to
14 *   the following conditions:
15 *
16 *   The above copyright notice and this permission notice shall be
17 *   included in all copies or substantial portions of the Software.
18 *
19 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
20 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
21 *   MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
22 *   IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
23 *   OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
24 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
25 *   OTHER DEALINGS IN THE SOFTWARE.
26 ***************************************************************************/
27 #include "SampleBase.h"
28 #include "CEGUI/CEGUI.h"
29 #include "Minesweeper_Timer.h"
30 #include <ctime>
31 #include <cstdlib>
32 struct Location
33 {
34     size_t d_row;
35     size_t d_col;
36 };
37 const size_t MinesweeperSize = 10;
38 const size_t MineCount = 15;
39 
40 class MinesweeperSample : public Sample
41 {
42 public:
43     // method to initialse the samples windows and events.
44     virtual bool initialise(CEGUI::GUIContext* guiContext);
45 
46     // method to perform any required cleanup operations.
47     virtual void deinitialise();
48 
49 protected:
50     // Handle new game
51     bool handleGameStartClicked(const CEGUI::EventArgs& event);
52     // Handle click on a button of the board
53     bool handleMineButtonClicked(const CEGUI::EventArgs& event);
54     // Handle mouse button down on a button of the board
55     bool handleMineButtonDown(const CEGUI::EventArgs& event);
56     // Update the timer if needed
57     bool handleUpdateTimer(const CEGUI::EventArgs& event);
58     // reset the board
59     void boardReset();
60     // place mine and computes mine neighborhood
61     void boardPositionMines();
62     // Test whether the player wins or not
63     bool isGameWin();
64     // Call this function if the game is finished
65     void gameEnd(bool victory);
66     // When a button is clicked
67     bool boardDiscover(const Location& location);
68     // Store all buttons needed
69     CEGUI::PushButton* d_buttons[MinesweeperSize][MinesweeperSize];
70     // Store button location
71     Location d_buttonsMapping[MinesweeperSize][MinesweeperSize];
72     // Store the value of the board itself
73     size_t d_board[MinesweeperSize][MinesweeperSize];
74     // Store the number of case the user discovered
75     size_t d_boardCellDiscovered;
76     // Store the number of mine to find
77     CEGUI::Editbox* d_counter;
78     // Store the number of second elapsed
79     CEGUI::Editbox* d_timer;
80     // Used to display the result text
81     CEGUI::Window* d_result;
82 
83     // True if the game is started false otherwise
84     bool d_gameStarted;
85     // time at the start of the game
86     clock_t d_timerStartTime;
87     // current value of the timer
88     clock_t d_timerValue;
89     // Custom window type to force refresh of the timer
90     Timer* d_alarm;
91 };
92 
93 //////////////////////////////////////////////////////////////////////////
94 /*************************************************************************
95 
96 MinesweeperSample class
97 
98 *************************************************************************/
99 //////////////////////////////////////////////////////////////////////////
100 /*************************************************************************
101 Sample specific initialisation goes here.
102 *************************************************************************/
initialise(CEGUI::GUIContext * guiContext)103 bool MinesweeperSample::initialise(CEGUI::GUIContext* guiContext)
104 {
105     using namespace CEGUI;
106 
107     d_usedFiles = CEGUI::String(__FILE__);
108 
109     // Register Timer Window
110     WindowFactoryManager::getSingleton().addFactory( &getTimerFactory() );
111 
112     // load font and setup default if not loaded via scheme
113     Font& defaultFont = FontManager::getSingleton().createFromFile("DejaVuSans-12.font");
114     // Set default font for the gui context
115     guiContext->setDefaultFont(&defaultFont);
116 
117     d_gameStarted = false;
118 
119     // Get window manager which we wil use for a few jobs here.
120     WindowManager& winMgr = WindowManager::getSingleton();
121 
122     // Load the scheme to initialse the VanillaSkin which we use in this sample
123     SchemeManager::getSingleton().createFromFile("VanillaSkin.scheme");
124     SchemeManager::getSingleton().createFromFile("TaharezLook.scheme");
125     guiContext->setDefaultTooltipType("TaharezLook/Tooltip");
126 
127     // set default mouse image
128     guiContext->getMouseCursor().setDefaultImage("Vanilla-Images/MouseArrow");
129 
130     // load an image to use as a background
131     if( !ImageManager::getSingleton().isDefined("SpaceBackgroundImage") )
132         ImageManager::getSingleton().addFromImageFile("SpaceBackgroundImage", "SpaceBackground.jpg");
133 
134     // here we will use a StaticImage as the root, then we can use it to place a background image
135     Window* background = winMgr.createWindow("Vanilla/StaticImage");
136 
137     // set area rectangle
138     background->setArea(URect(cegui_reldim(0), cegui_reldim(0), cegui_reldim(1), cegui_reldim(1)));
139 
140     // disable frame and standard background
141     background->setProperty("FrameEnabled", "false");
142     background->setProperty("BackgroundEnabled", "false");
143 
144     // set the background image
145     background->setProperty("Image", "SpaceBackgroundImage");
146 
147     // install this as the root GUI sheet
148     guiContext->setRootWindow(background);
149     d_alarm = (Timer*)winMgr.createWindow("Timer");
150     background->addChild(d_alarm);
151     d_alarm->setDelay(0.5); // Tick each 0.5 seconds
152 
153     // create the game frame
154     Window* frame = winMgr.createWindow("Vanilla/FrameWindow");
155     d_alarm->addChild(frame);
156     frame->setXPosition(UDim(0.3f, 0.0f));
157     frame->setYPosition(UDim(0.15f, 0.0f));
158     frame->setWidth(UDim(0.4f, 0.0f));
159     frame->setHeight(UDim(0.7f, 0.0f));
160     frame->setText("CEGUI Minesweeper");
161 
162     // create the action panel
163     Window* action = winMgr.createWindow("DefaultWindow");
164     frame->addChild(action);
165     action->setXPosition(UDim(0.03f, 0.0f));
166     action->setYPosition(UDim(0.10f, 0.0f));
167     action->setWidth(UDim(0.94f, 0.0f));
168     action->setHeight(UDim(0.1f, 0.0f));
169     d_counter = (Editbox*)winMgr.createWindow("Vanilla/Editbox", "mine_counter");
170     action->addChild(d_counter);
171     d_counter->setText("0");
172     d_counter->setTooltipText("Number of mine");
173     d_counter->setReadOnly(true);
174     d_counter->setXPosition(UDim(0.0f, 0.0f));
175     d_counter->setYPosition(UDim(0.0f, 0.0f));
176     d_counter->setWidth(UDim(0.3f, 0.0f));
177     d_counter->setHeight(UDim(1.0f, 0.0f));
178 
179     Window* newGame = winMgr.createWindow("Vanilla/Button", "new_game");
180     action->addChild(newGame);
181     newGame->setText("Start");
182     newGame->setTooltipText("Start a new game");
183     newGame->setXPosition(UDim(0.35f, 0.0f));
184     newGame->setYPosition(UDim(0.0f, 0.0f));
185     newGame->setWidth(UDim(0.3f, 0.0f));
186     newGame->setHeight(UDim(1.0f, 0.0f));
187     newGame->subscribeEvent(PushButton::EventClicked,  Event::Subscriber(&MinesweeperSample::handleGameStartClicked, this));
188 
189     d_timer = (Editbox*)winMgr.createWindow("Vanilla/Editbox", "timer");
190     action->addChild(d_timer);
191     d_timer->setText("0");
192     d_timer->setTooltipText("Time elapsed");
193     d_timer->setReadOnly(true);
194     d_timer->setXPosition(UDim(0.7f, 0.0f));
195     d_timer->setYPosition(UDim(0.0f, 0.0f));
196     d_timer->setWidth(UDim(0.3f, 0.0f));
197     d_timer->setHeight(UDim(1.0f, 0.0f));
198     d_alarm->subscribeEvent(Timer::EventTimerAlarm, Event::Subscriber(&MinesweeperSample::handleUpdateTimer, this));
199 
200     // Board button grid
201     Window* grid = winMgr.createWindow("DefaultWindow");
202     frame->addChild(grid);
203     grid->setXPosition(UDim(0.03f, 0.0f));
204     grid->setYPosition(UDim(0.23f, 0.0f));
205     grid->setWidth(    UDim(0.94f, 0.0f));
206     grid->setHeight(   UDim(0.74f, 0.0f));
207     const float d_inc = 1.0f / MinesweeperSize;
208     for(size_t i = 0 ; i < MinesweeperSize ; ++i)
209     {
210         // create a container for each row
211         Window* row = winMgr.createWindow("DefaultWindow");
212         row->setArea(URect(UDim(0,0), UDim(d_inc * i, 0),
213             UDim(1,0), UDim(d_inc * (i + 1), 0)));
214         grid->addChild(row);
215         for(size_t j = 0 ; j < MinesweeperSize ; ++j)
216         {
217             // Initialize buttons coordinate
218             d_buttonsMapping[i][j].d_col = j;
219             d_buttonsMapping[i][j].d_row = i;
220             d_buttons[i][j] = (PushButton*)winMgr.createWindow("Vanilla/Button");
221             row->addChild(d_buttons[i][j]);
222             d_buttons[i][j]->setArea(URect(UDim(d_inc * j, 0), UDim(0,0),
223                 UDim(d_inc * (j + 1), 0), UDim(1,0)));
224             d_buttons[i][j]->setEnabled(false);
225             // Associate user data
226             d_buttons[i][j]->setUserData(&(d_buttonsMapping[i][j]));
227             d_buttons[i][j]->setID(0);
228             // Connect event handlers
229             d_buttons[i][j]->subscribeEvent(PushButton::EventClicked, Event::Subscriber(&MinesweeperSample::handleMineButtonClicked, this));
230             d_buttons[i][j]->subscribeEvent(Window::EventMouseButtonDown, Event::Subscriber(&MinesweeperSample::handleMineButtonDown, this));
231         }
232     }
233     d_result = winMgr.createWindow("Vanilla/StaticText");
234     grid->addChild(d_result);
235     d_result->setXPosition(UDim(0.0, 0.0));
236     d_result->setYPosition(UDim(0.0, 0.0));
237     d_result->setWidth(UDim(1.0, 0.0));
238     d_result->setHeight(UDim(1.0, 0.0));
239     d_result->setAlwaysOnTop(true);
240     d_result->setProperty("HorzFormatting", "HorzCentred");
241     d_result->setVisible(false);
242     d_result->setAlpha(0.67f);
243     // activate the background window
244     background->activate();
245     // success!
246     return true;
247 }
248 
249 
250 /*************************************************************************
251 Cleans up resources allocated in the initialiseSample call.
252 *************************************************************************/
deinitialise()253 void MinesweeperSample::deinitialise()
254 {
255     //delete d_console;
256 }
257 /*************************************************************************
258 Handle new game started event
259 *************************************************************************/
handleGameStartClicked(const CEGUI::EventArgs &)260 bool MinesweeperSample::handleGameStartClicked(const CEGUI::EventArgs&)
261 {
262     d_result->setVisible(false);
263     boardReset();
264     boardPositionMines();
265     for (size_t i = 0 ; i < MinesweeperSize ; ++i)
266     {
267         for(size_t j = 0 ; j < MinesweeperSize ; ++j)
268         {
269             d_buttons[i][j]->setProperty("DisabledTextColour", "FF000000");
270             d_buttons[i][j]->setText("");
271             d_buttons[i][j]->setEnabled(true);
272         }
273     }
274     d_counter->setText(CEGUI::PropertyHelper<CEGUI::uint>::toString(MineCount));
275     // Handle timer
276     d_timerStartTime = ::clock();
277     d_timerValue = 0;
278     d_timer->setText("0");
279     d_gameStarted = true;
280     d_alarm->start();
281     return true;
282 }
283 /************************************************************************
284 Handle click on a mine button
285 ************************************************************************/
handleMineButtonClicked(const CEGUI::EventArgs & event)286 bool MinesweeperSample::handleMineButtonClicked(const CEGUI::EventArgs& event)
287 {
288     const CEGUI::WindowEventArgs* evt = static_cast<const CEGUI::WindowEventArgs*>(&event);
289     CEGUI::PushButton* button = static_cast<CEGUI::PushButton*>(evt->window);
290     Location* buttonLoc = static_cast<Location*>(button->getUserData());
291     if (button->getID() > 0)
292     {
293         // dont touch flagged buttons
294         return true;
295     }
296     if (boardDiscover(*buttonLoc))
297     {
298         // We did not find a mine
299         button->setText(CEGUI::PropertyHelper<CEGUI::uint>::toString(d_board[buttonLoc->d_row][buttonLoc->d_col]));
300         if (isGameWin())
301             gameEnd(true);
302     }
303     else
304     {
305         for(size_t i = 0 ; i < MinesweeperSize ; ++i)
306         {
307             for (size_t j = 0 ;  j < MinesweeperSize ; ++j)
308             {
309                 if (! d_buttons[i][j]->isDisabled())
310                 {
311                     if (d_board[i][j] > 8)
312                     {
313                         d_buttons[i][j]->setText("B");
314                         d_buttons[i][j]->setProperty("DisabledTextColour", "FFFF1010");
315                     }
316                     else
317                     {
318                         d_buttons[i][j]->setText(CEGUI::PropertyHelper<CEGUI::uint>::toString(d_board[i][j]));
319                     }
320                 }
321                 d_buttons[i][j]->setEnabled(false);
322             }
323         }
324         gameEnd(false);
325     }
326     return true;
327 }
328 /************************************************************************
329 Handle click on a mine button (any mouse button)
330 ************************************************************************/
handleMineButtonDown(const CEGUI::EventArgs & event)331 bool MinesweeperSample::handleMineButtonDown(const CEGUI::EventArgs& event)
332 {
333     const CEGUI::MouseEventArgs& me = static_cast<const CEGUI::MouseEventArgs&>(event);
334     if (me.button == CEGUI::RightButton)
335     {
336         CEGUI::Window* button = me.window;
337         if (!button->isDisabled())
338         {
339             if (button->getID() == 0)
340             {
341                 button->setID(1);
342                 button->setText("F");
343             }
344             else
345             {
346                 button->setID(0);
347                 button->setText("");
348             }
349             return true;
350         }
351     }
352     return false;
353 }
354 /***********************************************************************
355 Handle timer refresh
356 ***********************************************************************/
handleUpdateTimer(const CEGUI::EventArgs &)357 bool MinesweeperSample::handleUpdateTimer(const CEGUI::EventArgs&)
358 {
359     if (d_gameStarted)
360     {
361         clock_t time = ::clock();
362         time -= d_timerStartTime;
363         if (time != d_timerValue)
364         {
365             d_timer->setText(CEGUI::PropertyHelper<CEGUI::uint>::toString(time /  CLOCKS_PER_SEC));
366             d_timerValue = time;
367         }
368     }
369     return true;
370 }
371 /************************************************************************
372 Create the board
373 ************************************************************************/
boardReset()374 void MinesweeperSample::boardReset()
375 {
376     d_boardCellDiscovered = 0;
377     for(size_t i = 0 ; i < MinesweeperSize ; ++i)
378     {
379         for(size_t j = 0 ; j < MinesweeperSize ; ++j)
380         {
381             d_board[i][j] = 0;
382         }
383     }
384 }
385 /************************************************************************
386 Position mine on the board
387 ************************************************************************/
boardPositionMines()388 void MinesweeperSample::boardPositionMines()
389 {
390     size_t x = 0 ;
391     size_t y = 0 ;
392     ::srand(::clock());
393     for(size_t i = 0 ; i < MineCount ; ++i)
394     {
395         do
396         {
397             x = (size_t) ((float)MinesweeperSize * (::rand() / (RAND_MAX + 1.0)));
398             y = (size_t) ((float)MinesweeperSize * (::rand() / (RAND_MAX + 1.0)));
399         }
400         while(d_board[x][y] > 8);
401 
402         d_board[x][y] += 10;
403         if (x > 0)
404         {
405             if (y > 0)
406                 ++ d_board[x - 1][y - 1];
407 
408             ++ d_board[x - 1][y    ];
409 
410             if (y < MinesweeperSize - 1)
411                 ++ d_board[x - 1][y + 1];
412         }
413 
414         if (y > 0)
415             ++ d_board[x    ][y - 1];
416 
417         if (y < MinesweeperSize - 1)
418             ++ d_board[x    ][y + 1];
419 
420         if (x < MinesweeperSize - 1)
421         {
422             if (y > 0)
423                 ++ d_board[x + 1][y - 1];
424 
425             ++ d_board[x + 1][y    ];
426 
427             if (y < MinesweeperSize - 1)
428                 ++ d_board[x + 1][y + 1];
429         }
430     }
431 }
432 /************************************************************************
433 Check wether the game is won or not
434 ************************************************************************/
isGameWin()435 bool MinesweeperSample::isGameWin()
436 {
437     return d_boardCellDiscovered + MineCount == MinesweeperSize * MinesweeperSize;
438 }
439 
440 
gameEnd(bool victory)441 void MinesweeperSample::gameEnd(bool victory)
442 {
443     d_gameStarted = false;
444     d_alarm->stop();
445     CEGUI::String message;
446     if (victory)
447     {
448         message = CEGUI::String("You win");
449     }
450     else
451     {
452         message = CEGUI::String("You lose");
453     }
454     // Display a message to the user
455     d_result->setText(message);
456     d_result->setVisible(true);
457 }
458 
boardDiscover(const Location & loc)459 bool MinesweeperSample::boardDiscover(const Location& loc)
460 {
461     CEGUI::PushButton* btn = d_buttons[loc.d_row][loc.d_col];
462     if (btn->isDisabled() || btn->getID() > 0)
463         return true;
464 
465     if (d_board[loc.d_row][loc.d_col] > 8)
466         return false;
467     d_buttons[loc.d_row][loc.d_col]->setText(CEGUI::PropertyHelper<CEGUI::uint>::toString(d_board[loc.d_row][loc.d_col]));
468     d_buttons[loc.d_row][loc.d_col]->setEnabled(false);
469     ++d_boardCellDiscovered;
470     // Discover surrounding case
471     if (d_board[loc.d_row][loc.d_col] == 0)
472     {
473         Location l;
474         if (loc.d_row > 0)
475         {
476             l.d_row = loc.d_row - 1;
477             if ( loc.d_col > 0)
478             {
479                 l.d_col = loc.d_col - 1;
480                 boardDiscover(l);
481             }
482             l.d_col = loc.d_col;
483             boardDiscover(l);
484             if ( loc.d_col < MinesweeperSize - 1)
485             {
486                 l.d_col = loc.d_col + 1;
487                 boardDiscover(l);
488             }
489         }
490         l.d_row = loc.d_row;
491         if ( loc.d_col > 0)
492         {
493             l.d_col = loc.d_col - 1;
494             boardDiscover(l);
495         }
496         if ( loc.d_col < MinesweeperSize  - 1)
497         {
498             l.d_col = loc.d_col + 1;
499             boardDiscover(l);
500         }
501         if (loc.d_row < MinesweeperSize - 1)
502         {
503             l.d_row = loc.d_row + 1;
504             if ( loc.d_col > 0)
505             {
506                 l.d_col = loc.d_col - 1;
507                 boardDiscover(l);
508             }
509             l.d_col = loc.d_col;
510             boardDiscover(l);
511             if ( loc.d_col < MinesweeperSize - 1)
512             {
513                 l.d_col = loc.d_col + 1;
514                 boardDiscover(l);
515             }
516         }
517     }
518     return true;
519 }
520 
521 
getSampleInstance()522 extern "C" SAMPLE_EXPORT Sample& getSampleInstance()
523 {
524     static MinesweeperSample sample;
525     return sample;
526 }