1/*
2 * Copyright 2007-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 "coordinator.h"
21
22#include "engine.h"
23#include "numericdisplayitem.h"
24#include "ruleset.h"
25#include "scene.h"
26#include "settings.h"
27
28#include <kgamepopupitem.h>
29
30#include <KDE/KDebug>
31#include <KDE/KLocalizedString>
32
33#include <QtGui/QGraphicsView>
34
35#include <cmath>
36
37
38struct Killbots::Coordinator::AnimationStage
39{
40 AnimationStage()
41 : newRound( -1 ),
42 newScore( -1 ),
43 newEnemyCount( -1 ),
44 newEnergy( -1 )
45 {};
46
47 bool isEmpty() const
48 {
49 return spritesToCreate.isEmpty()
50 && spritesToSlide.isEmpty()
51 && spritesToTeleport.isEmpty()
52 && spritesToDestroy.isEmpty()
53 && message.isEmpty()
54 && newRound == -1
55 && newScore == -1
56 && newEnemyCount == -1
57 && newEnergy == -1;
58 };
59
60 QList<Sprite *> spritesToCreate;
61 QList<Sprite *> spritesToSlide;
62 QList<Sprite *> spritesToTeleport;
63 QList<Sprite *> spritesToDestroy;
64 QString message;
65 int oldRound, newRound;
66 int oldScore, newScore;
67 int oldEnemyCount, newEnemyCount;
68 int oldEnergy, newEnergy;
69};
70
71
72Killbots::Coordinator::Coordinator( QObject * parent )
73 : QObject( parent ),
74 m_engine( 0 ),
75 m_scene( 0 ),
76 m_roundDisplay( 0 ),
77 m_scoreDisplay( 0 ),
78 m_enemyCountDisplay( 0 ),
79 m_energyDisplay( 0 ),
80 m_unqueuedPopup( 0 ),
81 m_queuedPopup( 0 ),
82 m_timeLine( 1000, this ),
83 m_busyAnimating( false ),
84 m_newGameRequested( false ),
85 m_queuedAction( NoAction )
86{
87 m_timeLine.setCurveShape( QTimeLine::EaseInOutCurve );
88 connect( &m_timeLine, SIGNAL(valueChanged(qreal)), this, SLOT(animate(qreal)) );
89 connect( &m_timeLine, SIGNAL(finished()), this, SLOT(nextAnimationStage()) );
90}
91
92
93Killbots::Coordinator::~Coordinator()
94{
95}
96
97
98void Killbots::Coordinator::setEngine( Engine * engine )
99{
100 m_engine = engine;
101
102 connect( m_engine, SIGNAL(roundChanged(int)), this, SLOT(updateRound(int)) );
103 connect( m_engine, SIGNAL(scoreChanged(int)), this, SLOT(updateScore(int)) );
104 connect( m_engine, SIGNAL(enemyCountChanged(int)), this, SLOT(updateEnemyCount(int)) );
105 connect( m_engine, SIGNAL(energyChanged(int)), this, SLOT(updateEnergy(int)) );
106
107 connect( m_engine, SIGNAL(showNewGameMessage()), this, SLOT(showNewGameMessage()) );
108 connect( m_engine, SIGNAL(showRoundCompleteMessage()), this, SLOT(showRoundCompleteMessage()) );
109 connect( m_engine, SIGNAL(showBoardFullMessage()), this, SLOT(showBoardFullMessage()) );
110 connect( m_engine, SIGNAL(showGameOverMessage()), this, SLOT(showGameOverMessage()) );
111}
112
113
114void Killbots::Coordinator::setScene( Scene * scene )
115{
116 m_scene = scene;
117
118 m_roundDisplay = new NumericDisplayItem;
119 m_roundDisplay->setLabel( i18n("Round:") );
120 m_roundDisplay->setDigits( 2 );
121 m_scene->addNumericDisplay( m_roundDisplay );
122
123 m_scoreDisplay = new NumericDisplayItem;
124 m_scoreDisplay->setLabel( i18n("Score:") );
125 m_scoreDisplay->setDigits( 5 );
126 m_scene->addNumericDisplay( m_scoreDisplay );
127
128 m_enemyCountDisplay = new NumericDisplayItem;
129 m_enemyCountDisplay->setLabel( i18n("Enemies:") );
130 m_enemyCountDisplay->setDigits( 3 );
131 m_scene->addNumericDisplay( m_enemyCountDisplay );
132
133 m_energyDisplay = new NumericDisplayItem;
134 m_energyDisplay->setLabel( i18n("Energy:") );
135 m_energyDisplay->setDigits( 2 );
136 m_scene->addNumericDisplay( m_energyDisplay );
137
138 m_unqueuedPopup = new KGamePopupItem;
139 m_unqueuedPopup->setMessageOpacity( 0.85 );
140 m_unqueuedPopup->setHideOnMouseClick( true );
141 m_scene->addItem( m_unqueuedPopup );
142
143 m_queuedPopup = new KGamePopupItem;
144 m_queuedPopup->setMessageOpacity( 0.85 );
145 m_queuedPopup->setHideOnMouseClick( true );
146 m_scene->addItem( m_queuedPopup );
147
148 connect( m_queuedPopup, SIGNAL(hidden()), this, SLOT(nextAnimationStage()) );
149}
150
151
152void Killbots::Coordinator::setAnimationSpeed( int speed )
153{
154 // Equation converts speed in the range 0 to 10 to a duration in the range
155 // 1 to 0.05 seconds. There's probably a better way to do this.
156 m_timeLine.setDuration( int( pow( 1.35, -speed ) * 1000 ) );
157}
158
159
160void Killbots::Coordinator::requestNewGame()
161{
162 if ( !m_busyAnimating || m_engine->isHeroDead() )
163 startNewGame();
164 else
165 m_newGameRequested = true;
166}
167
168
169void Killbots::Coordinator::startNewGame()
170{
171 m_newGameRequested = false;
172 m_repeatedAction = NoAction;
173 m_queuedAction = NoAction;
174
175 const Ruleset * ruleset = m_engine->ruleset();
176 if ( !ruleset || ruleset->fileName() != Settings::ruleset() )
177 {
178 ruleset = Ruleset::load( Settings::ruleset() );
179 if ( !ruleset )
180 {
181 Settings::setRuleset( Settings::defaultRulesetValue() );
182 ruleset = Ruleset::load( Settings::ruleset() );
183 }
184 m_engine->setRuleset( ruleset );
185 }
186
187 m_energyDisplay->setVisible( ruleset->energyEnabled() );
188 m_scene->setGridSize( ruleset->rows(), ruleset->columns() );
189 m_scene->doLayout();
190
191 m_engine->startNewGame();
192
193 startAnimation();
194}
195
196
197void Killbots::Coordinator::requestAction( int action )
198{
199 // If we're doing a repeated move, ignore the request and just stop the current movement.
200 if ( m_repeatedAction != NoAction && m_repeatedAction != WaitOutRound )
201 {
202 m_repeatedAction = NoAction;
203 }
204 else if ( !m_engine->isHeroDead() )
205 {
206 if ( !m_busyAnimating )
207 {
208 doAction( HeroAction(action) );
209 }
210 else
211 {
212 m_queuedAction = HeroAction(action);
213 }
214 }
215}
216
217
218void Killbots::Coordinator::doAction( HeroAction action )
219{
220 bool actionSuccessful = false;
221 bool boardFull = false;
222
223 if ( action <= Hold )
224 {
225 actionSuccessful = m_engine->moveHero( action );
226 m_repeatedAction = action < 0 && actionSuccessful
227 ? action
228 : NoAction;
229 }
230 else if ( ( action == TeleportSafely || action == TeleportSafelyIfPossible )
231 && m_engine->canSafeTeleport()
232 )
233 {
234 actionSuccessful = m_engine->teleportHeroSafely();
235 boardFull = !actionSuccessful;
236 }
237 else if ( action == Teleport || action == TeleportSafelyIfPossible )
238 {
239 actionSuccessful = m_engine->teleportHero();
240 }
241 else if ( action == Vaporizer && m_engine->canUseVaporizer() )
242 {
243 actionSuccessful = m_engine->useVaporizer();
244 }
245 else if ( action == WaitOutRound )
246 {
247 actionSuccessful = m_engine->waitOutRound();
248 m_repeatedAction = WaitOutRound;
249 }
250
251 if ( actionSuccessful )
252 {
253 if ( action != Vaporizer )
254 m_engine->moveRobots();
255 m_engine->assessDamage();
256 if ( !m_engine->isRoundComplete() && action != Vaporizer )
257 {
258 m_engine->moveRobots( true );
259 m_engine->assessDamage();
260 }
261 startAnimation();
262 }
263 else if ( boardFull )
264 {
265 m_engine->resetBotCounts();
266 startAnimation();
267 }
268}
269
270
271void Killbots::Coordinator::animationDone()
272{
273 m_busyAnimating = false;
274
275 if ( m_newGameRequested )
276 {
277 startNewGame();
278 }
279 else if ( m_engine->isHeroDead() )
280 {
281 m_scene->forgetHero();
282 m_engine->endGame();
283 }
284 else if ( m_engine->isRoundComplete() )
285 {
286 m_repeatedAction = NoAction;
287 m_queuedAction = NoAction;
288 m_engine->startNewRound();
289 if ( m_engine->isBoardFull() )
290 m_engine->resetBotCounts();
291 startAnimation();
292 }
293 else if ( m_repeatedAction != NoAction )
294 {
295 doAction( m_repeatedAction );
296 }
297 else if ( m_queuedAction != NoAction )
298 {
299 doAction( m_queuedAction );
300 m_queuedAction = NoAction;
301 }
302}
303
304
305void Killbots::Coordinator::startAnimation()
306{
307 m_busyAnimating = true;
308 startAnimationStage();
309}
310
311
312void Killbots::Coordinator::startAnimationStage()
313{
314 const QString & message = m_stages.first().message;
315
316 if ( m_timeLine.duration() < 60 && message.isEmpty() )
317 {
318 animate( 1.0 );
319 nextAnimationStage();
320 }
321 else
322 {
323 if ( !message.isEmpty() )
324 showQueuedMessage( message );
325
326 m_timeLine.start();
327 }
328}
329
330
331void Killbots::Coordinator::animate( qreal value )
332{
333 AnimationStage & stage = m_stages.first();
334
335 if ( stage.newRound != -1 )
336 m_roundDisplay->setValue( int( stage.oldRound + value * ( stage.newRound - stage.oldRound ) ) );
337
338 if ( stage.newScore != -1 )
339 m_scoreDisplay->setValue( int( stage.oldScore + value * ( stage.newScore - stage.oldScore ) ) );
340
341 if ( stage.newEnemyCount != -1 )
342 m_enemyCountDisplay->setValue( int( stage.oldEnemyCount + value * ( stage.newEnemyCount - stage.oldEnemyCount ) ) );
343
344 if ( stage.newEnergy != -1 )
345 m_energyDisplay->setValue( int( stage.oldEnergy + value * ( stage.newEnergy - stage.oldEnergy ) ) );
346
347 m_scene->animateSprites( stage.spritesToCreate,
348 stage.spritesToSlide,
349 stage.spritesToTeleport,
350 stage.spritesToDestroy,
351 value
352 );
353}
354
355
356void Killbots::Coordinator::nextAnimationStage()
357{
358 // Wait for both the timeline and the popup to finish before moving to the next stage.
359 if ( m_timeLine.state() != QTimeLine::Running && !m_queuedPopup->isVisible() )
360 {
361 m_stages.removeFirst();
362
363 if ( m_stages.size() )
364 startAnimationStage();
365 else
366 animationDone();
367 }
368}
369
370
371void Killbots::Coordinator::beginNewAnimationStage()
372{
373 if ( m_stages.isEmpty() )
374 {
375 AnimationStage newStage;
376 newStage.oldRound = m_roundDisplay->value();
377 newStage.oldScore = m_scoreDisplay->value();
378 newStage.oldEnemyCount = m_enemyCountDisplay->value();
379 newStage.oldEnergy = m_energyDisplay->value();
380 m_stages << newStage;
381 }
382 else if ( !m_stages.last().isEmpty() )
383 {
384 AnimationStage newStage;
385 const AnimationStage & lastStage = m_stages.last();
386 newStage.oldRound = lastStage.newRound == -1 ? lastStage.oldRound : lastStage.newRound;
387 newStage.oldScore = lastStage.newScore == -1 ? lastStage.oldScore : lastStage.newScore;
388 newStage.oldEnemyCount = lastStage.newEnemyCount == -1 ? lastStage.oldEnemyCount : lastStage.newEnemyCount;
389 newStage.oldEnergy = lastStage.newEnergy == -1 ? lastStage.oldEnergy : lastStage.newEnergy;
390 m_stages << newStage;
391 }
392}
393
394
395Killbots::Sprite * Killbots::Coordinator::createSprite( SpriteType type, QPoint position )
396{
397 Sprite * sprite = m_scene->createSprite( type, position );
398 m_stages.last().spritesToCreate << sprite;
399 return sprite;
400}
401
402
403void Killbots::Coordinator::slideSprite( Sprite * sprite, QPoint position )
404{
405 sprite->enqueueGridPos( position );
406 m_stages.last().spritesToSlide << sprite;
407}
408
409
410void Killbots::Coordinator::teleportSprite( Sprite * sprite, QPoint position )
411{
412 sprite->enqueueGridPos( position );
413 m_stages.last().spritesToTeleport << sprite;
414}
415
416
417void Killbots::Coordinator::destroySprite( Sprite * sprite )
418{
419 if ( sprite->spriteType() == Hero )
420 m_scene->forgetHero();
421 m_stages.last().spritesToDestroy << sprite;
422}
423
424
425void Killbots::Coordinator::updateRound( int round )
426{
427 m_stages.last().newRound = round;
428}
429
430
431void Killbots::Coordinator::updateScore( int score )
432{
433 m_stages.last().newScore = score;
434}
435
436
437void Killbots::Coordinator::updateEnemyCount( int enemyCount )
438{
439 m_stages.last().newEnemyCount = enemyCount;
440}
441
442
443void Killbots::Coordinator::updateEnergy( int energy )
444{
445 m_stages.last().newEnergy = energy;
446}
447
448
449void Killbots::Coordinator::showQueuedMessage( const QString & message )
450{
451 if ( m_unqueuedPopup->isVisible() )
452 m_unqueuedPopup->hide();
453 KGamePopupItem::Position corner = m_scene->views().first()->layoutDirection() == Qt::LeftToRight ? KGamePopupItem::TopRight : KGamePopupItem::TopLeft;
454 m_queuedPopup->setMessageTimeout( 3000 );
455 m_queuedPopup->showMessage( message, corner, KGamePopupItem::ReplacePrevious );
456}
457
458
459void Killbots::Coordinator::showUnqueuedMessage( const QString & message, int timeout )
460{
461 if ( !m_queuedPopup->isVisible() )
462 {
463 KGamePopupItem::Position corner = m_scene->views().first()->layoutDirection() == Qt::LeftToRight ? KGamePopupItem::TopRight : KGamePopupItem::TopLeft;
464 m_unqueuedPopup->setMessageTimeout( timeout );
465 m_unqueuedPopup->showMessage( message, corner, KGamePopupItem::ReplacePrevious );
466 }
467}
468
469void Killbots::Coordinator::showRoundCompleteMessage()
470{
471 m_stages.last().message = i18n("Round complete.");
472}
473
474
475void Killbots::Coordinator::showBoardFullMessage()
476{
477 m_stages.last().message = i18n("Board is full.\nResetting enemy counts.");
478}
479
480
481void Killbots::Coordinator::showNewGameMessage()
482{
483 showUnqueuedMessage( i18n("New game.") );
484}
485
486
487void Killbots::Coordinator::showGameOverMessage()
488{
489 showUnqueuedMessage( i18n("Game over."), 15000 );
490}
491
492
493#include "moc_coordinator.cpp"
494