File indexing completed on 2023-10-01 08:02:05
0001 /* 0002 SPDX-FileCopyrightText: 2007-2008 Thomas Gallinari <tg8187@yahoo.fr> 0003 SPDX-FileCopyrightText: 2007-2008 Pierre-Benoit Besse <besse@gmail.com> 0004 SPDX-FileCopyrightText: 2007-2008 Alexandre Galinier <alex.galinier@hotmail.com> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "game.h" 0010 #include "kapmanparser.h" 0011 #include "settings.h" 0012 0013 #include <KgDifficulty> 0014 #include <QStandardPaths> 0015 #include <QFile> 0016 0017 const int Game::FPS = 40; 0018 int Game::s_bonusDuration; 0019 int Game::s_preyStateDuration; 0020 qreal Game::s_durationRatio; 0021 0022 Game::Game() 0023 : m_isCheater(false) 0024 , m_lives(3) 0025 , m_points(0) 0026 , m_level(1) 0027 , m_nbEatenGhosts(0) 0028 , m_soundGameOver(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/kapman/gameover.ogg"))) 0029 , m_soundGhost(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/kapman/ghost.ogg"))) 0030 , m_soundGainLife(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/kapman/life.ogg"))) 0031 , m_soundEnergizer(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/kapman/energizer.ogg"))) 0032 , m_soundBonus(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/kapman/bonus.ogg"))) 0033 , m_soundPill(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/kapman/pill.ogg"))) 0034 , m_soundLevelUp(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/kapman/levelup.ogg"))) 0035 { 0036 // Initialize the sound state 0037 setSoundsEnabled(Settings::sounds()); 0038 0039 // Timers for medium difficulty 0040 s_bonusDuration = 7000; 0041 s_preyStateDuration = 10000; 0042 // Difference ratio between low/high and medium speed 0043 s_durationRatio = 1.0; 0044 0045 // Tells the KgDifficulty singleton that the game is not running 0046 Kg::difficulty()->setGameRunning(false); 0047 0048 // Create the Maze instance 0049 m_maze = new Maze(); 0050 connect(m_maze, &Maze::allElementsEaten, this, &Game::nextLevel); 0051 // Create the parser that will parse the XML file in order to initialize the Maze instance 0052 // This also creates all the characters 0053 KapmanParser kapmanParser(this); 0054 // Set the XML file as input source for the parser 0055 QFile mazeXmlFile(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("defaultmaze.xml"))); 0056 mazeXmlFile.open(QIODevice::ReadOnly); 0057 kapmanParser.parse(&mazeXmlFile); 0058 0059 connect(m_kapman, &Kapman::sWinPoints, this, &Game::winPoints); 0060 0061 // Initialize the characters speed timers duration considering the difficulty level 0062 switch (Kg::difficultyLevel()) { 0063 case KgDifficultyLevel::Easy: 0064 // Ratio low/medium speed 0065 s_durationRatio = Character::MEDIUM_SPEED / Character::LOW_SPEED; 0066 break; 0067 case KgDifficultyLevel::Medium: 0068 s_durationRatio = 1; 0069 break; 0070 case KgDifficultyLevel::Hard: 0071 // Ratio high/medium speed 0072 s_durationRatio = Character::MEDIUM_SPEED / Character::HIGH_SPEED; 0073 break; 0074 default: 0075 break; 0076 } 0077 0078 for (int i = 0; i < m_ghosts.size(); ++i) { 0079 connect(m_ghosts[i], &Ghost::lifeLost, this, &Game::kapmanDeath); 0080 connect(m_ghosts[i], &Ghost::ghostEaten, this, &Game::ghostDeath); 0081 // Initialize the ghosts speed and the ghost speed increase considering the characters speed 0082 m_ghosts[i]->initSpeedInc(); 0083 } 0084 m_kapman->initSpeedInc(); 0085 0086 // Initialize Bonus timer from the difficulty level 0087 m_bonusTimer = new QTimer(this); 0088 m_bonusTimer->setInterval((int)(s_bonusDuration * s_durationRatio)); 0089 m_bonusTimer->setSingleShot(true); 0090 connect(m_bonusTimer, &QTimer::timeout, this, &Game::hideBonus); 0091 // Initialize the Preys timer from the difficulty level 0092 m_preyTimer = new QTimer(this); 0093 m_preyTimer->setInterval((int)(s_preyStateDuration * s_durationRatio)); 0094 m_preyTimer->setSingleShot(true); 0095 connect(m_preyTimer, &QTimer::timeout, this, &Game::endPreyState); 0096 0097 // Start the Game timer 0098 m_timer = new QTimer(this); 0099 m_timer->setInterval(int(1000 / Game::FPS)); 0100 connect(m_timer, &QTimer::timeout, this, &Game::update); 0101 m_timer->start(); 0102 m_state = RUNNING; 0103 // Init the characters coordinates on the Maze 0104 initCharactersPosition(); 0105 } 0106 0107 Game::~Game() 0108 { 0109 delete m_timer; 0110 delete m_bonusTimer; 0111 delete m_maze; 0112 delete m_kapman; 0113 for (int i = 0; i < m_ghosts.size(); ++i) { 0114 delete m_ghosts[i]; 0115 } 0116 delete m_bonus; 0117 } 0118 0119 void Game::start() 0120 { 0121 // Restart the Game timer 0122 m_timer->start(); 0123 m_bonusTimer->start(); 0124 m_preyTimer->start(); 0125 m_state = RUNNING; 0126 Q_EMIT pauseChanged(false, false); 0127 } 0128 0129 void Game::pause(bool p_locked) 0130 { 0131 // Stop the Game timer 0132 m_timer->stop(); 0133 0134 if (m_bonusTimer->isActive()) { 0135 const auto remainingTime{m_bonusTimer->remainingTime()}; 0136 m_bonusTimer->stop(); 0137 m_bonusTimer->setInterval(remainingTime); 0138 } 0139 0140 if (m_preyTimer->isActive()) { 0141 const auto remainingTime{m_preyTimer->remainingTime()}; 0142 m_preyTimer->stop(); 0143 m_preyTimer->setInterval(remainingTime); 0144 } 0145 0146 if (p_locked) { 0147 m_state = PAUSED_LOCKED; 0148 } else { 0149 m_state = PAUSED_UNLOCKED; 0150 } 0151 Q_EMIT pauseChanged(true, false); 0152 } 0153 0154 void Game::switchPause(bool p_locked) 0155 { 0156 // If the Game is not already paused 0157 if (m_state == RUNNING) { 0158 // Pause the Game 0159 pause(p_locked); 0160 Q_EMIT pauseChanged(true, true); 0161 } 0162 // If the Game is already paused 0163 else { 0164 // Resume the Game 0165 start(); 0166 Q_EMIT pauseChanged(false, true); 0167 } 0168 } 0169 0170 Kapman *Game::getKapman() const 0171 { 0172 return m_kapman; 0173 } 0174 0175 QList<Ghost *> Game::getGhosts() const 0176 { 0177 return m_ghosts; 0178 } 0179 0180 QTimer *Game::getTimer() const 0181 { 0182 return m_timer; 0183 } 0184 0185 Maze *Game::getMaze() const 0186 { 0187 return m_maze; 0188 } 0189 0190 bool Game::isPaused() const 0191 { 0192 return (m_state != RUNNING); 0193 } 0194 0195 bool Game::isCheater() const 0196 { 0197 return m_isCheater; 0198 } 0199 0200 int Game::getScore() const 0201 { 0202 return m_points; 0203 } 0204 int Game::getLives() const 0205 { 0206 return m_lives; 0207 } 0208 0209 int Game::getLevel() const 0210 { 0211 return m_level; 0212 } 0213 0214 void Game::setLevel(int p_level) 0215 { 0216 m_isCheater = true; 0217 m_level = p_level; 0218 m_maze->resetNbElem(); 0219 m_timer->start(); // Needed to reinit character positions 0220 initCharactersPosition(); 0221 for (int i = 0; i < m_ghosts.size(); ++i) { 0222 m_ghosts[i]->initSpeed(); 0223 } 0224 m_kapman->initSpeed(); 0225 for (int i = 0; i < m_level; ++i) { 0226 for (int j = 0; j < m_ghosts.size(); ++j) { 0227 m_ghosts[j]->increaseCharactersSpeed(); 0228 } 0229 m_kapman->increaseCharactersSpeed(); 0230 } 0231 setTimersDuration(); 0232 m_bonus->setPoints(m_level * 100); 0233 Q_EMIT scoreChanged(m_points); 0234 Q_EMIT livesChanged(m_lives); 0235 Q_EMIT levelChanged(m_level); 0236 Q_EMIT pauseChanged(false, true); 0237 Q_EMIT levelStarted(true); 0238 } 0239 0240 Bonus *Game::getBonus() const 0241 { 0242 return m_bonus; 0243 } 0244 0245 void Game::createBonus(QPointF p_position) 0246 { 0247 m_bonus = new Bonus(qreal(Cell::SIZE * p_position.x()), qreal(Cell::SIZE * p_position.y()), m_maze, 100); 0248 } 0249 0250 void Game::createKapman(QPointF p_position) 0251 { 0252 m_kapman = new Kapman(qreal(Cell::SIZE * p_position.x()), qreal(Cell::SIZE * p_position.y()), m_maze); 0253 } 0254 0255 void Game::createGhost(QPointF p_position, const QString &p_imageId) 0256 { 0257 m_ghosts.append(new Ghost(qreal(Cell::SIZE * p_position.x()), qreal(Cell::SIZE * p_position.y()), p_imageId, m_maze)); 0258 } 0259 0260 void Game::initMaze(const int p_nbRows, const int p_nbColumns) 0261 { 0262 m_maze->init(p_nbRows, p_nbColumns); 0263 } 0264 0265 void Game::setSoundsEnabled(bool p_enabled) 0266 { 0267 m_soundEnabled = p_enabled; 0268 Settings::setSounds(p_enabled); 0269 Settings::self()->save(); 0270 } 0271 0272 void Game::initCharactersPosition() 0273 { 0274 // If the timer is stopped, it means that collisions are already being handled 0275 if (m_timer->isActive()) { 0276 // At the beginning, the timer is stopped but the Game isn't paused (to allow keyPressedEvent detection) 0277 m_timer->stop(); 0278 m_state = RUNNING; 0279 // Initialize Ghost coordinates and state 0280 m_ghosts[0]->initCoordinate(); 0281 m_ghosts[1]->initCoordinate(); 0282 m_ghosts[2]->initCoordinate(); 0283 m_ghosts[3]->initCoordinate(); 0284 m_kapman->initCoordinate(); 0285 m_ghosts[0]->setState(Ghost::HUNTER); 0286 m_ghosts[1]->setState(Ghost::HUNTER); 0287 m_ghosts[2]->setState(Ghost::HUNTER); 0288 m_ghosts[3]->setState(Ghost::HUNTER); 0289 m_kapman->init(); 0290 // Initialize the Pills & Energizers coordinates 0291 for (int i = 0; i < m_maze->getNbRows(); ++i) { 0292 for (int j = 0; j < m_maze->getNbColumns(); ++j) { 0293 if (m_maze->getCell(i, j).getElement() != nullptr) { 0294 m_maze->getCell(i, j).getElement()->setX(Cell::SIZE * (j + 0.5)); 0295 m_maze->getCell(i, j).getElement()->setY(Cell::SIZE * (i + 0.5)); 0296 } 0297 } 0298 } 0299 } 0300 } 0301 0302 void Game::setTimersDuration() 0303 { 0304 // Updates the timers duration ratio with the ghosts speed 0305 s_durationRatio = Character::MEDIUM_SPEED / m_ghosts[0]->getNormalSpeed(); 0306 0307 setPreyTimerDuration(); 0308 setBonusTimerDuration(); 0309 } 0310 0311 void Game::setBonusTimerDuration() 0312 { 0313 m_bonusTimer->setInterval((int)(s_bonusDuration * s_durationRatio)); 0314 } 0315 0316 void Game::setPreyTimerDuration() 0317 { 0318 m_preyTimer->setInterval((int)(s_preyStateDuration * s_durationRatio)); 0319 } 0320 0321 void Game::keyPressEvent(QKeyEvent *p_event) 0322 { 0323 // At the beginning or when paused, we start the timer when an arrow key is pressed 0324 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) 0325 && !m_timer->isActive()) { 0326 // If paused 0327 if (m_state == PAUSED_UNLOCKED) { 0328 switchPause(); 0329 } else if (m_state == RUNNING) { // At the game beginning 0330 // Start the game 0331 m_timer->start(); 0332 Q_EMIT gameStarted(); 0333 } 0334 // Tells the KgDifficulty singleton that the game now runs 0335 Kg::difficulty()->setGameRunning(true); 0336 } 0337 // Behaviour when the game has begun 0338 switch (p_event->key()) { 0339 case Qt::Key_Up: 0340 if (m_state == RUNNING) { 0341 m_kapman->goUp(); 0342 } 0343 break; 0344 case Qt::Key_Down: 0345 if (m_state == RUNNING) { 0346 m_kapman->goDown(); 0347 } 0348 break; 0349 case Qt::Key_Right: 0350 if (m_state == RUNNING) { 0351 m_kapman->goRight(); 0352 } 0353 break; 0354 case Qt::Key_Left: 0355 if (m_state == RUNNING) { 0356 m_kapman->goLeft(); 0357 } 0358 break; 0359 case Qt::Key_P: 0360 case Qt::Key_Escape: 0361 switchPause(); 0362 break; 0363 case Qt::Key_K: 0364 // Cheat code to get one more life 0365 if (p_event->modifiers() == (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier)) { 0366 m_lives++; 0367 m_isCheater = true; 0368 Q_EMIT livesChanged(m_lives); 0369 } 0370 break; 0371 case Qt::Key_L: 0372 // Cheat code to go to the next level 0373 if (p_event->modifiers() == (Qt::AltModifier | Qt::ControlModifier | Qt::ShiftModifier)) { 0374 m_isCheater = true; 0375 nextLevel(); 0376 } 0377 break; 0378 default: 0379 break; 0380 } 0381 } 0382 0383 void Game::update() 0384 { 0385 int curKapmanRow, curKapmanCol; 0386 0387 // Check if the kapman is in the line of sight of a ghost 0388 curKapmanRow = m_maze->getRowFromY(m_kapman->getY()); 0389 curKapmanCol = m_maze->getColFromX(m_kapman->getX()); 0390 0391 for (int i = 0; i < m_ghosts.size(); ++i) { 0392 if (m_ghosts[i]->getState() == Ghost::HUNTER && m_ghosts[i]->isInLineSight(m_kapman)) { 0393 m_ghosts[i]->updateMove(curKapmanRow, curKapmanCol); 0394 } else { 0395 m_ghosts[i]->updateMove(); 0396 } 0397 0398 const int startBlinkingDelay = s_preyStateDuration * s_durationRatio - 5 * (500 * s_durationRatio); 0399 if (m_preyTimer->remainingTime() < (m_preyTimer->interval() - startBlinkingDelay)) { 0400 m_ghosts[i]->setPreyStateAlmostOverEnabled(true); 0401 } else { 0402 m_ghosts[i]->setPreyStateAlmostOverEnabled(false); 0403 } 0404 } 0405 m_kapman->updateMove(); 0406 m_kapman->emitGameUpdated(); 0407 } 0408 0409 void Game::kapmanDeath() 0410 { 0411 if (m_soundEnabled) { 0412 m_soundGameOver.start(); 0413 } 0414 0415 m_lives--; 0416 m_kapman->die(); 0417 // Make a 2 seconds pause while the kapman is blinking 0418 pause(true); 0419 QTimer::singleShot(2500, this, &Game::resumeAfterKapmanDeath); 0420 } 0421 0422 void Game::resumeAfterKapmanDeath() 0423 { 0424 Q_EMIT livesChanged(m_lives); 0425 // Start the timer 0426 start(); 0427 // Remove a possible bonus 0428 Q_EMIT bonusOff(); 0429 // If their is no lives left, we start a new game 0430 if (m_lives <= 0) { 0431 Q_EMIT gameOver(); 0432 } else { 0433 Q_EMIT levelStarted(false); 0434 // Move all characters to their initial positions 0435 initCharactersPosition(); 0436 } 0437 } 0438 0439 void Game::ghostDeath(Ghost *p_ghost) 0440 { 0441 m_nbEatenGhosts++; 0442 p_ghost->setState(Ghost::EATEN); 0443 winPoints(p_ghost); 0444 } 0445 0446 void Game::winPoints(Element *p_element) 0447 { 0448 // The value of won Points 0449 long wonPoints; 0450 0451 // If the eaten element is a ghost, win 200 * number of eaten ghosts since the energizer was eaten 0452 if (p_element->getType() == Element::GHOST) { 0453 if (m_soundEnabled) { 0454 m_soundGhost.start(); 0455 } 0456 0457 // Get the position of the ghost 0458 qreal xPos = p_element->getX(); 0459 qreal yPos = p_element->getY(); 0460 // Add points to the score 0461 wonPoints = p_element->getPoints() * m_nbEatenGhosts; 0462 // Send to the scene the number of points to display and its position 0463 Q_EMIT pointsToDisplay(wonPoints, xPos, yPos); 0464 } 0465 // Else you just win the value of the element 0466 else { 0467 wonPoints = p_element->getPoints(); 0468 } 0469 0470 // Update of the points value 0471 m_points += wonPoints; 0472 0473 // For each 10000 points we get a life more 0474 if (m_points / 10000 > (m_points - wonPoints) / 10000) { 0475 if (m_soundEnabled) { 0476 m_soundGainLife.start(); 0477 } 0478 0479 m_lives++; 0480 Q_EMIT livesChanged(m_lives); 0481 } 0482 // If the eaten element is an energyzer we change the ghosts state 0483 if (p_element->getType() == Element::ENERGYZER) { 0484 // We start the prey timer 0485 m_preyTimer->start(); 0486 0487 if (m_soundEnabled) { 0488 m_soundEnergizer.start(); 0489 } 0490 0491 for (int i = 0; i < m_ghosts.size(); ++i) { 0492 if (m_ghosts[i]->getState() != Ghost::EATEN) { 0493 m_ghosts[i]->setState(Ghost::PREY); 0494 } 0495 } 0496 // Reset the number of eaten ghosts 0497 m_nbEatenGhosts = 0; 0498 Q_EMIT elementEaten(p_element->getX(), p_element->getY()); 0499 } else if (p_element->getType() == Element::PILL) { 0500 if (m_soundEnabled) { 0501 m_soundPill.start(); 0502 } 0503 0504 Q_EMIT elementEaten(p_element->getX(), p_element->getY()); 0505 } else if (p_element->getType() == Element::BONUS) { 0506 if (m_soundEnabled) { 0507 m_soundBonus.start(); 0508 } 0509 0510 // Get the position of the Bonus 0511 qreal xPos = p_element->getX(); 0512 qreal yPos = p_element->getY(); 0513 0514 // Sends to the scene the number of points to display and its position 0515 Q_EMIT pointsToDisplay(wonPoints, xPos, yPos); 0516 0517 Q_EMIT bonusOff(); 0518 } 0519 // If 1/3 or 2/3 of the pills are eaten 0520 if (m_maze->getNbElem() == m_maze->getTotalNbElem() / 3 || m_maze->getNbElem() == (m_maze->getTotalNbElem() * 2 / 3)) { 0521 // Display the Bonus 0522 Q_EMIT bonusOn(); 0523 m_bonusTimer->start(); 0524 } 0525 Q_EMIT scoreChanged(m_points); 0526 } 0527 0528 void Game::nextLevel() 0529 { 0530 if (m_soundEnabled) { 0531 m_soundLevelUp.start(); 0532 } 0533 0534 // Increment the level 0535 m_level++; 0536 // Initialize the maze items 0537 m_maze->resetNbElem(); 0538 // Update Bonus 0539 m_bonus->setPoints(m_level * 100); 0540 // Move all characters to their initial positions 0541 initCharactersPosition(); 0542 // Increase the ghosts speed 0543 for (int i = 0; i < m_ghosts.size(); ++i) { 0544 // Increase the ghosts speed increase 0545 m_ghosts[i]->increaseCharactersSpeed(); 0546 } 0547 m_kapman->increaseCharactersSpeed(); 0548 // Update the timers duration with the new speed 0549 setTimersDuration(); 0550 // Update the score, level and lives labels 0551 Q_EMIT scoreChanged(m_points); 0552 Q_EMIT livesChanged(m_lives); 0553 Q_EMIT levelChanged(m_level); 0554 // Update the view 0555 Q_EMIT levelStarted(true); 0556 } 0557 0558 void Game::hideBonus() 0559 { 0560 Q_EMIT bonusOff(); 0561 0562 setBonusTimerDuration(); 0563 } 0564 0565 void Game::endPreyState() 0566 { 0567 for (int i = 0; i < m_ghosts.size(); ++i) { 0568 if (m_ghosts[i]->getState() != Ghost::EATEN) { 0569 m_ghosts[i]->setState(Ghost::HUNTER); 0570 } 0571 } 0572 0573 setPreyTimerDuration(); 0574 } 0575 0576 #include "moc_game.cpp"