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