Should pause at device lock
[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 #include <QSettings>
36 #include <QPixmap>
37
38 const QString ghostImageFilename_ = ":/pix/aave.png";
39 const QString rockImageFilename_ =":/pix/kari.png";
40 const QString octopusImageFilename_= ":/pix/tursas.png";
41
42
43 SeaScene::SeaScene(QObject *parent) :
44     QGraphicsScene(parent)
45 {
46
47     setItemPointersNull();
48
49     paused_ = false;
50     screenLitKeeper_.keepScreenLit(true);
51
52     //set background
53
54     QPixmap waves (":/pix/meri.png");
55     waves.scaled(20,20);
56     setBackgroundBrush(QBrush(waves));
57
58     //set random seed
59
60     qsrand(QTime::currentTime().msec()+2);  //+2 to avoid setting it to 1
61
62 //Setup the level list
63
64     Level level1(5,10);
65     levelList_.append(level1);
66     Level level2(5,10,2,50);
67     levelList_.append(level2);
68     Level level3(5,15,2,50);
69     levelList_.append(level3);
70     Level level4(5,15,4,50);
71     levelList_.append(level4);
72     Level level5(5,15,5,100);
73     levelList_.append(level5);
74
75     currentLevel_ = 0;
76
77
78     connect(this,SIGNAL(allGhostsPicked()),this,SLOT(nextLevel()));
79
80
81     pVibrateAction_ = new QAction(tr("Vibration effects"),this);
82     pVibrateAction_->setCheckable(true);
83     connect(pVibrateAction_,SIGNAL(toggled(bool)),this,SLOT(vibrationActivate(bool)));
84     QSettings settings;
85     pVibrateAction_->setChecked(settings.value("vibration",false).toBool());
86
87
88     pPauseAction_ = new QAction(tr("Pause"),this);
89     pPauseAction_->setCheckable(true);
90     connect(pPauseAction_,SIGNAL(toggled(bool)),this,SLOT(pause(bool)));
91
92     connect(&deviceInfo_,SIGNAL(lockStatusChanged(bool)),this,SLOT(handleDeviceLocked(bool)));
93
94
95     autopauseTimer.setSingleShot(true);
96     autopauseTimer.setInterval(15*60*1000);
97     connect(&autopauseTimer,SIGNAL(timeout()),this,SLOT(turnPauseOn()));
98
99
100 }
101
102 void SeaScene::setupMap(int ghosts, int rocks, int octopuses, int octopusSpeed)
103 {
104     //empty the map
105
106     clear();
107
108     setItemPointersNull();
109
110     createMenuItems();
111
112     createAboutBoxItems();
113
114     createVictoryItems();
115
116
117     //empty the list of moving items
118
119     movingItems_.clear();
120
121     //empty the list of free slots
122     freeTiles_.clear();
123
124     //fill the list of free slots
125
126     int numberOfXTiles  = width() / 40;
127     int numberOfYTiles = height() /40;
128
129 //    qDebug() << numberOfXTiles << " slots in x direction";
130 //    qDebug() << numberOfYTiles << " slots in y rirection";
131
132     for (int i = 0; i < numberOfXTiles; i++ )
133     {
134         for (int j = 0; j < numberOfYTiles; j++)
135         {
136             freeTiles_.append(QPointF(i*40,j*40));
137         }
138     }
139
140
141     //spread the rocks
142
143     for (int i = 0; i < rocks; i++)
144     {
145         QPointF * pPosition = findRandomFreeSlot();
146
147         //If there was no room no point to continue
148         if (pPosition == NULL)
149             break;
150
151         QPixmap rockPixmap (":/pix/kari.png");
152         QGraphicsPixmapItem * pRock = addPixmap(rockPixmap);
153         pRock->setData(0,"rock");
154         pRock->setPos(*pPosition);
155         delete pPosition;
156
157     }
158
159     //spread the ghosts
160
161     ghostsLeft_ = ghosts;
162     spreadGhosts(ghosts);
163
164
165
166     //spread the octopuses
167
168     QList <Octopus*> octopusList;
169
170     for (int i=0; i < octopuses; i++)
171     {
172         QPointF * pPosition = findRandomFreeSlot();
173
174         //If there was no room no point to continue
175         if (pPosition == NULL)
176             break;
177
178     QPixmap octopusPixmap (":/pix/tursas.png");
179     Octopus * pOctopus = new Octopus(octopusPixmap,octopusSpeed);
180     pOctopus->setData(0,"octopus");
181     pOctopus->setPos(*pPosition);
182     addItem(pOctopus);
183     pOctopus->startMoving();
184     movingItems_.append(pOctopus);
185     connect(this,SIGNAL(pauseOn()),pOctopus,SLOT(stopMoving()));
186     connect(this,SIGNAL(pauseOff()),pOctopus,SLOT(startMoving()));
187     octopusList.append(pOctopus);
188     delete pPosition;
189
190     }
191
192
193     //place the ship
194
195     QPointF * pPosition = findRandomFreeSlot();
196     if (pPosition == NULL)
197     {
198         // Game cannot begin without a free position for ship, so give an error message and return
199
200         QMessageBox::critical(NULL,"Error! Too many objects on screen","No free space to place the ship. The game cannot start. Please choose another level.");
201         return;
202     }
203
204     QList<QPixmap> shipImages;
205     shipImages.append(QPixmap(":/pix/laiva.png"));
206     shipImages.append(QPixmap(":/pix/laiva_1aave.png"));
207     shipImages.append(QPixmap(":/pix/laiva_2aave.png"));
208     shipImages.append(QPixmap(":/pix/laiva_3aave.png"));
209     shipImages.append(QPixmap(":/pix/laiva_4aave.png"));
210     shipImages.append(QPixmap(":/pix/laiva_5aave.png"));
211     shipImages.append(QPixmap(":/pix/laiva_6aave.png"));
212     shipImages.append(QPixmap(":/pix/laiva_7aave.png"));
213     shipImages.append(QPixmap(":/pix/laiva_8aave.png"));
214     shipImages.append(QPixmap(":/pix/laiva_9aave.png"));
215     shipImages.append(QPixmap(":/pix/laiva_10aave.png"));
216
217     Ship * pShip = new Ship (shipImages);
218     pShip->setData(0,"ship");
219     pShip->setPos(*pPosition);
220     addItem(pShip);
221     connect(pShip,SIGNAL(pickingGhost(QGraphicsItem*)),this, SLOT(removeGhost(QGraphicsItem*)) );
222     connect(pShip,SIGNAL(droppingGhosts(int)),this,SLOT(ghostsDropped(int)));
223     connect(this,SIGNAL(vibrationActivated(bool)),pShip,SLOT(setVibrationActivate(bool)));
224     pShip->startMoving();
225     movingItems_.append(pShip);
226     connect(this,SIGNAL(pauseOn()),pShip,SLOT(stopMoving()));
227     connect(this,SIGNAL(pauseOff()),pShip,SLOT(startMoving()));
228     foreach (Octopus* pOctopus, octopusList)
229     {
230         connect(pOctopus,SIGNAL(droppingGhosts()),pShip,SLOT(dropAllGhosts()));
231     }
232     delete pPosition;
233
234
235 }
236
237 void SeaScene::setupMap(Level level)
238 {
239     setupMap(level.getNumberOfGhosts(),level.getNumberOfRocks(),level.getNumberOfOctopuses(),level.getOctopusSpeed());
240 }
241
242 void SeaScene::spreadGhosts(int ghosts)
243 {
244
245
246     //the octopuses and the ship may have moved from their original positions,
247     //so the list of free slots must be adjusted to exclude their current positions
248
249     QList<QPointF> temporarilyReservedSlots;
250
251     foreach (QGraphicsItem* pItem, movingItems_)
252     {
253         if (pItem == NULL)
254         {
255  //           qDebug() << "NULL item in movingItems_";
256             continue;
257         }
258
259         //round x and y down to fit the slot size
260         int x = pItem->x();
261         x = x/40;
262         x = x*40;
263
264         int y = pItem->y();
265         y = y/40;
266         y=y*40;
267
268
269         QPointF position (x,y);
270
271         //remove the tiles (potentially) occupied by the item from free slots and place in temp list if was in the list before
272
273         if (freeTiles_.removeOne(position))
274             temporarilyReservedSlots.append(position);
275
276
277         position.setX(x+40);
278
279         if (freeTiles_.removeOne(position))
280             temporarilyReservedSlots.append(position);
281
282         position.setY(y+40);
283
284         if (freeTiles_.removeOne(position))
285             temporarilyReservedSlots.append(position);
286
287         position.setX(x);
288
289         if (freeTiles_.removeOne(position))
290             temporarilyReservedSlots.append(position);
291
292     }
293
294
295     //spread ghosts in random free slots
296
297     for (int i=0; i < ghosts; i++)
298     {
299         QPointF * pPosition = findRandomFreeSlot();
300
301         //If there was no room no point to continue
302         if (pPosition == NULL)
303             return;
304
305         QPixmap ghostPixmap(":/pix/aave.png");
306         QGraphicsPixmapItem * pGhost = addPixmap(ghostPixmap);
307         pGhost->setData(0,"ghost");
308         pGhost->setPos(*pPosition);
309         delete pPosition;
310     }
311
312     //return the slots occupied by moving items to free slots
313     freeTiles_.append(temporarilyReservedSlots);
314
315     //clear temp for the next round
316     temporarilyReservedSlots.clear();
317 }
318
319 QPointF* SeaScene::findRandomFreeSlot()
320 {
321     if (freeTiles_.isEmpty())
322         return NULL;
323
324     int index = qrand()%freeTiles_.size();
325
326 //    qDebug()  << index << " index";
327     return new QPointF (freeTiles_.takeAt(index));
328
329 }
330
331 void SeaScene::removeGhost(QGraphicsItem *pGhost)
332 {
333     removeItem(pGhost);  //remove the item from scene
334     freeTiles_.append(pGhost->scenePos()); //add the item's position to free slots
335     delete pGhost;
336     ghostsLeft_--;
337     if (ghostsLeft_ == 0)
338     {
339         emit allGhostsPicked();
340  //       qDebug() << "All ghosts picked!";
341     }
342 }
343
344 void SeaScene::ghostsDropped(int ghosts)
345 {
346     ghostsLeft_ += ghosts;
347
348     spreadGhosts(ghosts);
349 }
350
351 void SeaScene::pause(bool paused)
352 {
353     //    qDebug() << "pause pressed " << paused;
354         if (paused_ == paused)
355                 return;
356
357         paused_ = paused;
358
359         if (paused == false)
360         {
361      //       qDebug() << "starting to move again";
362             emit fullscreenRequested();
363             emit pauseOff();
364             screenLitKeeper_.keepScreenLit(true);
365             if (pPausetextItem_)
366                 pPausetextItem_->hide();
367
368             autopauseTimer.start(); //Start counting towards autopause
369         }
370
371         else
372         {
373 //         qDebug("about to stop movement");
374             emit pauseOn();
375             screenLitKeeper_.keepScreenLit(false);
376             if (pPausetextItem_ != NULL)
377             {
378 //                qDebug() << "about to show the pause text";
379                 pPausetextItem_->show();
380 //                qDebug() << "showing pause text";
381             }
382 //                else qDebug() << "No pause text available";
383
384             autopauseTimer.stop(); //No need to count toward autopause when already paused
385         }
386 }
387
388 void SeaScene::vibrationActivate(bool on)
389 {
390     emit vibrationActivated(on);
391 }
392
393 void SeaScene::handleScreenTapped()
394 {
395
396     //If the game is going just pause it
397     if (!paused_)
398     {
399         pPauseAction_->setChecked(true);
400         return;
401     }
402
403     //If the game is paused and about box is shown, close it and show the pause text and menu again
404
405     if(pAboutBoxItem_)
406     {
407         if(pAboutBoxItem_->isVisible())
408         {
409             pAboutBoxItem_->hide();
410             pPausetextItem_->show();
411         }
412     }
413
414   
415     //If the game is paused, check if the victory item is being shown
416     if(pVictoryCongratulationsItem_)
417     {
418         if (pVictoryCongratulationsItem_->isVisibleTo(NULL)) //returns visibility to scene
419         {
420             pVictoryCongratulationsItem_->hide();
421             restartGame();
422             pPauseAction_->setChecked(false); // unpause
423             return;
424         }
425     }
426
427     //If the game is paused and no victory, check if menu item was selected
428
429     QList<QGraphicsItem *> items = selectedItems();
430
431     //if nothing selected resume play
432
433     if (items.isEmpty())
434     {
435         pPauseAction_->setChecked(false);
436         return;
437
438     }
439
440     //If something was selected check if it was one of the menu items and act on it
441     //(Nothing else should be made selectable anyway)
442
443     //Menu functions
444
445     QGraphicsItem* pItem = items.at(0); //Selecting an item brings here, thus only selecting one item should be possible
446                                        //... so we can just take the first one
447
448     //Selection is just used to get notice of a menu item being clicked, removed after use
449
450     clearSelection();
451
452
453     //Act upon the selected item
454
455
456     if (pItem == pRestartGameItem_)
457     {
458 //        qDebug() << "game restart requested";
459         restartGame();
460         pPauseAction_->setChecked(false); //unpause game
461
462     }
463
464     else if (pItem == pRestartLevelItem_)
465     {
466 //        qDebug() << "Level restart requested";
467         restartLevel();
468         pPauseAction_->setChecked(false); //unpause game
469
470     }
471
472     else if (pItem == pSettingsItem_)
473     {
474         pVibrateAction_->toggle();
475
476         QSettings settings;
477         settings.setValue("vibration",pVibrateAction_->isChecked());
478
479         QString text = pSettingsItem_->toHtml();
480         if (pVibrateAction_->isChecked())
481             text.replace(" on"," off"); //don't remove spaces or you get vibratioff...
482         else
483             text.replace(" off"," on");
484         pSettingsItem_->setHtml(text);
485     }
486
487     else if (pItem == pAboutItem_)
488     {
489         about();
490     }
491
492     else if(pItem == pMinimizeItem_)
493     {
494         emit minimizeRequested();
495     }
496
497     else if (pItem == pQuitItem_)
498     {
499         qApp->quit();
500     }
501 }
502
503
504
505 void SeaScene::createMenuItems()
506 {
507
508     QFont font;
509     font.setPixelSize(35);
510
511
512
513     pPausetextItem_ = new QGraphicsTextItem;
514     pPausetextItem_->setHtml("<font size = \"5\" color = darkorange> Game paused. Tap to continue.");
515     pPausetextItem_->setZValue(1000);
516     pPausetextItem_->setPos(165,50);
517     addItem(pPausetextItem_);
518     pPausetextItem_->hide();
519
520     menuItemCount_ = 0;
521
522     QString menufonthtml = "<font size = \"4\" color = darkorange>";
523
524     pRestartGameItem_ = new QGraphicsTextItem;
525     pRestartGameItem_->setHtml(tr("Restart <br> game").prepend(menufonthtml));
526     prepareForMenu(pRestartGameItem_);
527
528     pRestartLevelItem_ = new QGraphicsTextItem;
529     pRestartLevelItem_->setHtml(tr("Restart <br> level").prepend(menufonthtml));
530     prepareForMenu(pRestartLevelItem_);
531
532     pSettingsItem_ = new QGraphicsTextItem;
533     QString vibraText(tr("Turn vibration <br> effects "));
534     QString statusText;
535     if (pVibrateAction_->isChecked())
536     {
537         statusText = "off";
538     }
539     else
540     {
541         statusText = "on";
542     }
543     vibraText.append(statusText);
544     pSettingsItem_->setHtml(vibraText.prepend(menufonthtml));
545     prepareForMenu(pSettingsItem_);
546
547     pAboutItem_ = new QGraphicsTextItem;
548     pAboutItem_->setHtml(tr("About <br> game").prepend(menufonthtml));
549     prepareForMenu(pAboutItem_);
550
551     pMinimizeItem_ = new QGraphicsTextItem;
552     pMinimizeItem_->setHtml(tr("Show <br> status bar").prepend(menufonthtml));
553     prepareForMenu(pMinimizeItem_);
554
555     pQuitItem_ = new QGraphicsTextItem;
556     pQuitItem_->setHtml(tr("Quit <br> game").prepend(menufonthtml));
557     prepareForMenu(pQuitItem_);
558
559 }
560
561 void SeaScene::prepareForMenu(QGraphicsItem * pItem)
562 {
563
564     //Menu items have pause text item as their parent and are thus added to scene automatically
565     //They are also shown and hidden with it, resulting in the menu being visble when the game is paused
566     //Their coordinates are given relative to the parent.
567
568
569
570
571     int itemsPerRow = 3;
572
573     pItem->setParentItem(pPausetextItem_);
574     pItem->setZValue(1000);
575     pItem->setFlag(QGraphicsItem::ItemIsSelectable);
576
577     int row = menuItemCount_/(itemsPerRow);
578     pItem->setY(150+row*120);
579     pItem->setX(((menuItemCount_%(itemsPerRow))*180+5));
580
581     menuItemCount_++;
582  }
583
584
585 void SeaScene::about()
586 {
587     pPausetextItem_->hide();
588     pAboutBoxItem_->show();
589 }
590
591
592 void SeaScene::restartLevel()
593 {
594     setupMap(levelList_.value(currentLevel_));  //value() returns default constructor Level if index is invalid, so no risk of crash
595     vibrationActivate(pVibrateAction_->isChecked());  //Vibration effects are lost without this
596    // qDebug() << pVibrateAction_->isChecked();
597     autopauseTimer.start();  //reset counting towards autopause
598 }
599
600
601
602 void SeaScene::nextLevel()
603 {
604
605     currentLevel_++;
606
607     if (levelList_.empty())
608         setupMap(Level());
609
610
611     if ( currentLevel_ < levelList_.size() )
612     {
613        restartLevel();
614     }
615
616     else //Victory!
617     {
618
619         pPauseAction_->setChecked(true); //Pause the game while showing the victory dialog
620
621         pPausetextItem_->hide();
622
623         pVictoryCongratulationsItem_->show();
624
625     }
626 }
627
628
629 void SeaScene::restartGame()
630 {
631     currentLevel_ = 0;
632     restartLevel();
633 }
634
635
636 void SeaScene::forcePause()
637 {
638     //Pause without setting the pause action state
639     pause(true);
640 }
641
642 void SeaScene::softContinue()
643 {
644     //Continue if not being paused by the user
645     // Reverts forcePause()
646
647     pause(pPauseAction_->isChecked());
648 }
649
650
651 void SeaScene::createVictoryItems()
652 {
653     pVictoryCongratulationsItem_ = new QGraphicsTextItem;
654     pVictoryCongratulationsItem_->setHtml("<font size=\"6\" color = darkorange> Victory!");
655     pVictoryCongratulationsItem_->hide();
656     pVictoryCongratulationsItem_->setPos(355,50);
657     pVictoryCongratulationsItem_->setZValue(1000);
658     addItem(pVictoryCongratulationsItem_);
659
660
661     //coordinates are relative to the parent
662
663     QGraphicsTextItem * pTextItem = new QGraphicsTextItem(pVictoryCongratulationsItem_);
664     pTextItem->setHtml("<font size=\"5\" color = darkorange> Congratulations!");
665     pTextItem->setPos(-50,100);
666     pTextItem->setZValue(1000);
667
668     QGraphicsTextItem * pMiddleTextItem = new QGraphicsTextItem(pVictoryCongratulationsItem_);
669     pMiddleTextItem->setHtml("<font size=\"5\" color = darkorange> You have saved all the ghosts.");
670     pMiddleTextItem->setPos(-145,140);
671     pMiddleTextItem->setZValue(1000);
672
673     QGraphicsTextItem * pLowestTextItem = new QGraphicsTextItem(pVictoryCongratulationsItem_);
674     pLowestTextItem->setHtml("<font size=\"5\" color = darkorange> Tap to play again");
675     pLowestTextItem->setPos(-50,220);
676     pLowestTextItem->setZValue(1000);
677 }
678
679 void SeaScene::createAboutBoxItems()
680 {
681     pAboutBoxItem_ = new QGraphicsTextItem;
682     addItem(pAboutBoxItem_);
683     pAboutBoxItem_->setPos(25,50);
684     pAboutBoxItem_->setZValue(1000);
685     pAboutBoxItem_->hide();
686
687     pAboutBoxItem_->setHtml(tr("<font color = darkorange size = \"7\">"
688                           "%1 <br> <font size = \"5\"> Version %2"
689                           "<p><font size = \"4\"> Copyright 2011 Heli Hyv&auml;ttinen"
690                           "<p><font size = \"4\"> License: General Public License v2"
691                           "<p><font size = \"3\"> Web: http://ghostsoverboard.garage.maemo.org/<br>"
692                           "Bug Reports: <br> https://bugs.maemo.org/"
693                           "enter_bug.cgi?product=Ghosts%20Overboard"
694                           ).arg(QApplication::applicationName(),QApplication::applicationVersion()));
695
696 }
697
698 void SeaScene::setItemPointersNull()
699 {
700     pPausetextItem_ = NULL;
701     pRestartLevelItem_ = NULL;
702     pRestartGameItem_ = NULL;
703     pSettingsItem_ = NULL;
704     pAboutItem_ = NULL;
705     pQuitItem_ = NULL ;
706     pMinimizeItem_ = NULL;
707
708     pAboutBoxItem_ = NULL;
709     pVictoryCongratulationsItem_ = NULL;
710
711 }
712
713 void SeaScene::turnPauseOn()
714 {
715     pPauseAction_->setChecked(true);
716 }
717
718 void SeaScene::handleDeviceLocked(bool isLocked)
719 {
720     //pauses if locked but does not unpause if unlocked
721     if(isLocked)
722     {
723         pPauseAction_->setChecked(true);
724     }
725 }