1/*
2 * Copyright 2006-2009 Parker Coates <coates@kde.org>
3 *
4 * This file is part of Killbots.
5 *
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.
10 *
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.
15 *
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/>.
18 */
19
20#include "engine.h"
21
22#include "coordinator.h"
23#include "settings.h"
24#include "sprite.h"
25
26#include <KDE/KDebug>
27#include <KDE/KRandom>
28
29uint qHash( const QPoint & point )
30{
31 return qHash( point.x() * 1000 + point.y() );
32}
33
34
35inline int sign( int num )
36{
37 return (num > 0) ? 1 : (num == 0) ? 0 : -1;
38}
39
40
41Killbots::Engine::Engine( Killbots::Coordinator* scene, QObject* parent )
42 : QObject( parent ),
43 m_coordinator( scene ),
44 m_hero( 0 ),
45 m_rules( 0 ),
46 m_round( 0 ),
47 m_score( 0 ),
48 m_energy( 0 ),
49 m_maxEnergy( 0.0 ),
50 m_robotCount( 0.0 ),
51 m_fastbotCount( 0.0 ),
52 m_junkheapCount( 0.0 ),
53 m_heroIsDead( false ),
54 m_waitingOutRound( false ),
55 m_spriteMap()
56{
57}
58
59
60Killbots::Engine::~Engine()
61{
62 delete m_rules;
63}
64
65void Killbots::Engine::setRuleset( const Ruleset * ruleset )
66{
67 if ( ruleset && ruleset != m_rules )
68 {
69 delete m_rules;
70 m_rules = ruleset;
71 }
72}
73
74
75const Killbots::Ruleset * Killbots::Engine::ruleset() const
76{
77 return m_rules;
78}
79
80
81bool Killbots::Engine::gameHasStarted() const
82{
83 return m_hero && m_score > 0;
84}
85
86
87bool Killbots::Engine::isRoundComplete() const
88{
89 return m_bots.isEmpty();
90}
91
92
93bool Killbots::Engine::isHeroDead() const
94{
95 return m_heroIsDead;
96}
97
98
99bool Killbots::Engine::isBoardFull() const
100{
101 return m_robotCount + m_fastbotCount + m_junkheapCount
102 > m_rules->rows() * m_rules->columns() / 2;
103}
104
105
106bool Killbots::Engine::canSafeTeleport() const
107{
108 return m_rules->safeTeleportEnabled()
109 && m_energy >= m_rules->costOfSafeTeleport();
110}
111
112
113bool Killbots::Engine::canUseVaporizer() const
114{
115 return m_rules->vaporizerEnabled()
116 && m_energy >= m_rules->costOfVaporizer();
117}
118
119
120void Killbots::Engine::startNewGame()
121{
122 Q_ASSERT( m_rules != 0 );
123
124 // Don't show the new game message on first start.
125 if ( m_round != 0 )
126 emit showNewGameMessage();
127
128 m_heroIsDead = false;
129
130 m_round = 1;
131 m_score = 0;
132 m_maxEnergy = m_rules->energyEnabled() ? m_rules->maxEnergyAtGameStart() : 0;
133 m_energy = m_rules->energyEnabled() ? m_rules->energyAtGameStart() : 0;
134 m_robotCount = m_rules->enemiesAtGameStart();
135 m_fastbotCount = m_rules->fastEnemiesAtGameStart();
136 m_junkheapCount = m_rules->junkheapsAtGameStart();
137
138 emit teleportAllowed( true );
139 emit waitOutRoundAllowed( true );
140 emit teleportSafelyAllowed( canSafeTeleport() );
141 emit vaporizerAllowed( canUseVaporizer() );
142
143 // Code used to generate theme previews
144 //newRound( " r\nhjf", false );
145
146 startNewRound( false );
147}
148
149
150void Killbots::Engine::startNewRound( bool incrementRound, const QString & layout )
151{
152 cleanUpRound();
153
154 m_waitingOutRound = false;
155
156 m_coordinator->beginNewAnimationStage();
157
158 if ( incrementRound )
159 {
160 ++m_round;
161
162 if ( m_rules->energyEnabled() )
163 {
164 m_maxEnergy += m_rules->maxEnergyAddedEachRound();
165 updateEnergy( m_rules->energyAddedEachRound() );
166 }
167 m_robotCount += m_rules->enemiesAddedEachRound();
168 m_fastbotCount += m_rules->fastEnemiesAddedEachRound();
169 m_junkheapCount += m_rules->junkheapsAddedEachRound();
170 }
171
172 if ( layout.isEmpty() )
173 {
174 // Place the hero in the centre of the board.
175 const QPoint centre = QPoint( qRound( m_rules->columns() / 2 ), qRound( m_rules->rows() / 2 ) );
176 m_hero = m_coordinator->createSprite( Hero, centre );
177
178 // Create and randomly place junkheaps.
179 for ( int i = m_junkheapCount; i > 0 ; --i )
180 {
181 const QPoint point = randomEmptyCell();
182 m_junkheaps << m_coordinator->createSprite( Junkheap, point );
183 m_spriteMap.insert( point, m_junkheaps.last() );
184 }
185
186 // Create and randomly place robots.
187 for ( int i = m_robotCount; i > 0; --i )
188 {
189 const QPoint point = randomEmptyCell();
190 m_bots << m_coordinator->createSprite( Robot, point );
191 m_spriteMap.insert( point, m_bots.last() );
192 }
193
194 // Create and randomly place fastbots.
195 for ( int i = m_fastbotCount; i > 0; --i )
196 {
197 const QPoint point = randomEmptyCell();
198 m_bots << m_coordinator->createSprite( Fastbot, point );
199 m_spriteMap.insert( point, m_bots.last() );
200 }
201 }
202 else
203 {
204 const QStringList rows = layout.split('\n');
205 for ( int r = 0; r < rows.size(); ++r )
206 {
207 for ( int c = 0; c < rows.at( r ).size(); ++c )
208 {
209 const QChar ch = rows.at( r ).at( c );
210 const QPoint point( c, r );
211
212 if ( ch == 'h' && m_hero == 0 )
213 m_hero = m_coordinator->createSprite( Hero, point );
214 else if ( ch == 'r' )
215 m_bots << m_coordinator->createSprite( Robot, point );
216 else if ( ch == 'f' )
217 m_bots << m_coordinator->createSprite( Fastbot, point );
218 else if ( ch == 'j' )
219 m_junkheaps << m_coordinator->createSprite( Junkheap, point );
220 }
221 }
222 }
223
224 emit roundChanged( m_round );
225 emit scoreChanged( m_score );
226 emit enemyCountChanged( m_bots.size() );
227 emit energyChanged( m_energy );
228
229 refreshSpriteMap();
230}
231
232
233// Returns true if the move was performed, returns false otherwise.
234bool Killbots::Engine::moveHero( Killbots::HeroAction direction )
235{
236 refreshSpriteMap();
237 const QPoint newCell = m_hero->gridPos() + offsetFromDirection( direction );
238 const bool preventUnsafeMoves = Settings::preventUnsafeMoves() || direction < 0;
239
240 if ( moveIsValid( newCell, direction ) && ( moveIsSafe( newCell, direction ) || !preventUnsafeMoves ) )
241 {
242 if ( direction != Hold )
243 {
244 m_coordinator->beginNewAnimationStage();
245
246 if ( spriteTypeAt( newCell ) == Junkheap )
247 pushJunkheap( m_spriteMap.value( newCell ), direction );
248
249 m_coordinator->slideSprite( m_hero, newCell );
250 }
251 return true;
252 }
253 else
254 {
255 return false;
256 }
257}
258
259
260// Always returns true as teleports always succeed.
261bool Killbots::Engine::teleportHero()
262{
263 refreshSpriteMap();
264 const QPoint point = randomEmptyCell();
265 m_coordinator->beginNewAnimationStage();
266 m_coordinator->teleportSprite( m_hero, point );
267 return true;
268}
269
270
271// Returns true if a safe cell was found. If no safe cell was found than
272// the board must be full.
273bool Killbots::Engine::teleportHeroSafely()
274{
275 refreshSpriteMap();
276
277 // Choose a random cell...
278 const QPoint startPoint = QPoint( KRandom::random() % m_rules->columns(), KRandom::random() % m_rules->rows() );
279 QPoint point = startPoint;
280
281 // ...and step through all the cells on the board looking for a safe cell.
282 do
283 {
284 if ( point.x() < m_rules->columns() - 1 )
285 point.rx()++;
286 else
287 {
288 point.rx() = 0;
289 if ( point.y() < m_rules->rows() - 1 )
290 point.ry()++;
291 else
292 point.ry() = 0;
293 }
294
295 // Looking for an empty and safe cell.
296 if ( spriteTypeAt( point ) == NoSprite && point != m_hero->gridPos() && moveIsSafe( point, Teleport ) )
297 break;
298 }
299 while ( point != startPoint );
300
301 // If we stepped through every cell and found none that were safe, reset the robot counts.
302 if ( point == startPoint )
303 {
304 return false;
305 }
306 else
307 {
308 m_coordinator->beginNewAnimationStage();
309 updateEnergy( -m_rules->costOfSafeTeleport() );
310 m_coordinator->teleportSprite( m_hero, point );
311
312 return true;
313 }
314}
315
316
317// Returns true if any enemies were within range.
318bool Killbots::Engine::useVaporizer()
319{
320 refreshSpriteMap();
321 QList<Sprite *> neighbors;
322 for ( int i = Right; i <= DownRight; ++i )
323 {
324 const QPoint neighbor = m_hero->gridPos() + offsetFromDirection( i );
325 if ( cellIsValid( neighbor ) && ( spriteTypeAt( neighbor ) == Robot || spriteTypeAt( neighbor ) == Fastbot ) )
326 neighbors << m_spriteMap.value(neighbor);
327 }
328
329 if ( !neighbors.isEmpty() )
330 {
331 m_coordinator->beginNewAnimationStage();
332 foreach ( Sprite * sprite, neighbors )
333 destroySprite( sprite );
334 updateEnergy( -m_rules->costOfVaporizer() );
335 return true;
336 }
337 else
338 {
339 return false;
340 }
341}
342
343
344bool Killbots::Engine::waitOutRound()
345{
346 m_waitingOutRound = true;
347 return true;
348}
349
350
351void Killbots::Engine::moveRobots( bool justFastbots )
352{
353 m_coordinator->beginNewAnimationStage();
354
355 if ( justFastbots )
356 {
357 refreshSpriteMap();
358 foreach( Sprite * bot, m_bots )
359 {
360 if ( bot->spriteType() == Fastbot )
361 {
362 const QPoint offset( sign( m_hero->gridPos().x() - bot->gridPos().x() ), sign( m_hero->gridPos().y() - bot->gridPos().y() ) );
363 const QPoint target = bot->gridPos() + offset;
364 if ( spriteTypeAt( target ) != Robot || !m_rules->fastEnemiesArePatient() )
365 m_coordinator->slideSprite( bot, target );
366 }
367 }
368 }
369 else
370 {
371 foreach( Sprite * bot, m_bots )
372 {
373 const QPoint offset( sign( m_hero->gridPos().x() - bot->gridPos().x() ), sign( m_hero->gridPos().y() - bot->gridPos().y() ) );
374 m_coordinator->slideSprite( bot, bot->gridPos() + offset );
375 }
376 }
377}
378
379
380void Killbots::Engine::assessDamage()
381{
382 refreshSpriteMap();
383
384 m_coordinator->beginNewAnimationStage();
385
386 if ( m_spriteMap.count( m_hero->gridPos() ) > 0 )
387 m_heroIsDead = true;
388
389 // Check junkheaps for dead robots
390 foreach ( Sprite * junkheap, m_junkheaps )
391 destroyAllCollidingBots( junkheap, !m_heroIsDead );
392
393 // Check for robot-on-robot violence
394 int i = 0;
395 while ( i < m_bots.size() )
396 {
397 Sprite * bot = m_bots[i];
398 if ( bot->gridPos() != m_hero->gridPos() && destroyAllCollidingBots( bot, !m_heroIsDead ) )
399 {
400 m_junkheaps << m_coordinator->createSprite( Junkheap, bot->gridPos() );
401 destroySprite( bot, !m_heroIsDead );
402 }
403 else
404 {
405 i++;
406 }
407 }
408
409 if ( isRoundComplete() )
410 {
411 m_coordinator->beginNewAnimationStage();
412 emit showRoundCompleteMessage();
413 }
414}
415
416
417void Killbots::Engine::resetBotCounts()
418{
419 m_coordinator->beginNewAnimationStage();
420 emit showBoardFullMessage();
421
422 m_maxEnergy = m_rules->maxEnergyAtGameStart();
423 m_robotCount = m_rules->enemiesAtGameStart();
424 m_fastbotCount = m_rules->fastEnemiesAtGameStart();
425 m_junkheapCount = m_rules->junkheapsAtGameStart();
426
427 m_coordinator->beginNewAnimationStage();
428 startNewRound(false);
429}
430
431
432void Killbots::Engine::endGame()
433{
434 emit showGameOverMessage();
435 emit teleportAllowed( false );
436 emit waitOutRoundAllowed( false );
437 emit teleportSafelyAllowed( false );
438 emit vaporizerAllowed( false );
439 emit gameOver( m_score, m_round );
440}
441
442
443// The hero action functions and the assessDamage functions must know the
444// contents of each cell. This function updates the hash that maps cells to
445// their contents.
446void Killbots::Engine::refreshSpriteMap()
447{
448 m_spriteMap.clear();
449 foreach( Sprite * bot, m_bots )
450 m_spriteMap.insert( bot->gridPos(), bot );
451 foreach( Sprite * junkheap, m_junkheaps )
452 m_spriteMap.insert( junkheap->gridPos(), junkheap );
453}
454
455
456// A convenience function to query the type of a sprite any the given cell.
457int Killbots::Engine::spriteTypeAt( const QPoint & cell ) const
458{
459 if ( m_spriteMap.contains( cell ) )
460 return m_spriteMap.value( cell )->spriteType();
461 else
462 return NoSprite;
463}
464
465
466QPoint Killbots::Engine::offsetFromDirection( int direction ) const
467{
468 if ( direction < 0 )
469 direction = -direction - 1;
470
471 switch( direction )
472 {
473 case Right:
474 return QPoint( 1, 0 );
475 case UpRight:
476 return QPoint( 1, -1 );
477 case Up:
478 return QPoint( 0, -1 );
479 case UpLeft:
480 return QPoint( -1, -1 );
481 case Left:
482 return QPoint( -1, 0 );
483 case DownLeft:
484 return QPoint( -1, 1 );
485 case Down:
486 return QPoint( 0, 1 );
487 case DownRight:
488 return QPoint( 1, 1 );
489 default:
490 return QPoint( 0, 0 );
491 };
492}
493
494
495// Returns a random empty cell on the grid. Depends on a fresh spritemap.
496QPoint Killbots::Engine::randomEmptyCell() const
497{
498 QPoint point;
499 do
500 point = QPoint( KRandom::random() % m_rules->columns(), KRandom::random() % m_rules->rows() );
501 while ( spriteTypeAt( point ) != NoSprite || point == m_hero->gridPos() );
502 return point;
503}
504
505
506// Returns true if the given cell lies inside the game grid.
507bool Killbots::Engine::cellIsValid( const QPoint & cell ) const
508{
509 return ( 0 <= cell.x()
510 && cell.x() < m_rules->columns()
511 && 0 <= cell.y()
512 && cell.y() < m_rules->rows()
513 );
514}
515
516
517bool Killbots::Engine::moveIsValid( const QPoint & cell, HeroAction direction ) const
518{
519 // The short version
520 return ( cellIsValid( cell )
521 && ( spriteTypeAt( cell ) == NoSprite
522 || ( spriteTypeAt( cell ) == Junkheap
523 && canPushJunkheap( m_spriteMap.value( cell ), direction )
524 )
525 )
526 );
527
528
529/* // The debuggable version
530 bool result = true;
531
532 if ( cellIsValid( cell ) )
533 {
534 if ( spriteTypeAt( cell ) != NoSprite )
535 {
536 if ( spriteTypeAt( cell ) == Junkheap )
537 {
538 if ( !canPushJunkheap( m_spriteMap.value( cell ), direction ) )
539 {
540 result = false;
541 kDebug() << "Move is invalid. Cannot push junkheap.";
542 }
543 }
544 else
545 {
546 result = false;
547 kDebug() << "Move is invalid. Cell is occupied by an unpushable object.";
548 }
549 }
550 }
551 else
552 {
553 result = false;
554 kDebug() << "Move is invalid. Cell is lies outside grid.";
555 }
556
557 return result;
558*/
559}
560
561
562bool Killbots::Engine::moveIsSafe( const QPoint & cell, HeroAction direction ) const
563{
564 /*
565 Warning: This algorithm might break your head. The following diagrams and descriptions try to help.
566
567 Note: This algorithm assumes that the proposed move has already been checked for validity.
568
569 Legend
570 H = The position of the hero after the proposed move (the cell who's safeness we're trying to determine).
571 J = The position of a junkheap after the proposed move, whether moved by the hero or sitting there already.
572 R = The position of a robot.
573 F = The position of a fastbot.
574 * = A cell that we don't particularly care about in this diagram.
575
576 +---+---+---+---+---+
577 | * | * | * | * | * |
578 +---+---+---+---+---+
579 | * | | | F | * |
580 +---+---+---+---+---+
581 | * | | H | | * | If any of the neighbouring cells contain a robot or fastbot, the move is unsafe.
582 +---+---+---+---+---+
583 | * | | R | | * |
584 +---+---+---+---+---+
585 | * | * | * | * | * |
586 +---+---+---+---+---+
587
588 +---+---+---+---+---+
589 | | | | | |
590 +---+---+---+---+---+
591 | | | | | |
592 +---+---+---+---+---+
593 | | *<==J<==H | | If the proposed move involved pushing a junkheap, we can ignore the cell that the junkheap
594 +---+---+---+---+---+ will end up in, because if there were an enemy there, it would be crushed.
595 | | | | | |
596 +---+---+---+---+---+
597 | | | | | |
598 +---+---+---+---+---+
599
600 +---+---+---+---+---+
601 |C01| | | | |
602 +---+---+---+---+---+ Fastbots can attack from two cells away, making it trickier to determine whether they
603 | |N01| | |E01| pose a threat. First we have to understand the attack vector of a fastbot. A fastbot
604 +---+---+---+---+---+ attacking from a "corner" cell such as C01 will pass through a diagonal neighbour like
605 | | | H |N02|E02| like N01. Any fastbot attacking from an "edge" cell like E01, E02 or E03 will have to
606 +---+---+---+---+---+ pass through a horizontal or vertical neighbour like N02. This mean that when checking
607 | | | | |E03| a diagonal neighbour we only need to check the one cell "behind" it for fastbots, but
608 +---+---+---+---+---+ when checking a horizontal or vertical neighbour we need to check the three cells
609 | | | | | | "behind" it for fastbots.
610 +---+---+---+---+---+
611
612 +---+---+---+---+---+
613 | | | | | * |
614 +---+---+---+---+---+
615 | * | | | J | |
616 +---+---+---+---+---+ Back to junkheaps. If a neighbouring cell contains a junkheap, we don't need to check
617 | * | J | H | | | the cells behind it for fastbots because if there were any there, they'd just collide
618 +---+---+---+---+---+ with the junkheap anyway.
619 | * | | | | |
620 +---+---+---+---+---+
621 | | | | | |
622 +---+---+---+---+---+
623
624 +---+---+---+---+---+
625 | * | * | * | * | F |
626 +---+---+---+---+---+
627 | * | * | * | | * |
628 +---+---+---+---+---+
629 | * | * | H | * | * | "Corner" fastbot threats are easy enough to detect. If a diagonal neighbour is empty
630 +---+---+---+---+---+ and the cell behind it contains a fastbot, the move is unsafe.
631 | * | * | * | * | * |
632 +---+---+---+---+---+
633 | * | * | * | * | * |
634 +---+---+---+---+---+
635
636 +---+---+---+---+---+
637 | * | * | * | * | * |
638 +---+---+---+---+---+
639 | R | * | * | * | * |
640 +---+---+---+---+---+ "Edge" fastbots threats are much harder to detect because any fastbots on an edge might
641 | F | | H | * | * | collide with robots or other fastbots on their way to the neighbouring cell. For example,
642 +---+---+---+---+---+ the hero in this diagram is perfectly safe because all the fastbots will be destroyed
643 | | * | | * | * | before they can become dangerous.
644 +---+---+---+---+---+
645 | * | F | | F | * |
646 +---+---+---+---+---+
647
648 +---+---+---+---+---+
649 | * | F | | | * |
650 +---+---+---+---+---+
651 | * | * | | * | |
652 +---+---+---+---+---+ With a bit of thought, it's easy to see that an "edge" fastbot is only a threat if there
653 | * | * | H | | | is exactly one fastbot and zero robots on that edge.
654 +---+---+---+---+---+
655 | * | * | | * | F | When you put all of the above facts together you (hopefully) get the following algorithm.
656 +---+---+---+---+---+
657 | * | | F | | * |
658 +---+---+---+---+---+
659 */
660
661 // The move is assumed safe until proven unsafe.
662 bool result = true;
663
664 // If we're pushing a junkheap, store the cell that the junkheap will end up in. Otherwise store an invalid cell.
665 const QPoint cellBehindJunkheap = ( spriteTypeAt( cell ) != Junkheap )
666 ? QPoint( -1, -1 )
667 : cell + offsetFromDirection( direction );
668
669 // We check the each of the target cells neighbours.
670 for ( int i = Right; i <= DownRight && result; ++i )
671 {
672 const QPoint neighbor = cell + offsetFromDirection( i );
673
674 // If the neighbour is invalid or the cell behind the junkheap, continue to the next neighbour.
675 if ( !cellIsValid( neighbor ) || spriteTypeAt( neighbor ) == Junkheap || neighbor == cellBehindJunkheap )
676 continue;
677
678 // If the neighbour contains an enemy, the move is unsafe.
679 if ( spriteTypeAt( neighbor ) == Robot || spriteTypeAt( neighbor ) == Fastbot )
680 {
681 result = false;
682 }
683 else
684 {
685 // neighboursNeighbour is the cell behind the neighbour, with respect to the target cell.
686 const QPoint neighborsNeighbor = neighbor + offsetFromDirection( i );
687
688 // If we're examining a diagonal neighbour (an odd direction)...
689 if ( i % 2 == 1 )
690 {
691 // ...and neighboursNeighbour is a fastbot then the move is unsafe.
692 if ( spriteTypeAt( neighborsNeighbor ) == Fastbot )
693 result = false;
694 }
695 // If we're examining an vertical or horizontal neighbour, things are more complicated...
696 else
697 {
698 // Assemble a list of the cells behind the neighbour.
699 QList<QPoint> cellsBehindNeighbor;
700 cellsBehindNeighbor << neighborsNeighbor;
701 // Add neighboursNeighbour's anticlockwise neighbour.
702 // ( i + 2 ) % 8 is the direction a quarter turn anticlockwise from i.
703 cellsBehindNeighbor << neighborsNeighbor + offsetFromDirection( ( i + 2 ) % 8 );
704 // Add neighboursNeighbour's clockwise neighbour.
705 // ( i + 6 ) % 8 is the direction a quarter turn clockwise from i.
706 cellsBehindNeighbor << neighborsNeighbor + offsetFromDirection( ( i + 6 ) % 8 );
707
708 // Then we just count the number of fastbots and robots in the list of cells.
709 int fastbotsFound = 0;
710 int robotsFound = 0;
711 foreach( const QPoint & cell, cellsBehindNeighbor )
712 {
713 if ( spriteTypeAt( cell ) == Fastbot )
714 ++fastbotsFound;
715 else if ( spriteTypeAt( cell ) == Robot )
716 ++robotsFound;
717 }
718
719 // If there is exactly one fastbots and zero robots, the move is unsafe.
720 if ( fastbotsFound == 1 && robotsFound == 0 )
721 result = false;
722 }
723 }
724 }
725
726 return result;
727}
728
729
730bool Killbots::Engine::canPushJunkheap( const Sprite * junkheap, HeroAction direction ) const
731{
732 Q_ASSERT( junkheap->spriteType() == Junkheap );
733
734 const QPoint nextCell = junkheap->gridPos() + offsetFromDirection( direction );
735
736 if ( m_rules->pushableJunkheaps() != Ruleset::None && cellIsValid( nextCell ) )
737 {
738 if ( spriteTypeAt( nextCell ) == NoSprite )
739 return true;
740 else if ( spriteTypeAt( nextCell ) == Junkheap )
741 return m_rules->pushableJunkheaps() == Ruleset::Many && canPushJunkheap( m_spriteMap.value( nextCell ), direction );
742 else
743 return m_rules->squaskKillsEnabled();
744 }
745 else
746 {
747 return false;
748 }
749}
750
751
752void Killbots::Engine::pushJunkheap( Sprite * junkheap, HeroAction direction )
753{
754 const QPoint nextCell = junkheap->gridPos() + offsetFromDirection( direction );
755 Sprite * currentOccupant = m_spriteMap.value( nextCell );
756 if ( currentOccupant )
757 {
758 if ( currentOccupant->spriteType() == Junkheap )
759 {
760 pushJunkheap( currentOccupant, direction );
761 }
762 else
763 {
764 destroySprite( currentOccupant );
765 updateScore( m_rules->squashKillPointBonus() );
766 updateEnergy( m_rules->squashKillEnergyBonus() );
767 }
768 }
769
770 m_coordinator->slideSprite( junkheap, nextCell );
771}
772
773
774void Killbots::Engine::cleanUpRound()
775{
776 m_coordinator->beginNewAnimationStage();
777
778 if ( m_hero )
779 destroySprite( m_hero );
780 m_hero = 0;
781
782 foreach( Sprite * bot, m_bots )
783 destroySprite( bot, false );
784 m_bots.clear();
785
786 foreach( Sprite * junkheap, m_junkheaps )
787 destroySprite( junkheap );
788 m_junkheaps.clear();
789
790 m_spriteMap.clear();
791}
792
793
794void Killbots::Engine::destroySprite( Sprite * sprite, bool calculatePoints )
795{
796 const SpriteType type = sprite->spriteType();
797
798 if ( type == Robot || type == Fastbot)
799 {
800 if ( calculatePoints )
801 {
802 if ( type == Robot )
803 updateScore( m_rules->pointsPerEnemyKilled() );
804 else
805 updateScore( m_rules->pointsPerFastEnemyKilled() );
806
807 if ( m_waitingOutRound )
808 {
809 updateScore( m_rules->waitKillPointBonus() );
810 updateEnergy( m_rules->waitKillEnergyBonus() );
811 }
812 }
813 m_bots.removeOne( sprite );
814 emit enemyCountChanged( m_bots.size() );
815 }
816 else if ( type == Junkheap )
817 {
818 m_junkheaps.removeOne( sprite );
819 }
820
821 m_coordinator->destroySprite( sprite );
822}
823
824
825bool Killbots::Engine::destroyAllCollidingBots( const Sprite * sprite, bool calculatePoints )
826{
827 bool result = false;
828
829 foreach ( Sprite * robot, m_spriteMap.values( sprite->gridPos() ) )
830 {
831 if ( robot != sprite && ( robot->spriteType() == Robot || robot->spriteType() == Fastbot ) )
832 {
833 destroySprite( robot, calculatePoints );
834 result = true;
835 }
836 }
837
838 return result;
839}
840
841
842void Killbots::Engine::updateScore( int changeInScore )
843{
844 if ( changeInScore != 0 )
845 {
846 m_score = m_score + changeInScore;
847 emit scoreChanged( m_score );
848 }
849}
850
851
852void Killbots::Engine::updateEnergy( int changeInEnergy )
853{
854 if ( m_rules->energyEnabled() && changeInEnergy != 0 )
855 {
856 if ( changeInEnergy > 0 && m_energy > int( m_maxEnergy ) )
857 {
858 m_score += changeInEnergy * m_rules->pointsPerEnergyAboveMax();
859 }
860 else if ( changeInEnergy > 0 && m_energy + changeInEnergy > int( m_maxEnergy ) )
861 {
862 m_score += ( m_energy + changeInEnergy - int( m_maxEnergy ) ) * m_rules->pointsPerEnergyAboveMax();
863 m_energy = int( m_maxEnergy );
864 }
865 else
866 {
867 m_energy = m_energy + changeInEnergy;
868 }
869
870 emit energyChanged( m_energy );
871 emit teleportSafelyAllowed( canSafeTeleport() );
872 emit vaporizerAllowed( canUseVaporizer() );
873 }
874}
875
876
877QString Killbots::Engine::gridToString() const
878{
879 QString string;
880 for ( int r = 0; r < m_rules->rows(); ++r )
881 {
882 for ( int c = 0; c < m_rules->columns(); ++c )
883 {
884 switch ( spriteTypeAt( QPoint( c, r ) ) )
885 {
886 case Robot:
887 string += 'r';
888 break;
889 case Fastbot:
890 string += 'f';
891 break;
892 case Junkheap:
893 string += 'j';
894 break;
895 default:
896 string += ' ';
897 break;
898 }
899 }
900 string += '\n';
901 }
902 return string;
903}
904