Commit a52cb8c3 authored by Dmitry Suzdalev's avatar Dmitry Suzdalev
Browse files

Whew! It took quite some time :).

Reorganized some code, moved almost all animations
to KLinesAnimator class, leaved only jumping animation to
be handled by the ball itself...

I think I need a break now :-).

svn path=/trunk/KDE/kdegames/klines/; revision=616252
parent ba2e7072
......@@ -23,6 +23,7 @@
#include "animator.h"
#include "scene.h"
#include "ballitem.h"
#include "renderer.h"
#include <kdebug.h>
#include <math.h> // for pow, sqrt
......@@ -55,8 +56,31 @@ static inline int indexOfNodeWithPos( const FieldPos& pos, const QList<PathNode*
KLinesAnimator::KLinesAnimator( KLinesScene* scene )
: m_scene(scene), m_movingBall(0)
{
connect(&m_timeLine, SIGNAL(frameChanged(int)), SLOT(animFrameChanged(int)) );
connect(&m_timeLine, SIGNAL(finished()), SIGNAL(moveFinished()));
connect(&m_moveTimeLine, SIGNAL(frameChanged(int)), SLOT(moveAnimationFrame(int)) );
connect(&m_moveTimeLine, SIGNAL(finished()), SIGNAL(moveFinished()));
m_removeTimeLine.setDuration(200);
m_removeTimeLine.setCurveShape(QTimeLine::LinearCurve);
// we setup here one 'empty' frame at the end, because without it
// m_scene will delete 'burned' items in removeAnimFinished() slot so quickly
// that last frame won't get shown in the scene
m_removeTimeLine.setFrameRange(0, m_scene->renderer()->numFireFrames());
connect(&m_removeTimeLine, SIGNAL(frameChanged(int)), SLOT(removeAnimationFrame(int)) );
connect(&m_removeTimeLine, SIGNAL(finished()), SIGNAL(removeFinished()));
m_bornTimeLine.setDuration(200);
m_bornTimeLine.setCurveShape(QTimeLine::LinearCurve);
m_bornTimeLine.setFrameRange(0, m_scene->renderer()->numBornFrames()-1);
connect(&m_bornTimeLine, SIGNAL(frameChanged(int)), SLOT(bornAnimationFrame(int)) );
connect(&m_bornTimeLine, SIGNAL(finished()), SIGNAL(bornFinished()));
}
bool KLinesAnimator::isAnimating() const
{
return (m_moveTimeLine.state() == QTimeLine::Running
|| m_removeTimeLine.state() == QTimeLine::Running);
}
void KLinesAnimator::animateMove( const FieldPos& from, const FieldPos& to )
......@@ -72,15 +96,34 @@ void KLinesAnimator::animateMove( const FieldPos& from, const FieldPos& to )
int numPoints = m_foundPath.count();
// there will be numPoints-1 intervals of
// movement (interval=cell). We want each of them to take 100ms
m_timeLine.setDuration((numPoints-1)*100);
m_moveTimeLine.setDuration((numPoints-1)*100);
// FIXME dimsuz: 30 <=> m_scene->ballSize() or smth like that
// each interval will take 30 frames
m_timeLine.setFrameRange(0, (numPoints-1)*30);
m_timeLine.setCurrentTime(0);
m_timeLine.start();
m_moveTimeLine.setFrameRange(0, (numPoints-1)*30);
m_moveTimeLine.setCurrentTime(0);
m_moveTimeLine.start();
}
void KLinesAnimator::animateRemove( const QList<BallItem*>& list )
{
if(list.isEmpty())
{
emit removeFinished();
return;
}
m_removeTimeLine.stop();
m_removedBalls = list;
m_removeTimeLine.start();
}
void KLinesAnimator::animateBorn( const QList<BallItem*>& list )
{
m_bornBalls = list;
m_bornTimeLine.start();
}
void KLinesAnimator::animFrameChanged(int frame)
void KLinesAnimator::moveAnimationFrame(int frame)
{
int intervalNum = frame/30;
......@@ -115,6 +158,20 @@ void KLinesAnimator::animFrameChanged(int frame)
pos.y()+ky*frameWithinInterval );
}
void KLinesAnimator::removeAnimationFrame(int frame)
{
if(frame == m_scene->renderer()->numFireFrames())
return;
foreach(BallItem* ball, m_removedBalls)
ball->setPixmap( m_scene->renderer()->firePixmap(frame) );
}
void KLinesAnimator::bornAnimationFrame(int frame)
{
foreach(BallItem* ball, m_bornBalls)
ball->setPixmap( m_scene->renderer()->bornPixmap(ball->color(), frame) );
}
void KLinesAnimator::findPath( const FieldPos& from, const FieldPos& to )
{
// Implementation of A* pathfinding algorithm
......
......@@ -35,20 +35,32 @@ class KLinesAnimator : public QObject
public:
KLinesAnimator( KLinesScene *scene );
void animateMove( const FieldPos& from, const FieldPos& to );
bool isAnimating() const { return m_timeLine.state() == QTimeLine::Running; }
void animateRemove( const QList<BallItem*>& list );
void animateBorn( const QList<BallItem*>& list );
bool isAnimating() const;
signals:
void moveFinished();
void removeFinished();
void bornFinished();
private slots:
void animFrameChanged(int);
void moveAnimationFrame(int);
void removeAnimationFrame(int);
void bornAnimationFrame(int);
private:
/**
* Implements A* pathfinding algorithm.
*/
void findPath(const FieldPos& from, const FieldPos& to);
QTimeLine m_timeLine;
QTimeLine m_moveTimeLine;
QTimeLine m_removeTimeLine;
QTimeLine m_bornTimeLine;
KLinesScene* m_scene;
BallItem* m_movingBall;
QList<FieldPos> m_foundPath;
QList<BallItem*> m_removedBalls;
QList<BallItem*> m_bornBalls;
};
#endif
......@@ -29,54 +29,38 @@ BallItem::BallItem( QGraphicsScene* parent, const KLinesRenderer* renderer )
: QGraphicsPixmapItem( 0, parent ), m_renderer(renderer)
{
m_color = NumColors; // = uninitialized
m_timeLine.setCurveShape( QTimeLine::LinearCurve );
m_timeLine.setDuration(400);
m_timeLine.setLoopCount(0);
m_timeLine.setFrameRange(0, m_renderer->numSelectedFrames()-1);
// starting by going lower
m_timeLine.setCurrentTime( m_timeLine.duration()/2 );
connect(&m_timeLine, SIGNAL(frameChanged(int)), SLOT(animFrameChanged(int)) );
}
void BallItem::setColor( BallColor c )
{
m_color = c;
//setPixmap( m_renderer->ballPixmap(c) );
}
void BallItem::startAnimation( BallAnimationType type )
void BallItem::startSelectedAnimation()
{
m_curAnim = type;
switch(type)
{
case SelectedAnimation:
m_timeLine.setDuration(400);
m_timeLine.setLoopCount(0);
m_timeLine.setFrameRange(0, m_renderer->numAnimationFrames(SelectedAnimation)-1);
// starting by going lower
m_timeLine.setCurrentTime( m_timeLine.duration()/2 );
break;
case BornAnimation:
m_timeLine.setDuration(200);
m_timeLine.setLoopCount(1);
m_timeLine.setFrameRange(0, (m_renderer->numAnimationFrames(BornAnimation)-1));
break;
}
if(m_timeLine.state() == QTimeLine::Running)
return;
m_timeLine.start();
}
void BallItem::stopAnimation()
{
m_timeLine.stop();
// type dependant actions:
switch(m_curAnim)
{
case BornAnimation:
case SelectedAnimation:
setPixmap( m_renderer->ballPixmap(m_color) );
break;
}
setPixmap( m_renderer->ballPixmap( m_color ) );
}
void BallItem::animFrameChanged(int frame)
{
setPixmap(m_renderer->animationFrame( m_color, m_curAnim, frame ));
setPixmap(m_renderer->selectedPixmap( m_color, frame ));
}
#include "ballitem.moc"
......@@ -39,28 +39,17 @@ public:
void setColor( BallColor c );
BallColor color() const { return m_color; }
/**
* Starts animation.
* When animation will finish, animationFinished() signal
* will be emitted (except for animation types which are looped)
*
* @param type type of animation sequence to play
* Starts "Selected" animation
*/
void startAnimation( BallAnimationType type );
void startSelectedAnimation();
/**
* Interrupts current animation
* Interrupts animation
*/
void stopAnimation();
// enable use of qgraphicsitem_cast
enum { Type = UserType + 1 };
virtual int type() const { return Type; }
signals:
/**
* Emitted when animation finishes.
* If current animation type is looped, this signal won't be emitted
*/
// FIXME dimsuz: implement
void animationFinished();
private slots:
void animFrameChanged(int);
private:
......@@ -76,11 +65,6 @@ private:
* Color of the ball
*/
BallColor m_color;
/**
* Type of running animation
* FIXME dimsuz: reset to something like NoAnimation when it isnt running
*/
BallAnimationType m_curAnim;
};
#endif
......@@ -25,7 +25,6 @@
#include <QList>
enum BallAnimationType { SelectedAnimation, BornAnimation, BurnAnimation };
enum BallColor { Blue=0, Brown, Cyan, Green, Red, Violet, Yellow, NumColors };
struct FieldPos
......
......@@ -27,6 +27,7 @@
KLinesRenderer::KLinesRenderer()
{
m_fieldPix = QPixmap( KStandardDirs::locate( "appdata", "field.jpg" ));
m_firePix = QPixmap( KStandardDirs::locate( "appdata", "fire.jpg" ));
m_ballsPix = QPixmap( KStandardDirs::locate( "appdata", "balls.jpg" ));
}
......@@ -40,21 +41,29 @@ QPixmap KLinesRenderer::ballPixmap(BallColor color) const
return m_ballsPix.copy( 7*30, static_cast<int>(color)*30, 30, 30 );
}
QPixmap KLinesRenderer::animationFrame( BallColor color, BallAnimationType type, int frameNo ) const
QPixmap KLinesRenderer::firePixmap(int frame) const
{
// FIXME dimsuz: copying every time: not very efficient.
// FIXME dimsuz: hardcoded "magic" numbers
// Switching to svg will make this fixmes obsolete
// col, row, width, height - hardcoded. balls.jpg has such a format.
return m_firePix.copy(frame*30, 0, 30, 30 );
}
QPixmap KLinesRenderer::bornPixmap(BallColor color, int frame) const
{
// FIXME dimsuz: copying every time: not very efficient.
// FIXME dimsuz: hardcoded "magic" numbers
// Switching to svg will make this fixmes obsolete
// col, row, width, height - hardcoded. balls.jpg has such a format.
return m_ballsPix.copy( 13*30 + frame*30, static_cast<int>(color)*30, 30, 30 );
}
switch(type)
{
case BornAnimation:
return m_ballsPix.copy( 13*30 + frameNo*30, static_cast<int>(color)*30, 30, 30 );
case SelectedAnimation:
return m_ballsPix.copy( frameNo*30, static_cast<int>(color)*30, 30, 30 );
default:
return QPixmap();
}
QPixmap KLinesRenderer::selectedPixmap( BallColor color, int frame ) const
{
return m_ballsPix.copy( frame*30, static_cast<int>(color)*30, 30, 30 );
}
QPixmap KLinesRenderer::backgroundTilePixmap() const
......
......@@ -27,30 +27,27 @@
#include "commondefs.h"
// FIXME dimsuz: make singleton - it's used in three classes BallItem, KLinesAnimator, KLinesScene
// FIXME dimsuz: give {fire,born,selected}Pixmap methods better names
class KLinesRenderer
{
public:
KLinesRenderer();
QPixmap ballPixmap( BallColor c ) const;
QPixmap animationFrame( BallColor c, BallAnimationType t, int frameNo ) const;
QPixmap firePixmap( int frame ) const;
QPixmap bornPixmap( BallColor c, int frame ) const;
QPixmap selectedPixmap( BallColor c, int frame ) const;
QPixmap backgroundTilePixmap() const;
inline int numAnimationFrames(BallAnimationType t) const
{
switch(t)
{
case SelectedAnimation:
return 13;
case BornAnimation:
return 5;
case BurnAnimation:
return 4;
}
return 0;
}
inline int numFireFrames() const { return 5; }
inline int numBornFrames() const { return 5; }
inline int numSelectedFrames() const { return 13; }
private:
QPixmap m_ballsPix; // to be removed when SVG comes to us
QPixmap m_fieldPix; // to be removed when SVG comes to us
QPixmap m_firePix; // to be removed when SVG comes to us
};
#endif
......@@ -49,6 +49,8 @@ KLinesScene::KLinesScene( QObject* parent )
m_renderer = new KLinesRenderer;
m_animator = new KLinesAnimator(this);
connect( m_animator, SIGNAL(moveFinished()), SLOT(moveAnimFinished() ) );
connect( m_animator, SIGNAL(removeFinished()), SLOT(removeAnimFinished() ) );
connect( m_animator, SIGNAL(bornFinished()), SLOT(bornAnimFinished() ) );
for(int x=0; x<FIELD_SIZE; ++x)
for(int y=0; y<FIELD_SIZE; ++y)
......@@ -71,12 +73,15 @@ void KLinesScene::resizeScene(int width,int height)
void KLinesScene::nextThreeBalls()
{
placeRandomBall();
placeRandomBall();
placeRandomBall();
QList<BallItem*> newItems;
newItems.append( placeRandomBall() );
newItems.append( placeRandomBall() );
newItems.append( placeRandomBall() );
m_animator->animateBorn( newItems );
}
void KLinesScene::placeRandomBall()
BallItem* KLinesScene::placeRandomBall()
{
// FIXME dimsuz: in old klines ball positon had score and levels of
// difficulty were implemented around it. Check this out and consider implementing
......@@ -97,9 +102,8 @@ void KLinesScene::placeRandomBall()
newBall->setColor(c);
newBall->setPos( fieldToPix( FieldPos(posx,posy) ) );
m_field[posx][posy] = newBall;
newBall->startAnimation( BornAnimation );
m_numBalls++;
return newBall;
}
void KLinesScene::mousePressEvent( QGraphicsSceneMouseEvent* ev )
......@@ -114,7 +118,7 @@ void KLinesScene::mousePressEvent( QGraphicsSceneMouseEvent* ev )
if( m_selPos.isValid() )
m_field[m_selPos.x][m_selPos.y]->stopAnimation();
m_field[fpos.x][fpos.y]->startAnimation( SelectedAnimation );
m_field[fpos.x][fpos.y]->startSelectedAnimation();
m_selPos = fpos;
}
else // move selected ball to new location
......@@ -122,6 +126,7 @@ void KLinesScene::mousePressEvent( QGraphicsSceneMouseEvent* ev )
if( m_selPos.isValid() && m_field[fpos.x][fpos.y] == 0 )
{
// start move animation
// slot moveAnimFinished() will be called when it finishes
m_animator->animateMove(m_selPos, fpos);
}
}
......@@ -142,10 +147,45 @@ void KLinesScene::moveAnimFinished()
m_selPos.x = m_selPos.y = -1; // invalidate position
kDebug() << "calling sae1" << endl;
m_placeBallsAfterErase = true;
// after anim finished, slot removeAnimFinished()
// will be called
searchAndErase();
}
nextThreeBalls();
void KLinesScene::removeAnimFinished()
{
qDeleteAll( m_itemsToDelete );
m_itemsToDelete.clear();
if(m_placeBallsAfterErase)
// slot bornAnimFinished() will be called
// when born animation finishes
nextThreeBalls();
else
{
// it is needed after qDeleteAll()
// as an optimisation we may update only rects
// in which items from m_itemsToDelete were before
// deletion
update();
}
}
void KLinesScene::bornAnimFinished()
{
// There's a little trick here:
// searchAndErase() will cause m_animator to emit removeFinished()
// If there wasn't m_placeBallsAfterErase var
// it would cause an infinite loop like this:
// SaE()->removeAnimFinished()->next3Balls()->bornAnimFinished()->
// SaE()->removeAnimFinished()->next3Balls()->...
// etc etc
m_placeBallsAfterErase = false;
// after placing new balls new 5-in-a-row chunks can occur
// so we need to check for them
kDebug() << "calling sae2" << endl;
searchAndErase();
}
......@@ -169,7 +209,7 @@ void KLinesScene::searchAndErase()
{
for(int i=x; i<tmpx;++i)
{
delete m_field[i][y];
m_itemsToDelete.append(m_field[i][y]);
m_field[i][y] = 0;
}
}
......@@ -193,7 +233,7 @@ void KLinesScene::searchAndErase()
{
for(int j=y; j<tmpy;++j)
{
delete m_field[x][j];
m_itemsToDelete.append(m_field[x][j]);
m_field[x][j] = 0;
}
}
......@@ -222,7 +262,7 @@ void KLinesScene::searchAndErase()
{
for(int i=x,j=y; i<tmpx;++i,++j)
{
delete m_field[i][j];
m_itemsToDelete.append(m_field[i][j]);
m_field[i][j] = 0;
}
}
......@@ -251,13 +291,16 @@ void KLinesScene::searchAndErase()
{
for(int i=x,j=y; i<tmpx;++i,--j)
{
delete m_field[i][j];
m_itemsToDelete.append(m_field[i][j]);
m_field[i][j] = 0;
}
}
else
continue;
}
// after it finishes slot removeAnimFinished() will be called
m_animator->animateRemove( m_itemsToDelete );
}
void KLinesScene::drawBackground(QPainter *p, const QRectF&)
......
......@@ -56,6 +56,10 @@ public:
* Overloaded above function
*/
BallItem* ballAt( int x, int y ) { return m_field[x][y]; }
/**
* Returns games' renderer
*/
const KLinesRenderer* renderer() const { return m_renderer; }
/**
* Field coords to pixel coords
*/
......@@ -70,11 +74,14 @@ public:
private slots:
void moveAnimFinished();
void removeAnimFinished();
void bornAnimFinished();
private:
/**
* Creates a ball of random color and places it in random free cell
* @return ball placed
*/
void placeRandomBall();
BallItem* placeRandomBall();
/**
* Searches for 5 or more balls in a row and deletes them from field
*/
......@@ -89,7 +96,7 @@ private:
* or 0 if there's no ball in that cell
*/
BallItem* m_field[FIELD_SIZE][FIELD_SIZE];
KLinesRenderer* m_renderer;
const KLinesRenderer* m_renderer;
KLinesAnimator* m_animator;
KRandomSequence m_randomSeq;
......@@ -102,6 +109,15 @@ private:
* Number of balls currently in field
*/
int m_numBalls;
/**
* Varable which is needed for little trick (tm).
* Read more about it in removeAnimFinished() slot
*/
bool m_placeBallsAfterErase;
/**
* Items pending for removal after remove-anim finishes
*/
QList<BallItem*> m_itemsToDelete;
};
class KLinesView : public QGraphicsView
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment