2 * Copyright 2006-2009 Parker Coates <parker.coates@gmail.com>
4 * This file is part of Killbots.
6 * Killbots is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 2 of the License, or
9 * (at your option) any later version.
11 * Killbots 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.
16 * You should have received a copy of the GNU General Public License
17 * along with Killbots. If not, see <http://www.gnu.org/licenses/>.
27 #include <KDE/KLocale>
28 #include <KDE/KRandom>
30 uint
qHash( const QPoint
& point
)
32 return qHash( point
.x() * 1000 + point
.y() );
36 inline int sign( int num
)
38 return (num
> 0) ? 1 : (num
== 0) ? 0 : -1;
42 Killbots::Engine::Engine( Scene
* scene
, QObject
* parent
)
46 m_busyAnimating( false ),
47 m_processFastbots( false ),
49 m_newGameRequested( false ),
50 m_repeatedAction( NoAction
),
51 m_queuedAction( NoAction
),
58 m_fastbotCount( 0.0 ),
59 m_junkheapCount( 0.0 ),
62 connect( m_scene
, SIGNAL(animationDone()), this, SLOT(animationDone()), Qt::QueuedConnection
);
66 Killbots::Engine::~Engine()
72 const Killbots::Ruleset
* Killbots::Engine::ruleset()
78 bool Killbots::Engine::gameHasStarted()
80 return m_hero
&& m_score
> 0;
84 void Killbots::Engine::requestNewGame()
86 if ( !m_busyAnimating
|| m_gameOver
)
89 m_newGameRequested
= true;
93 void Killbots::Engine::newGame()
95 if ( !m_rules
|| m_rules
->fileName() != Settings::ruleset() )
99 m_rules
= Ruleset::load( Settings::ruleset() );
102 m_rules
= Ruleset::loadDefault();
103 Q_ASSERT( m_rules
!= 0 );
105 // Don't show the new game message on first start.
107 emit
showNewGameMessage();
108 emit
newGame( m_rules
->rows(), m_rules
->columns(), m_rules
->energyEnabled() );
109 emit
teleportAllowed( true );
110 emit
waitOutRoundAllowed( true );
111 emit
teleportSafelyAllowed( m_rules
->safeTeleportEnabled() && m_energy
>= m_rules
->costOfSafeTeleport() );
112 emit
sonicScrewdriverAllowed( m_rules
->sonicScrewdriverEnabled() && m_energy
>= m_rules
->costOfSonicScrewdriver() );
114 m_newGameRequested
= false;
119 m_maxEnergy
= m_rules
->energyEnabled() ? m_rules
->maxEnergyAtGameStart() : 0;
120 m_energy
= m_rules
->energyEnabled() ? m_rules
->energyAtGameStart() : 0;
121 m_robotCount
= m_rules
->enemiesAtGameStart();
122 m_fastbotCount
= m_rules
->fastEnemiesAtGameStart();
123 m_junkheapCount
= m_rules
->junkheapsAtGameStart();
125 // Code used to generate theme previews
126 //newRound( " r\nhjf", false );
129 m_busyAnimating
= true;
130 m_scene
->startAnimation();
134 void Killbots::Engine::requestAction( HeroAction action
)
136 // If the hero is doing a repeated move the new request only stops the hero.
137 const bool cancelRepeatedMove
= m_repeatedAction
!= WaitOutRound
138 && m_repeatedAction
!= NoAction
;
139 if ( cancelRepeatedMove
)
141 m_repeatedAction
= NoAction
;
144 if ( !m_busyAnimating
&& !m_gameOver
)
148 else if ( !cancelRepeatedMove
)
150 m_queuedAction
= action
;
155 // This slot is provided only for QSignalMapper compatibility.
156 void Killbots::Engine::requestAction( int action
)
158 requestAction( static_cast<HeroAction
>( action
) );
162 void Killbots::Engine::doAction( HeroAction action
)
164 bool actionSuccessful
= false;
165 bool boardFull
= false;
169 if ( action
<= Hold
)
171 actionSuccessful
= moveHero( action
);
173 else if ( ( action
== TeleportSafely
|| action
== TeleportSafelyIfPossible
)
174 && m_rules
->safeTeleportEnabled()
175 && m_energy
>= m_rules
->costOfSafeTeleport()
178 actionSuccessful
= teleportHeroSafely();
179 boardFull
= !actionSuccessful
;
181 else if ( action
== Teleport
|| action
== TeleportSafelyIfPossible
)
183 actionSuccessful
= teleportHero();
185 else if ( action
== SonicScrewdriver
&& m_energy
>= m_rules
->costOfSonicScrewdriver() )
187 actionSuccessful
= sonicScrewdriver();
189 else if ( action
== WaitOutRound
)
191 m_repeatedAction
= WaitOutRound
;
192 actionSuccessful
= true;
195 if ( actionSuccessful
)
199 if ( m_bots
.isEmpty() )
200 emit
showRoundCompleteMessage();
202 m_processFastbots
= true;
203 m_busyAnimating
= true;
204 m_scene
->startAnimation();
206 else if ( boardFull
)
209 m_busyAnimating
= true;
210 m_scene
->startAnimation();
215 void Killbots::Engine::animationDone()
217 m_busyAnimating
= false;
219 if ( m_newGameRequested
)
223 else if ( m_processFastbots
)
225 m_processFastbots
= false;
228 if ( m_bots
.isEmpty() )
229 emit
showRoundCompleteMessage();
230 m_busyAnimating
= true;
231 m_scene
->startAnimation();
233 else if ( m_gameOver
)
235 emit
showGameOverMessage();
236 emit
teleportAllowed( false );
237 emit
waitOutRoundAllowed( false );
238 emit
teleportSafelyAllowed( false );
239 emit
sonicScrewdriverAllowed( false );
240 emit
gameOver( m_score
, m_round
);
242 else if ( m_bots
.isEmpty() )
245 if ( m_robotCount
+ m_fastbotCount
+ m_junkheapCount
> m_rules
->rows() * m_rules
->columns() / 2 )
247 m_busyAnimating
= true;
248 m_scene
->startAnimation();
250 else if ( m_repeatedAction
!= NoAction
)
252 doAction( m_repeatedAction
);
254 else if ( m_queuedAction
!= NoAction
)
256 doAction( m_queuedAction
);
257 m_queuedAction
= NoAction
;
262 void Killbots::Engine::newRound( bool incrementRound
, const QString
& layout
)
266 m_processFastbots
= false;
267 m_repeatedAction
= NoAction
;
268 m_queuedAction
= NoAction
;
270 m_scene
->beginNewAnimationStage();
272 if ( incrementRound
)
276 if ( m_rules
->energyEnabled() )
278 m_maxEnergy
+= m_rules
->maxEnergyAddedEachRound();
279 updateEnergy( m_rules
->energyAddedEachRound() );
281 m_robotCount
+= m_rules
->enemiesAddedEachRound();
282 m_fastbotCount
+= m_rules
->fastEnemiesAddedEachRound();
283 m_junkheapCount
+= m_rules
->junkheapsAddedEachRound();
286 if ( layout
.isEmpty() )
288 // Place the hero in the centre of the board.
289 const QPoint centre
= QPoint( qRound( m_rules
->columns() / 2 ), qRound( m_rules
->rows() / 2 ) );
290 m_hero
= m_scene
->createSprite( Hero
, centre
);
292 // Create and randomly place junkheaps.
293 for ( int i
= m_junkheapCount
; i
> 0 ; --i
)
295 const QPoint point
= randomEmptyCell();
296 m_junkheaps
<< m_scene
->createSprite( Junkheap
, point
);
297 m_spriteMap
.insert( point
, m_junkheaps
.last() );
300 // Create and randomly place robots.
301 for ( int i
= m_robotCount
; i
> 0; --i
)
303 const QPoint point
= randomEmptyCell();
304 m_bots
<< m_scene
->createSprite( Robot
, point
);
305 m_spriteMap
.insert( point
, m_bots
.last() );
308 // Create and randomly place fastbots.
309 for ( int i
= m_fastbotCount
; i
> 0; --i
)
311 const QPoint point
= randomEmptyCell();
312 m_bots
<< m_scene
->createSprite( Fastbot
, point
);
313 m_spriteMap
.insert( point
, m_bots
.last() );
318 const QStringList rows
= layout
.split('\n');
319 for ( int r
= 0; r
< rows
.size(); ++r
)
321 for ( int c
= 0; c
< rows
.at( r
).size(); ++c
)
323 const QChar ch
= rows
.at( r
).at( c
);
324 const QPoint
point( c
, r
);
326 if ( ch
== 'h' && m_hero
== 0 )
327 m_hero
= m_scene
->createSprite( Hero
, point
);
328 else if ( ch
== 'r' )
329 m_bots
<< m_scene
->createSprite( Robot
, point
);
330 else if ( ch
== 'f' )
331 m_bots
<< m_scene
->createSprite( Fastbot
, point
);
332 else if ( ch
== 'j' )
333 m_junkheaps
<< m_scene
->createSprite( Junkheap
, point
);
338 emit
roundChanged( m_round
);
339 emit
scoreChanged( m_score
);
340 emit
enemyCountChanged( m_bots
.size() );
341 emit
energyChanged( m_energy
);
347 // Returns true if the move was performed, returns false otherwise.
348 bool Killbots::Engine::moveHero( HeroAction direction
)
350 const QPoint newCell
= m_hero
->gridPos() + offsetFromDirection( direction
);
352 if ( moveIsValid( newCell
, direction
)
353 && ( moveIsSafe( newCell
, direction
)
354 || ( !Settings::preventUnsafeMoves()
355 && m_repeatedAction
== NoAction
360 m_repeatedAction
= direction
< 0 ? direction
: NoAction
;
362 if ( direction
!= Hold
)
364 m_scene
->beginNewAnimationStage();
366 if ( spriteTypeAt( newCell
) == Junkheap
)
367 pushJunkheap( m_spriteMap
.value( newCell
), direction
);
369 m_scene
->slideSprite( m_hero
, newCell
);
375 m_repeatedAction
= NoAction
;
381 void Killbots::Engine::pushJunkheap( Sprite
* junkheap
, HeroAction direction
)
383 const QPoint nextCell
= junkheap
->gridPos() + offsetFromDirection( direction
);
384 Sprite
* currentOccupant
= m_spriteMap
.value( nextCell
);
385 if ( currentOccupant
)
387 if ( currentOccupant
->spriteType() == Junkheap
)
389 pushJunkheap( currentOccupant
, direction
);
393 destroySprite( currentOccupant
);
394 updateScore( m_rules
->squashKillPointBonus() );
395 updateEnergy( m_rules
->squashKillEnergyBonus() );
399 m_scene
->slideSprite( junkheap
, nextCell
);
403 // Always returns true as teleports always succeed.
404 bool Killbots::Engine::teleportHero()
406 const QPoint point
= randomEmptyCell();
407 m_scene
->beginNewAnimationStage();
408 m_scene
->teleportSprite( m_hero
, point
);
413 // Returns true if a safe cell was found. If no safe cell was found than
414 // the board must be full.
415 bool Killbots::Engine::teleportHeroSafely()
417 // Choose a random cell...
418 const QPoint startPoint
= QPoint( KRandom::random() % m_rules
->columns(), KRandom::random() % m_rules
->rows() );
419 QPoint point
= startPoint
;
421 // ...and step through all the cells on the board looking for a safe cell.
424 if ( point
.x() < m_rules
->columns() - 1 )
429 if ( point
.y() < m_rules
->rows() - 1 )
435 // Looking for an empty and safe cell.
436 if ( spriteTypeAt( point
) == NoSprite
&& point
!= m_hero
->gridPos() && moveIsSafe( point
, Teleport
) )
439 while ( point
!= startPoint
);
441 // If we stepped through every cell and found none that were safe, reset the robot counts.
442 if ( point
== startPoint
)
448 m_scene
->beginNewAnimationStage();
449 updateEnergy( -m_rules
->costOfSafeTeleport() );
450 m_scene
->teleportSprite( m_hero
, point
);
457 // Returns true if any enemies were within range.
458 bool Killbots::Engine::sonicScrewdriver()
460 QList
<Sprite
*> neighbors
;
461 for ( int i
= Right
; i
<= DownRight
; ++i
)
463 const QPoint neighbor
= m_hero
->gridPos() + offsetFromDirection( i
);
464 if ( cellIsValid( neighbor
) && ( spriteTypeAt( neighbor
) == Robot
|| spriteTypeAt( neighbor
) == Fastbot
) )
465 neighbors
<< m_spriteMap
.value(neighbor
);
468 if ( neighbors
.size() )
470 m_scene
->beginNewAnimationStage();
471 foreach ( Sprite
* sprite
, neighbors
)
472 destroySprite( sprite
);
473 updateEnergy( -m_rules
->costOfSonicScrewdriver() );
483 void Killbots::Engine::moveRobots( bool justFastbots
)
485 m_scene
->beginNewAnimationStage();
487 foreach( Sprite
* bot
, m_bots
)
489 if ( bot
->spriteType() == Fastbot
|| !justFastbots
)
491 const QPoint
offset( sign( m_hero
->gridPos().x() - bot
->gridPos().x() ), sign( m_hero
->gridPos().y() - bot
->gridPos().y() ) );
492 m_scene
->slideSprite( bot
, bot
->gridPos() + offset
);
498 void Killbots::Engine::assessDamage()
502 m_scene
->beginNewAnimationStage();
504 if ( m_spriteMap
.count( m_hero
->gridPos() ) > 0 )
507 // Check junkheaps for dead robots
508 foreach ( Sprite
* junkheap
, m_junkheaps
)
509 destroyAllCollidingBots( junkheap
, !m_gameOver
);
511 // Check for robot-on-robot violence
513 while ( i
< m_bots
.size() )
515 Sprite
* bot
= m_bots
[i
];
516 if ( bot
->gridPos() != m_hero
->gridPos() && destroyAllCollidingBots( bot
, !m_gameOver
) )
518 m_junkheaps
<< m_scene
->createSprite( Junkheap
, bot
->gridPos() );
519 destroySprite( bot
, !m_gameOver
);
529 void Killbots::Engine::cleanUpRound()
531 m_scene
->beginNewAnimationStage();
534 destroySprite( m_hero
);
537 foreach( Sprite
* bot
, m_bots
)
538 destroySprite( bot
, false );
541 foreach( Sprite
* junkheap
, m_junkheaps
)
542 destroySprite( junkheap
);
549 void Killbots::Engine::destroySprite( Sprite
* sprite
, bool calculatePoints
)
551 const SpriteType type
= sprite
->spriteType();
553 if ( type
== Robot
|| type
== Fastbot
)
555 if ( calculatePoints
)
558 updateScore( m_rules
->pointsPerEnemyKilled() );
560 updateScore( m_rules
->pointsPerFastEnemyKilled() );
562 if ( m_repeatedAction
== WaitOutRound
)
564 updateScore( m_rules
->waitKillPointBonus() );
565 updateEnergy( m_rules
->waitKillEnergyBonus() );
568 m_bots
.removeOne( sprite
);
569 emit
enemyCountChanged( m_bots
.size() );
571 else if ( type
== Junkheap
)
573 m_junkheaps
.removeOne( sprite
);
576 m_scene
->destroySprite( sprite
);
580 bool Killbots::Engine::destroyAllCollidingBots( const Sprite
* sprite
, bool calculatePoints
)
584 foreach ( Sprite
* robot
, m_spriteMap
.values( sprite
->gridPos() ) )
586 if ( robot
!= sprite
&& ( robot
->spriteType() == Robot
|| robot
->spriteType() == Fastbot
) )
588 destroySprite( robot
, calculatePoints
);
597 void Killbots::Engine::updateScore( int changeInScore
)
599 if ( changeInScore
!= 0 )
601 m_score
= m_score
+ changeInScore
;
602 emit
scoreChanged( m_score
);
607 void Killbots::Engine::updateEnergy( int changeInEnergy
)
609 if ( m_rules
->energyEnabled() && changeInEnergy
!= 0 )
611 if ( changeInEnergy
> 0 && m_energy
> int( m_maxEnergy
) )
613 m_score
+= changeInEnergy
* m_rules
->pointsPerEnergyAboveMax();
615 else if ( changeInEnergy
> 0 && m_energy
+ changeInEnergy
> int( m_maxEnergy
) )
617 m_score
+= ( m_energy
+ changeInEnergy
- int( m_maxEnergy
) ) * m_rules
->pointsPerEnergyAboveMax();
618 m_energy
= int( m_maxEnergy
);
622 m_energy
= m_energy
+ changeInEnergy
;
625 emit
energyChanged( m_energy
);
626 emit
teleportSafelyAllowed( m_rules
->safeTeleportEnabled() && m_energy
>= m_rules
->costOfSafeTeleport() );
627 emit
sonicScrewdriverAllowed( m_rules
->sonicScrewdriverEnabled() && m_energy
>= m_rules
->costOfSonicScrewdriver() );
632 void Killbots::Engine::resetBotCounts()
634 m_scene
->beginNewAnimationStage();
635 emit
showBoardFullMessage();
637 m_maxEnergy
= m_rules
->maxEnergyAtGameStart();
638 m_robotCount
= m_rules
->enemiesAtGameStart();
639 m_fastbotCount
= m_rules
->fastEnemiesAtGameStart();
640 m_junkheapCount
= m_rules
->junkheapsAtGameStart();
642 m_scene
->beginNewAnimationStage();
647 // The hero action functions and the assessDamage functions must know the
648 // contents of each cell. This function updates the hash that maps cells to
650 void Killbots::Engine::refreshSpriteMap()
653 foreach( Sprite
* bot
, m_bots
)
654 m_spriteMap
.insert( bot
->gridPos(), bot
);
655 foreach( Sprite
* junkheap
, m_junkheaps
)
656 m_spriteMap
.insert( junkheap
->gridPos(), junkheap
);
660 // A convenience function to query the type of a sprite any the given cell.
661 int Killbots::Engine::spriteTypeAt( const QPoint
& cell
) const
663 if ( m_spriteMap
.contains( cell
) )
664 return m_spriteMap
.value( cell
)->spriteType();
670 // Returns a random empty cell on the grid. Depends on a fresh spritemap.
671 QPoint
Killbots::Engine::randomEmptyCell() const
675 point
= QPoint( KRandom::random() % m_rules
->columns(), KRandom::random() % m_rules
->rows() );
676 while ( spriteTypeAt( point
) != NoSprite
|| point
== m_hero
->gridPos() );
681 // Returns true if the given cell lies inside the game grid.
682 bool Killbots::Engine::cellIsValid( const QPoint
& cell
) const
684 return ( 0 <= cell
.x()
685 && cell
.x() < m_rules
->columns()
687 && cell
.y() < m_rules
->rows()
692 bool Killbots::Engine::moveIsValid( const QPoint
& cell
, HeroAction direction
) const
694 const QPoint cellBehindCell
= cell
+ offsetFromDirection( direction
);
697 return ( cellIsValid( cell
)
698 && ( spriteTypeAt( cell
) == NoSprite
699 || ( spriteTypeAt( cell
) == Junkheap
700 && canPushJunkheap( m_spriteMap
.value( cell
), direction
)
706 /* // The debuggable version
709 if ( cellIsValid( cell ) )
711 if ( spriteTypeAt( cell ) != NoSprite )
713 if ( spriteTypeAt( cell ) == Junkheap )
715 if ( !canPushJunkheap( m_spriteMap.value( cell ), direction ) )
718 kDebug() << "Move is invalid. Cannot push junkheap.";
724 kDebug() << "Move is invalid. Cell is occupied by an unpushable object.";
731 kDebug() << "Move is invalid. Cell is lies outside grid.";
739 bool Killbots::Engine::moveIsSafe( const QPoint
& cell
, HeroAction direction
) const
742 Warning: This algorithm might break your head. The following diagrams and descriptions try to help.
744 Note: This algorithm assumes that the proposed move has already been checked for validity.
747 H = The position of the hero after the proposed move (the cell who's safeness we're trying to determine).
748 J = The position of a junkheap after the proposed move, whether moved by the hero or sitting there already.
749 R = The position of a robot.
750 F = The position of a fastbot.
751 * = A cell that we don't particularly care about in this diagram.
753 +---+---+---+---+---+
754 | * | * | * | * | * |
755 +---+---+---+---+---+
757 +---+---+---+---+---+
758 | * | | H | | * | If any of the neighbouring cells contain a robot or fastbot, the move is unsafe.
759 +---+---+---+---+---+
761 +---+---+---+---+---+
762 | * | * | * | * | * |
763 +---+---+---+---+---+
765 +---+---+---+---+---+
767 +---+---+---+---+---+
769 +---+---+---+---+---+
770 | | *<==J<==H | | If the proposed move involved pushing a junkheap, we can ignore the cell that the junkheap
771 +---+---+---+---+---+ will end up in, because if there were an enemy there, it would be crushed.
773 +---+---+---+---+---+
775 +---+---+---+---+---+
777 +---+---+---+---+---+
779 +---+---+---+---+---+ Fastbots can attack from two cells away, making it trickier to determine whether they
780 | |N01| | |E01| pose a threat. First we have to understand the attack vector of a fastbot. A fastbot
781 +---+---+---+---+---+ attacking from a "corner" cell such as C01 will pass through a diagonal neighbour like
782 | | | H |N02|E02| like N01. Any fastbot attacking from an "edge" cell like E01, E02 or E03 will have to
783 +---+---+---+---+---+ pass through a horizontal or vertical neighbour like N02. This mean that when checking
784 | | | | |E03| a diagonal neighbour we only need to check the one cell "behind" it for fastbots, but
785 +---+---+---+---+---+ when checking a horizontal or vertical neighbour we need to check the three cells
786 | | | | | | "behind" it for fastbots.
787 +---+---+---+---+---+
789 +---+---+---+---+---+
791 +---+---+---+---+---+
793 +---+---+---+---+---+ Back to junkheaps. If a neighbouring cell contains a junkheap, we don't need to check
794 | * | J | H | | | the cells behind it for fastbots because if there were any there, they'd just collide
795 +---+---+---+---+---+ with the junkheap anyway.
797 +---+---+---+---+---+
799 +---+---+---+---+---+
801 +---+---+---+---+---+
802 | * | * | * | * | F |
803 +---+---+---+---+---+
805 +---+---+---+---+---+
806 | * | * | H | * | * | "Corner" fastbot threats are easy enough to detect. If a diagonal neighbour is empty
807 +---+---+---+---+---+ and the cell behind it contains a fastbot, the move is unsafe.
808 | * | * | * | * | * |
809 +---+---+---+---+---+
810 | * | * | * | * | * |
811 +---+---+---+---+---+
813 +---+---+---+---+---+
814 | * | * | * | * | * |
815 +---+---+---+---+---+
816 | R | * | * | * | * |
817 +---+---+---+---+---+ "Edge" fastbots threats are much harder to detect because any fastbots on an edge might
818 | F | | H | * | * | collide with robots or other fastbots on their way to the neighbouring cell. For example,
819 +---+---+---+---+---+ the hero in this diagram is perfectly safe because all the fastbots will be destroyed
820 | | * | | * | * | before they can become dangerous.
821 +---+---+---+---+---+
823 +---+---+---+---+---+
825 +---+---+---+---+---+
827 +---+---+---+---+---+
829 +---+---+---+---+---+ With a bit of thought, it's easy to see that an "edge" fastbot is only a threat if there
830 | * | * | H | | | is exactly one fastbot and zero robots on that edge.
831 +---+---+---+---+---+
832 | * | * | | * | F | When you put all of the above facts together you (hopefully) get the following algorithm.
833 +---+---+---+---+---+
835 +---+---+---+---+---+
838 // The move is assumed safe until proven unsafe.
841 // If we're pushing a junkheap, store the cell that the junkheap will end up in. Otherwise store an invalid cell.
842 const QPoint cellBehindJunkheap
= ( spriteTypeAt( cell
) != Junkheap
)
844 : cell
+ offsetFromDirection( direction
);
846 // We check the each of the target cells neighbours.
847 for ( int i
= Right
; i
<= DownRight
&& result
; ++i
)
849 const QPoint neighbor
= cell
+ offsetFromDirection( i
);
851 // If the neighbour is invalid or the cell behind the junkheap, continue to the next neighbour.
852 if ( !cellIsValid( neighbor
) || spriteTypeAt( neighbor
) == Junkheap
|| neighbor
== cellBehindJunkheap
)
855 // If the neighbour contains an enemy, the move is unsafe.
856 if ( spriteTypeAt( neighbor
) == Robot
|| spriteTypeAt( neighbor
) == Fastbot
)
862 // neighboursNeighbour is the cell behind the neighbour, with respect to the target cell.
863 const QPoint neighborsNeighbor
= neighbor
+ offsetFromDirection( i
);
865 // If we're examining a diagonal neighbour (an odd direction)...
868 // ...and neighboursNeighbour is a fastbot then the move is unsafe.
869 if ( spriteTypeAt( neighborsNeighbor
) == Fastbot
)
872 // If we're examining an vertical or horizontal neighbour, things are more complicated...
875 // Assemble a list of the cells behind the neighbour.
876 QList
<QPoint
> cellsBehindNeighbor
;
877 cellsBehindNeighbor
<< neighborsNeighbor
;
878 // Add neighboursNeighbour's anticlockwise neighbour.
879 // ( i + 2 ) % 8 is the direction a quarter turn anticlockwise from i.
880 cellsBehindNeighbor
<< neighborsNeighbor
+ offsetFromDirection( ( i
+ 2 ) % 8 );
881 // Add neighboursNeighbour's clockwise neighbour.
882 // ( i + 6 ) % 8 is the direction a quarter turn clockwise from i.
883 cellsBehindNeighbor
<< neighborsNeighbor
+ offsetFromDirection( ( i
+ 6 ) % 8 );
885 // Then we just count the number of fastbots and robots in the list of cells.
886 int fastbotsFound
= 0;
888 foreach( const QPoint
& cell
, cellsBehindNeighbor
)
890 if ( spriteTypeAt( cell
) == Fastbot
)
892 else if ( spriteTypeAt( cell
) == Robot
)
896 // If there is exactly one fastbots and zero robots, the move is unsafe.
897 if ( fastbotsFound
== 1 && robotsFound
== 0 )
907 bool Killbots::Engine::canPushJunkheap( const Sprite
* junkheap
, HeroAction direction
) const
909 Q_ASSERT( junkheap
->spriteType() == Junkheap
);
911 const QPoint nextCell
= junkheap
->gridPos() + offsetFromDirection( direction
);
913 if ( m_rules
->pushableJunkheaps() != Ruleset::None
&& cellIsValid( nextCell
) )
915 if ( spriteTypeAt( nextCell
) == NoSprite
)
917 else if ( spriteTypeAt( nextCell
) == Junkheap
)
918 return m_rules
->pushableJunkheaps() == Ruleset::Many
&& canPushJunkheap( m_spriteMap
.value( nextCell
), direction
);
920 return m_rules
->squaskKillsEnabled();
929 QPoint
Killbots::Engine::offsetFromDirection( int direction
) const
932 direction
= -direction
- 1;
937 return QPoint( 1, 0 );
939 return QPoint( 1, -1 );
941 return QPoint( 0, -1 );
943 return QPoint( -1, -1 );
945 return QPoint( -1, 0 );
947 return QPoint( -1, 1 );
949 return QPoint( 0, 1 );
951 return QPoint( 1, 1 );
953 return QPoint( 0, 0 );