File indexing completed on 2025-02-09 04:34:06
0001 /* 0002 This file is part of Killbots. 0003 0004 SPDX-FileCopyrightText: 2007-2009 Parker Coates <coates@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "coordinator.h" 0010 0011 #include "engine.h" 0012 #include "numericdisplayitem.h" 0013 #include "ruleset.h" 0014 #include "scene.h" 0015 #include "settings.h" 0016 0017 #include <KGamePopupItem> 0018 0019 #include <KLocalizedString> 0020 0021 #include <QGraphicsView> 0022 0023 #include <cmath> 0024 0025 struct Killbots::Coordinator::AnimationStage { 0026 AnimationStage() 0027 : newRound(-1), 0028 newScore(-1), 0029 newEnemyCount(-1), 0030 newEnergy(-1) 0031 {} 0032 0033 bool isEmpty() const 0034 { 0035 return spritesToCreate.isEmpty() 0036 && spritesToSlide.isEmpty() 0037 && spritesToTeleport.isEmpty() 0038 && spritesToDestroy.isEmpty() 0039 && message.isEmpty() 0040 && newRound == -1 0041 && newScore == -1 0042 && newEnemyCount == -1 0043 && newEnergy == -1; 0044 } 0045 0046 QList<Sprite *> spritesToCreate; 0047 QList<Sprite *> spritesToSlide; 0048 QList<Sprite *> spritesToTeleport; 0049 QList<Sprite *> spritesToDestroy; 0050 QString message; 0051 int oldRound, newRound; 0052 int oldScore, newScore; 0053 int oldEnemyCount, newEnemyCount; 0054 int oldEnergy, newEnergy; 0055 }; 0056 0057 Killbots::Coordinator::Coordinator(QObject *parent) 0058 : QObject(parent), 0059 m_engine(nullptr), 0060 m_scene(nullptr), 0061 m_roundDisplay(nullptr), 0062 m_scoreDisplay(nullptr), 0063 m_enemyCountDisplay(nullptr), 0064 m_energyDisplay(nullptr), 0065 m_unqueuedPopup(nullptr), 0066 m_queuedPopup(nullptr), 0067 m_timeLine(1000, this), 0068 m_busyAnimating(false), 0069 m_newGameRequested(false), 0070 m_queuedAction(NoAction) 0071 { 0072 m_timeLine.setEasingCurve(QEasingCurve::InOutSine); 0073 connect(&m_timeLine, &QTimeLine::valueChanged, this, &Coordinator::animate); 0074 connect(&m_timeLine, &QTimeLine::finished, this, &Coordinator::nextAnimationStage); 0075 } 0076 0077 Killbots::Coordinator::~Coordinator() 0078 { 0079 } 0080 0081 void Killbots::Coordinator::setEngine(Engine *engine) 0082 { 0083 m_engine = engine; 0084 0085 connect(m_engine, &Engine::roundChanged, this, &Coordinator::updateRound); 0086 connect(m_engine, &Engine::scoreChanged, this, &Coordinator::updateScore); 0087 connect(m_engine, &Engine::enemyCountChanged, this, &Coordinator::updateEnemyCount); 0088 connect(m_engine, &Engine::energyChanged, this, &Coordinator::updateEnergy); 0089 0090 connect(m_engine, &Engine::showNewGameMessage, this, &Coordinator::showNewGameMessage); 0091 connect(m_engine, &Engine::showRoundCompleteMessage, this, &Coordinator::showRoundCompleteMessage); 0092 connect(m_engine, &Engine::showBoardFullMessage, this, &Coordinator::showBoardFullMessage); 0093 connect(m_engine, &Engine::showGameOverMessage, this, &Coordinator::showGameOverMessage); 0094 } 0095 0096 void Killbots::Coordinator::setScene(Scene *scene) 0097 { 0098 m_scene = scene; 0099 0100 m_roundDisplay = new NumericDisplayItem; 0101 m_roundDisplay->setLabel(i18nc("@label", "Round:")); 0102 m_roundDisplay->setDigits(2); 0103 m_scene->addNumericDisplay(m_roundDisplay); 0104 0105 m_scoreDisplay = new NumericDisplayItem; 0106 m_scoreDisplay->setLabel(i18nc("@label", "Score:")); 0107 m_scoreDisplay->setDigits(5); 0108 m_scene->addNumericDisplay(m_scoreDisplay); 0109 0110 m_enemyCountDisplay = new NumericDisplayItem; 0111 m_enemyCountDisplay->setLabel(i18nc("@label", "Enemies:")); 0112 m_enemyCountDisplay->setDigits(3); 0113 m_scene->addNumericDisplay(m_enemyCountDisplay); 0114 0115 m_energyDisplay = new NumericDisplayItem; 0116 m_energyDisplay->setLabel(i18nc("@label", "Energy:")); 0117 m_energyDisplay->setDigits(2); 0118 m_scene->addNumericDisplay(m_energyDisplay); 0119 0120 m_unqueuedPopup = new KGamePopupItem; 0121 m_unqueuedPopup->setMessageOpacity(0.85); 0122 m_unqueuedPopup->setHideOnMouseClick(true); 0123 m_scene->addItem(m_unqueuedPopup); 0124 0125 m_queuedPopup = new KGamePopupItem; 0126 m_queuedPopup->setMessageOpacity(0.85); 0127 m_queuedPopup->setHideOnMouseClick(true); 0128 m_scene->addItem(m_queuedPopup); 0129 0130 connect(m_queuedPopup, &KGamePopupItem::hidden, this, &Coordinator::nextAnimationStage); 0131 } 0132 0133 void Killbots::Coordinator::setAnimationSpeed(int speed) 0134 { 0135 // Equation converts speed in the range 0 to 10 to a duration in the range 0136 // 1 to 0.05 seconds. There's probably a better way to do this. 0137 m_timeLine.setDuration(int(pow(1.35, -speed) * 1000)); 0138 } 0139 0140 void Killbots::Coordinator::requestNewGame() 0141 { 0142 if (!m_busyAnimating || m_engine->isHeroDead()) { 0143 startNewGame(); 0144 } else { 0145 m_newGameRequested = true; 0146 } 0147 } 0148 0149 void Killbots::Coordinator::startNewGame() 0150 { 0151 m_newGameRequested = false; 0152 m_repeatedAction = NoAction; 0153 m_queuedAction = NoAction; 0154 0155 const Ruleset *ruleset = m_engine->ruleset(); 0156 if (!ruleset || ruleset->fileName() != Settings::ruleset()) { 0157 ruleset = Ruleset::load(Settings::ruleset()); 0158 if (!ruleset) { 0159 Settings::setRuleset(Settings::defaultRulesetValue()); 0160 ruleset = Ruleset::load(Settings::ruleset()); 0161 } 0162 m_engine->setRuleset(ruleset); 0163 } 0164 0165 m_energyDisplay->setVisible(ruleset->energyEnabled()); 0166 m_scene->setGridSize(ruleset->rows(), ruleset->columns()); 0167 m_scene->doLayout(); 0168 0169 m_engine->startNewGame(); 0170 0171 startAnimation(); 0172 } 0173 0174 void Killbots::Coordinator::requestAction(int action) 0175 { 0176 // If we're doing a repeated move, ignore the request and just stop the current movement. 0177 if (m_repeatedAction != NoAction && m_repeatedAction != WaitOutRound) { 0178 m_repeatedAction = NoAction; 0179 } else if (!m_engine->isHeroDead()) { 0180 if (!m_busyAnimating) { 0181 doAction(HeroAction(action)); 0182 } else { 0183 m_queuedAction = HeroAction(action); 0184 } 0185 } 0186 } 0187 0188 void Killbots::Coordinator::doAction(HeroAction action) 0189 { 0190 bool actionSuccessful = false; 0191 bool boardFull = false; 0192 0193 if (action <= Hold) { 0194 actionSuccessful = m_engine->moveHero(action); 0195 m_repeatedAction = action < 0 && actionSuccessful 0196 ? action 0197 : NoAction; 0198 } else if ((action == TeleportSafely || action == TeleportSafelyIfPossible) 0199 && m_engine->canSafeTeleport() 0200 ) { 0201 actionSuccessful = m_engine->teleportHeroSafely(); 0202 boardFull = !actionSuccessful; 0203 } else if (action == Teleport || action == TeleportSafelyIfPossible) { 0204 actionSuccessful = m_engine->teleportHero(); 0205 } else if (action == Vaporizer && m_engine->canUseVaporizer()) { 0206 actionSuccessful = m_engine->useVaporizer(); 0207 } else if (action == WaitOutRound) { 0208 actionSuccessful = m_engine->waitOutRound(); 0209 m_repeatedAction = WaitOutRound; 0210 } 0211 0212 if (actionSuccessful) { 0213 if (action != Vaporizer) { 0214 m_engine->moveRobots(); 0215 } 0216 m_engine->assessDamage(); 0217 if (!m_engine->isRoundComplete() && action != Vaporizer) { 0218 m_engine->moveRobots(true); 0219 m_engine->assessDamage(); 0220 } 0221 startAnimation(); 0222 } else if (boardFull) { 0223 m_engine->resetBotCounts(); 0224 startAnimation(); 0225 } 0226 } 0227 0228 void Killbots::Coordinator::animationDone() 0229 { 0230 m_busyAnimating = false; 0231 0232 if (m_newGameRequested) { 0233 startNewGame(); 0234 } else if (m_engine->isHeroDead()) { 0235 m_scene->forgetHero(); 0236 m_engine->endGame(); 0237 } else if (m_engine->isRoundComplete()) { 0238 m_repeatedAction = NoAction; 0239 m_queuedAction = NoAction; 0240 m_engine->startNewRound(); 0241 if (m_engine->isBoardFull()) { 0242 m_engine->resetBotCounts(); 0243 } 0244 startAnimation(); 0245 } else if (m_repeatedAction != NoAction) { 0246 doAction(m_repeatedAction); 0247 } else if (m_queuedAction != NoAction) { 0248 doAction(m_queuedAction); 0249 m_queuedAction = NoAction; 0250 } 0251 } 0252 0253 void Killbots::Coordinator::startAnimation() 0254 { 0255 m_busyAnimating = true; 0256 startAnimationStage(); 0257 } 0258 0259 void Killbots::Coordinator::startAnimationStage() 0260 { 0261 const QString &message = m_stages.first().message; 0262 0263 if (m_timeLine.duration() < 60 && message.isEmpty()) { 0264 animate(1.0); 0265 nextAnimationStage(); 0266 } else { 0267 if (!message.isEmpty()) { 0268 showQueuedMessage(message); 0269 } 0270 0271 m_timeLine.start(); 0272 } 0273 } 0274 0275 void Killbots::Coordinator::animate(qreal value) 0276 { 0277 AnimationStage &stage = m_stages.first(); 0278 0279 if (stage.newRound != -1) { 0280 m_roundDisplay->setValue(int(stage.oldRound + value * (stage.newRound - stage.oldRound))); 0281 } 0282 0283 if (stage.newScore != -1) { 0284 m_scoreDisplay->setValue(int(stage.oldScore + value * (stage.newScore - stage.oldScore))); 0285 } 0286 0287 if (stage.newEnemyCount != -1) { 0288 m_enemyCountDisplay->setValue(int(stage.oldEnemyCount + value * (stage.newEnemyCount - stage.oldEnemyCount))); 0289 } 0290 0291 if (stage.newEnergy != -1) { 0292 m_energyDisplay->setValue(int(stage.oldEnergy + value * (stage.newEnergy - stage.oldEnergy))); 0293 } 0294 0295 m_scene->animateSprites(stage.spritesToCreate, 0296 stage.spritesToSlide, 0297 stage.spritesToTeleport, 0298 stage.spritesToDestroy, 0299 value 0300 ); 0301 } 0302 0303 void Killbots::Coordinator::nextAnimationStage() 0304 { 0305 // Wait for both the timeline and the popup to finish before moving to the next stage. 0306 if (m_timeLine.state() != QTimeLine::Running && !m_queuedPopup->isVisible()) { 0307 m_stages.removeFirst(); 0308 0309 if (!m_stages.isEmpty()) { 0310 startAnimationStage(); 0311 } else { 0312 animationDone(); 0313 } 0314 } 0315 } 0316 0317 void Killbots::Coordinator::beginNewAnimationStage() 0318 { 0319 if (m_stages.isEmpty()) { 0320 AnimationStage newStage; 0321 newStage.oldRound = m_roundDisplay->value(); 0322 newStage.oldScore = m_scoreDisplay->value(); 0323 newStage.oldEnemyCount = m_enemyCountDisplay->value(); 0324 newStage.oldEnergy = m_energyDisplay->value(); 0325 m_stages << newStage; 0326 } else if (!m_stages.last().isEmpty()) { 0327 AnimationStage newStage; 0328 const AnimationStage &lastStage = m_stages.last(); 0329 newStage.oldRound = lastStage.newRound == -1 ? lastStage.oldRound : lastStage.newRound; 0330 newStage.oldScore = lastStage.newScore == -1 ? lastStage.oldScore : lastStage.newScore; 0331 newStage.oldEnemyCount = lastStage.newEnemyCount == -1 ? lastStage.oldEnemyCount : lastStage.newEnemyCount; 0332 newStage.oldEnergy = lastStage.newEnergy == -1 ? lastStage.oldEnergy : lastStage.newEnergy; 0333 m_stages << newStage; 0334 } 0335 } 0336 0337 Killbots::Sprite *Killbots::Coordinator::createSprite(SpriteType type, QPoint position) 0338 { 0339 Sprite *sprite = m_scene->createSprite(type, position); 0340 m_stages.last().spritesToCreate << sprite; 0341 return sprite; 0342 } 0343 0344 void Killbots::Coordinator::slideSprite(Sprite *sprite, QPoint position) 0345 { 0346 sprite->enqueueGridPos(position); 0347 m_stages.last().spritesToSlide << sprite; 0348 } 0349 0350 void Killbots::Coordinator::teleportSprite(Sprite *sprite, QPoint position) 0351 { 0352 sprite->enqueueGridPos(position); 0353 m_stages.last().spritesToTeleport << sprite; 0354 } 0355 0356 void Killbots::Coordinator::destroySprite(Sprite *sprite) 0357 { 0358 if (sprite->spriteType() == Hero) { 0359 m_scene->forgetHero(); 0360 } 0361 m_stages.last().spritesToDestroy << sprite; 0362 } 0363 0364 void Killbots::Coordinator::updateRound(int round) 0365 { 0366 m_stages.last().newRound = round; 0367 } 0368 0369 void Killbots::Coordinator::updateScore(int score) 0370 { 0371 m_stages.last().newScore = score; 0372 } 0373 0374 void Killbots::Coordinator::updateEnemyCount(int enemyCount) 0375 { 0376 m_stages.last().newEnemyCount = enemyCount; 0377 } 0378 0379 void Killbots::Coordinator::updateEnergy(int energy) 0380 { 0381 m_stages.last().newEnergy = energy; 0382 } 0383 0384 void Killbots::Coordinator::showQueuedMessage(const QString &message) 0385 { 0386 if (m_unqueuedPopup->isVisible()) { 0387 m_unqueuedPopup->hide(); 0388 } 0389 KGamePopupItem::Position corner = m_scene->views().first()->layoutDirection() == Qt::LeftToRight ? KGamePopupItem::TopRight : KGamePopupItem::TopLeft; 0390 m_queuedPopup->setMessageTimeout(3000); 0391 m_queuedPopup->showMessage(message, corner, KGamePopupItem::ReplacePrevious); 0392 } 0393 0394 void Killbots::Coordinator::showUnqueuedMessage(const QString &message, int timeout) 0395 { 0396 if (!m_queuedPopup->isVisible()) { 0397 KGamePopupItem::Position corner = m_scene->views().first()->layoutDirection() == Qt::LeftToRight ? KGamePopupItem::TopRight : KGamePopupItem::TopLeft; 0398 m_unqueuedPopup->setMessageTimeout(timeout); 0399 m_unqueuedPopup->showMessage(message, corner, KGamePopupItem::ReplacePrevious); 0400 } 0401 } 0402 0403 void Killbots::Coordinator::showRoundCompleteMessage() 0404 { 0405 m_stages.last().message = i18nc("@info", "Round complete."); 0406 } 0407 0408 void Killbots::Coordinator::showBoardFullMessage() 0409 { 0410 m_stages.last().message = i18nc("@info", "Board is full.\nResetting enemy counts."); 0411 } 0412 0413 void Killbots::Coordinator::showNewGameMessage() 0414 { 0415 showUnqueuedMessage(i18nc("@info", "New game.")); 0416 } 0417 0418 void Killbots::Coordinator::showGameOverMessage() 0419 { 0420 showUnqueuedMessage(i18nc("@info", "Game over."), 15000); 0421 } 0422 0423 #include "moc_coordinator.cpp"