4 * Author: Petr Kubizňák
8 /* -------------------------------------------------------------------------- */
11 #include "../../asserts.h"
12 #include "../../exceptions/GeneralException.h"
13 #include <QtGui/QPushButton>
14 #include <QtGui/QLabel>
15 #include <QtGui/QMouseEvent>
16 #include <QtGui/QInputDialog>
17 #include <QtGui/QMessageBox>
23 /***************************** CLASS FieldButton ******************************/
25 FieldButton::FieldButton(MainForm
*mainForm
, int posX
, int posY
, int posZ
) : QPushButton(mainForm
) {
26 this->mainForm
= mainForm
;
33 this->setCheckable(true);
34 this->setFocusPolicy(Qt::NoFocus
);
35 this->setMinimumWidth(23);
36 this->setMinimumHeight(23);
39 QSizePolicy
sizePolicy(QSizePolicy::Expanding
, QSizePolicy::Expanding
);
40 sizePolicy
.setHorizontalStretch(0);
41 sizePolicy
.setVerticalStretch(0);
42 sizePolicy
.setHeightForWidth(true);
43 this->setSizePolicy(sizePolicy
);
45 //fonty: normalni a zvyrazneny (pro oznacena tlacitka)
46 fontNormal
= new QFont(this->font());
47 fontHighlight
= new QFont(this->font());
48 fontHighlight
->setUnderline(true);
49 fontHighlight
->setBold(true);
52 /* -------------------------------------------------------------------------- */
54 FieldButton::~FieldButton(void) {
59 /* -------------------------------------------------------------------------- */
62 void FieldButton::onClick(bool b
) {
63 this->setChecked(true);
64 if(b
) mainForm
->uncover(posZ
, posY
, posX
);
67 /* -------------------------------------------------------------------------- */
69 /* udalost najeti mysi nad tlacitko */
70 void FieldButton::enterEvent(QEvent
*e
) {
72 mainForm
->buttonHovered(posZ
, posY
, posX
);
74 QPushButton::enterEvent(e
);
77 /* -------------------------------------------------------------------------- */
79 /* udalost odjeti mysi z tlacitka */
80 void FieldButton::leaveEvent(QEvent
*e
) {
82 mainForm
->buttonLeft(posZ
, posY
, posX
);
84 QPushButton::leaveEvent(e
);
87 /* -------------------------------------------------------------------------- */
89 /* udalost stisku tlacitka */
90 void FieldButton::mousePressEvent(QMouseEvent
*e
) {
91 if(e
->button() == Qt::RightButton
) {
92 if(!this->isChecked()) mainForm
->mark(posZ
, posY
, posX
);
95 if(e
->button() == Qt::MiddleButton
)
97 mainForm
->locked
= !mainForm
->locked
;
100 QPushButton::mousePressEvent(e
);
103 /* -------------------------------------------------------------------------- */
105 /* zvyrazni tlacitko zmenou fontu */
106 void FieldButton::highlight(void) {
107 this->setFont(*fontHighlight
);
110 /* -------------------------------------------------------------------------- */
112 /* "odvyrazni" tlacitko zpet */
113 void FieldButton::unhighlight(void) {
114 this->setFont(*fontNormal
);
118 /*********************************/// CLASS
///********************************/
120 /**************************** Class MarksIndicator ****************************/
122 /* -------------------------------------------------------------------------- */
124 MarksIndicator::MarksIndicator(QWidget
*parent
, int total
) : QLabel(parent
) {
127 fontNormal
= new QFont(this->font());
128 fontBold
= new QFont(this->font());
129 fontBold
->setBold(true);
132 /* -------------------------------------------------------------------------- */
134 MarksIndicator::~MarksIndicator(void) {
139 /* -------------------------------------------------------------------------- */
141 /* prekresli komponentu */
142 void MarksIndicator::repaint(void) {
144 sprintf(str
, "Marks: %d / %d ", marked
, total
);
146 this->setFont(marked
<= total
? *fontNormal
: *fontBold
);
149 /* -------------------------------------------------------------------------- */
151 /* nastavi celkovy pocet vlajecek, totez vraci */
152 int MarksIndicator::setTotal(int value
) {
158 /* -------------------------------------------------------------------------- */
160 /* nastavi oznaceny pocet vlajecek, totez vraci */
161 int MarksIndicator::setMarked(int value
) {
167 /*********************************/// CLASS
///********************************/
169 /******************************* Class MainForm *******************************/
171 //makro pro otestovani existence tlacitka se zadanym umistenim
172 #define TEST_COORDS(l,r,c) { assert(l>=0 && l<this->layers); \
173 assert(r>=0 && r<this->rows); assert(c>=0 && c<this->cols); }
175 //nazev souboru s nastavenim
176 #define FILE_SETTINGS ".mines3d_settings"
177 #define FILE_SCORES_ROOKIE ".mines3d_scores_rookie"
178 #define FILE_SCORES_ADVANCED ".mines3d_scores_advanced"
179 #define FILE_SCORES_SUICIDE ".mines3d_scores_suicide"
180 #define FILE_SCORES_CUSTOM ".mines3d_scores_custom"
182 /* -------------------------------------------------------------------------- */
184 MainForm::MainForm(QWidget
*parent
) : QMainWindow(parent
) {
187 //zrusime zbytecne okraje
188 ui
.scrollArea
->setContentsMargins(0,0,0,0);
189 ui
.scrollAreaWidgetContents
->setContentsMargins(0,0,0,0);
190 ui
.horizontalLayout
->setContentsMargins(0,0,0,0);
191 ui
.boardWidget
->setContentsMargins(0,0,0,0);
192 ui
.verticalLayout_3
->setContentsMargins(0,0,0,0);
195 marksIndicator
= new MarksIndicator(this);
196 statusBar()->addPermanentWidget(marksIndicator
);
198 //napojeni signalu na sloty
199 QObject::connect(ui
.actionNew
, SIGNAL(triggered()), this, SLOT(actionNew_Trigger()));
200 QObject::connect(ui
.actionSettings
, SIGNAL(triggered()), this, SLOT(actionSettings_Trigger()));
201 QObject::connect(ui
.actionScores
, SIGNAL(triggered()), this, SLOT(actionScores_Trigger()));
202 QObject::connect(ui
.actionAbout
, SIGNAL(triggered()), this, SLOT(actionAbout_Trigger()));
209 tc
= new Thread_controller();
210 sql_c
= new Sql_connector();
213 //nacteme nastaveni ze souboru, pri neuspechu pouzijeme vychozi hodnoty
214 if(!loadSettings()) {
215 layers
= PRESETS
[0][0];
216 rows
= PRESETS
[0][1];
217 cols
= PRESETS
[0][2];
218 mines
= PRESETS
[0][3];
220 //nacteme vysledky predchozich her
222 name
= "<unknown>"; //vychozi jmeno uzivatele (pri zapisu do vysledku)
228 /* -------------------------------------------------------------------------- */
230 MainForm::~MainForm() {
237 delete marksIndicator
;
240 /* -------------------------------------------------------------------------- */
242 /* nacte nastaveni ze standardniho souboru, vraci uspech operace */
243 bool MainForm::loadSettings(void) {
244 ifstream
ifs(FILE_SETTINGS
, ifstream::in
);
245 if(!ifs
.good()) return false;
246 ifs
>> layers
>> rows
>> cols
>> mines
;
247 if(!ifs
.good() || layers
<=0 || rows
<=0 || cols
<=0 || mines
<=0) return false;
252 /* -------------------------------------------------------------------------- */
254 /* ulozi nastaveni do standardniho souboru, vraci uspech operace */
255 bool MainForm::saveSettings(void) {
256 ofstream
ofs(FILE_SETTINGS
, ofstream::out
);
257 if(!ofs
.good()) return false;
258 ofs
<< layers
<< " " << rows
<< " " << cols
<< " " << mines
<< endl
<< endl
;
259 if(!ofs
.good()) return false;
264 /* -------------------------------------------------------------------------- */
266 /* nacte vsechna skore, vraci uspech operace */
267 bool MainForm::loadScores(void) {
268 return loadScoresFile(scoreRookie
, FILE_SCORES_ROOKIE
) &
269 loadScoresFile(scoreAdvanced
, FILE_SCORES_ADVANCED
) &
270 loadScoresFile(scoreSuicide
, FILE_SCORES_SUICIDE
) &
271 loadScoresFile(scoreCustom
, FILE_SCORES_CUSTOM
);
274 /* -------------------------------------------------------------------------- */
276 /* ulozi vsechna skore, vraci uspech operace */
277 bool MainForm::saveScores(void) {
278 return saveScoresFile(scoreRookie
, FILE_SCORES_ROOKIE
) &
279 saveScoresFile(scoreAdvanced
, FILE_SCORES_ADVANCED
) &
280 saveScoresFile(scoreSuicide
, FILE_SCORES_SUICIDE
) &
281 saveScoresFile(scoreCustom
, FILE_SCORES_CUSTOM
);
284 /* -------------------------------------------------------------------------- */
286 /* nacte skore ze zadaneho souboru do daneho listu, vraci uspech operace */
287 bool MainForm::loadScoresFile(list
<ScoreRecord
> &scoresList
, const char *filename
) {
288 ifstream
ifs(filename
, ifstream::in
);
289 if(!ifs
.good()) return false;
290 //dokud je co cist, cteme soubor do struktury
292 ScoreRecord scoreItem
;
293 ifs
>> scoreItem
.time
;
294 ifs
.ignore(1); //ignorujeme mezeru
295 ifs
.getline(scoreItem
.name
, sizeof(scoreItem
.name
));
300 scoresList
.push_back(scoreItem
);
304 /* -------------------------------------------------------------------------- */
306 /* ulozi skore do zadaneho souboru z daneho listu, vraci uspech operace */
307 bool MainForm::saveScoresFile(list
<ScoreRecord
> &scoresList
, const char *filename
) {
308 ofstream
ofs(filename
, ofstream::out
);
309 if(!ofs
.good()) return false;
310 //zapiseme strukturu do souboru
311 for(list
<ScoreRecord
>::iterator it
= scoresList
.begin(); it
!= scoresList
.end(); it
++)
312 ofs
<< (*it
).time
<< " " << (*it
).name
<< endl
;
314 if(!ofs
.good()) return false;
319 /* -------------------------------------------------------------------------- */
321 /* vraci true, pokud "a" predchazi pred "b" (podle casu), jinak false */
322 bool MainForm::cmpScoreByTime(const ScoreRecord
&a
, const ScoreRecord
&b
) {
323 return a
.time
< b
.time
;
326 /* -------------------------------------------------------------------------- */
328 /* vyrobi tlacitka a dalsi komponenty potrebne pro hru
329 * jako meze cyklu pouziva hodnoty layers, rows, cols a mines! */
330 void MainForm::buildGui(void) {
331 layerWidget
= new QWidget
* [layers
];
332 layerGrid
= new QGridLayout
* [layers
];
333 fieldBtn
= new FieldButton
*** [layers
];
334 for(int i
=0; i
<layers
; i
++) {
335 layerWidget
[i
] = new QWidget(ui
.scrollAreaWidgetContents
);
336 ui
.verticalLayout_3
->addWidget(layerWidget
[i
]);
337 layerGrid
[i
] = new QGridLayout(layerWidget
[i
]);
338 layerGrid
[i
]->setHorizontalSpacing(0);
339 layerGrid
[i
]->setVerticalSpacing(0);
341 fieldBtn
[i
] = new FieldButton
** [rows
];
342 for(int j
=0; j
<rows
; j
++) {
343 fieldBtn
[i
][j
] = new FieldButton
* [cols
];
344 for(int k
=0; k
<cols
; k
++) {
345 fieldBtn
[i
][j
][k
] = new FieldButton(this, k
, j
, i
);
346 layerGrid
[i
]->addWidget(fieldBtn
[i
][j
][k
], j
, k
);
347 QObject::connect(fieldBtn
[i
][j
][k
], SIGNAL(clicked(bool)), fieldBtn
[i
][j
][k
], SLOT(onClick(bool)));
352 tc
->update_data(rows
,cols
,layers
,board
);
356 /* -------------------------------------------------------------------------- */
358 /* uvolni prostredky alokovane metodou buildGui()
359 * jako meze cyklu pouziva rozmery v board! */
360 void MainForm::freeGui(void) {
361 for(int i
=0; i
<board
->getLayersCount(); i
++) {
362 for(int j
=0; j
<board
->getRowsCount(); j
++) {
363 for(int k
=0; k
<board
->getColsCount(); k
++) delete fieldBtn
[i
][j
][k
];
364 delete [] fieldBtn
[i
][j
];
366 delete [] fieldBtn
[i
];
370 for(int i
=0; i
<board
->getLayersCount(); i
++) {
372 delete layerWidget
[i
];
375 delete [] layerWidget
;
378 /* -------------------------------------------------------------------------- */
380 /* volano tridou FieldButton pri udalosti enterEvent, zvyrazni souvisejici tlacitka */
381 void MainForm::buttonHovered(int l
, int r
, int c
) {
392 if ( tc
->ptf
!= 0 )tc
->ptf();
399 for(int nl
=l
-1; nl
<=l
+1; nl
++) {
400 if(nl
<0 || nl
>=this->layers
) continue;
401 for(int nr
=r
-1; nr
<=r
+1; nr
++) {
402 if(nr
<0 || nr
>=this->rows
) continue;
403 for(int nc
=c
-1; nc
<=c
+1; nc
++) {
404 if(nc
<0 || nc
>=this->cols
) continue;
405 fieldBtn
[nl
][nr
][nc
]->highlight();
411 /* -------------------------------------------------------------------------- */
413 /* volano tridou FieldButton pri udalosti leaveEvent, odzvyrazni souvisejici tlacitka */
414 void MainForm::buttonLeft(int l
, int r
, int c
) {
419 for(int nl
=l
-1; nl
<=l
+1; nl
++) {
420 if(nl
<0 || nl
>=this->layers
) continue;
421 for(int nr
=r
-1; nr
<=r
+1; nr
++) {
422 if(nr
<0 || nr
>=this->rows
) continue;
423 for(int nc
=c
-1; nc
<=c
+1; nc
++) {
424 if(nc
<0 || nc
>=this->cols
) continue;
425 fieldBtn
[nl
][nr
][nc
]->unhighlight();
431 /* -------------------------------------------------------------------------- */
433 /* volano tridou FieldButton pri stisku tlacitka, odkryje souvisejici tlacitka */
434 void MainForm::uncover(int l
, int r
, int c
) {
438 startTime(); //pokud jeste nemerime cas, zacneme nyni
440 //pole je oznacene jako zaminovane, nejde odkryt
441 if(board
->getField(l
,r
,c
).hasMark()) {
442 fieldBtn
[l
][r
][c
]->setChecked(false);
447 if(board
->uncover(l
, r
, c
)) { //VYBUCH!
448 fieldBtn
[l
][r
][c
]->setText("!!");
451 int num
= board
->getField(l
,r
,c
).getNeighboursCnt();
453 sprintf(numStr
, "%d", num
);
454 fieldBtn
[l
][r
][c
]->setText(numStr
); //vypiseme pocet min v okoli
455 } else { //0 min -> musime zaktualizovat celou desku
456 for(int i
=0; i
<this->layers
; i
++)
457 for(int j
=0; j
<this->rows
; j
++)
458 for(int k
=0; k
<this->cols
; k
++) {
459 fieldBtn
[i
][j
][k
]->setChecked(!board
->getField(i
,j
,k
).isCovered());
460 if(fieldBtn
[i
][j
][k
]->isChecked()) {
461 num
= board
->getField(i
,j
,k
).getNeighboursCnt();
462 sprintf(numStr
, "%d", num
);
463 if(num
) fieldBtn
[i
][j
][k
]->setText(numStr
);
467 if(board
->isCleared()) setGameOver(true);
471 /* -------------------------------------------------------------------------- */
473 /* volano tridou FieldButton pri stisku praveho tlacitka, (od)znaci dane policko */
474 void MainForm::mark(int l
, int r
, int c
) {
477 startTime(); //pokud jeste nemerime cas, zacneme nyni
479 Field
&field
= board
->getField(l
, r
, c
);
480 if(!field
.isCovered()) return;
481 field
.setMark(!field
.hasMark());
482 fieldBtn
[l
][r
][c
]->setText(field
.hasMark() ? "M" : " "); //znacka
484 marksIndicator
->setMarked(board
->getMarkedCount());
487 /* -------------------------------------------------------------------------- */
489 /* nastavi pocatecni cas hry */
490 void MainForm::startTime(void) {
491 if(tStart
==-1) tStart
= time(NULL
); //pocatek mereni casu
494 /* -------------------------------------------------------------------------- */
496 /* vytvori novou hru s aktualnim nastavenim */
497 void MainForm::newGame(void) {
501 freeGui(); //uvolni stare gui (jeste podle stareho nastaveni)
502 delete board
; board
= NULL
;
508 board
= new Board(rows
, cols
, layers
, mines
);
509 } catch(GeneralException e
) {
511 snprintf(text
, 199, "An error occured:\n%s\nUnable to start game.", e
.errText
.data());
512 QMessageBox::warning(this, "Mines3D", text
, QMessageBox::Ok
);
515 ui
.scrollAreaWidgetContents
->setEnabled(true);
517 marksIndicator
->setTotal(mines
);
518 marksIndicator
->setMarked(0);
520 tStart
= -1; //indikuje, ze jeste nezacalo mereni (zacne prvnim kliknutim)
523 /* -------------------------------------------------------------------------- */
525 /* ukonci hru -> znemozni stisk tlacitek atp.;
526 * atr success je true, pokud uzivatel vyhral, jinak false */
527 void MainForm::setGameOver(bool success
) {
530 ui
.scrollAreaWidgetContents
->setEnabled(false);
532 //odvyraznime vsechna tlacitka a vykreslime polohy min
533 for(int i
=0; i
<this->layers
; i
++) {
534 for(int j
=0; j
<this->rows
; j
++)
535 for(int k
=0; k
<this->cols
; k
++) {
536 fieldBtn
[i
][j
][k
]->unhighlight();
537 if(board
->getField(i
,j
,k
).hasMine())
538 fieldBtn
[i
][j
][k
]->setText(success
? "M" : "!!");
541 //vyhodnoceni vysledku
543 statusBar()->showMessage("Oooops, you stepped on a mine which exploxed!", 10000);
546 const int tTotal
= tStop
-tStart
;
547 sprintf(str
, "Very well, mister! You cleared the board in %d:%02d.", tTotal
/60, tTotal
%60);
548 statusBar()->showMessage(str
, 10000);
549 processTopTen(tTotal
); //zapis do tabulky (pokud je cas v top10)
553 /* -------------------------------------------------------------------------- */
555 /* pokud je zadany cas v top10, umozni zapis do tabulky a vraci true, jinak false */
556 bool MainForm::processTopTen(int time
) {
559 list
<ScoreRecord
> *l
;
560 switch(board
->getMode()) {
561 case Board::ModeRookie
: l
=&scoreRookie
; break;
562 case Board::ModeAdvanced
: l
=&scoreAdvanced
; break;
563 case Board::ModeSuicide
: l
=&scoreSuicide
; break;
564 case Board::ModeCustom
: l
=&scoreCustom
; break;
565 default: return false;
568 l
->sort(cmpScoreByTime
); //seradime vzestupne podle casu
569 //je-li v seznamu jeste misto nebo nejhorsi cas je horsi, nez soucasny, jedna se o top10 -> pridame zaznam
570 if(l
->size() < 10 || l
->back().time
> time
) {
572 //vyzveme uzivatele k zadani jmena
573 QString text
= QInputDialog::getText(this, "Top 10!", "You have made the top 10!\nPlease type in your name:",
574 QLineEdit::Normal
, name
, &ok
);
575 //pokud bylo stisknuto OK
576 if(ok
) name
= text
; //zapamatujem si vlozene jmeno
577 else return false; //uzivatel si nepreje vlozit zaznam
579 sql_c
->connect(); // nahrani dat do databaze
581 sql_c
->insertScore(name
.toUtf8().data(), time
, board
->getMode());
584 ScoreRecord scoreItem
;
585 snprintf(scoreItem
.name
, 40, "%s", name
.toUtf8().data());
586 scoreItem
.time
= time
;
587 if(l
->size() >= 10) l
->pop_back(); //neni jiz misto -> vymazeme nejhorsi cas
588 l
->push_back(scoreItem
); //vlozime zaznam
589 if(scoresFrm
) scoresFrm
->replaceModel(board
->getMode(), l
); //zaktualizujeme model dat ve formulari vysledku
590 actionScores_Trigger(); //zobrazime vysledky
593 return false; //v seznamu neni misto (cas nebyl dostatecne rychly)
596 /* -------------------------------------------------------------------------- */
598 /* stisk tlacitka "New" v menu */
599 void MainForm::actionNew_Trigger(void) {
603 /* -------------------------------------------------------------------------- */
605 /* stisk tlacitka "Settings..." v menu */
606 void MainForm::actionSettings_Trigger(void) {
607 if(!settingsFrm
) settingsFrm
= new SettingsForm();
609 //zobrazime aktualni nastaveni
610 int mode
= board
? board
->getMode() : Board::ModeRookie
;
612 case Board::ModeRookie
:
613 settingsFrm
->ui
.rookieRadio
->setChecked(true);
614 settingsFrm
->rookieRadio_Clicked();
616 case Board::ModeAdvanced
:
617 settingsFrm
->ui
.advancedRadio
->setChecked(true);
618 settingsFrm
->advancedRadio_Clicked();
620 case Board::ModeSuicide
:
621 settingsFrm
->ui
.suicideRadio
->setChecked(true);
622 settingsFrm
->suicideRadio_Clicked();
624 case Board::ModeCustom
:
625 settingsFrm
->ui
.customRadio
->setChecked(true);
626 settingsFrm
->ui
.layersSpinBox
->setValue(this->layers
);
627 settingsFrm
->ui
.rowsSpinBox
->setValue(this->rows
);
628 settingsFrm
->ui
.columnsSpinBox
->setValue(this->cols
);
629 settingsFrm
->ui
.minesSpinBox
->setValue(this->mines
);
630 settingsFrm
->customRadio_Clicked();
633 //zobrazime dialog a vyhodnotime odpoved
634 if(settingsFrm
->exec() == QDialog::Accepted
) {
635 layers
= settingsFrm
->ui
.layersSpinBox
->value();
636 rows
= settingsFrm
->ui
.rowsSpinBox
->value();
637 cols
= settingsFrm
->ui
.columnsSpinBox
->value();
638 mines
= settingsFrm
->ui
.minesSpinBox
->value();
643 /* -------------------------------------------------------------------------- */
645 /* stisk tlacitka "Hall of Fame" v menu */
646 void MainForm::actionScores_Trigger(void) {
647 if(!scoresFrm
) scoresFrm
= new ScoresForm(&scoreRookie
, &scoreAdvanced
, &scoreSuicide
, &scoreCustom
);
648 scoresFrm
->exec(board
->getMode());
651 /* -------------------------------------------------------------------------- */
653 void MainForm::actionAbout_Trigger(void) {
654 if(!aboutFrm
) aboutFrm
= new AboutForm();
658 /* -------------------------------------------------------------------------- */