Use player name in the KScoreDialog
[kdegames.git] / killbots / scene.cpp
blob48e36f70ca1ab8a65648cffedb0288ed6ec7eda1
1 /*
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/>.
20 #include "scene.h"
22 #include "gamestatusdisplayitem.h"
23 #include "render.h"
24 #include "ruleset.h"
25 #include "settings.h"
26 #include "sprite.h"
28 #include <kgamepopupitem.h>
30 #include <KDE/KDebug>
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>
40 #include <cmath>
43 struct Killbots::Scene::AnimationStage
45 AnimationStage()
46 : newRound( -1 ),
47 newScore( -1 ),
48 newEnemyCount( -1 ),
49 newEnergy( -1 )
50 {};
52 bool isEmpty() const
54 return spritesToCreate.isEmpty()
55 && spritesToSlide.isEmpty()
56 && spritesToTeleport.isEmpty()
57 && spritesToDestroy.isEmpty()
58 && message.isEmpty()
59 && newRound == -1
60 && newScore == -1
61 && newEnemyCount == -1
62 && newEnergy == -1;
65 QList<Sprite *> spritesToCreate;
66 QList<Sprite *> spritesToSlide;
67 QList<Sprite *> spritesToTeleport;
68 QList<Sprite *> spritesToDestroy;
69 QString message;
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 ),
79 m_hero( 0 ),
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() ),
87 m_rows( 0 ),
88 m_columns( 0 )
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 );
145 return;
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
152 // tweaking here.
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();
158 QSize displaySize;
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 );
179 else
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.
191 qreal cellWidthSide;
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;
196 else
197 cellWidthSide = availableHeight / m_rows * aspectRatio;
199 qreal cellWidthTop;
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;
204 else
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 );
220 if ( sprite )
222 sprite->setSize( m_cellSize );
223 updateSpritePos( sprite );
228 if ( displaysOnTop )
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;
247 else
249 qreal sceneRectXPos;
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;
252 qreal displayXPos;
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 );
265 else
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() ) );
285 update();
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 );
332 addItem( sprite );
333 m_stages.last().spritesToCreate << sprite;
335 if ( type == Hero )
336 m_hero = sprite;
338 return 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 )
361 m_hero = 0;
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() )
420 animate( 1.0 );
421 emit animationStageDone();
423 else
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 );
434 m_timeLine.start();
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 ) ) );
456 if ( value == 0.0 )
458 halfDone = false;
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() ) );
480 qreal scaleFactor;
481 if ( value >= 0.5 )
483 if ( !halfDone )
485 halfDone = true;
486 foreach ( Sprite * sprite, stage.spritesToTeleport )
487 updateSpritePos( sprite );
489 scaleFactor = 2 * value - 1.0;
491 else
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();
532 else
533 emit animationDone();
538 void Killbots::Scene::onNewGame( int rows, int columns, bool gameIncludesEnergy )
540 if ( m_rows != rows
541 || m_columns != columns
542 || m_energyDisplay->isVisible() != gameIncludesEnergy
545 m_rows = rows;
546 m_columns = columns;
547 m_energyDisplay->setVisible( gameIncludesEnergy );
548 doLayout();
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()
573 m_hero = 0;
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();
606 else
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 )
634 HeroAction result;
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 ) )
645 result = Hold;
646 else
648 const qreal piOver4 = 0.78539816339744830961566L;
650 QPointF delta = cursorPosition - m_hero->sceneBoundingRect().center();
651 int direction = qRound( atan2( -delta.y(), delta.x() ) / piOver4 );
652 if ( direction < 0 )
653 direction += 8;
655 result = static_cast<HeroAction>( direction );
658 views().first()->setCursor( Render::cursorFromAction( result ) );
660 else
662 views().first()->unsetCursor();
663 result = NoAction;
666 return result;
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"