1/*
2 * Copyright 2009 Mathias Kraus <k.hias@gmx.de>
3 * Copyright 2007-2008 Thomas Gallinari <tg8187@yahoo.fr>
4 * Copyright 2007-2008 Pierre-Benoit Besse <besse@gmail.com>
5 * Copyright 2007-2008 Alexandre Galinier <alex.galinier@hotmail.com>
6 *
7 * This program is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of
10 * the License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 */
20
21#include "game.h"
22#include "mapparser.h"
23#include "settings.h"
24#include "gamescene.h"
25#include "arena.h"
26#include "player.h"
27#include "bonus.h"
28#include "bomb.h"
29#include "block.h"
30#include "config/playersettings.h"
31
32#include <QPointF>
33#include <QTimer>
34#include <QKeyEvent>
35
36#include <KStandardDirs>
37#include <KConfig>
38#include <KComponentData>
39#include <kgsound.h>
40
41Game::Game(PlayerSettings* playerSettings)
42{
43 m_playerSettings = playerSettings;
44
45 // Initialize the sound state
46 setSoundsEnabled(Settings::sounds());
47 m_wilhelmScream = Settings::useWilhelmScream();
48
49 m_soundPutBomb = new KgSound(KStandardDirs::locate("appdata", "sounds/putbomb.wav"));
50 m_soundExplode = new KgSound(KStandardDirs::locate("appdata", "sounds/explode.wav"));
51 m_soundBonus = new KgSound(KStandardDirs::locate("appdata", "sounds/wow.wav"));
52 m_soundFalling = new KgSound(KStandardDirs::locate("appdata", "sounds/deepfall.wav"));
53 m_soundDie = new KgSound(KStandardDirs::locate("appdata", "sounds/die.wav"));
54
55 m_arena = 0;
56 m_randomArenaModeArenaList.clear();
57 m_gameScene = 0;
58 m_winPoints = Settings::self()->pointsToWin();
59
60 QStringList strPlayerIDs = m_playerSettings->playerIDs();
61 for(int i = 0; i < strPlayerIDs.count(); i++)
62 {
63 if(m_playerSettings->enabled(strPlayerIDs[i]))
64 {
65 Player* player = new Player(qreal(Granatier::CellSize * (-0.5)),qreal(Granatier::CellSize * 0.5), strPlayerIDs[i], playerSettings, m_arena);
66 m_players.append(player);
67 connect(player, SIGNAL(dying()), this, SLOT(playerDeath()));
68 connect(player, SIGNAL(falling()), this, SLOT(playerFalling()));
69 connect(player, SIGNAL(resurrectBonusTaken()), this, SLOT(resurrectBonusTaken()));
70 }
71 }
72
73 init();
74
75 for (int i = 0; i < m_players.size(); i++)
76 {
77 connect(m_players[i], SIGNAL(bombDropped(Player*,qreal,qreal,bool,int)), this, SLOT(createBomb(Player*,qreal,qreal,bool,int)));
78 }
79
80 m_gameOver = false;
81}
82
83void Game::init()
84{
85 // Create the Arena instance
86 m_arena = new Arena();
87
88 m_remainingTime = Settings::roundTime();
89 m_bombCount = 0;
90
91 // Create the parser that will parse the XML file in order to initialize the Arena instance
92 // This also creates all the characters
93 MapParser mapParser(m_arena);
94 // Set the XML file as input source for the parser
95 QString filePath;
96 if(Settings::self()->randomArenaMode())
97 {
98 if(m_randomArenaModeArenaList.isEmpty())
99 {
100 QStringList arenasAvailable;
101 m_randomArenaModeArenaList = Settings::self()->randomArenaModeArenaList();
102
103 KGlobal::dirs()->addResourceType("arenaselector", "data", KGlobal::mainComponent().componentName() + "/arenas/");
104 KGlobal::dirs()->findAllResources("arenaselector", "*.desktop", KStandardDirs::Recursive, arenasAvailable);
105
106 QStringList::Iterator i = m_randomArenaModeArenaList.begin();
107 while(i != m_randomArenaModeArenaList.end())
108 {
109 if(arenasAvailable.contains(*i))
110 {
111 i++;
112 }
113 else
114 {
115 i = m_randomArenaModeArenaList.erase(i);
116 }
117 }
118
119 if(m_randomArenaModeArenaList.isEmpty())
120 {
121 m_randomArenaModeArenaList = arenasAvailable;
122 }
123 }
124
125 int nIndex = ((double) qrand() / RAND_MAX) * m_randomArenaModeArenaList.count();
126 if(nIndex < 0)
127 {
128 nIndex = 0;
129 }
130 else if(nIndex >= m_randomArenaModeArenaList.count())
131 {
132 nIndex = m_randomArenaModeArenaList.count() - 1;
133 }
134 filePath = KStandardDirs::locate("appdata", "arenas/" + m_randomArenaModeArenaList.at(nIndex));
135 m_randomArenaModeArenaList.removeAt(nIndex);
136 }
137 else
138 {
139 filePath = KStandardDirs::locate("appdata", Settings::self()->arena());
140 }
141
142 if(!QFile::exists(filePath))
143 {
144 Settings::self()->useDefaults(true);
145 filePath = KStandardDirs::locate("appdata", Settings::self()->arena());
146 Settings::self()->useDefaults(false);
147 }
148
149 KConfig arenaConfig(filePath, KConfig::SimpleConfig);
150 KConfigGroup group = arenaConfig.group("Arena");
151 QString arenaFileName = group.readEntry("FileName");
152
153 QFile arenaXmlFile(KStandardDirs::locate("appdata", QString("arenas/%1").arg(arenaFileName)));
154 //QFile arenaXmlFile(KStandardDirs::locate("appdata", "arenas/granatier.xml"));
155 QXmlInputSource source(&arenaXmlFile);
156 // Create the XML file reader
157 QXmlSimpleReader reader;
158 reader.setContentHandler(&mapParser);
159 // Parse the XML file
160 reader.parse(source);
161
162 QString arenaName = group.readEntry("Name");
163 m_arena->setName(arenaName);
164
165 //create the block items
166 for (int i = 0; i < m_arena->getNbRows(); ++i)
167 {
168 for (int j = 0; j < m_arena->getNbColumns(); ++j)
169 {
170 if(m_arena->getCell(i,j).getType() == Granatier::Cell::BLOCK)
171 {
172 Block* block = new Block((j + 0.5) * Granatier::CellSize, (i + 0.5) * Granatier::CellSize, m_arena, "arena_block");
173 m_blocks.append(block);
174 m_arena->setCellElement(i, j, block);
175 }
176 }
177 }
178
179 // Start the Game timer
180 m_timer = new QTimer(this);
181 m_timer->setInterval(int(1000 / Granatier::FPS));
182 connect(m_timer, SIGNAL(timeout()), this, SLOT(update()));
183 m_timer->start();
184 m_state = RUNNING;
185
186 m_roundTimer = new QTimer(this);
187 m_roundTimer->setInterval(1000);
188 connect(m_roundTimer, SIGNAL(timeout()), this, SLOT(decrementRemainingRoundTime()));
189 m_roundTimer->start();
190
191 m_setRoundFinishedTimer = new QTimer(this);
192 m_setRoundFinishedTimer->setSingleShot(true);
193 connect(m_setRoundFinishedTimer, SIGNAL(timeout()), this, SLOT(setRoundFinished()));
194
195 // Init the characters coordinates on the Arena
196 for (int i = 0; i < m_players.size(); i++)
197 {
198 m_players[i]->setArena(m_arena);
199 QPointF playerPosition = m_arena->getPlayerPosition(i);
200 m_players[i]->setInitialCoordinates(qreal(Granatier::CellSize * playerPosition.x()), qreal(Granatier::CellSize * playerPosition.y()));
201 }
202 initCharactersPosition();
203
204 // Create the hidden Bonuses
205 createBonus();
206}
207
208Game::~Game()
209{
210 //pause is needed to stop all animations and therefore the access of the *items to their model
211 pause(true);
212
213 qDeleteAll(m_players);
214 m_players.clear();
215
216 cleanUp();
217
218 delete m_soundPutBomb;
219 delete m_soundExplode;
220 delete m_soundBonus;
221 delete m_soundFalling;
222 delete m_soundDie;
223}
224
225void Game::cleanUp()
226{
227 qDeleteAll(m_blocks);
228 m_blocks.clear();
229 qDeleteAll(m_bonus);
230 m_bonus.clear();
231 qDeleteAll(m_bombs);
232 m_bombs.clear();
233 delete m_arena;
234 m_arena = 0;
235 delete m_timer;
236 m_timer = 0;
237 delete m_roundTimer;
238 m_roundTimer = 0;
239 delete m_setRoundFinishedTimer;
240 m_setRoundFinishedTimer = 0;
241}
242
243void Game::setGameScene(GameScene* p_gameScene)
244{
245 m_gameScene = p_gameScene;
246}
247
248void Game::start()
249{
250 // Restart the Game timer
251 m_timer->start();
252 m_state = RUNNING;
253 m_roundTimer->start();
254 emit(pauseChanged(false, false));
255}
256
257void Game::pause(bool p_locked)
258{
259 // Stop the Game timer
260 m_timer->stop();
261 m_roundTimer->stop();
262 if (p_locked)
263 {
264 m_state = PAUSED_LOCKED;
265 }
266 else
267 {
268 m_state = PAUSED_UNLOCKED;
269 }
270 emit(pauseChanged(true, false));
271}
272
273void Game::switchPause()
274{
275 // If the Game is not already paused
276 if (m_state == RUNNING)
277 {
278 // Pause the Game
279 pause();
280 emit(pauseChanged(true, true));
281 }
282 // If the Game is already paused
283 else
284 {
285 // Resume the Game
286 start();
287 emit(pauseChanged(false, true));
288 }
289}
290
291QList<Player*> Game::getPlayers() const
292{
293 return m_players;
294}
295
296QTimer* Game::getTimer() const
297{
298 return m_timer;
299}
300
301int Game::getRemainingTime() const
302{
303 return m_remainingTime;
304}
305
306Arena* Game::getArena() const
307{
308 return m_arena;
309}
310
311bool Game::isPaused() const
312{
313 return (m_state != RUNNING);
314}
315
316bool Game::getGameOver() const
317{
318 return m_gameOver;
319}
320
321QString Game::getWinner() const
322{
323 return m_strWinner;
324}
325
326int Game::getWinPoints() const
327{
328 return m_winPoints;
329}
330
331QList<Bonus*> Game::getBonus() const
332{
333 return m_bonus;
334}
335
336void Game::createBonus()
337{
338 Bonus* bonus;
339 int nBonusCount = 0.3 * m_blocks.size();
340 int nBadBonusCount = 0.1 * m_blocks.size();
341 int nNeutralBonusCount = static_cast <int> ((qrand()/1.0)/RAND_MAX * 2);
342 QList<Granatier::Bonus::Type> bonusTypeList;
343 Granatier::Bonus::Type bonusType;
344 for (int i = 0; i < m_blocks.size(); i++)
345 {
346 bonusType = Granatier::Bonus::NONE;
347 if(i < nBonusCount)
348 {
349 int nNumberOfBonuses = 6;
350 switch (static_cast <int> ((qrand()/1.0)/RAND_MAX * nNumberOfBonuses))
351 {
352 case 0: bonusType = Granatier::Bonus::SPEED;
353 break;
354 case 1: bonusType = Granatier::Bonus::BOMB;
355 break;
356 case 2: bonusType = Granatier::Bonus::POWER;
357 break;
358 case 3: bonusType = Granatier::Bonus::SHIELD;
359 break;
360 case 4: bonusType = Granatier::Bonus::THROW;
361 break;
362 case 5: bonusType = Granatier::Bonus::KICK;
363 break;
364 default: bonusType = Granatier::Bonus::SPEED;
365 }
366 }
367 else if (i-nBonusCount < nBadBonusCount)
368 {
369 switch (static_cast <int> ((qrand()/1.0)/RAND_MAX * 5))
370 {
371 case 0: bonusType = Granatier::Bonus::HYPERACTIVE;
372 break;
373 case 1: bonusType = Granatier::Bonus::SLOW;
374 break;
375 case 2: bonusType = Granatier::Bonus::MIRROR;
376 break;
377 case 3: bonusType = Granatier::Bonus::SCATTY;
378 break;
379 case 4: bonusType = Granatier::Bonus::RESTRAIN;
380 break;
381 default: bonusType = Granatier::Bonus::HYPERACTIVE;
382 }
383 }
384 else if(i-nBonusCount-nBadBonusCount < nNeutralBonusCount)
385 {
386 bonusType = Granatier::Bonus::RESURRECT;
387 }
388 bonusTypeList.append(bonusType);
389 }
390
391 int nShuffle;
392 for (int i = 0; i < m_blocks.size(); i++)
393 {
394 nShuffle = m_blocks.size() * (qrand()/1.0)/RAND_MAX;
395 if(nShuffle >= m_blocks.size())
396 {
397 nShuffle = m_blocks.size() - 1;
398 }
399 else if(nShuffle < 0)
400 {
401 nShuffle = 0;
402 }
403 bonusTypeList.swap(i, nShuffle);
404 }
405
406 for (int i = 0; i < m_blocks.size(); ++i)
407 {
408 if(bonusTypeList[i] != Granatier::Bonus::NONE)
409 {
410 bonus = new Bonus(m_blocks[i]->getX(), m_blocks[i]->getY(), m_arena, bonusTypeList[i]);
411 m_bonus.append(bonus);
412 m_blocks[i]->setBonus(bonus);
413 }
414 }
415}
416
417void Game::removeBonus(Bonus* bonus)
418{
419 m_bonus.removeAt(m_bonus.indexOf(bonus));
420 //do not delete the Bonus, because the ElementItem will delete it
421 if(m_soundEnabled && !bonus->isDestroyed())
422 {
423 m_soundBonus->start();
424 }
425}
426
427void Game::setSoundsEnabled(bool p_enabled)
428{
429 m_soundEnabled = p_enabled;
430 Settings::setSounds(p_enabled);
431 Settings::self()->writeConfig();
432}
433
434void Game::initCharactersPosition()
435{
436 // If the timer is stopped, it means that collisions are already being handled
437 if (m_timer->isActive())
438 {
439 // At the beginning, the timer is stopped but the Game isn't paused (to allow keyPressedEvent detection)
440 m_timer->stop();
441 m_roundTimer->stop();
442 m_state = RUNNING;
443 // Initialize the Player coordinates
444 for(int i = 0; i < m_players.size(); i++)
445 {
446 m_players[i]->initCoordinate();
447 m_players[i]->init();
448 }
449 }
450}
451
452void Game::keyPressEvent(QKeyEvent* p_event)
453{
454 if(p_event->isAutoRepeat())
455 {
456 return;
457 }
458
459 // At the beginning or when paused, we start the timer when a key is pressed
460 if (!m_timer->isActive())
461 {
462 if(p_event->key() == Qt::Key_Space)
463 {
464 // If paused
465 if (m_state == PAUSED_UNLOCKED)
466 {
467 switchPause();
468 }
469 else if (m_state == RUNNING) // At the game beginning
470 {
471 // Start the game
472 m_timer->start();
473 m_roundTimer->start();
474 emit(gameStarted());
475 }
476 else if (m_state == PAUSED_LOCKED)
477 {
478 // if the game is over, start a new game
479 if (m_gameOver)
480 {
481 emit(gameOver());
482 return;
483 }
484 else
485 {
486 m_gameScene->cleanUp();
487 cleanUp();
488 init();
489 m_gameScene->init();
490 for(int i = 0; i < m_players.length(); i++)
491 {
492 m_players[i]->resurrect();
493 }
494 }
495 }
496 }
497 return;
498 }
499 // Behaviour when the game has begun
500 switch (p_event->key())
501 {
502 case Qt::Key_P:
503 case Qt::Key_Escape:
504 switchPause();
505 return;
506 default:
507 break;
508 }
509
510 //TODO: make signal
511 for(int i = 0; i < m_players.size(); i++)
512 {
513 m_players[i]->keyPressed(p_event);
514 }
515}
516
517void Game::keyReleaseEvent(QKeyEvent* p_event)
518{
519 if(p_event->isAutoRepeat())
520 {
521 return;
522 }
523 //TODO: make signal
524 for(int i = 0; i < m_players.size(); i++)
525 {
526 m_players[i]->keyReleased(p_event);
527 }
528}
529
530void Game::update()
531{
532 //update Bombs
533 for (int i = 0; i < m_bombs.size(); ++i)
534 {
535 m_bombs[i]->updateMove();
536 }
537
538 //update Player
539 for(int i = 0; i < m_players.size(); i++)
540 {
541 m_players[i]->updateMove();
542 m_players[i]->emitGameUpdated();
543 }
544}
545
546void Game::decrementRemainingRoundTime()
547{
548 m_remainingTime--;
549 if(m_remainingTime >= 0)
550 {
551 emit(infoChanged(Granatier::Info::TimeInfo));
552 }
553 else
554 {
555 if(m_remainingTime % 2 == 0)
556 {
557 //create bombs at randoms places
558 int nRow;
559 int nCol;
560 Granatier::Cell::Type cellType;
561 bool bFound = false;
562 do
563 {
564 nRow = m_arena->getNbRows() * (qrand()/1.0)/RAND_MAX;
565 nCol = m_arena->getNbColumns() * (qrand()/1.0)/RAND_MAX;
566 cellType = m_arena->getCell(nRow, nCol).getType();
567 if(cellType != Granatier::Cell::WALL && cellType != Granatier::Cell::HOLE && m_arena->getCell(nRow, nCol).isWalkable(0))
568 {
569 bFound = true;
570 }
571 }
572 while (!bFound);
573
574 m_bombCount++;
575 Bomb* bomb = new Bomb((nCol + 0.5) * Granatier::CellSize, (nRow + 0.5) * Granatier::CellSize, m_arena, m_bombCount, 1000); // time in ms
576 bomb->setBombPower((qrand()/1.0)/RAND_MAX * 2 + 1);
577 emit bombCreated(bomb);
578 connect(bomb, SIGNAL(bombDetonated(Bomb*)), this, SLOT(bombDetonated()));
579 m_bombs.append(bomb);
580 if(m_remainingTime > -100 && m_roundTimer->interval() > 150)
581 {
582 m_roundTimer->setInterval(m_roundTimer->interval() + m_remainingTime);
583 }
584 else if (m_roundTimer->interval() > 30)
585 {
586 m_roundTimer->setInterval(m_roundTimer->interval() - 1);
587 }
588 }
589 }
590}
591
592void Game::playerFalling()
593{
594 if(m_soundEnabled)
595 {
596 m_soundFalling->start();
597 }
598}
599
600void Game::playerDeath()
601{
602 if(m_soundEnabled)
603 {
604 m_soundDie->start();
605 }
606
607 //check if at most one player is alive if not already finished
608 if(!m_setRoundFinishedTimer->isActive())
609 {
610 int nPlayerAlive = 0;
611 for(int i = 0; i < m_players.length(); i++)
612 {
613 if(m_players[i]->isAlive())
614 {
615 nPlayerAlive++;
616 }
617 }
618 if(nPlayerAlive <= 1)
619 {
620 //wait some time until the game stops
621 m_setRoundFinishedTimer->start(1500);
622 }
623 }
624}
625
626void Game::resurrectBonusTaken()
627{
628 for(int i = 0; i < m_players.length(); i++)
629 {
630 if(!(m_players[i]->isAlive()))
631 {
632 m_players[i]->resurrect();
633 }
634 }
635}
636
637void Game::setRoundFinished()
638{
639 int nPlayerAlive = 0;
640 int nIndex = 0;;
641 if(m_gameOver)
642 {
643 return;
644 }
645 for(int i = 0; i < m_players.length(); i++)
646 {
647 if(m_players[i]->isAlive())
648 {
649 nPlayerAlive++;
650 nIndex = i;
651 }
652 }
653 //this check is needed, if in the meantime the resurrect bonus was taken
654 if (nPlayerAlive > 1)
655 {
656 return;
657 }
658
659 if (nPlayerAlive == 1)
660 {
661 m_players[nIndex]->addPoint();
662 }
663
664 pause(true);
665
666 for(int i = 0; i < m_players.length(); i++)
667 {
668 // check if a player reaches the win points
669 if (m_players[i]->points() >= m_winPoints)
670 {
671 m_gameOver = true;
672 m_strWinner = m_players[i]->getPlayerName();
673 break;
674 }
675 }
676 m_gameScene->showScore();
677}
678
679void Game::createBomb(Player* player, qreal x, qreal y, bool newBomb, int throwDistance)
680{
681 int col = m_arena->getColFromX(x);
682 int row = m_arena->getRowFromY(y);
683 if(col >= 0 && col < m_arena->getNbColumns() && row >= 0 && row < m_arena->getNbRows())
684 {
685 QList<Element*> bombElements = m_arena->getCell(row, col).getElements(Granatier::Element::BOMB);
686 if (!bombElements.isEmpty())
687 {
688 if(player->hasThrowBomb() && throwDistance > 0)
689 {
690 foreach(Element* element, bombElements)
691 {
692 dynamic_cast <Bomb*> (element)->setThrown(player->direction());
693 }
694 }
695 return;
696 }
697 }
698
699 if(!newBomb)
700 {
701 return;
702 }
703
704 m_bombCount++;
705 Bomb* bomb = new Bomb((col + 0.5) * Granatier::CellSize, (row + 0.5) * Granatier::CellSize, m_arena, m_bombCount, 2500); // time in ms
706 bomb->setBombPower(player->getBombPower());
707 emit bombCreated(bomb);
708 connect(bomb, SIGNAL(bombDetonated(Bomb*)), this, SLOT(bombDetonated()));
709 connect(bomb, SIGNAL(releaseBombArmory()), player, SLOT(slot_refillBombArmory()));
710 m_bombs.append(bomb);
711 player->decrementBombArmory();
712
713 if(m_soundEnabled)
714 {
715 m_soundPutBomb->start();
716 }
717}
718
719void Game::removeBomb(Bomb* bomb)
720{
721 // Find the Bomb
722 int index = m_bombs.indexOf(bomb);
723 //remove the bomb
724 if(index != -1)
725 {
726 //do not delete the bomb because it will be deleted through the destructor of elementitem
727 m_bombs.removeAt(index);
728 }
729}
730
731void Game::bombDetonated()
732{
733 if(m_soundEnabled)
734 {
735 m_soundExplode->start();
736 }
737}
738
739void Game::blockDestroyed(const int row, const int col, Block* block)
740{
741 // find the Block
742 int index = m_blocks.indexOf(block);
743 // remove the Block
744 if(index != -1)
745 {
746 //do not delete the block because it will be deleted through the destructor of elementitem
747 m_arena->removeCellElement(row, col, block);
748 }
749}
750