File indexing completed on 2024-04-21 04:02:27

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"