1 /*****************************************************************************
2 * LibreMines *
3 * Copyright (C) 2020-2021 Bruno Bollos Correa *
4 * *
5 * This program is free software: you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation, either version 3 of the License, or *
8 * (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program. If not, see <http://www.gnu.org/licenses/>. *
17 *****************************************************************************
18 */
19
20
21 #include "libreminesgameengine.h"
22
23 #include <QRandomGenerator>
24 #include <QTimer>
25
CellGameEngine()26 LibreMinesGameEngine::CellGameEngine::CellGameEngine():
27 state(ZERO),
28 isHidden(true),
29 hasFlag(false)
30 {
31
32 }
33
LibreMinesGameEngine()34 LibreMinesGameEngine::LibreMinesGameEngine()
35 {
36
37 }
38
vNewGame(const uchar _X,const uchar _Y,ushort i_nMines_,const uchar i_X_Clean,const uchar i_Y_Clean)39 void LibreMinesGameEngine::vNewGame(const uchar _X,
40 const uchar _Y,
41 ushort i_nMines_,
42 const uchar i_X_Clean,
43 const uchar i_Y_Clean)
44 {
45 // Update the instance parameters
46 iX = _X;
47 iY = _Y;
48 nMines = i_nMines_;
49 iMinesLeft = i_nMines_;
50
51 iHiddenCells = 0;
52
53 // Check if we are just remaking the game with one cell clean
54 const bool bRemakingGame = (i_X_Clean != 255 && i_Y_Clean != 255);
55
56 if(!bRemakingGame)
57 {
58 bGameActive = false;
59 bFirst = true;
60 principalMatrix = std::vector<std::vector<CellGameEngine>> (iX, std::vector<CellGameEngine>(iY));
61 }
62
63
64 if(bRemakingGame)
65 {
66 for(std::vector<CellGameEngine>& i: principalMatrix)
67 {
68 for(CellGameEngine& j: i)
69 {
70 j.state = ZERO;
71 }
72 }
73 }
74 else
75 {
76 for(uchar j=0; j<iY; j++)
77 {
78 for (uchar i=0; i<iX; i++)
79 {
80 CellGameEngine& cell = principalMatrix[i][j];
81
82 cell.state = ZERO;
83 cell.isHidden = true;
84 cell.hasFlag = false;
85
86 // qApp->processEvents();
87 }
88 }
89 }
90
91
92 QVector<Vector2Dshort> vt_vt2d_CleanPoints = QVector<Vector2Dshort>();
93
94 // If we are remaking the game, we make sure that all of the neighbors
95 // of the clean cell does not have mines.
96 if(bRemakingGame)
97 {
98 vt_vt2d_CleanPoints.reserve(9);
99
100 for (short i=-1; i<=1; i++)
101 {
102 for (short j=-1; j<=1; j++)
103 {
104 vt_vt2d_CleanPoints.append(Vector2Dshort(i_X_Clean + i, i_Y_Clean + j));
105 }
106 }
107 }
108
109 // Add mines on random places until the number of mines is correct
110 while(i_nMines_ > 0)
111 {
112 uchar i = QRandomGenerator::global()->bounded(0, iX);
113 uchar j = QRandomGenerator::global()->bounded(0, iY);
114
115 if(bRemakingGame)
116 {
117 bool forbidden = false;
118 for (const Vector2Dshort& n: vt_vt2d_CleanPoints)
119 {
120 if(n.x == i &&
121 n.y == j)
122 {
123 forbidden = true;
124 break;
125 }
126 }
127 if(forbidden)
128 continue;
129 }
130
131 CellGameEngine& cell = principalMatrix[i][j];
132
133 if(cell.state == ZERO)
134 {
135 i_nMines_--;
136 cell.state = MINE;
137 }
138 }
139
140 // Update the state of all cells
141 for(uchar j=0; j<iY; j++)
142 {
143 for (uchar i=0; i<iX; i++)
144 {
145 CellGameEngine& cell = principalMatrix[i][j];
146
147 if(cell.state == ZERO)
148 {
149 iHiddenCells++;
150
151 uchar minesNeighbors = 0;
152
153 if(i == 0 &&
154 j == 0)
155 {
156 if(principalMatrix[i+1][j].state == MINE)
157 minesNeighbors++;
158 if(principalMatrix[i][j+1].state == MINE)
159 minesNeighbors++;
160 if(principalMatrix[i+1][j+1].state == MINE)
161 minesNeighbors++;
162 }
163 else if(i == 0 &&
164 j == iY-1)
165 {
166 if(principalMatrix[i+1][j].state == MINE)
167 minesNeighbors++;
168 if(principalMatrix[i][j-1].state == MINE)
169 minesNeighbors++;
170 if(principalMatrix[i+1][j-1].state == MINE)
171 minesNeighbors++;
172 }
173 else if(i == iX-1 &&
174 j==0)
175 {
176 if(principalMatrix[i-1][j].state == MINE)
177 minesNeighbors++;
178 if(principalMatrix[i][j+1].state == MINE)
179 minesNeighbors++;
180 if(principalMatrix[i-1][j+1].state == MINE)
181 minesNeighbors++;
182 }
183 else if(i == iX-1 &&
184 j == iY-1)
185 {
186 if(principalMatrix[i-1][j].state == MINE)
187 minesNeighbors++;
188 if(principalMatrix[i][j-1].state == MINE)
189 minesNeighbors++;
190 if(principalMatrix[i-1][j-1].state == MINE)
191 minesNeighbors++;
192 }
193 else if(i == 0 &&
194 j > 0 &&
195 j < iY-1)
196 {
197 if(principalMatrix[i+1][j].state == MINE)
198 minesNeighbors++;
199 if(principalMatrix[i][j+1].state == MINE)
200 minesNeighbors++;
201 if(principalMatrix[i+1][j+1].state == MINE)
202 minesNeighbors++;
203 if(principalMatrix[i][j-1].state == MINE)
204 minesNeighbors++;
205 if(principalMatrix[i+1][j-1].state == MINE)
206 minesNeighbors++;
207 }
208 else if(i == iX-1 &&
209 j >0 &&
210 j < iY-1)
211 {
212 if(principalMatrix[i-1][j].state == MINE)
213 minesNeighbors++;
214 if(principalMatrix[i][j+1].state == MINE)
215 minesNeighbors++;
216 if(principalMatrix[i-1][j+1].state == MINE)
217 minesNeighbors++;
218 if(principalMatrix[i][j-1].state == MINE)
219 minesNeighbors++;
220 if(principalMatrix[i-1][j-1].state == MINE)
221 minesNeighbors++;
222 }
223 else if(i > 0 &&
224 i < iX-1 &&
225 j == 0){
226 if(principalMatrix[i-1][j].state == MINE)
227 minesNeighbors++;
228 if(principalMatrix[i+1][j].state == MINE)
229 minesNeighbors++;
230 if(principalMatrix[i-1][j+1].state == MINE)
231 minesNeighbors++;
232 if(principalMatrix[i][j+1].state == MINE)
233 minesNeighbors++;
234 if(principalMatrix[i+1][j+1].state == MINE)
235 minesNeighbors++;
236 }
237 else if(i > 0 &&
238 i < iX-1 &&
239 j == iY-1)
240 {
241 if(principalMatrix[i+1][j].state == MINE)
242 minesNeighbors++;
243 if(principalMatrix[i-1][j].state == MINE)
244 minesNeighbors++;
245 if(principalMatrix[i-1][j-1].state == MINE)
246 minesNeighbors++;
247 if(principalMatrix[i][j-1].state == MINE)
248 minesNeighbors++;
249 if(principalMatrix[i+1][j-1].state == MINE)
250 minesNeighbors++;
251 }
252 else
253 {
254 if(principalMatrix[i-1][j-1].state == MINE)
255 minesNeighbors++;
256 if(principalMatrix[i-1][j].state == MINE)
257 minesNeighbors++;
258 if(principalMatrix[i-1][j+1].state == MINE)
259 minesNeighbors++;
260 if(principalMatrix[i][j-1].state == MINE)
261 minesNeighbors++;
262 if(principalMatrix[i][j+1].state == MINE)
263 minesNeighbors++;
264 if(principalMatrix[i+1][j-1].state == MINE)
265 minesNeighbors++;
266 if(principalMatrix[i+1][j].state == MINE)
267 minesNeighbors++;
268 if(principalMatrix[i+1][j+1].state == MINE)
269 minesNeighbors++;
270 }
271
272 cell.state = (CELL_STATE)minesNeighbors;
273 }
274 }
275 }
276 iCellsToUnlock = iHiddenCells;
277 bGameActive = true;
278
279 if(bRemakingGame)
280 {
281 Q_EMIT SIGNAL_remakeGame();
282 }
283 }
284
vResetPrincipalMatrix()285 void LibreMinesGameEngine::vResetPrincipalMatrix()
286 {
287 principalMatrix.clear();
288 }
289
bCleanCell(const uchar _X,const uchar _Y)290 bool LibreMinesGameEngine::bCleanCell(const uchar _X, const uchar _Y)
291 {
292 if(!principalMatrix[_X][_Y].hasFlag &&
293 principalMatrix[_X][_Y].state == MINE)
294 {
295 // If the user tried to unlock an unflaged mined cell, (s)he lose
296 vGameLost(_X, _Y);
297 return false;
298 }
299
300 if(principalMatrix[_X][_Y].isHidden &&
301 !principalMatrix[_X][_Y].hasFlag)
302 {
303 // Unlock the cell
304 principalMatrix[_X][_Y].isHidden = false;
305 Q_EMIT SIGNAL_showCell(_X, _Y);
306
307 // If the state of the cell is ZERO, unlock all neighbor cells
308 if(principalMatrix[_X][_Y].state == ZERO)
309 {
310 if(_X == 0 &&
311 _Y == 0)
312 {
313 if(principalMatrix[_X+1][_Y].isHidden)
314 bCleanCell(_X+1, _Y);
315 if(principalMatrix[_X][_Y+1].isHidden)
316 bCleanCell(_X, _Y+1);
317 if(principalMatrix[_X+1][_Y+1].isHidden)
318 bCleanCell(_X+1, _Y+1);
319 }
320 else if(_X == 0 &&
321 _Y == iY-1)
322 {
323 if(principalMatrix[_X+1][_Y].isHidden)
324 bCleanCell(_X+1, _Y);
325 if(principalMatrix[_X][_Y-1].isHidden)
326 bCleanCell(_X, _Y-1);
327 if(principalMatrix[_X+1][_Y-1].isHidden)
328 bCleanCell(_X+1, _Y-1);
329 }
330 else if(_X == iX-1 &&
331 _Y == 0)
332 {
333 if(principalMatrix[_X-1][_Y].isHidden)
334 bCleanCell(_X-1, _Y);
335 if(principalMatrix[_X][_Y+1].isHidden)
336 bCleanCell(_X, _Y+1);
337 if(principalMatrix[_X-1][_Y+1].isHidden)
338 bCleanCell(_X-1, _Y+1);
339 }
340 else if(_X == iX-1 &&
341 _Y == iY-1)
342 {
343 if(principalMatrix[_X-1][_Y].isHidden)
344 bCleanCell(_X-1, _Y);
345 if(principalMatrix[_X][_Y-1].isHidden)
346 bCleanCell(_X, _Y-1);
347 if(principalMatrix[_X-1][_Y-1].isHidden)
348 bCleanCell(_X-1, _Y-1);
349 }
350 else if(_X == iX-1 &&
351 _Y == 0)
352 {
353 if(principalMatrix[_X-1][_Y].isHidden)
354 bCleanCell(_X-1, _Y);
355 if(principalMatrix[_X][_Y+1].isHidden)
356 bCleanCell(_X, _Y+1);
357 if(principalMatrix[_X-1][_Y+1].isHidden)
358 bCleanCell(_X-1, _Y+1);
359 }
360 else if(_X == iX-1 &&
361 _Y == iY-1)
362 {
363 if(principalMatrix[_X-1][_Y].isHidden)
364 bCleanCell(_X-1, _Y);
365 if(principalMatrix[_X][_Y-1].isHidden)
366 bCleanCell(_X, _Y-1);
367 if(principalMatrix[_X-1][_Y-1].isHidden)
368 bCleanCell(_X-1, _Y-1);
369 }
370 else if(_X == 0 &&
371 _Y > 0 &&
372 _Y < iY-1)
373 {
374 if(principalMatrix[_X+1][_Y].isHidden)
375 bCleanCell(_X+1, _Y);
376 if(principalMatrix[_X][_Y+1].isHidden)
377 bCleanCell(_X, _Y+1);
378 if(principalMatrix[_X+1][_Y+1].isHidden)
379 bCleanCell(_X+1, _Y+1);
380 if(principalMatrix[_X][_Y-1].isHidden)
381 bCleanCell(_X, _Y-1);
382 if(principalMatrix[_X+1][_Y-1].isHidden)
383 bCleanCell(_X+1, _Y-1);
384 }
385 else if(_X == iX-1 &&
386 _Y > 0 &&
387 _Y < iY-1)
388 {
389 if(principalMatrix[_X-1][_Y].isHidden)
390 bCleanCell(_X-1, _Y);
391 if(principalMatrix[_X][_Y+1].isHidden)
392 bCleanCell(_X, _Y+1);
393 if(principalMatrix[_X-1][_Y+1].isHidden)
394 bCleanCell(_X-1, _Y+1);
395 if(principalMatrix[_X][_Y-1].isHidden)
396 bCleanCell(_X, _Y-1);
397 if(principalMatrix[_X-1][_Y-1].isHidden)
398 bCleanCell(_X-1, _Y-1);
399 }
400 else if(_X > 0 &&
401 _X < iX-1 &&
402 _Y == 0)
403 {
404 if(principalMatrix[_X-1][_Y].isHidden)
405 bCleanCell(_X-1, _Y);
406 if(principalMatrix[_X+1][_Y].isHidden)
407 bCleanCell(_X+1, _Y);
408 if(principalMatrix[_X-1][_Y+1].isHidden)
409 bCleanCell(_X-1, _Y+1);
410 if(principalMatrix[_X][_Y+1].isHidden)
411 bCleanCell(_X, _Y+1);
412 if(principalMatrix[_X+1][_Y+1].isHidden)
413 bCleanCell(_X+1, _Y+1);
414 }
415 else if(_X > 0 &&
416 _X < iX-1 &&
417 _Y == iY-1)
418 {
419 if(principalMatrix[_X+1][_Y].isHidden)
420 bCleanCell(_X+1, _Y);
421 if(principalMatrix[_X-1][_Y].isHidden)
422 bCleanCell(_X-1, _Y);
423 if(principalMatrix[_X-1][_Y-1].isHidden)
424 bCleanCell(_X-1, _Y-1);
425 if(principalMatrix[_X][_Y-1].isHidden)
426 bCleanCell(_X, _Y-1);
427 if(principalMatrix[_X+1][_Y-1].isHidden)
428 bCleanCell(_X+1, _Y-1);
429 }
430 else
431 {
432 if(principalMatrix[_X-1][_Y-1].isHidden)
433 bCleanCell(_X-1, _Y-1);
434 if(principalMatrix[_X-1][_Y].isHidden)
435 bCleanCell(_X-1, _Y);
436 if(principalMatrix[_X-1][_Y+1].isHidden)
437 bCleanCell(_X-1, _Y+1);
438 if(principalMatrix[_X][_Y-1].isHidden)
439 bCleanCell(_X, _Y-1);
440 if(principalMatrix[_X][_Y+1].isHidden)
441 bCleanCell(_X, _Y+1);
442 if(principalMatrix[_X+1][_Y-1].isHidden)
443 bCleanCell(_X+1, _Y-1);
444 if(principalMatrix[_X+1][_Y].isHidden)
445 bCleanCell(_X+1, _Y);
446 if(principalMatrix[_X+1][_Y+1].isHidden)
447 bCleanCell(_X+1, _Y+1);
448 }
449 }
450
451 iHiddenCells--;
452 if(iHiddenCells == 0)
453 {
454 // If there is none hidden cells left, the user wins
455 vGameWon();
456 }
457 }
458
459 return true;
460 }
461
vGameLost(const uchar _X,const uchar _Y)462 void LibreMinesGameEngine::vGameLost(const uchar _X, const uchar _Y)
463 {
464 qint64 timeInNs = elapsedPreciseTimeInGame.nsecsElapsed();
465 bGameActive = false;
466
467 timerTimeInGame.reset();
468 Q_EMIT SIGNAL_gameLost(_X, _Y);
469 vGenerateEndGameScore(timeInNs);
470 }
471
vGameWon()472 void LibreMinesGameEngine::vGameWon()
473 {
474 qint64 timeInNs = elapsedPreciseTimeInGame.nsecsElapsed();
475 bGameActive = false;
476
477 timerTimeInGame.reset();
478 Q_EMIT SIGNAL_gameWon();
479 vGenerateEndGameScore(timeInNs);
480 }
481
482
vGenerateEndGameScore(qint64 iTimeInNs,bool ignorePreferences)483 void LibreMinesGameEngine::vGenerateEndGameScore(qint64 iTimeInNs, bool ignorePreferences)
484 {
485 static qint64 timeLastGame = -1;
486
487 if(iTimeInNs != -1)
488 timeLastGame = iTimeInNs;
489
490 int iCorrectFlags = 0,
491 iWrongFlags = 0,
492 iUnlockedCells = iCellsToUnlock - iHiddenCells;
493
494 for (int i=0; i<iX; i++)
495 {
496 for (int j=0; j<iY; j++)
497 {
498 if(principalMatrix[i][j].hasFlag)
499 {
500 if (principalMatrix[i][j].state == MINE)
501 iCorrectFlags++;
502 else
503 iWrongFlags++;
504 }
505 }
506 }
507
508 double timeInSecs = (double)timeLastGame*1e-9;
509
510 double dFlagsPerSecond = (double)iCorrectFlags/timeInSecs,
511 dCellsPerSecond = (double)iUnlockedCells/timeInSecs,
512 dPercentageGameComplete = (double)100*iUnlockedCells/iCellsToUnlock;
513
514 LibreMinesScore score;
515 score.iTimeInNs = timeLastGame;
516 score.gameDifficulty = NONE;
517 score.width = iX;
518 score.heigth = iY;
519 score.mines = nMines;
520 score.bGameCompleted = iUnlockedCells == iCellsToUnlock;
521 score.dPercentageGameCompleted = dPercentageGameComplete;
522
523 Q_EMIT SIGNAL_endGameScore(score, iCorrectFlags, iWrongFlags, iUnlockedCells,
524 dFlagsPerSecond, dCellsPerSecond, ignorePreferences);
525 }
526
getPrincipalMatrix() const527 const std::vector<std::vector<LibreMinesGameEngine::CellGameEngine> >& LibreMinesGameEngine::getPrincipalMatrix() const
528 {
529 return principalMatrix;
530 }
531
rows() const532 uchar LibreMinesGameEngine::rows() const
533 {
534 return iX;
535 }
536
lines() const537 uchar LibreMinesGameEngine::lines() const
538 {
539 return iY;
540 }
541
mines() const542 ushort LibreMinesGameEngine::mines() const
543 {
544 return nMines;
545 }
546
cellsToUnlock() const547 ushort LibreMinesGameEngine::cellsToUnlock() const
548 {
549 return iCellsToUnlock;
550 }
551
hiddenCells() const552 ushort LibreMinesGameEngine::hiddenCells() const
553 {
554 return iHiddenCells;
555 }
556
isGameActive() const557 bool LibreMinesGameEngine::isGameActive() const
558 {
559 return bGameActive;
560 }
561
setFirstCellClean(const bool x)562 void LibreMinesGameEngine::setFirstCellClean(const bool x)
563 {
564 bFirstCellClean = x;
565 }
566
567
SLOT_cleanCell(const uchar _X,const uchar _Y)568 void LibreMinesGameEngine::SLOT_cleanCell(const uchar _X, const uchar _Y)
569 {
570 if(bFirst && bFirstCellClean && principalMatrix[_X][_Y].state != ZERO)
571 {
572 vNewGame(iX, iY, nMines, _X, _Y);
573 SLOT_startTimer();
574 bFirst = false;
575 }
576 else if(bFirst)
577 {
578 SLOT_startTimer();
579 bFirst = false;
580 }
581 bCleanCell(_X, _Y);
582 }
583
SLOT_addOrRemoveFlag(const uchar _X,const uchar _Y)584 void LibreMinesGameEngine::SLOT_addOrRemoveFlag(const uchar _X, const uchar _Y)
585 {
586 if(!principalMatrix[_X][_Y].isHidden)
587 return;
588
589 if(principalMatrix[_X][_Y].hasFlag)
590 {
591 principalMatrix[_X][_Y].hasFlag = false;
592 iMinesLeft++;
593 Q_EMIT SIGNAL_unflagCell(_X, _Y);
594 }
595 else
596 {
597 principalMatrix[_X][_Y].hasFlag = true;
598 iMinesLeft--;
599 Q_EMIT SIGNAL_flagCell(_X, _Y);
600 }
601
602 Q_EMIT SIGNAL_minesLeft(iMinesLeft);
603 }
604
SLOT_stop()605 void LibreMinesGameEngine::SLOT_stop()
606 {
607 timerTimeInGame.reset();
608 bGameActive = false;
609 vResetPrincipalMatrix();
610 }
611
SLOT_startTimer()612 void LibreMinesGameEngine::SLOT_startTimer()
613 {
614 iTimeInSeconds = 0;
615 timerTimeInGame.reset(new QTimer());
616 QObject::connect(timerTimeInGame.get(), &QTimer::timeout,
617 this, &LibreMinesGameEngine::SLOT_UpdateTime);
618
619 timerTimeInGame->start(1000);
620 elapsedPreciseTimeInGame.start();
621 }
622
SLOT_cleanNeighborCells(const uchar _X,const uchar _Y)623 void LibreMinesGameEngine::SLOT_cleanNeighborCells(const uchar _X, const uchar _Y)
624 {
625 // Clean all hided and unflaged neighbor flags
626 for(short i=_X-1; i<=_X+1; i++)
627 {
628 if(i<0 || i>=iX)
629 continue;
630
631 for(short j=_Y-1; j<=_Y+1; j++)
632 {
633 if(j<0 || j>=iY)
634 continue;
635
636 const CellGameEngine& cell = principalMatrix[i][j];
637
638 if(cell.isHidden && !cell.hasFlag)
639 {
640 if(!bCleanCell(i, j))
641 return;
642 }
643 }
644 }
645 }
646
SLOT_generateEndGameScoreAgain()647 void LibreMinesGameEngine::SLOT_generateEndGameScoreAgain()
648 {
649 vGenerateEndGameScore(-1, true);
650 }
651
SLOT_UpdateTime()652 void LibreMinesGameEngine::SLOT_UpdateTime()
653 {
654 iTimeInSeconds++;
655 Q_EMIT SIGNAL_currentTime(iTimeInSeconds);
656 }
657
658