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