Warning, file /games/killbots/src/engine.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 This file is part of Killbots. 0003 0004 SPDX-FileCopyrightText: 2006-2009 Parker Coates <coates@kde.org> 0005 0006 SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "engine.h" 0010 0011 #include "coordinator.h" 0012 #include "settings.h" 0013 #include "sprite.h" 0014 0015 #include "killbots_debug.h" 0016 0017 #include <QRandomGenerator> 0018 0019 #include <array> 0020 0021 uint qHash(const QPoint &point) 0022 { 0023 return qHash(point.x() * 1000 + point.y()); 0024 } 0025 0026 inline int sign(int num) 0027 { 0028 return (num > 0) ? 1 : (num == 0) ? 0 : -1; 0029 } 0030 0031 Killbots::Engine::Engine(Killbots::Coordinator *scene, QObject *parent) 0032 : QObject(parent), 0033 m_coordinator(scene), 0034 m_hero(nullptr), 0035 m_rules(nullptr), 0036 m_round(0), 0037 m_score(0), 0038 m_energy(0), 0039 m_maxEnergy(0.0), 0040 m_robotCount(0.0), 0041 m_fastbotCount(0.0), 0042 m_junkheapCount(0.0), 0043 m_heroIsDead(false), 0044 m_waitingOutRound(false), 0045 m_spriteMap() 0046 { 0047 } 0048 0049 Killbots::Engine::~Engine() 0050 { 0051 delete m_rules; 0052 } 0053 0054 void Killbots::Engine::setRuleset(const Ruleset *ruleset) 0055 { 0056 if (ruleset && ruleset != m_rules) { 0057 delete m_rules; 0058 m_rules = ruleset; 0059 } 0060 } 0061 0062 const Killbots::Ruleset *Killbots::Engine::ruleset() const 0063 { 0064 return m_rules; 0065 } 0066 0067 bool Killbots::Engine::gameHasStarted() const 0068 { 0069 return m_hero && m_score > 0; 0070 } 0071 0072 bool Killbots::Engine::isRoundComplete() const 0073 { 0074 return m_bots.isEmpty(); 0075 } 0076 0077 bool Killbots::Engine::isHeroDead() const 0078 { 0079 return m_heroIsDead; 0080 } 0081 0082 bool Killbots::Engine::isBoardFull() const 0083 { 0084 return m_robotCount + m_fastbotCount + m_junkheapCount 0085 > m_rules->rows() * m_rules->columns() / 2; 0086 } 0087 0088 bool Killbots::Engine::canSafeTeleport() const 0089 { 0090 return m_rules->safeTeleportEnabled() 0091 && m_energy >= m_rules->costOfSafeTeleport(); 0092 } 0093 0094 bool Killbots::Engine::canUseVaporizer() const 0095 { 0096 return m_rules->vaporizerEnabled() 0097 && m_energy >= m_rules->costOfVaporizer(); 0098 } 0099 0100 void Killbots::Engine::startNewGame() 0101 { 0102 Q_ASSERT(m_rules != nullptr); 0103 0104 // Don't show the new game message on first start. 0105 if (m_round != 0) { 0106 Q_EMIT showNewGameMessage(); 0107 } 0108 0109 m_heroIsDead = false; 0110 0111 m_round = 1; 0112 m_score = 0; 0113 m_maxEnergy = m_rules->energyEnabled() ? m_rules->maxEnergyAtGameStart() : 0; 0114 m_energy = m_rules->energyEnabled() ? m_rules->energyAtGameStart() : 0; 0115 m_robotCount = m_rules->enemiesAtGameStart(); 0116 m_fastbotCount = m_rules->fastEnemiesAtGameStart(); 0117 m_junkheapCount = m_rules->junkheapsAtGameStart(); 0118 0119 Q_EMIT teleportAllowed(true); 0120 Q_EMIT waitOutRoundAllowed(true); 0121 Q_EMIT teleportSafelyAllowed(canSafeTeleport()); 0122 Q_EMIT vaporizerAllowed(canUseVaporizer()); 0123 0124 // Code used to generate theme previews 0125 //newRound( " r\nhjf", false ); 0126 0127 startNewRound(false); 0128 } 0129 0130 void Killbots::Engine::startNewRound(bool incrementRound, const QString &layout) 0131 { 0132 cleanUpRound(); 0133 0134 m_waitingOutRound = false; 0135 0136 m_coordinator->beginNewAnimationStage(); 0137 0138 if (incrementRound) { 0139 ++m_round; 0140 0141 if (m_rules->energyEnabled()) { 0142 m_maxEnergy += m_rules->maxEnergyAddedEachRound(); 0143 updateEnergy(m_rules->energyAddedEachRound()); 0144 } 0145 m_robotCount += m_rules->enemiesAddedEachRound(); 0146 m_fastbotCount += m_rules->fastEnemiesAddedEachRound(); 0147 m_junkheapCount += m_rules->junkheapsAddedEachRound(); 0148 } 0149 0150 if (layout.isEmpty()) { 0151 // Place the hero in the centre of the board. 0152 const QPoint centre = QPoint(qRound((float)(m_rules->columns() / 2)), qRound((float)(m_rules->rows() / 2))); 0153 m_hero = m_coordinator->createSprite(Hero, centre); 0154 0155 // Create and randomly place junkheaps. 0156 for (int i = m_junkheapCount; i > 0 ; --i) { 0157 const QPoint point = randomEmptyCell(); 0158 m_junkheaps << m_coordinator->createSprite(Junkheap, point); 0159 m_spriteMap.insert(point, m_junkheaps.last()); 0160 } 0161 0162 // Create and randomly place robots. 0163 for (int i = m_robotCount; i > 0; --i) { 0164 const QPoint point = randomEmptyCell(); 0165 m_bots << m_coordinator->createSprite(Robot, point); 0166 m_spriteMap.insert(point, m_bots.last()); 0167 } 0168 0169 // Create and randomly place fastbots. 0170 for (int i = m_fastbotCount; i > 0; --i) { 0171 const QPoint point = randomEmptyCell(); 0172 m_bots << m_coordinator->createSprite(Fastbot, point); 0173 m_spriteMap.insert(point, m_bots.last()); 0174 } 0175 } else { 0176 const QStringList rows = layout.split(QLatin1Char('\n')); 0177 for (int r = 0; r < rows.size(); ++r) { 0178 for (int c = 0; c < rows.at(r).size(); ++c) { 0179 const QChar ch = rows.at(r).at(c); 0180 const QPoint point(c, r); 0181 0182 if (ch == QLatin1Char('h') && m_hero == nullptr) { 0183 m_hero = m_coordinator->createSprite(Hero, point); 0184 } else if (ch == QLatin1Char('r')) { 0185 m_bots << m_coordinator->createSprite(Robot, point); 0186 } else if (ch == QLatin1Char('f')) { 0187 m_bots << m_coordinator->createSprite(Fastbot, point); 0188 } else if (ch == QLatin1Char('j')) { 0189 m_junkheaps << m_coordinator->createSprite(Junkheap, point); 0190 } 0191 } 0192 } 0193 } 0194 0195 Q_EMIT roundChanged(m_round); 0196 Q_EMIT scoreChanged(m_score); 0197 Q_EMIT enemyCountChanged(m_bots.size()); 0198 Q_EMIT energyChanged(m_energy); 0199 0200 refreshSpriteMap(); 0201 } 0202 0203 // Returns true if the move was performed, returns false otherwise. 0204 bool Killbots::Engine::moveHero(Killbots::HeroAction direction) 0205 { 0206 refreshSpriteMap(); 0207 const QPoint newCell = m_hero->gridPos() + offsetFromDirection(direction); 0208 const bool preventUnsafeMoves = Settings::preventUnsafeMoves() || direction < 0; 0209 0210 if (moveIsValid(newCell, direction) && (moveIsSafe(newCell, direction) || !preventUnsafeMoves)) { 0211 if (direction != Hold) { 0212 m_coordinator->beginNewAnimationStage(); 0213 0214 if (spriteTypeAt(newCell) == Junkheap) { 0215 pushJunkheap(m_spriteMap.value(newCell), direction); 0216 } 0217 0218 m_coordinator->slideSprite(m_hero, newCell); 0219 } 0220 return true; 0221 } else { 0222 return false; 0223 } 0224 } 0225 0226 // Always returns true as teleports always succeed. 0227 bool Killbots::Engine::teleportHero() 0228 { 0229 refreshSpriteMap(); 0230 const QPoint point = randomEmptyCell(); 0231 m_coordinator->beginNewAnimationStage(); 0232 m_coordinator->teleportSprite(m_hero, point); 0233 return true; 0234 } 0235 0236 // Returns true if a safe cell was found. If no safe cell was found than 0237 // the board must be full. 0238 bool Killbots::Engine::teleportHeroSafely() 0239 { 0240 refreshSpriteMap(); 0241 0242 // Choose a random cell... 0243 const QPoint startPoint = QPoint(QRandomGenerator::global()->bounded(m_rules->columns()), 0244 QRandomGenerator::global()->bounded(m_rules->rows())); 0245 QPoint point = startPoint; 0246 0247 // ...and step through all the cells on the board looking for a safe cell. 0248 do { 0249 if (point.x() < m_rules->columns() - 1) { 0250 point.rx()++; 0251 } else { 0252 point.rx() = 0; 0253 if (point.y() < m_rules->rows() - 1) { 0254 point.ry()++; 0255 } else { 0256 point.ry() = 0; 0257 } 0258 } 0259 0260 // Looking for an empty and safe cell. 0261 if (spriteTypeAt(point) == NoSprite && point != m_hero->gridPos() && moveIsSafe(point, Teleport)) { 0262 break; 0263 } 0264 } while (point != startPoint); 0265 0266 // If we stepped through every cell and found none that were safe, reset the robot counts. 0267 if (point == startPoint) { 0268 return false; 0269 } else { 0270 m_coordinator->beginNewAnimationStage(); 0271 updateEnergy(-m_rules->costOfSafeTeleport()); 0272 m_coordinator->teleportSprite(m_hero, point); 0273 0274 return true; 0275 } 0276 } 0277 0278 // Returns true if any enemies were within range. 0279 bool Killbots::Engine::useVaporizer() 0280 { 0281 refreshSpriteMap(); 0282 QList<Sprite *> neighbors; 0283 for (int i = Right; i <= DownRight; ++i) { 0284 const QPoint neighbor = m_hero->gridPos() + offsetFromDirection(i); 0285 if (cellIsValid(neighbor) && (spriteTypeAt(neighbor) == Robot || spriteTypeAt(neighbor) == Fastbot)) { 0286 neighbors << m_spriteMap.value(neighbor); 0287 } 0288 } 0289 0290 if (!neighbors.isEmpty()) { 0291 m_coordinator->beginNewAnimationStage(); 0292 for (Sprite *sprite : std::as_const(neighbors)) { 0293 destroySprite(sprite); 0294 } 0295 updateEnergy(-m_rules->costOfVaporizer()); 0296 return true; 0297 } else { 0298 return false; 0299 } 0300 } 0301 0302 bool Killbots::Engine::waitOutRound() 0303 { 0304 m_waitingOutRound = true; 0305 return true; 0306 } 0307 0308 void Killbots::Engine::moveRobots(bool justFastbots) 0309 { 0310 m_coordinator->beginNewAnimationStage(); 0311 0312 if (justFastbots) { 0313 refreshSpriteMap(); 0314 for (Sprite *bot : std::as_const(m_bots)) { 0315 if (bot->spriteType() == Fastbot) { 0316 const QPoint offset(sign(m_hero->gridPos().x() - bot->gridPos().x()), sign(m_hero->gridPos().y() - bot->gridPos().y())); 0317 const QPoint target = bot->gridPos() + offset; 0318 if (spriteTypeAt(target) != Robot || !m_rules->fastEnemiesArePatient()) { 0319 m_coordinator->slideSprite(bot, target); 0320 } 0321 } 0322 } 0323 } else { 0324 for (Sprite *bot : std::as_const(m_bots)) { 0325 const QPoint offset(sign(m_hero->gridPos().x() - bot->gridPos().x()), sign(m_hero->gridPos().y() - bot->gridPos().y())); 0326 m_coordinator->slideSprite(bot, bot->gridPos() + offset); 0327 } 0328 } 0329 } 0330 0331 void Killbots::Engine::assessDamage() 0332 { 0333 refreshSpriteMap(); 0334 0335 m_coordinator->beginNewAnimationStage(); 0336 0337 if (m_spriteMap.count(m_hero->gridPos()) > 0) { 0338 m_heroIsDead = true; 0339 } 0340 0341 // Check junkheaps for dead robots 0342 const auto junkheaps = m_junkheaps; 0343 for (Sprite *junkheap : junkheaps) { 0344 destroyAllCollidingBots(junkheap, !m_heroIsDead); 0345 } 0346 0347 // Check for robot-on-robot violence 0348 int i = 0; 0349 while (i < m_bots.size()) { 0350 Sprite *bot = m_bots[i]; 0351 if (bot->gridPos() != m_hero->gridPos() && destroyAllCollidingBots(bot, !m_heroIsDead)) { 0352 m_junkheaps << m_coordinator->createSprite(Junkheap, bot->gridPos()); 0353 destroySprite(bot, !m_heroIsDead); 0354 } else { 0355 i++; 0356 } 0357 } 0358 0359 if (isRoundComplete()) { 0360 m_coordinator->beginNewAnimationStage(); 0361 Q_EMIT showRoundCompleteMessage(); 0362 } 0363 } 0364 0365 void Killbots::Engine::resetBotCounts() 0366 { 0367 m_coordinator->beginNewAnimationStage(); 0368 Q_EMIT showBoardFullMessage(); 0369 0370 m_maxEnergy = m_rules->maxEnergyAtGameStart(); 0371 m_robotCount = m_rules->enemiesAtGameStart(); 0372 m_fastbotCount = m_rules->fastEnemiesAtGameStart(); 0373 m_junkheapCount = m_rules->junkheapsAtGameStart(); 0374 0375 m_coordinator->beginNewAnimationStage(); 0376 startNewRound(false); 0377 } 0378 0379 void Killbots::Engine::endGame() 0380 { 0381 Q_EMIT showGameOverMessage(); 0382 Q_EMIT teleportAllowed(false); 0383 Q_EMIT waitOutRoundAllowed(false); 0384 Q_EMIT teleportSafelyAllowed(false); 0385 Q_EMIT vaporizerAllowed(false); 0386 Q_EMIT gameOver(m_score, m_round); 0387 } 0388 0389 // The hero action functions and the assessDamage functions must know the 0390 // contents of each cell. This function updates the hash that maps cells to 0391 // their contents. 0392 void Killbots::Engine::refreshSpriteMap() 0393 { 0394 m_spriteMap.clear(); 0395 for (Sprite *bot : std::as_const(m_bots)) { 0396 m_spriteMap.insert(bot->gridPos(), bot); 0397 } 0398 for (Sprite *junkheap : std::as_const(m_junkheaps)) { 0399 m_spriteMap.insert(junkheap->gridPos(), junkheap); 0400 } 0401 } 0402 0403 // A convenience function to query the type of a sprite any the given cell. 0404 int Killbots::Engine::spriteTypeAt(const QPoint &cell) const 0405 { 0406 if (m_spriteMap.contains(cell)) { 0407 return m_spriteMap.value(cell)->spriteType(); 0408 } else { 0409 return NoSprite; 0410 } 0411 } 0412 0413 QPoint Killbots::Engine::offsetFromDirection(int direction) const 0414 { 0415 if (direction < 0) { 0416 direction = -direction - 1; 0417 } 0418 0419 switch (direction) { 0420 case Right: 0421 return QPoint(1, 0); 0422 case UpRight: 0423 return QPoint(1, -1); 0424 case Up: 0425 return QPoint(0, -1); 0426 case UpLeft: 0427 return QPoint(-1, -1); 0428 case Left: 0429 return QPoint(-1, 0); 0430 case DownLeft: 0431 return QPoint(-1, 1); 0432 case Down: 0433 return QPoint(0, 1); 0434 case DownRight: 0435 return QPoint(1, 1); 0436 default: 0437 return QPoint(0, 0); 0438 }; 0439 } 0440 0441 // Returns a random empty cell on the grid. Depends on a fresh spritemap. 0442 QPoint Killbots::Engine::randomEmptyCell() const 0443 { 0444 QPoint point; 0445 do { 0446 point = QPoint(QRandomGenerator::global()->bounded(m_rules->columns()), 0447 QRandomGenerator::global()->bounded(m_rules->rows())); 0448 } while (spriteTypeAt(point) != NoSprite || point == m_hero->gridPos()); 0449 return point; 0450 } 0451 0452 // Returns true if the given cell lies inside the game grid. 0453 bool Killbots::Engine::cellIsValid(const QPoint &cell) const 0454 { 0455 return (0 <= cell.x() 0456 && cell.x() < m_rules->columns() 0457 && 0 <= cell.y() 0458 && cell.y() < m_rules->rows() 0459 ); 0460 } 0461 0462 bool Killbots::Engine::moveIsValid(const QPoint &cell, HeroAction direction) const 0463 { 0464 // The short version 0465 return (cellIsValid(cell) 0466 && (spriteTypeAt(cell) == NoSprite 0467 || (spriteTypeAt(cell) == Junkheap 0468 && canPushJunkheap(m_spriteMap.value(cell), direction) 0469 ) 0470 ) 0471 ); 0472 0473 /* // The debuggable version 0474 bool result = true; 0475 0476 if ( cellIsValid( cell ) ) 0477 { 0478 if ( spriteTypeAt( cell ) != NoSprite ) 0479 { 0480 if ( spriteTypeAt( cell ) == Junkheap ) 0481 { 0482 if ( !canPushJunkheap( m_spriteMap.value( cell ), direction ) ) 0483 { 0484 result = false; 0485 //qCDebug(KILLBOTS_LOG) << "Move is invalid. Cannot push junkheap."; 0486 } 0487 } 0488 else 0489 { 0490 result = false; 0491 //qCDebug(KILLBOTS_LOG) << "Move is invalid. Cell is occupied by an unpushable object."; 0492 } 0493 } 0494 } 0495 else 0496 { 0497 result = false; 0498 //qCDebug(KILLBOTS_LOG) << "Move is invalid. Cell is lies outside grid."; 0499 } 0500 0501 return result; 0502 */ 0503 } 0504 0505 bool Killbots::Engine::moveIsSafe(const QPoint &cell, HeroAction direction) const 0506 { 0507 /* 0508 Warning: This algorithm might break your head. The following diagrams and descriptions try to help. 0509 0510 Note: This algorithm assumes that the proposed move has already been checked for validity. 0511 0512 Legend 0513 H = The position of the hero after the proposed move (the cell who's safeness we're trying to determine). 0514 J = The position of a junkheap after the proposed move, whether moved by the hero or sitting there already. 0515 R = The position of a robot. 0516 F = The position of a fastbot. 0517 * = A cell that we don't particularly care about in this diagram. 0518 0519 +---+---+---+---+---+ 0520 | * | * | * | * | * | 0521 +---+---+---+---+---+ 0522 | * | | | F | * | 0523 +---+---+---+---+---+ 0524 | * | | H | | * | If any of the neighbouring cells contain a robot or fastbot, the move is unsafe. 0525 +---+---+---+---+---+ 0526 | * | | R | | * | 0527 +---+---+---+---+---+ 0528 | * | * | * | * | * | 0529 +---+---+---+---+---+ 0530 0531 +---+---+---+---+---+ 0532 | | | | | | 0533 +---+---+---+---+---+ 0534 | | | | | | 0535 +---+---+---+---+---+ 0536 | | *<==J<==H | | If the proposed move involved pushing a junkheap, we can ignore the cell that the junkheap 0537 +---+---+---+---+---+ will end up in, because if there were an enemy there, it would be crushed. 0538 | | | | | | 0539 +---+---+---+---+---+ 0540 | | | | | | 0541 +---+---+---+---+---+ 0542 0543 +---+---+---+---+---+ 0544 |C01| | | | | 0545 +---+---+---+---+---+ Fastbots can attack from two cells away, making it trickier to determine whether they 0546 | |N01| | |E01| pose a threat. First we have to understand the attack vector of a fastbot. A fastbot 0547 +---+---+---+---+---+ attacking from a "corner" cell such as C01 will pass through a diagonal neighbour like 0548 | | | H |N02|E02| like N01. Any fastbot attacking from an "edge" cell like E01, E02 or E03 will have to 0549 +---+---+---+---+---+ pass through a horizontal or vertical neighbour like N02. This mean that when checking 0550 | | | | |E03| a diagonal neighbour we only need to check the one cell "behind" it for fastbots, but 0551 +---+---+---+---+---+ when checking a horizontal or vertical neighbour we need to check the three cells 0552 | | | | | | "behind" it for fastbots. 0553 +---+---+---+---+---+ 0554 0555 +---+---+---+---+---+ 0556 | | | | | * | 0557 +---+---+---+---+---+ 0558 | * | | | J | | 0559 +---+---+---+---+---+ Back to junkheaps. If a neighbouring cell contains a junkheap, we don't need to check 0560 | * | J | H | | | the cells behind it for fastbots because if there were any there, they'd just collide 0561 +---+---+---+---+---+ with the junkheap anyway. 0562 | * | | | | | 0563 +---+---+---+---+---+ 0564 | | | | | | 0565 +---+---+---+---+---+ 0566 0567 +---+---+---+---+---+ 0568 | * | * | * | * | F | 0569 +---+---+---+---+---+ 0570 | * | * | * | | * | 0571 +---+---+---+---+---+ 0572 | * | * | H | * | * | "Corner" fastbot threats are easy enough to detect. If a diagonal neighbour is empty 0573 +---+---+---+---+---+ and the cell behind it contains a fastbot, the move is unsafe. 0574 | * | * | * | * | * | 0575 +---+---+---+---+---+ 0576 | * | * | * | * | * | 0577 +---+---+---+---+---+ 0578 0579 +---+---+---+---+---+ 0580 | * | * | * | * | * | 0581 +---+---+---+---+---+ 0582 | R | * | * | * | * | 0583 +---+---+---+---+---+ "Edge" fastbots threats are much harder to detect because any fastbots on an edge might 0584 | F | | H | * | * | collide with robots or other fastbots on their way to the neighbouring cell. For example, 0585 +---+---+---+---+---+ the hero in this diagram is perfectly safe because all the fastbots will be destroyed 0586 | | * | | * | * | before they can become dangerous. 0587 +---+---+---+---+---+ 0588 | * | F | | F | * | 0589 +---+---+---+---+---+ 0590 0591 +---+---+---+---+---+ 0592 | * | F | | | * | 0593 +---+---+---+---+---+ 0594 | * | * | | * | | 0595 +---+---+---+---+---+ With a bit of thought, it's easy to see that an "edge" fastbot is only a threat if there 0596 | * | * | H | | | is exactly one fastbot and zero robots on that edge. 0597 +---+---+---+---+---+ 0598 | * | * | | * | F | When you put all of the above facts together you (hopefully) get the following algorithm. 0599 +---+---+---+---+---+ 0600 | * | | F | | * | 0601 +---+---+---+---+---+ 0602 */ 0603 0604 // The move is assumed safe until proven unsafe. 0605 bool result = true; 0606 0607 // If we're pushing a junkheap, store the cell that the junkheap will end up in. Otherwise store an invalid cell. 0608 const QPoint cellBehindJunkheap = (spriteTypeAt(cell) != Junkheap) 0609 ? QPoint(-1, -1) 0610 : cell + offsetFromDirection(direction); 0611 0612 // We check the each of the target cells neighbours. 0613 for (int i = Right; i <= DownRight && result; ++i) { 0614 const QPoint neighbor = cell + offsetFromDirection(i); 0615 0616 // If the neighbour is invalid or the cell behind the junkheap, continue to the next neighbour. 0617 if (!cellIsValid(neighbor) || spriteTypeAt(neighbor) == Junkheap || neighbor == cellBehindJunkheap) { 0618 continue; 0619 } 0620 0621 // If the neighbour contains an enemy, the move is unsafe. 0622 if (spriteTypeAt(neighbor) == Robot || spriteTypeAt(neighbor) == Fastbot) { 0623 result = false; 0624 } else { 0625 // neighboursNeighbour is the cell behind the neighbour, with respect to the target cell. 0626 const QPoint neighborsNeighbor = neighbor + offsetFromDirection(i); 0627 0628 // If we're examining a diagonal neighbour (an odd direction)... 0629 if (i % 2 == 1) { 0630 // ...and neighboursNeighbour is a fastbot then the move is unsafe. 0631 if (spriteTypeAt(neighborsNeighbor) == Fastbot) { 0632 result = false; 0633 } 0634 } 0635 // If we're examining an vertical or horizontal neighbour, things are more complicated... 0636 else { 0637 // Assemble a list of the cells behind the neighbour. 0638 const std::array<QPoint, 3> cellsBehindNeighbor { 0639 neighborsNeighbor, 0640 // Add neighboursNeighbour's anticlockwise neighbour. 0641 // ( i + 2 ) % 8 is the direction a quarter turn anticlockwise from i. 0642 neighborsNeighbor + offsetFromDirection((i + 2) % 8), 0643 // Add neighboursNeighbour's clockwise neighbour. 0644 // ( i + 6 ) % 8 is the direction a quarter turn clockwise from i. 0645 neighborsNeighbor + offsetFromDirection((i + 6) % 8), 0646 }; 0647 0648 // Then we just count the number of fastbots and robots in the list of cells. 0649 int fastbotsFound = 0; 0650 int robotsFound = 0; 0651 for (const QPoint &cell : cellsBehindNeighbor) { 0652 if (spriteTypeAt(cell) == Fastbot) { 0653 ++fastbotsFound; 0654 } else if (spriteTypeAt(cell) == Robot) { 0655 ++robotsFound; 0656 } 0657 } 0658 0659 // If there is exactly one fastbots and zero robots, the move is unsafe. 0660 if (fastbotsFound == 1 && robotsFound == 0) { 0661 result = false; 0662 } 0663 } 0664 } 0665 } 0666 0667 return result; 0668 } 0669 0670 bool Killbots::Engine::canPushJunkheap(const Sprite *junkheap, HeroAction direction) const 0671 { 0672 Q_ASSERT(junkheap->spriteType() == Junkheap); 0673 0674 const QPoint nextCell = junkheap->gridPos() + offsetFromDirection(direction); 0675 0676 if (m_rules->pushableJunkheaps() != Ruleset::None && cellIsValid(nextCell)) { 0677 if (spriteTypeAt(nextCell) == NoSprite) { 0678 return true; 0679 } else if (spriteTypeAt(nextCell) == Junkheap) { 0680 return m_rules->pushableJunkheaps() == Ruleset::Many && canPushJunkheap(m_spriteMap.value(nextCell), direction); 0681 } else { 0682 return m_rules->squaskKillsEnabled(); 0683 } 0684 } else { 0685 return false; 0686 } 0687 } 0688 0689 void Killbots::Engine::pushJunkheap(Sprite *junkheap, HeroAction direction) 0690 { 0691 const QPoint nextCell = junkheap->gridPos() + offsetFromDirection(direction); 0692 Sprite *currentOccupant = m_spriteMap.value(nextCell); 0693 if (currentOccupant) { 0694 if (currentOccupant->spriteType() == Junkheap) { 0695 pushJunkheap(currentOccupant, direction); 0696 } else { 0697 destroySprite(currentOccupant); 0698 updateScore(m_rules->squashKillPointBonus()); 0699 updateEnergy(m_rules->squashKillEnergyBonus()); 0700 } 0701 } 0702 0703 m_coordinator->slideSprite(junkheap, nextCell); 0704 } 0705 0706 void Killbots::Engine::cleanUpRound() 0707 { 0708 m_coordinator->beginNewAnimationStage(); 0709 0710 if (m_hero) { 0711 destroySprite(m_hero); 0712 } 0713 m_hero = nullptr; 0714 0715 const auto bots = m_bots; 0716 for (Sprite *bot : bots) { 0717 destroySprite(bot, false); 0718 } 0719 Q_ASSERT(m_bots.isEmpty()); 0720 m_bots.clear(); 0721 0722 const auto junkheaps = m_junkheaps; 0723 for (Sprite *junkheap : junkheaps) { 0724 destroySprite(junkheap); 0725 } 0726 Q_ASSERT(m_junkheaps.isEmpty()); 0727 m_junkheaps.clear(); 0728 0729 m_spriteMap.clear(); 0730 } 0731 0732 void Killbots::Engine::destroySprite(Sprite *sprite, bool calculatePoints) 0733 { 0734 const SpriteType type = sprite->spriteType(); 0735 0736 if (type == Robot || type == Fastbot) { 0737 if (calculatePoints) { 0738 if (type == Robot) { 0739 updateScore(m_rules->pointsPerEnemyKilled()); 0740 } else { 0741 updateScore(m_rules->pointsPerFastEnemyKilled()); 0742 } 0743 0744 if (m_waitingOutRound) { 0745 updateScore(m_rules->waitKillPointBonus()); 0746 updateEnergy(m_rules->waitKillEnergyBonus()); 0747 } 0748 } 0749 m_bots.removeOne(sprite); 0750 Q_EMIT enemyCountChanged(m_bots.size()); 0751 } else if (type == Junkheap) { 0752 m_junkheaps.removeOne(sprite); 0753 } 0754 0755 m_coordinator->destroySprite(sprite); 0756 } 0757 0758 bool Killbots::Engine::destroyAllCollidingBots(const Sprite *sprite, bool calculatePoints) 0759 { 0760 bool result = false; 0761 0762 const auto robotsAtPos = m_spriteMap.values(sprite->gridPos()); 0763 for (Sprite *robot : robotsAtPos) { 0764 if (robot != sprite && (robot->spriteType() == Robot || robot->spriteType() == Fastbot)) { 0765 destroySprite(robot, calculatePoints); 0766 result = true; 0767 } 0768 } 0769 0770 return result; 0771 } 0772 0773 void Killbots::Engine::updateScore(int changeInScore) 0774 { 0775 if (changeInScore != 0) { 0776 m_score = m_score + changeInScore; 0777 Q_EMIT scoreChanged(m_score); 0778 } 0779 } 0780 0781 void Killbots::Engine::updateEnergy(int changeInEnergy) 0782 { 0783 if (m_rules->energyEnabled() && changeInEnergy != 0) { 0784 if (changeInEnergy > 0 && m_energy > int(m_maxEnergy)) { 0785 m_score += changeInEnergy * m_rules->pointsPerEnergyAboveMax(); 0786 } else if (changeInEnergy > 0 && m_energy + changeInEnergy > int(m_maxEnergy)) { 0787 m_score += (m_energy + changeInEnergy - int(m_maxEnergy)) * m_rules->pointsPerEnergyAboveMax(); 0788 m_energy = int(m_maxEnergy); 0789 } else { 0790 m_energy = m_energy + changeInEnergy; 0791 } 0792 0793 Q_EMIT energyChanged(m_energy); 0794 Q_EMIT teleportSafelyAllowed(canSafeTeleport()); 0795 Q_EMIT vaporizerAllowed(canUseVaporizer()); 0796 } 0797 } 0798 0799 QString Killbots::Engine::gridToString() const 0800 { 0801 QString string; 0802 for (int r = 0; r < m_rules->rows(); ++r) { 0803 for (int c = 0; c < m_rules->columns(); ++c) { 0804 switch (spriteTypeAt(QPoint(c, r))) { 0805 case Robot: 0806 string += QLatin1Char('r'); 0807 break; 0808 case Fastbot: 0809 string += QLatin1Char('f'); 0810 break; 0811 case Junkheap: 0812 string += QLatin1Char('j'); 0813 break; 0814 default: 0815 string += QLatin1Char(' '); 0816 break; 0817 } 0818 } 0819 string += QLatin1Char('\n'); 0820 } 0821 return string; 0822 }