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 | |
38 | struct 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 | |
72 | Killbots::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 | |
93 | Killbots::Coordinator::~Coordinator() |
94 | { |
95 | } |
96 | |
97 | |
98 | void 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 | |
114 | void 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 | |
152 | void 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 | |
160 | void Killbots::Coordinator::requestNewGame() |
161 | { |
162 | if ( !m_busyAnimating || m_engine->isHeroDead() ) |
163 | startNewGame(); |
164 | else |
165 | m_newGameRequested = true; |
166 | } |
167 | |
168 | |
169 | void 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 | |
197 | void 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 | |
218 | void 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 | |
271 | void 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 | |
305 | void Killbots::Coordinator::startAnimation() |
306 | { |
307 | m_busyAnimating = true; |
308 | startAnimationStage(); |
309 | } |
310 | |
311 | |
312 | void 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 | |
331 | void 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 | |
356 | void 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 | |
371 | void 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 | |
395 | Killbots::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 | |
403 | void Killbots::Coordinator::slideSprite( Sprite * sprite, QPoint position ) |
404 | { |
405 | sprite->enqueueGridPos( position ); |
406 | m_stages.last().spritesToSlide << sprite; |
407 | } |
408 | |
409 | |
410 | void Killbots::Coordinator::teleportSprite( Sprite * sprite, QPoint position ) |
411 | { |
412 | sprite->enqueueGridPos( position ); |
413 | m_stages.last().spritesToTeleport << sprite; |
414 | } |
415 | |
416 | |
417 | void Killbots::Coordinator::destroySprite( Sprite * sprite ) |
418 | { |
419 | if ( sprite->spriteType() == Hero ) |
420 | m_scene->forgetHero(); |
421 | m_stages.last().spritesToDestroy << sprite; |
422 | } |
423 | |
424 | |
425 | void Killbots::Coordinator::updateRound( int round ) |
426 | { |
427 | m_stages.last().newRound = round; |
428 | } |
429 | |
430 | |
431 | void Killbots::Coordinator::updateScore( int score ) |
432 | { |
433 | m_stages.last().newScore = score; |
434 | } |
435 | |
436 | |
437 | void Killbots::Coordinator::updateEnemyCount( int enemyCount ) |
438 | { |
439 | m_stages.last().newEnemyCount = enemyCount; |
440 | } |
441 | |
442 | |
443 | void Killbots::Coordinator::updateEnergy( int energy ) |
444 | { |
445 | m_stages.last().newEnergy = energy; |
446 | } |
447 | |
448 | |
449 | void 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 | |
459 | void 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 | |
469 | void Killbots::Coordinator::showRoundCompleteMessage() |
470 | { |
471 | m_stages.last().message = i18n("Round complete." ); |
472 | } |
473 | |
474 | |
475 | void Killbots::Coordinator::showBoardFullMessage() |
476 | { |
477 | m_stages.last().message = i18n("Board is full.\nResetting enemy counts." ); |
478 | } |
479 | |
480 | |
481 | void Killbots::Coordinator::showNewGameMessage() |
482 | { |
483 | showUnqueuedMessage( i18n("New game." ) ); |
484 | } |
485 | |
486 | |
487 | void Killbots::Coordinator::showGameOverMessage() |
488 | { |
489 | showUnqueuedMessage( i18n("Game over." ), 15000 ); |
490 | } |
491 | |
492 | |
493 | #include "moc_coordinator.cpp" |
494 | |