2 * Copyright 2007-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/>.
22 #include "gamestatusdisplayitem.h"
28 #include <kgamepopupitem.h>
31 #include <KDE/KLocalizedString>
32 #include <KDE/KStandardDirs>
34 #include <QtGui/QFontInfo>
35 #include <QtGui/QPainter>
36 #include <QtGui/QGraphicsSceneMouseEvent>
37 #include <QtGui/QGraphicsTextItem>
38 #include <QtGui/QGraphicsView>
43 struct Killbots::Scene::AnimationStage
54 return spritesToCreate
.isEmpty()
55 && spritesToSlide
.isEmpty()
56 && spritesToTeleport
.isEmpty()
57 && spritesToDestroy
.isEmpty()
61 && newEnemyCount
== -1
65 QList
<Sprite
*> spritesToCreate
;
66 QList
<Sprite
*> spritesToSlide
;
67 QList
<Sprite
*> spritesToTeleport
;
68 QList
<Sprite
*> spritesToDestroy
;
70 int oldRound
, newRound
;
71 int oldScore
, newScore
;
72 int oldEnemyCount
, newEnemyCount
;
73 int oldEnergy
, newEnergy
;
77 Killbots::Scene::Scene( QObject
* parent
)
78 : QGraphicsScene( parent
),
80 m_timeLine( 1000, this ),
81 m_unqueuedPopup( new KGamePopupItem
),
82 m_queuedPopup( new KGamePopupItem
),
83 m_roundDisplay( new GameStatusDisplayItem() ),
84 m_scoreDisplay( new GameStatusDisplayItem() ),
85 m_enemyCountDisplay( new GameStatusDisplayItem() ),
86 m_energyDisplay( new GameStatusDisplayItem() ),
90 setItemIndexMethod( QGraphicsScene::NoIndex
);
92 m_timeLine
.setCurveShape( QTimeLine::EaseInOutCurve
);
93 setAnimationSpeed( Settings::animationSpeed() );
94 connect( &m_timeLine
, SIGNAL(valueChanged(qreal
)), this, SLOT(animate(qreal
)) );
95 connect( &m_timeLine
, SIGNAL(finished()), this, SIGNAL(animationStageDone()) );
96 connect( this, SIGNAL(animationStageDone()), this, SLOT(nextAnimationStage()) );
98 m_unqueuedPopup
->setMessageOpacity( 0.85 );
99 m_unqueuedPopup
->setHideOnMouseClick( true );
100 addItem( m_unqueuedPopup
);
102 m_queuedPopup
->setMessageOpacity( 0.85 );
103 m_queuedPopup
->setHideOnMouseClick( true );
104 addItem( m_queuedPopup
);
105 connect( m_queuedPopup
, SIGNAL(hidden()), this, SIGNAL(animationStageDone()) );
107 m_roundDisplay
->setText( i18n("Round:") );
108 m_roundDisplay
->setDigits( 2 );
109 addItem( m_roundDisplay
);
111 m_scoreDisplay
->setText( i18n("Score:") );
112 m_scoreDisplay
->setDigits( 5 );
113 addItem( m_scoreDisplay
);
115 m_enemyCountDisplay
->setText( i18n("Enemies:") );
116 m_enemyCountDisplay
->setDigits( 3 );
117 addItem( m_enemyCountDisplay
);
119 m_energyDisplay
->setText( i18n("Energy:") );
120 m_energyDisplay
->setDigits( 2 );
121 addItem( m_energyDisplay
);
125 Killbots::Scene::~Scene()
130 void Killbots::Scene::doLayout()
132 QSize size
= views().first()->size();
134 QList
<GameStatusDisplayItem
*> displayList
;
135 displayList
<< m_roundDisplay
<< m_scoreDisplay
<< m_enemyCountDisplay
;
136 if ( m_energyDisplay
->isVisible() )
137 displayList
<< m_energyDisplay
;
139 // If no game has been started
140 if ( m_rows
== 0 || m_columns
== 0 )
142 setSceneRect( QRectF( QPointF( 0, 0 ), size
) );
143 foreach ( GameStatusDisplayItem
* display
, displayList
)
144 display
->setPos( -1000000, 0 );
148 kDebug() << "Laying out scene at" << size
;
150 // Make certain layout properties proportional to the scene height,
151 // but clamp them between reasonable values. There's probably room for more
153 const int baseDimension
= qMin( size
.width(), size
.height() ) / 35;
154 const int spacing
= qBound( 5, baseDimension
, 15 );
155 const int newPixelSize
= qBound( QFontInfo( QFont() ).pixelSize(), baseDimension
, 25 );
156 const qreal aspectRatio
= Render::aspectRatio();
159 // If the font size has changed, resize the display items.
160 // Note that we check the font size of the last display in the list so we
161 // notice if the energy display has just been included.
162 if ( displayList
.last()->font().pixelSize() != newPixelSize
)
164 QFont font
= displayList
.first()->font();
165 font
.setPixelSize( newPixelSize
);
167 foreach ( GameStatusDisplayItem
* display
, displayList
)
169 display
->setFont( font
);
170 QSize preferredSize
= display
->preferredSize();
171 if ( preferredSize
.width() > displaySize
.width() )
172 displaySize
.setWidth( preferredSize
.width() );
173 if ( preferredSize
.height() > displaySize
.height() )
174 displaySize
.setHeight( preferredSize
.height() );
176 foreach ( GameStatusDisplayItem
* display
, displayList
)
177 display
->setSize( displaySize
);
181 displaySize
= displayList
.first()->boundingRect().size().toSize();
184 // Determine the total width required to arrange the displays horizontally.
185 int widthOfDisplaysOnTop
= displayList
.size() * displaySize
.width()
186 + ( displayList
.size() - 1 ) * spacing
;
188 // The displays can either be placed centred, across the top of the
189 // scene or top-aligned, down the side of the scene. We first calculate
190 // what the cell size would be for both options.
192 int availableWidth
= size
.width() - 3 * spacing
- displaySize
.width();
193 int availableHeight
= size
.height() - 2 * spacing
;
194 if ( availableWidth
/ m_columns
< availableHeight
/ m_rows
* aspectRatio
)
195 cellWidthSide
= availableWidth
/ m_columns
;
197 cellWidthSide
= availableHeight
/ m_rows
* aspectRatio
;
200 availableWidth
= size
.width() - 2 * spacing
;
201 availableHeight
= size
.height() - 3 * spacing
- displaySize
.height();
202 if ( availableWidth
/ m_columns
< availableHeight
/ m_rows
* aspectRatio
)
203 cellWidthTop
= availableWidth
/ m_columns
;
205 cellWidthTop
= availableHeight
/ m_rows
* aspectRatio
;
207 // If placing the displays on top would result in larger cells, we take
208 // that option, but only if the displays would actually fit.
209 const bool displaysOnTop
= ( cellWidthTop
> cellWidthSide
&& size
.width() > widthOfDisplaysOnTop
);
210 const qreal newCellWidth
= displaysOnTop
? cellWidthTop
: cellWidthSide
;
211 const QSize newCellSize
= QSize( qRound( newCellWidth
), qRound( newCellWidth
/ aspectRatio
) );
213 // If the cellSize has actually changed, update all the sprites.
214 if ( newCellSize
!= m_cellSize
)
216 m_cellSize
= newCellSize
;
217 foreach ( QGraphicsItem
* item
, items() )
219 Sprite
* sprite
= qgraphicsitem_cast
<Sprite
*>( item
);
222 sprite
->setSize( m_cellSize
);
223 updateSpritePos( sprite
);
230 // Set the sceneRect to centre the grid if possible, but ensure the display items are visible
231 const qreal sceneRectXPos
= -( size
.width() - m_cellSize
.width() * ( m_columns
- 1 ) ) / 2.0;
232 const qreal centeredYPos
= - ( size
.height() - m_cellSize
.height() * ( m_rows
- 1 ) ) / 2.0;
233 const qreal indentedYPos
= - ( m_cellSize
.height() / 2.0 + 2 * spacing
+ displaySize
.height() );
234 const qreal sceneRectYPos
= qMin( centeredYPos
, indentedYPos
);
235 setSceneRect( QRectF( sceneRectXPos
, sceneRectYPos
, size
.width(), size
.height() ) );
237 // Position the display items centered at the top of the scene
238 const qreal displayYPos
= ( sceneRectYPos
- ( displaySize
.height() + m_cellSize
.height() / 2.0 ) ) / 2;
240 int xPos
= sceneRectXPos
+ ( size
.width() - widthOfDisplaysOnTop
) / 2.0;
241 foreach ( GameStatusDisplayItem
* display
, displayList
)
243 display
->setPos( xPos
, displayYPos
);
244 xPos
+= displaySize
.width() + spacing
;
250 const qreal centeredXPos
= - ( size
.width() - m_cellSize
.width() * ( m_columns
- 1 ) ) / 2.0;
251 const qreal sceneRectYPos
= -( size
.height() - m_cellSize
.height() * ( m_rows
- 1 ) ) / 2.0;
254 // If the application layout is LTR, place the displays on left,
255 // otherwise, place them on the right.
256 if ( views().first()->layoutDirection() == Qt::LeftToRight
)
258 // Set the sceneRect to centre the grid if possible, but ensure the display items are visible
259 const qreal indentedXPos
= - ( m_cellSize
.width() / 2.0 + 2 * spacing
+ displaySize
.width() );
260 sceneRectXPos
= qMin( centeredXPos
, indentedXPos
);
262 // Position the display items to the left of the grid
263 displayXPos
= - ( spacing
+ displaySize
.width() + m_cellSize
.width() / 2 );
267 // Set the sceneRect to centre the grid if possible, but ensure the display items are visible
268 const qreal indentedXPos
= ( m_cellSize
.width() * m_columns
+ 1 * spacing
+ displaySize
.width() ) - size
.width();
269 sceneRectXPos
= qMax( centeredXPos
, indentedXPos
);
271 // Position the display items to the right of the grid
272 displayXPos
= m_cellSize
.width() * ( m_columns
- 0.5 ) + spacing
;
275 int yPos
= -m_cellSize
.height() / 2;
276 foreach ( GameStatusDisplayItem
* display
, displayList
)
278 display
->setPos( displayXPos
, yPos
);
279 yPos
+= displaySize
.height() + spacing
;
282 setSceneRect( QRectF( sceneRectXPos
, sceneRectYPos
, size
.width(), size
.height() ) );
289 void Killbots::Scene::setAnimationSpeed( int speed
)
291 // Equation converts speed in the range 0 to 10 to a duration in the range
292 // 1 to 0.05 seconds. There's probably a better way to do this.
293 m_timeLine
.setDuration( int( pow( 1.35, -speed
) * 1000 ) );
297 void Killbots::Scene::beginNewAnimationStage()
299 if ( m_stages
.isEmpty() )
301 AnimationStage newStage
;
302 newStage
.oldRound
= m_roundDisplay
->value();
303 newStage
.oldScore
= m_scoreDisplay
->value();
304 newStage
.oldEnemyCount
= m_enemyCountDisplay
->value();
305 newStage
.oldEnergy
= m_energyDisplay
->value();
306 m_stages
<< newStage
;
308 else if ( !m_stages
.last().isEmpty() )
310 AnimationStage newStage
;
311 const AnimationStage
& lastStage
= m_stages
.last();
312 newStage
.oldRound
= lastStage
.newRound
== -1 ? lastStage
.oldRound
: lastStage
.newRound
;
313 newStage
.oldScore
= lastStage
.newScore
== -1 ? lastStage
.oldScore
: lastStage
.newScore
;
314 newStage
.oldEnemyCount
= lastStage
.newEnemyCount
== -1 ? lastStage
.oldEnemyCount
: lastStage
.newEnemyCount
;
315 newStage
.oldEnergy
= lastStage
.newEnergy
== -1 ? lastStage
.oldEnergy
: lastStage
.newEnergy
;
316 m_stages
<< newStage
;
321 Killbots::Sprite
* Killbots::Scene::createSprite( SpriteType type
, QPoint position
)
323 Sprite
* sprite
= new Sprite();
324 sprite
->setSpriteType( type
);
325 sprite
->setSize( m_cellSize
);
326 sprite
->setGridPos( position
);
327 sprite
->setPos( -1000000.0, -1000000.0 );
329 // A bit of a hack, but we use the sprite type for stacking order.
330 sprite
->setZValue( type
);
333 m_stages
.last().spritesToCreate
<< sprite
;
342 void Killbots::Scene::slideSprite( Sprite
* sprite
, QPoint position
)
344 sprite
->storeGridPos();
345 sprite
->setGridPos( position
);
346 m_stages
.last().spritesToSlide
<< sprite
;
350 void Killbots::Scene::teleportSprite( Sprite
* sprite
, QPoint position
)
352 sprite
->storeGridPos();
353 sprite
->setGridPos( position
);
354 m_stages
.last().spritesToTeleport
<< sprite
;
358 void Killbots::Scene::destroySprite( Sprite
* sprite
)
360 if ( sprite
->spriteType() == Hero
)
363 m_stages
.last().spritesToDestroy
<< sprite
;
367 void Killbots::Scene::showQueuedMessage( const QString
& message
)
369 m_stages
.last().message
= message
;
373 void Killbots::Scene::showUnqueuedMessage( const QString
& message
, int timeout
)
375 if ( !m_queuedPopup
->isVisible() )
377 KGamePopupItem::Position corner
= views().first()->layoutDirection() == Qt::LeftToRight
? KGamePopupItem::TopRight
: KGamePopupItem::TopLeft
;
378 m_unqueuedPopup
->setMessageTimeout( timeout
);
379 m_unqueuedPopup
->showMessage( message
, corner
, KGamePopupItem::ReplacePrevious
);
384 void Killbots::Scene::updateRound( int round
)
386 m_stages
.last().newRound
= round
;
390 void Killbots::Scene::updateScore( int score
)
392 m_stages
.last().newScore
= score
;
396 void Killbots::Scene::updateEnemyCount( int enemyCount
)
398 m_stages
.last().newEnemyCount
= enemyCount
;
402 void Killbots::Scene::updateEnergy( int energy
)
404 m_stages
.last().newEnergy
= energy
;
408 void Killbots::Scene::startAnimation()
410 startAnimationStage();
414 void Killbots::Scene::startAnimationStage()
416 QString message
= m_stages
.first().message
;
418 if ( m_timeLine
.duration() < 60 && message
.isEmpty() )
421 emit
animationStageDone();
425 if ( !message
.isEmpty() )
427 if ( m_unqueuedPopup
->isVisible() )
428 m_unqueuedPopup
->hide();
429 KGamePopupItem::Position corner
= views().first()->layoutDirection() == Qt::LeftToRight
? KGamePopupItem::TopRight
: KGamePopupItem::TopLeft
;
430 m_queuedPopup
->setMessageTimeout( 3000 );
431 m_queuedPopup
->showMessage( message
, corner
, KGamePopupItem::ReplacePrevious
);
439 void Killbots::Scene::animate( qreal value
)
441 static bool halfDone
;
442 AnimationStage stage
= m_stages
.first();
444 if ( stage
.newRound
!= -1 )
445 m_roundDisplay
->setValue( int( stage
.oldRound
+ value
* ( stage
.newRound
- stage
.oldRound
) ) );
447 if ( stage
.newScore
!= -1 )
448 m_scoreDisplay
->setValue( int( stage
.oldScore
+ value
* ( stage
.newScore
- stage
.oldScore
) ) );
450 if ( stage
.newEnemyCount
!= -1 )
451 m_enemyCountDisplay
->setValue( int( stage
.oldEnemyCount
+ value
* ( stage
.newEnemyCount
- stage
.oldEnemyCount
) ) );
453 if ( stage
.newEnergy
!= -1 )
454 m_energyDisplay
->setValue( int( stage
.oldEnergy
+ value
* ( stage
.newEnergy
- stage
.oldEnergy
) ) );
459 foreach ( Sprite
* sprite
, stage
.spritesToCreate
)
461 sprite
->scale( value
, value
);
462 updateSpritePos( sprite
);
465 else if ( 0.0 < value
&& value
< 1.0 )
467 foreach ( Sprite
* sprite
, stage
.spritesToCreate
)
469 updateSpritePos( sprite
);
470 sprite
->resetTransform();
471 sprite
->scale( value
, value
);
474 foreach ( Sprite
* sprite
, stage
.spritesToSlide
)
476 QPointF posInGridCoordinates
= value
* QPointF( sprite
->gridPos() - sprite
->storedGridPos() ) + sprite
->storedGridPos();
477 sprite
->setPos( QPointF( posInGridCoordinates
.x() * m_cellSize
.width(), posInGridCoordinates
.y() * m_cellSize
.height() ) );
486 foreach ( Sprite
* sprite
, stage
.spritesToTeleport
)
487 updateSpritePos( sprite
);
489 scaleFactor
= 2 * value
- 1.0;
493 scaleFactor
= 1.0 - 2 * value
;
496 foreach ( Sprite
* sprite
, stage
.spritesToTeleport
)
498 sprite
->resetTransform();
499 sprite
->scale( scaleFactor
, scaleFactor
);
502 foreach ( Sprite
* sprite
, stage
.spritesToDestroy
)
504 sprite
->resetTransform();
505 sprite
->scale( 1 - value
, 1 - value
);
506 sprite
->rotate( value
* 360 );
509 else if ( value
== 1.0 )
511 foreach ( Sprite
* sprite
, stage
.spritesToSlide
+ stage
.spritesToTeleport
+ stage
.spritesToCreate
)
513 updateSpritePos( sprite
);
514 sprite
->resetTransform();
515 sprite
->storeGridPos();
518 qDeleteAll( stage
.spritesToDestroy
);
523 void Killbots::Scene::nextAnimationStage()
525 // Wait for both the timeline and the popup to finish before moving to the next stage.
526 if ( m_timeLine
.state() != QTimeLine::Running
&& !m_queuedPopup
->isVisible() )
528 m_stages
.removeFirst();
530 if ( m_stages
.size() )
531 startAnimationStage();
533 emit
animationDone();
538 void Killbots::Scene::onNewGame( int rows
, int columns
, bool gameIncludesEnergy
)
541 || m_columns
!= columns
542 || m_energyDisplay
->isVisible() != gameIncludesEnergy
547 m_energyDisplay
->setVisible( gameIncludesEnergy
);
553 void Killbots::Scene::showNewGameMessage()
555 showUnqueuedMessage( i18n("New game.") );
559 void Killbots::Scene::showRoundCompleteMessage()
561 showQueuedMessage( i18n("Round complete.") );
565 void Killbots::Scene::showBoardFullMessage()
567 showQueuedMessage( i18n("Board is full.\nResetting enemy counts.") );
571 void Killbots::Scene::showGameOverMessage()
574 showUnqueuedMessage( i18n("Game over."), 15000 );
578 void Killbots::Scene::drawBackground( QPainter
* painter
, const QRectF
& )
580 painter
->drawPixmap( sceneRect().topLeft(), Render::renderElement( "background", QSize( qRound( sceneRect().width() ), qRound( sceneRect().height() ) ) ) );
582 QRect
gridArea( -m_cellSize
.width() / 2, -m_cellSize
.height() / 2, m_columns
* m_cellSize
.width(), m_rows
* m_cellSize
.height() );
583 painter
->drawTiledPixmap( gridArea
, Render::renderElement( "cell", m_cellSize
) );
587 void Killbots::Scene::mouseMoveEvent( QGraphicsSceneMouseEvent
* event
)
589 getMouseDirection( event
);
590 QGraphicsScene::mouseMoveEvent( event
);
594 void Killbots::Scene::mouseReleaseEvent( QGraphicsSceneMouseEvent
* event
)
596 HeroAction actionFromPosition
= getMouseDirection( event
);
598 if ( actionFromPosition
!= NoAction
)
600 Settings::ClickAction userAction
= Settings::Nothing
;
602 if ( event
->button() == Qt::LeftButton
)
604 if ( event
->modifiers() & Qt::ControlModifier
)
605 userAction
= Settings::middleClickAction();
607 userAction
= Settings::Step
;
609 else if ( event
->button() == Qt::RightButton
)
610 userAction
= Settings::rightClickAction();
611 else if ( event
->button() == Qt::MidButton
)
612 userAction
= Settings::middleClickAction();
614 if ( userAction
== Settings::Step
)
615 emit
clicked( actionFromPosition
);
616 else if ( userAction
== Settings::RepeatedStep
)
617 emit
clicked( -actionFromPosition
- 1 );
618 else if ( userAction
== Settings::Teleport
)
619 emit
clicked( Teleport
);
620 else if ( userAction
== Settings::TeleportSafely
)
621 emit
clicked( TeleportSafely
);
622 else if ( userAction
== Settings::TeleportSafelyIfPossible
)
623 emit
clicked( TeleportSafelyIfPossible
);
624 else if ( userAction
== Settings::WaitOutRound
)
625 emit
clicked( WaitOutRound
);
628 QGraphicsScene::mouseReleaseEvent( event
);
632 Killbots::HeroAction
Killbots::Scene::getMouseDirection( QGraphicsSceneMouseEvent
* event
)
635 QPointF cursorPosition
= event
->scenePos();
637 bool heroOnScreen
= m_hero
&& sceneRect().contains( m_hero
->sceneBoundingRect() );
639 bool popupUnderCursor
= m_queuedPopup
->sceneBoundingRect().contains( cursorPosition
)
640 || m_unqueuedPopup
->sceneBoundingRect().contains( cursorPosition
);
642 if ( heroOnScreen
&& !popupUnderCursor
)
644 if ( m_hero
->sceneBoundingRect().contains( cursorPosition
) )
648 const qreal piOver4
= 0.78539816339744830961566L;
650 QPointF delta
= cursorPosition
- m_hero
->sceneBoundingRect().center();
651 int direction
= qRound( atan2( -delta
.y(), delta
.x() ) / piOver4
);
655 result
= static_cast<HeroAction
>( direction
);
658 views().first()->setCursor( Render::cursorFromAction( result
) );
662 views().first()->unsetCursor();
670 void Killbots::Scene::updateSpritePos( Sprite
* sprite
) const
672 sprite
->setPos( QPointF( sprite
->gridPos().x() * m_cellSize
.width(), sprite
->gridPos().y() * m_cellSize
.height() ) );
675 #include "moc_scene.cpp"