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