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