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 }