1/*
2 * Copyright 2007-2008 Thomas Gallinari <tg8187@yahoo.fr>
3 * Copyright 2007-2008 Pierre-Benoit Besse <besse@gmail.com>
4 * Copyright 2007-2008 Alexandre Galinier <alex.galinier@hotmail.com>
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License as
8 * published by the Free Software Foundation; either version 2 of
9 * the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 */
19
20#include "game.h"
21#include "kapmanparser.h"
22#include "settings.h"
23
24#include <KStandardDirs>
25#include <KgDifficulty>
26
27const int Game::FPS = 40;
28int Game::s_bonusDuration;
29int Game::s_preyStateDuration;
30qreal Game::s_durationRatio;
31
32Game::Game() :
33 m_isCheater(false),
34 m_lives(3),
35 m_points(0),
36 m_level(1),
37 m_nbEatenGhosts(0),
38 m_soundGameOver(KStandardDirs::locate("sound", "kapman/gameover.ogg")),
39 m_soundGhost(KStandardDirs::locate("sound", "kapman/ghost.ogg")),
40 m_soundGainLife(KStandardDirs::locate("sound", "kapman/life.ogg")),
41 m_soundEnergizer(KStandardDirs::locate("sound", "kapman/energizer.ogg")),
42 m_soundBonus(KStandardDirs::locate("sound", "kapman/bonus.ogg")),
43 m_soundPill(KStandardDirs::locate("sound", "kapman/pill.ogg")),
44 m_soundLevelUp(KStandardDirs::locate("sound", "kapman/levelup.ogg"))
45{
46 // Initialize the sound state
47 setSoundsEnabled(Settings::sounds());
48
49 // Timers for medium difficulty
50 s_bonusDuration = 7000;
51 s_preyStateDuration = 10000;
52 // Difference ratio between low/high and medium speed
53 s_durationRatio = 1.0;
54
55 // Tells the KgDifficulty singleton that the game is not running
56 Kg::difficulty()->setGameRunning(false);
57
58 // Create the Maze instance
59 m_maze = new Maze();
60 connect(m_maze, SIGNAL(allElementsEaten()), this, SLOT(nextLevel()));
61
62 // Create the parser that will parse the XML file in order to initialize the Maze instance
63 // This also creates all the characters
64 KapmanParser kapmanParser(this);
65 // Set the XML file as input source for the parser
66 QFile mazeXmlFile(KStandardDirs::locate("appdata", "defaultmaze.xml"));
67 QXmlInputSource source(&mazeXmlFile);
68 // Create the XML file reader
69 QXmlSimpleReader reader;
70 reader.setContentHandler(&kapmanParser);
71 // Parse the XML file
72 reader.parse(source);
73
74 connect(m_kapman, SIGNAL(sWinPoints(Element*)), this, SLOT(winPoints(Element*)));
75
76
77
78 // Initialize the characters speed timers duration considering the difficulty level
79 switch (Kg::difficultyLevel()) {
80 case KgDifficultyLevel::Easy:
81 // Ratio low/medium speed
82 s_durationRatio = Character::MEDIUM_SPEED / Character::LOW_SPEED;
83 break;
84 case KgDifficultyLevel::Medium:
85 s_durationRatio = 1;
86 break;
87 case KgDifficultyLevel::Hard:
88 // Ratio high/medium speed
89 s_durationRatio = Character::MEDIUM_SPEED / Character::HIGH_SPEED;
90 break;
91 default:
92 break;
93 }
94
95 for (int i = 0; i < m_ghosts.size(); ++i) {
96 connect(m_ghosts[i], SIGNAL(lifeLost()), this, SLOT(kapmanDeath()));
97 connect(m_ghosts[i], SIGNAL(ghostEaten(Ghost*)), this, SLOT(ghostDeath(Ghost*)));
98 // Initialize the ghosts speed and the ghost speed increase considering the characters speed
99 m_ghosts[i]->initSpeedInc();
100 }
101 m_kapman->initSpeedInc();
102
103 // Initialize Bonus timer from the difficulty level
104 m_bonusTimer = new QTimer(this);
105 m_bonusTimer->setInterval( (int)(s_bonusDuration * s_durationRatio) );
106 m_bonusTimer->setSingleShot(true);
107 connect(m_bonusTimer, SIGNAL(timeout()), this, SLOT(hideBonus()));
108 // Initialize the Preys timer from the difficulty level
109 m_preyTimer = new QTimer(this);
110 m_preyTimer->setInterval( (int)(s_preyStateDuration * s_durationRatio) );
111 m_preyTimer->setSingleShot(true);
112 connect(m_preyTimer, SIGNAL(timeout()), this, SLOT(endPreyState()));
113
114 // Start the Game timer
115 m_timer = new QTimer(this);
116 m_timer->setInterval(int(1000 / Game::FPS));
117 connect(m_timer, SIGNAL(timeout()), this, SLOT(update()));
118 m_timer->start();
119 m_state = RUNNING;
120 // Init the characters coordinates on the Maze
121 initCharactersPosition();
122}
123
124Game::~Game() {
125 delete m_timer;
126 delete m_bonusTimer;
127 delete m_maze;
128 delete m_kapman;
129 for (int i = 0; i < m_ghosts.size(); ++i) {
130 delete m_ghosts[i];
131 }
132 delete m_bonus;
133}
134
135void Game::start() {
136 // Restart the Game timer
137 m_timer->start();
138 m_state = RUNNING;
139 emit(pauseChanged(false, false));
140}
141
142void Game::pause(bool p_locked) {
143 // Stop the Game timer
144 m_timer->stop();
145 if (p_locked) {
146 m_state = PAUSED_LOCKED;
147 } else {
148 m_state = PAUSED_UNLOCKED;
149 }
150 emit(pauseChanged(true, false));
151}
152
153void Game::switchPause(bool p_locked) {
154 // If the Game is not already paused
155 if (m_state == RUNNING) {
156 // Pause the Game
157 pause(p_locked);
158 emit(pauseChanged(true, true));
159 }
160 // If the Game is already paused
161 else {
162 // Resume the Game
163 start();
164 emit(pauseChanged(false, true));
165 }
166}
167
168Kapman* Game::getKapman() const {
169 return m_kapman;
170}
171
172QList<Ghost*> Game::getGhosts () const {
173 return m_ghosts;
174}
175
176QTimer* Game::getTimer() const {
177 return m_timer;
178}
179
180Maze* Game::getMaze() const {
181 return m_maze;
182}
183
184bool Game::isPaused() const {
185 return (m_state != RUNNING);
186}
187
188bool Game::isCheater() const {
189 return m_isCheater;
190}
191
192int Game::getScore() const {
193 return m_points;
194}
195int Game::getLives() const {
196 return m_lives;
197}
198
199int Game::getLevel() const {
200 return m_level;
201}
202
203void Game::setLevel(int p_level) {
204 m_isCheater = true;
205 m_level = p_level;
206 m_maze->resetNbElem();
207 m_timer->start(); // Needed to reinit character positions
208 initCharactersPosition();
209 for (int i = 0; i < m_ghosts.size(); ++i) {
210 m_ghosts[i]->initSpeed();
211 }
212 m_kapman->initSpeed();
213 for (int i = 0; i < m_level; ++i) {
214 for (int j = 0; j < m_ghosts.size(); ++j) {
215 m_ghosts[j]->increaseCharactersSpeed();
216 }
217 m_kapman->increaseCharactersSpeed();
218 }
219 setTimersDuration();
220 m_bonus->setPoints(m_level * 100);
221 emit(scoreChanged(m_points));
222 emit(livesChanged(m_lives));
223 emit(levelChanged(m_level));
224 emit(pauseChanged(false, true));
225 emit(levelStarted(true));
226}
227
228Bonus* Game::getBonus() const {
229 return m_bonus;
230}
231
232void Game::createBonus(QPointF p_position){
233 m_bonus = new Bonus(qreal(Cell::SIZE * p_position.x()),qreal(Cell::SIZE * p_position.y()), m_maze, 100);
234}
235
236void Game::createKapman(QPointF p_position){
237 m_kapman = new Kapman(qreal(Cell::SIZE * p_position.x()),qreal(Cell::SIZE * p_position.y()), m_maze);
238}
239
240void Game::createGhost(QPointF p_position, const QString & p_imageId){
241 m_ghosts.append(new Ghost(qreal(Cell::SIZE * p_position.x()),qreal(Cell::SIZE * p_position.y()), p_imageId, m_maze));
242}
243
244void Game::initMaze(const int p_nbRows, const int p_nbColumns){
245 m_maze->init(p_nbRows, p_nbColumns);
246}
247
248void Game::setSoundsEnabled(bool p_enabled)
249{
250 m_soundEnabled = p_enabled;
251 Settings::setSounds(p_enabled);
252 Settings::self()->writeConfig();
253}
254
255void Game::initCharactersPosition() {
256 // If the timer is stopped, it means that collisions are already being handled
257 if (m_timer->isActive()) {
258 // At the beginning, the timer is stopped but the Game isn't paused (to allow keyPressedEvent detection)
259 m_timer->stop();
260 m_state = RUNNING;
261 // Initialize Ghost coordinates and state
262 m_ghosts[0]->initCoordinate();
263 m_ghosts[1]->initCoordinate();
264 m_ghosts[2]->initCoordinate();
265 m_ghosts[3]->initCoordinate();
266 m_kapman->initCoordinate();
267 m_ghosts[0]->setState(Ghost::HUNTER);
268 m_ghosts[1]->setState(Ghost::HUNTER);
269 m_ghosts[2]->setState(Ghost::HUNTER);
270 m_ghosts[3]->setState(Ghost::HUNTER);
271 m_kapman->init();
272 // Initialize the Pills & Energizers coordinates
273 for (int i = 0; i < m_maze->getNbRows(); ++i) {
274 for (int j = 0; j < m_maze->getNbColumns(); ++j) {
275 if (m_maze->getCell(i,j).getElement() != NULL){
276 m_maze->getCell(i,j).getElement()->setX(Cell::SIZE * (j + 0.5));
277 m_maze->getCell(i,j).getElement()->setY(Cell::SIZE * (i + 0.5));
278 }
279 }
280 }
281 }
282}
283
284void Game::setTimersDuration() {
285 // Updates the timers duration ratio with the ghosts speed
286 s_durationRatio = Character::MEDIUM_SPEED / m_ghosts[0]->getNormalSpeed();
287
288 // Updates the timers duration
289 m_bonusTimer->setInterval( (int)(s_bonusDuration * s_durationRatio) );
290 m_preyTimer->setInterval( (int)(s_preyStateDuration * s_durationRatio) );
291}
292
293void Game::keyPressEvent(QKeyEvent* p_event) {
294 // At the beginning or when paused, we start the timer when an arrow key is pressed
295 if ((p_event->key() == Qt::Key_Up || p_event->key() == Qt::Key_Down || p_event->key() == Qt::Key_Left || p_event->key() == Qt::Key_Right) && !m_timer->isActive()) {
296 // If paused
297 if (m_state == PAUSED_UNLOCKED) {
298 switchPause();
299 } else if (m_state == RUNNING) { // At the game beginning
300 // Start the game
301 m_timer->start();
302 emit(gameStarted());
303 }
304 // Tells the KgDifficulty singleton that the game now runs
305 Kg::difficulty()->setGameRunning(true);
306 }
307 // Behaviour when the game has begun
308 switch (p_event->key()) {
309 case Qt::Key_Up:
310 if (m_state == RUNNING) {
311 m_kapman->goUp();
312 }
313 break;
314 case Qt::Key_Down:
315 if (m_state == RUNNING) {
316 m_kapman->goDown();
317 }
318 break;
319 case Qt::Key_Right:
320 if (m_state == RUNNING) {
321 m_kapman->goRight();
322 }
323 break;
324 case Qt::Key_Left:
325 if (m_state == RUNNING) {
326 m_kapman->goLeft();
327 }
328 break;
329 case Qt::Key_P:
330 case Qt::Key_Escape:
331 switchPause();
332 break;
333 case Qt::Key_K:
334 // Cheat code to get one more life
335 if (p_event->modifiers() == (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier)) {
336 m_lives++;
337 m_isCheater = true;
338 emit(livesChanged(m_lives));
339 }
340 break;
341 case Qt::Key_L:
342 // Cheat code to go to the next level
343 if (p_event->modifiers() == (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier)) {
344 m_isCheater = true;
345 nextLevel();
346 }
347 break;
348 default:
349 break;
350 }
351}
352
353void Game::update() {
354 int curKapmanRow, curKapmanCol;
355
356 // Check if the kapman is in the line of sight of a ghost
357 curKapmanRow = m_maze->getRowFromY(m_kapman->getY());
358 curKapmanCol = m_maze->getColFromX(m_kapman->getX());
359
360 for (int i = 0; i < m_ghosts.size(); ++i) {
361 if (m_ghosts[i]->getState() == Ghost::HUNTER && m_ghosts[i]->isInLineSight(m_kapman)) {
362 m_ghosts[i]->updateMove(curKapmanRow, curKapmanCol);
363 }
364 else {
365 m_ghosts[i]->updateMove();
366 }
367 }
368 m_kapman->updateMove();
369 m_kapman->emitGameUpdated();
370}
371
372void Game::kapmanDeath() {
373 if(m_soundEnabled)
374 m_soundGameOver.start();
375
376 m_lives--;
377 m_kapman->die();
378 // Make a 2 seconds pause while the kapman is blinking
379 pause(true);
380 QTimer::singleShot(2500, this, SLOT(resumeAfterKapmanDeath()));
381}
382
383void Game::resumeAfterKapmanDeath() {
384 emit(livesChanged(m_lives));
385 // Start the timer
386 start();
387 // Remove a possible bonus
388 emit(bonusOff());
389 // If their is no lives left, we start a new game
390 if (m_lives <= 0) {
391 emit(gameOver(true));
392 } else {
393 emit(levelStarted(false));
394 // Move all characters to their initial positions
395 initCharactersPosition();
396 }
397}
398
399void Game::ghostDeath(Ghost* p_ghost) {
400 m_nbEatenGhosts++;
401 p_ghost->setState(Ghost::EATEN);
402 winPoints(p_ghost);
403}
404
405void Game::winPoints(Element* p_element) {
406
407 // The value of won Points
408 long wonPoints;
409
410 // If the eaten element is a ghost, win 200 * number of eaten ghosts since the energizer was eaten
411 if (p_element->getType() == Element::GHOST) {
412 if(m_soundEnabled)
413 m_soundGhost.start();
414
415 // Get the position of the ghost
416 qreal xPos = p_element->getX();
417 qreal yPos = p_element->getY();
418 // Add points to the score
419 wonPoints = p_element->getPoints() * m_nbEatenGhosts;
420 // Send to the scene the number of points to display and its position
421 emit(pointsToDisplay(wonPoints, xPos, yPos));
422 }
423 // Else you just win the value of the element
424 else {
425 wonPoints = p_element->getPoints();
426 }
427
428 // Update of the points value
429 m_points += wonPoints;
430
431 // For each 10000 points we get a life more
432 if (m_points / 10000 > (m_points - wonPoints) / 10000) {
433 if(m_soundEnabled)
434 m_soundGainLife.start();
435
436 m_lives++;
437 emit(livesChanged(m_lives));
438 }
439 // If the eaten element is an energyzer we change the ghosts state
440 if (p_element->getType() == Element::ENERGYZER) {
441 // We start the prey timer
442 m_preyTimer->start();
443
444 if(m_soundEnabled)
445 m_soundEnergizer.start();
446
447 for (int i = 0; i < m_ghosts.size(); ++i) {
448 if(m_ghosts[i]->getState() != Ghost::EATEN) {
449 m_ghosts[i]->setState(Ghost::PREY);
450 }
451 }
452 // Reset the number of eaten ghosts
453 m_nbEatenGhosts = 0;
454 emit(elementEaten(p_element->getX(), p_element->getY()));
455 } else if (p_element->getType() == Element::PILL) {
456 if(m_soundEnabled)
457 m_soundPill.start();
458
459 emit(elementEaten(p_element->getX(), p_element->getY()));
460 } else if (p_element->getType() == Element::BONUS) {
461 if(m_soundEnabled)
462 m_soundBonus.start();
463
464 // Get the position of the Bonus
465 qreal xPos = p_element->getX();
466 qreal yPos = p_element->getY();
467
468 // Sends to the scene the number of points to display and its position
469 emit(pointsToDisplay(wonPoints, xPos, yPos));
470
471 emit(bonusOff());
472 }
473 // If 1/3 or 2/3 of the pills are eaten
474 if (m_maze->getNbElem() == m_maze->getTotalNbElem() / 3 || m_maze->getNbElem() == (m_maze->getTotalNbElem() * 2 / 3)) {
475 // Display the Bonus
476 emit(bonusOn());
477 m_bonusTimer->start();
478 }
479 emit(scoreChanged(m_points));
480}
481
482void Game::nextLevel() {
483 if(m_soundEnabled)
484 m_soundLevelUp.start();
485
486 // Increment the level
487 m_level++;
488 // Initialize the maze items
489 m_maze->resetNbElem();
490 // Update Bonus
491 m_bonus->setPoints(m_level * 100);
492 // Move all characters to their initial positions
493 initCharactersPosition();
494 // Increase the ghosts speed
495 for (int i = 0; i < m_ghosts.size(); ++i) {
496 // Increase the ghosts speed increase
497 m_ghosts[i]->increaseCharactersSpeed();
498 }
499 m_kapman->increaseCharactersSpeed();
500 // Update the timers duration with the new speed
501 setTimersDuration();
502 // Update the score, level and lives labels
503 emit(scoreChanged(m_points));
504 emit(livesChanged(m_lives));
505 emit(levelChanged(m_level));
506 // Update the view
507 emit(levelStarted(true));
508}
509
510void Game::hideBonus() {
511 emit(bonusOff());
512}
513
514void Game::endPreyState() {
515 for (int i = 0; i < m_ghosts.size(); ++i) {
516 if(m_ghosts[i]->getState() != Ghost::EATEN) {
517 m_ghosts[i]->setState(Ghost::HUNTER);
518 }
519 }
520}
521