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 | |
27 | const int Game::FPS = 40; |
28 | int Game::s_bonusDuration; |
29 | int Game::s_preyStateDuration; |
30 | qreal Game::s_durationRatio; |
31 | |
32 | Game::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 | |
124 | Game::~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 | |
135 | void Game::start() { |
136 | // Restart the Game timer |
137 | m_timer->start(); |
138 | m_state = RUNNING; |
139 | emit(pauseChanged(false, false)); |
140 | } |
141 | |
142 | void 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 | |
153 | void 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 | |
168 | Kapman* Game::getKapman() const { |
169 | return m_kapman; |
170 | } |
171 | |
172 | QList<Ghost*> Game::getGhosts () const { |
173 | return m_ghosts; |
174 | } |
175 | |
176 | QTimer* Game::getTimer() const { |
177 | return m_timer; |
178 | } |
179 | |
180 | Maze* Game::getMaze() const { |
181 | return m_maze; |
182 | } |
183 | |
184 | bool Game::isPaused() const { |
185 | return (m_state != RUNNING); |
186 | } |
187 | |
188 | bool Game::isCheater() const { |
189 | return m_isCheater; |
190 | } |
191 | |
192 | int Game::getScore() const { |
193 | return m_points; |
194 | } |
195 | int Game::getLives() const { |
196 | return m_lives; |
197 | } |
198 | |
199 | int Game::getLevel() const { |
200 | return m_level; |
201 | } |
202 | |
203 | void 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 | |
228 | Bonus* Game::getBonus() const { |
229 | return m_bonus; |
230 | } |
231 | |
232 | void 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 | |
236 | void 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 | |
240 | void 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 | |
244 | void Game::initMaze(const int p_nbRows, const int p_nbColumns){ |
245 | m_maze->init(p_nbRows, p_nbColumns); |
246 | } |
247 | |
248 | void Game::setSoundsEnabled(bool p_enabled) |
249 | { |
250 | m_soundEnabled = p_enabled; |
251 | Settings::setSounds(p_enabled); |
252 | Settings::self()->writeConfig(); |
253 | } |
254 | |
255 | void 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 | |
284 | void 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 | |
293 | void 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 | |
353 | void 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 | |
372 | void 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 | |
383 | void 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 | |
399 | void Game::ghostDeath(Ghost* p_ghost) { |
400 | m_nbEatenGhosts++; |
401 | p_ghost->setState(Ghost::EATEN); |
402 | winPoints(p_ghost); |
403 | } |
404 | |
405 | void 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 | |
482 | void 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 | |
510 | void Game::hideBonus() { |
511 | emit(bonusOff()); |
512 | } |
513 | |
514 | void 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 | |