Bigger font in pause text and menu
[ghostsoverboard] / seascene.cpp
1 /**************************************************************************
2         Ghosts Overboard - a game for Maemo 5
3
4         Copyright (C) 2011  Heli Hyvättinen
5
6         This file is part of Ghosts Overboard
7
8         Ghosts Overboard is free software: you can redistribute it and/or modify
9         it under the terms of the GNU General Public License as published by
10         the Free Software Foundation, either version 2 of the License, or
11         (at your option) any later version.
12
13         This program is distributed in the hope that it will be useful,
14         but WITHOUT ANY WARRANTY; without even the implied warranty of
15         MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16         GNU General Public License for more details.
17
18         You should have received a copy of the GNU General Public License
19         along with this program.  If not, see <http://www.gnu.org/licenses/>.
20
21 **************************************************************************/
22
23 #include "seascene.h"
24 #include "octopus.h"
25 #include "ship.h"
26 #include <QGraphicsPixmapItem>
27 #include <QDebug>
28 #include <QMessageBox>
29 #include <QTime>
30 #include <QApplication>
31 #include <QAction>
32 #include <QPushButton>
33 #include <QLabel>
34 #include <QVBoxLayout>
35
36 const QString ghostImageFilename_ = ":/pix/aave.png";
37 const QString rockImageFilename_ =":/pix/kari.png";
38 const QString octopusImageFilename_= ":/pix/tursas.png";
39
40
41 SeaScene::SeaScene(QObject *parent) :
42     QGraphicsScene(parent)
43 {
44     paused_ = false;
45     screenLitKeeper_.keepScreenLit(true);
46
47     //set background
48
49     QPixmap waves (":/pix/meri.png");
50     waves.scaled(20,20);
51     setBackgroundBrush(QBrush(waves));
52
53     //set random seed
54
55     qsrand(QTime::currentTime().msec()+2);  //+2 to avoid setting it to 1
56
57 //Setup the level list
58
59     Level level1(5,10);
60     levelList_.append(level1);
61     Level level2(5,10,2,50);
62     levelList_.append(level2);
63     Level level3(5,15,2,50);
64     levelList_.append(level3);
65     Level level4(5,15,4,50);
66     levelList_.append(level4);
67     Level level5(5,15,5,100);
68     levelList_.append(level5);
69
70     currentLevel_ = 0;
71
72     //This ensures that nextlevel will not be called until its safe to delete the Ship object.
73     //Leaving out Qt::QueuedConnection or calling nextlevel directly instead of emitting the signal will CRASH
74     connect(this,SIGNAL(allGhostsPicked()),this,SLOT(nextLevel()),Qt::QueuedConnection);
75
76
77     pVibrateAction_ = new QAction(tr("Vibration effects"),this);
78     pVibrateAction_->setCheckable(true);
79     connect(pVibrateAction_,SIGNAL(toggled(bool)),this,SLOT(vibrationActivate(bool)));
80
81
82     pPauseAction_ = new QAction(tr("Pause"),this);
83     pPauseAction_->setCheckable(true);
84     connect(pPauseAction_,SIGNAL(toggled(bool)),this,SLOT(pause(bool)));
85
86
87 }
88
89 void SeaScene::setupMap(int ghosts, int rocks, int octopuses, int octopusSpeed)
90 {
91     //empty the map
92
93     clear();
94
95     createMenuItems();
96
97     //empty the list of moving items
98
99     movingItems_.clear();
100
101     //empty the list of free slots
102     freeTiles_.clear();
103
104     //fill the list of free slots
105
106     int numberOfXTiles  = width() / 40;
107     int numberOfYTiles = height() /40;
108
109 //    qDebug() << numberOfXTiles << " slots in x direction";
110 //    qDebug() << numberOfYTiles << " slots in y rirection";
111
112     for (int i = 0; i < numberOfXTiles; i++ )
113     {
114         for (int j = 0; j < numberOfYTiles; j++)
115         {
116             freeTiles_.append(QPointF(i*40,j*40));
117         }
118     }
119
120
121     //spread the rocks
122
123     for (int i = 0; i < rocks; i++)
124     {
125         QPointF * pPosition = findRandomFreeSlot();
126
127         //If there was no room no point to continue
128         if (pPosition == NULL)
129             break;
130
131         QPixmap rockPixmap (":/pix/kari.png");
132         QGraphicsPixmapItem * pRock = addPixmap(rockPixmap);
133         pRock->setData(0,"rock");
134         pRock->setPos(*pPosition);
135         delete pPosition;
136
137     }
138
139     //spread the ghosts
140
141     ghostsLeft_ = ghosts;
142     spreadGhosts(ghosts);
143
144
145
146     //spread the octopuses
147
148     QList <Octopus*> octopusList;
149
150     for (int i=0; i < octopuses; i++)
151     {
152         QPointF * pPosition = findRandomFreeSlot();
153
154         //If there was no room no point to continue
155         if (pPosition == NULL)
156             break;
157
158     QPixmap octopusPixmap (":/pix/tursas.png");
159     Octopus * pOctopus = new Octopus(octopusPixmap,octopusSpeed);
160     pOctopus->setData(0,"octopus");
161     pOctopus->setPos(*pPosition);
162     addItem(pOctopus);
163     pOctopus->startMoving();
164     movingItems_.append(pOctopus);
165     connect(this,SIGNAL(pauseOn()),pOctopus,SLOT(stopMoving()));
166     connect(this,SIGNAL(pauseOff()),pOctopus,SLOT(startMoving()));
167     octopusList.append(pOctopus);
168     delete pPosition;
169
170     }
171
172
173     //place the ship
174
175     QPointF * pPosition = findRandomFreeSlot();
176     if (pPosition == NULL)
177     {
178         // Game cannot begin without a free position for ship, so give an error message and return
179
180         QMessageBox::critical(NULL,"Error! Too many objects on screen","No free space to place the ship. The game cannot start. Please choose another level.");
181         return;
182     }
183
184     QList<QPixmap> shipImages;
185     shipImages.append(QPixmap(":/pix/laiva.png"));
186     shipImages.append(QPixmap(":/pix/laiva_1aave.png"));
187     shipImages.append(QPixmap(":/pix/laiva_2aave.png"));
188     shipImages.append(QPixmap(":/pix/laiva_3aave.png"));
189     shipImages.append(QPixmap(":/pix/laiva_4aave.png"));
190     shipImages.append(QPixmap(":/pix/laiva_5aave.png"));
191     shipImages.append(QPixmap(":/pix/laiva_6aave.png"));
192     shipImages.append(QPixmap(":/pix/laiva_7aave.png"));
193     shipImages.append(QPixmap(":/pix/laiva_8aave.png"));
194     shipImages.append(QPixmap(":/pix/laiva_9aave.png"));
195     shipImages.append(QPixmap(":/pix/laiva_10aave.png"));
196
197     Ship * pShip = new Ship (shipImages);
198     pShip->setData(0,"ship");
199     pShip->setPos(*pPosition);
200     addItem(pShip);
201     connect(pShip,SIGNAL(pickingGhost(QGraphicsItem*)),this, SLOT(removeGhost(QGraphicsItem*)) );
202     connect(pShip,SIGNAL(droppingGhosts(int)),this,SLOT(ghostsDropped(int)));
203     connect(this,SIGNAL(vibrationActivated(bool)),pShip,SLOT(setVibrationActivate(bool)));
204     pShip->startMoving();
205     movingItems_.append(pShip);
206     connect(this,SIGNAL(pauseOn()),pShip,SLOT(stopMoving()));
207     connect(this,SIGNAL(pauseOff()),pShip,SLOT(startMoving()));
208     foreach (Octopus* pOctopus, octopusList)
209     {
210         connect(pOctopus,SIGNAL(droppingGhosts()),pShip,SLOT(dropAllGhosts()));
211     }
212     delete pPosition;
213 }
214
215 void SeaScene::setupMap(Level level)
216 {
217     setupMap(level.getNumberOfGhosts(),level.getNumberOfRocks(),level.getNumberOfOctopuses(),level.getOctopusSpeed());
218 }
219
220 void SeaScene::spreadGhosts(int ghosts)
221 {
222
223
224     //the octopuses and the ship may have moved from their original positions,
225     //so the list of free slots must be adjusted to exclude their current positions
226
227     QList<QPointF> temporarilyReservedSlots;
228
229     foreach (QGraphicsItem* pItem, movingItems_)
230     {
231         if (pItem == NULL)
232         {
233  //           qDebug() << "NULL item in movingItems_";
234             continue;
235         }
236
237         //round x and y down to fit the slot size
238         int x = pItem->x();
239         x = x/40;
240         x = x*40;
241
242         int y = pItem->y();
243         y = y/40;
244         y=y*40;
245
246
247         QPointF position (x,y);
248
249         //remove the tiles (potentially) occupied by the item from free slots and place in temp list if was in the list before
250
251         if (freeTiles_.removeOne(position))
252             temporarilyReservedSlots.append(position);
253
254
255         position.setX(x+40);
256
257         if (freeTiles_.removeOne(position))
258             temporarilyReservedSlots.append(position);
259
260         position.setY(y+40);
261
262         if (freeTiles_.removeOne(position))
263             temporarilyReservedSlots.append(position);
264
265         position.setX(x);
266
267         if (freeTiles_.removeOne(position))
268             temporarilyReservedSlots.append(position);
269
270     }
271
272
273     //spread ghosts in random free slots
274
275     for (int i=0; i < ghosts; i++)
276     {
277         QPointF * pPosition = findRandomFreeSlot();
278
279         //If there was no room no point to continue
280         if (pPosition == NULL)
281             return;
282
283         QPixmap ghostPixmap(":/pix/aave.png");
284         QGraphicsPixmapItem * pGhost = addPixmap(ghostPixmap);
285         pGhost->setData(0,"ghost");
286         pGhost->setPos(*pPosition);
287         delete pPosition;
288     }
289
290     //return the slots occupied by moving items to free slots
291     freeTiles_.append(temporarilyReservedSlots);
292
293     //clear temp for the next round
294     temporarilyReservedSlots.clear();
295 }
296
297 QPointF* SeaScene::findRandomFreeSlot()
298 {
299     if (freeTiles_.isEmpty())
300         return NULL;
301
302     int index = qrand()%freeTiles_.size();
303
304 //    qDebug()  << index << " index";
305     return new QPointF (freeTiles_.takeAt(index));
306
307 }
308
309 void SeaScene::removeGhost(QGraphicsItem *pGhost)
310 {
311     removeItem(pGhost);  //remove the item from scene
312     freeTiles_.append(pGhost->scenePos()); //add the item's position to free slots
313     delete pGhost;
314     ghostsLeft_--;
315     if (ghostsLeft_ == 0)
316     {
317         emit allGhostsPicked();
318  //       qDebug() << "All ghosts picked!";
319     }
320 }
321
322 void SeaScene::ghostsDropped(int ghosts)
323 {
324     ghostsLeft_ += ghosts;
325
326     spreadGhosts(ghosts);
327 }
328
329 void SeaScene::pause(bool paused)
330 {
331     //    qDebug() << "pause pressed " << paused;
332         if (paused_ == paused)
333                 return;
334
335         paused_ = paused;
336
337         if (paused == false)
338         {
339      //       qDebug() << "starting to move again";
340             emit pauseOff();
341             screenLitKeeper_.keepScreenLit(true);
342             if (pPausetextItem_)
343                 pPausetextItem_->hide();
344         }
345
346         else
347         {
348          qDebug("about to stop movement");
349             emit pauseOn();
350             screenLitKeeper_.keepScreenLit(false);
351             if (pPausetextItem_ != NULL)
352             {
353                 qDebug() << "about to show the pause text";
354                 pPausetextItem_->show();
355                 qDebug() << "showing pause text";
356             }
357                 else qDebug() << "No pause text available";
358         }
359 }
360
361 void SeaScene::vibrationActivate(bool on)
362 {
363     emit vibrationActivated(on);
364 }
365
366 void SeaScene::handleScreenTapped()
367 {
368
369     //If the game is going just pause it
370     if (!paused_)
371     {
372         pPauseAction_->setChecked(true);
373         return;
374     }
375
376     //If the game is paused, chacl if menu item was selected
377
378     QList<QGraphicsItem *> items = selectedItems();
379
380     //if nothing selected resume play
381
382     if (items.isEmpty())
383     {
384         pPauseAction_->setChecked(false);
385         return;
386
387     }
388
389     //If something was selected check if it was one of the menu items and act on it
390     //(Nothing else should be made selectable anyway)
391
392     //Menu functions
393
394     QGraphicsItem* pItem = items.at(0); //Selecting an item brings here, thus only selecting one item should be possible
395                                        //... so we can just take the first one
396
397
398     if (pItem == pRestartGameItem_)
399     {
400         qDebug() << "game restart requested";
401         restartGame();
402     }
403
404     else if (pItem == pRestartLevelItem_)
405     {
406         qDebug() << "Level restart requested";
407         restartLevel();
408
409     }
410
411     else if (pItem == pSettingsItem_)
412     {
413     //Temporary code for settings, likely to be turned into a QML dialog
414
415           QMessageBox::StandardButton buttonpressed = QMessageBox::question(NULL,"Settings","Do you wish to have vibration effects enabled?", QMessageBox::Yes | QMessageBox::No);
416
417           if (buttonpressed == QMessageBox::Yes)
418               pVibrateAction_->setChecked(true);
419           if (buttonpressed == QMessageBox::No)
420               pVibrateAction_->setChecked(false);
421     }
422
423     else if (pItem == pAboutItem_)
424     {
425         about();
426     }
427
428     else if (pItem == pQuitItem_)
429     {
430         qApp->quit();
431     }
432
433
434     //Selection is just used to get notice of a menu item being clicked, removed after use
435
436     clearSelection();
437
438     //The user propably went to paused state just to access menu, so unpause
439
440     pPauseAction_->setChecked(false);
441
442 }
443
444
445
446 void SeaScene::createMenuItems()
447 {
448
449     QFont font;
450     font.setPixelSize(35);
451
452
453
454     pPausetextItem_ = new QGraphicsTextItem;
455
456  // This is for fremantle
457 //    pPausetextItem_->setHtml("<font size = \"5\" color = darkorange> Game paused. Tap to continue.");
458
459 //Harmattan needs bigger font
460     pPausetextItem_->setHtml("<font size = \"7\" color = darkorange> Game paused. Tap to continue.");
461
462
463     pPausetextItem_->setZValue(1000);
464     pPausetextItem_->setPos(200,50);
465     addItem(pPausetextItem_);
466     pPausetextItem_->hide();
467
468     menuItemCount_ = 0;
469
470   // This is for fremantle
471  //   QString menufonthtml = "<font size = \"4\" color = darkorange>";
472
473
474  //Harmattan needs bigger font
475         QString menufonthtml = "<font size = \"6\" color = darkorange>";
476
477
478     pRestartGameItem_ = new QGraphicsTextItem;
479     pRestartGameItem_->setHtml(tr("Restart <br> game").prepend(menufonthtml));
480     prepareForMenu(pRestartGameItem_);
481
482     pRestartLevelItem_ = new QGraphicsTextItem;
483     pRestartLevelItem_->setHtml(tr("Restart <br> level").prepend(menufonthtml));
484     prepareForMenu(pRestartLevelItem_);
485
486     pSettingsItem_ = new QGraphicsTextItem;
487     pSettingsItem_->setHtml(tr("Vibration <br> effects").prepend(menufonthtml));
488     prepareForMenu(pSettingsItem_);
489
490     pAboutItem_ = new QGraphicsTextItem;
491     pAboutItem_->setHtml(tr("About <br> game").prepend(menufonthtml));
492     prepareForMenu(pAboutItem_);
493
494     pQuitItem_ = new QGraphicsTextItem;
495     pQuitItem_->setHtml(tr("Quit <br> game").prepend(menufonthtml));
496     prepareForMenu(pQuitItem_);
497
498 }
499
500 void SeaScene::prepareForMenu(QGraphicsItem * pItem)
501 {
502
503     //Menu items have pause text item as their parent and are thus added to scene automatically
504     //They are also shown and hidden with it, resulting in the menu being visble when the game is paused
505     //Their coordinates are given relative to the parent.
506
507
508     pItem->setParentItem(pPausetextItem_);
509     pItem->setZValue(1000);
510     pItem->setFlag(QGraphicsItem::ItemIsSelectable);
511     pItem->setY(150);
512     pItem->setX(menuItemCount_++*160-150);
513  }
514
515
516 void SeaScene::about()
517 {
518     QMessageBox::about(NULL, tr("About %1").arg(QApplication::applicationName()),
519                        tr("Version %1"
520                           "<p>Copyright 2011 Heli Hyv&auml;ttinen"
521                           "<p>License: General Public License v2"
522                           "<p>Bug Reports: https://bugs.maemo.org/ "
523                           "enter_bug.cgi?product=Ghosts%20Overboard"
524                           ).arg(QApplication::applicationVersion()));
525
526
527
528 }
529
530
531 void SeaScene::restartLevel()
532 {
533     setupMap(levelList_.value(currentLevel_));  //value() returns default constructor Level if index is invalid, so no risk of crash
534     vibrationActivate(pVibrateAction_->isChecked());  //Vibration effects are lost without this
535    // qDebug() << pVibrateAction_->isChecked();
536 }
537
538
539
540 void SeaScene::nextLevel()
541 {
542
543     currentLevel_++;
544
545     if (levelList_.empty())
546         setupMap(Level());
547
548
549     if ( currentLevel_ < levelList_.size() )
550     {
551        restartLevel();
552     }
553
554     else //Victory!
555     {
556
557        QDialog* pVictoryDialog = new QDialog();
558        pVictoryDialog->setWindowTitle(tr("You won!"));
559
560
561        QPushButton* pPlayAgainButton = new QPushButton(tr("Play again"));
562 //       QPushButton* pQuitButton = new QPushButton(tr("Quit game"));
563
564        QPixmap victoryIcon (":/pix/aavesaari.png");
565        QLabel* pVictoryLabel = new QLabel();
566        pVictoryLabel->setPixmap(victoryIcon);
567
568        QLabel* pTextLabel = new QLabel(tr("Congratulations! <p>You have saved all the ghosts."));
569
570
571        QVBoxLayout* pMainLayout = new QVBoxLayout;
572
573        QHBoxLayout* pTopLayout = new QHBoxLayout;
574        pMainLayout->addLayout(pTopLayout);
575
576        pTopLayout->addWidget(pVictoryLabel);
577        pTopLayout->addWidget(pTextLabel);
578
579
580
581        QHBoxLayout* pButtonLayout = new QHBoxLayout();
582        pMainLayout->addLayout(pButtonLayout);
583
584  //      pButtonLayout->addWidget(pQuitButton);
585        pButtonLayout->addWidget(pPlayAgainButton);
586
587
588
589        pVictoryDialog->setLayout(pMainLayout);
590
591        connect(pPlayAgainButton, SIGNAL(clicked()),pVictoryDialog,SLOT(accept()));
592
593        pVictoryDialog->exec();
594
595         //Never mind if the user cancels the dialog: restart the game anyway
596
597        restartGame();
598     }
599 }
600
601
602 void SeaScene::restartGame()
603 {
604     currentLevel_ = 0;
605     restartLevel();
606 }
607
608
609 void SeaScene::forcePause()
610 {
611     //Pause without setting the pause action state
612     pause(true);
613 }
614
615 void::SeaScene::softContinue()
616 {
617     //Continue if not being paused by the user
618     // Reverts forcePause()
619
620     pause(pPauseAction_->isChecked());
621 }