File indexing completed on 2024-04-21 04:02:35

0001 /*
0002     This file is part of the KDE project "KLines"
0003 
0004     SPDX-FileCopyrightText: 2006-2008 Dmitry Suzdalev <dimsuz@gmail.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "scene.h"
0010 #include "ballitem.h"
0011 #include "previewitem.h"
0012 #include "animator.h"
0013 #include "renderer.h"
0014 
0015 #include <QGraphicsSceneMouseEvent>
0016 #include <QPainter>
0017 #include <QSet>
0018 #include "klines_debug.h"
0019 
0020 #include <KGamePopupItem>
0021 #include <KLocalizedString>
0022 
0023 inline uint qHash( FieldPos pos )
0024 {
0025     return qHash( QPair<int,int>(pos.x,pos.y) );
0026 }
0027 
0028 KLinesScene::KLinesScene( QObject* parent )
0029     : QGraphicsScene(parent),
0030       m_randomSeq(QRandomGenerator::global()->generate()),
0031       m_playFieldBorderSize(0), m_numFreeCells(FIELD_SIZE*FIELD_SIZE),
0032       m_score(0), m_bonusScore(0), m_cellSize(32), m_previewZoneVisible(true)
0033 {
0034     m_animator = new KLinesAnimator(this);
0035     connect(m_animator, &KLinesAnimator::moveFinished, this, &KLinesScene::moveAnimFinished);
0036     connect(m_animator, &KLinesAnimator::removeFinished, this, &KLinesScene::removeAnimFinished);
0037     connect(m_animator, &KLinesAnimator::bornFinished, this, &KLinesScene::bornAnimFinished);
0038 
0039     m_focusItem = new QGraphicsRectItem( QRectF(0, 0, m_cellSize, m_cellSize), nullptr);
0040     m_focusItem->setZValue(1.0);
0041     m_focusItem->setPen( Qt::DashLine );
0042     addItem(m_focusItem);
0043 
0044     m_previewItem = new PreviewItem(this);
0045     m_previewItem->setPos( 0, 0 );
0046     addItem(m_previewItem);
0047 
0048     m_popupItem = new KGamePopupItem;
0049     addItem(m_popupItem);
0050 
0051     startNewGame();
0052 }
0053 
0054 void KLinesScene::startNewGame()
0055 {
0056     if(m_animator->isAnimating())
0057         return;
0058 
0059     // reset all vars
0060     m_selPos = FieldPos();
0061     m_numFreeCells = FIELD_SIZE*FIELD_SIZE;
0062     m_score = 0;
0063     m_bonusScore = 0;
0064     m_placeBalls = true;
0065     m_gameOver = false;
0066     m_itemsToDelete.clear();
0067     m_nextColors.clear();
0068     m_focusItem->setPos(0, 0);
0069     m_focusItem->hide();
0070 
0071     m_popupItem->forceHide();
0072 
0073     // remove all ball items from the scene leaving other items untouched
0074     const QList<QGraphicsItem*> itemlist = items();
0075     for (QGraphicsItem* item : itemlist) {
0076         BallItem* ball = qgraphicsitem_cast<BallItem*>(item);
0077         if( ball )
0078         {
0079             removeItem(item);
0080             delete item;
0081         }
0082     }
0083 
0084     for(int x=0; x<FIELD_SIZE; ++x)
0085         for(int y=0; y<FIELD_SIZE; ++y)
0086             m_field[x][y] = nullptr;
0087 
0088     // init m_nextColors
0089     for(int i=0; i<3; i++)
0090     {
0091         // random color
0092         BallColor c = static_cast<BallColor>(m_randomSeq.bounded(static_cast<int>(NumColors)));
0093         m_nextColors.append(c);
0094     }
0095 
0096     Q_EMIT stateChanged(QStringLiteral( "not_undoable" ));
0097 
0098     nextThreeBalls();
0099 }
0100 
0101 KLinesScene::~KLinesScene()
0102 {
0103     delete m_animator;
0104 }
0105 
0106 
0107 void KLinesScene::endGame()
0108 {
0109     gameOverHandler();
0110 }
0111 
0112 void KLinesScene::resizeScene(int width,int height)
0113 {
0114     // store focus item field pos (calculated using old cellSize)
0115     FieldPos focusRectFieldPos = pixToField( m_focusItem->pos() );
0116 
0117     bool hasBorder = KLinesRenderer::hasBorderElement();
0118 
0119     int minDim = qMin( width, height );
0120     // border width is hardcoded to be half of cell size.
0121     // take it into account if it exists
0122     m_cellSize = hasBorder ? minDim/(FIELD_SIZE+1) : minDim/FIELD_SIZE;
0123 
0124     // set it only if current theme supports it
0125     m_playFieldBorderSize = hasBorder ? m_cellSize/2 : 0;
0126 
0127     int boardSize = m_cellSize * FIELD_SIZE;
0128     if ( m_previewZoneVisible && boardSize +m_playFieldBorderSize*2 + m_cellSize > width) // No space enough for balls preview
0129     {
0130         minDim = width;
0131         m_cellSize = hasBorder ? (minDim - m_cellSize - m_playFieldBorderSize*2)/FIELD_SIZE : (minDim - m_cellSize)/FIELD_SIZE;
0132         boardSize = m_cellSize * FIELD_SIZE;
0133     }
0134 
0135 
0136     m_playFieldRect.setX( (width - (m_previewZoneVisible ? m_cellSize : 0))/2 - boardSize/2 - m_playFieldBorderSize );
0137     m_playFieldRect.setY( height/2 - boardSize/2 - m_playFieldBorderSize );
0138 
0139     m_playFieldRect.setWidth( boardSize + m_playFieldBorderSize*2 );
0140     m_playFieldRect.setHeight( boardSize + m_playFieldBorderSize*2 );
0141 
0142     setSceneRect( 0, 0, width, height );
0143 
0144     // sets render sizes for cells
0145     KLinesRenderer::setCellSize( m_cellSize );
0146     QSize cellSize(m_cellSize, m_cellSize);
0147 
0148     // re-render && recalc positions for all balls
0149     for( int x=0; x<FIELD_SIZE; ++x)
0150         for(int y=0; y< FIELD_SIZE; ++y)
0151         {
0152             if( m_field[x][y] )
0153             {
0154                 // this will update pixmap
0155                 m_field[x][y]->setRenderSize(cellSize);
0156                 m_field[x][y]->setPos( fieldToPix( FieldPos(x,y) ) );
0157                 m_field[x][y]->setColor( m_field[x][y]->color() );
0158             }
0159         }
0160 
0161     m_focusItem->setRect( QRect(0,0, m_cellSize, m_cellSize) );
0162     m_focusItem->setPos( fieldToPix( focusRectFieldPos ) );
0163 
0164     int previewOriginY = height / 2 - (3 * m_cellSize) / 2;
0165     int previewOriginX = m_playFieldRect.x() + m_playFieldRect.width();
0166     m_previewItem->setPos( previewOriginX, previewOriginY );
0167     m_previewItem->setPreviewColors( m_nextColors );
0168 
0169     //qCDebug(KLINES_LOG) << "resize:" << width << "," << height << "; cellSize:" << m_cellSize;
0170 }
0171 
0172 void KLinesScene::endTurn()
0173 {
0174     if( m_gameOver )
0175         return;
0176 
0177     saveUndoInfo();
0178     nextThreeBalls();
0179 }
0180 
0181 void KLinesScene::nextThreeBalls()
0182 {
0183     if( m_animator->isAnimating() )
0184         return;
0185 
0186     QList<BallItem*> newItems;
0187     BallItem* newBall;
0188     for(int i=0; i<3; i++)
0189     {
0190         newBall = randomlyPlaceBall( m_nextColors.at(i) );
0191         if( newBall )
0192             newItems.append(newBall);
0193         else
0194             break; // the field is filled :).
0195     }
0196 
0197     for(int i=0; i<3; i++)
0198     {
0199         // random color
0200         BallColor c = static_cast<BallColor>(m_randomSeq.bounded(static_cast<int>(NumColors)));
0201         m_nextColors[i] = c;
0202     }
0203 
0204     m_previewItem->setPreviewColors( m_nextColors );
0205 
0206     m_animator->animateBorn( newItems );
0207 }
0208 
0209 void KLinesScene::setPreviewZoneVisible( bool visible )
0210 {
0211     if (visible == m_previewZoneVisible)
0212         return;
0213 
0214     m_previewZoneVisible = visible;
0215     m_previewItem->setVisible( visible );
0216     resizeScene((int) width(), (int) height());
0217     invalidate( sceneRect() );
0218 }
0219 
0220 BallItem* KLinesScene::randomlyPlaceBall(BallColor c)
0221 {
0222     m_numFreeCells--;
0223     if(m_numFreeCells < 0)
0224     {
0225         // restore m_numFreeCells value, it will trigger
0226         // saveAndErase() after bornAnimFinished to check if
0227         // we have 5-in-a-row to erase
0228         m_numFreeCells = 0;
0229         return nullptr; // game over, we won't create more balls
0230     }
0231 
0232     int posx = -1, posy = -1;
0233     // let's find random free cell
0234     do
0235     {
0236         posx = m_randomSeq.bounded(FIELD_SIZE);
0237         posy = m_randomSeq.bounded(FIELD_SIZE);
0238     } while( m_field[posx][posy] != nullptr );
0239 
0240     BallItem* newBall = new BallItem( this);
0241     newBall->setColor(c, false); // pixmap will be set by born animation
0242     newBall->setPos( fieldToPix( FieldPos(posx,posy) ) );
0243 
0244     m_field[posx][posy] = newBall;
0245     return newBall;
0246 }
0247 
0248 void KLinesScene::mousePressEvent( QGraphicsSceneMouseEvent* ev )
0249 {
0250     QGraphicsScene::mousePressEvent(ev);
0251     QRect boardRect = m_playFieldRect.adjusted( m_playFieldBorderSize,
0252                                                 m_playFieldBorderSize,
0253                                                 -m_playFieldBorderSize,
0254                                                 -m_playFieldBorderSize );
0255 
0256     if ( !boardRect.contains( ev->scenePos().toPoint() ) )
0257         return;
0258 
0259     selectOrMove( pixToField(ev->scenePos()) );
0260 }
0261 
0262 void KLinesScene::selectOrMove( FieldPos fpos )
0263 {
0264     if (fpos.x < 0 || fpos.y < 0) {
0265         return;
0266     }
0267     if( m_animator->isAnimating() )
0268         return;
0269     if( m_field[fpos.x][fpos.y] ) // ball was selected
0270     {
0271         if( m_selPos.isValid() )
0272         {
0273             m_field[m_selPos.x][m_selPos.y]->stopAnimation();
0274 
0275             if ( m_selPos == fpos )
0276             {
0277                 m_selPos.x = m_selPos.y = -1; // invalidate position
0278                 return;
0279             }
0280         }
0281 
0282         m_field[fpos.x][fpos.y]->startSelectedAnimation();
0283         m_selPos = fpos;
0284     }
0285     else // move selected ball to new location
0286     {
0287         if( m_selPos.isValid() && m_field[fpos.x][fpos.y] == nullptr )
0288         {
0289             saveUndoInfo();
0290             // start move animation
0291             // slot moveAnimFinished() will be called when it finishes
0292             bool pathExists = m_animator->animateMove(m_selPos, fpos);
0293             if(!pathExists)
0294             {
0295                 m_popupItem->setMessageTimeout(2500);
0296                 m_popupItem->showMessage(i18n("There is no path from the selected piece to this cell"), KGamePopupItem::BottomLeft);
0297             }
0298         }
0299     }
0300 }
0301 
0302 void KLinesScene::moveAnimFinished()
0303 {
0304     // m_field[m_selPos.x][m_selPos.y] still holds the ball pointer
0305     // but animation placed it to new location.
0306     // But it updated only pixel position, not the field one
0307     // So let's do it here
0308     BallItem *movedBall = m_field[m_selPos.x][m_selPos.y];
0309     // movedBall has new pixel position - let's find out corresponding field pos
0310     FieldPos newpos = pixToField(movedBall->pos());
0311 
0312     m_field[m_selPos.x][m_selPos.y] = nullptr; // no more ball here
0313     m_field[newpos.x][newpos.y] = movedBall;
0314 
0315     m_selPos.x = m_selPos.y = -1; // invalidate position
0316 
0317     m_placeBalls = true;
0318     // after anim finished, slot removeAnimFinished()
0319     // will be called
0320     searchAndErase();
0321 }
0322 
0323 void KLinesScene::removeAnimFinished()
0324 {
0325     if( m_itemsToDelete.isEmpty() && m_numFreeCells == 0 )
0326     {
0327         // game over
0328         gameOverHandler();
0329         return;
0330     }
0331 
0332     if( m_itemsToDelete.isEmpty() && m_placeBalls)
0333     {
0334         // slot bornAnimFinished() will be called
0335         // when born animation finishes
0336         // NOTE: removeAnimFinished() will be called again
0337         // after new balls will born (because searchAndErase() will be called)
0338         // but other if branch will be taken
0339         nextThreeBalls();
0340     }
0341     else
0342     {
0343         // this is kind of 'things to do after one turn is finished'
0344         // place in code :)
0345 
0346         int numBallsErased = m_itemsToDelete.count();
0347         if(numBallsErased)
0348         {
0349             // expression taked from previous code in klines.cpp
0350             m_score += 2*numBallsErased*numBallsErased - 20*numBallsErased + 60 ;
0351             m_score += m_bonusScore;
0352         }
0353 
0354         for (BallItem* item : std::as_const(m_itemsToDelete)) {
0355             removeItem(item);
0356             delete item;
0357         }
0358         m_itemsToDelete.clear();
0359 
0360         if(numBallsErased)
0361             Q_EMIT scoreChanged(m_score);
0362     }
0363 }
0364 
0365 void KLinesScene::bornAnimFinished()
0366 {
0367     // note that if m_numFreeCells == 0, we still need to
0368     // check for possible 5-in-a-row balls, i.e. call searchAndErase()
0369     // So there's another gameOver-check in removeAnimFinished()
0370     if( m_numFreeCells < 0 )
0371     {
0372         gameOverHandler();
0373         return;
0374     }
0375     // There's a little trick here:
0376     // searchAndErase() will cause m_animator to emit removeFinished()
0377     // If there wasn't m_placeBalls var
0378     // it would cause an infinite loop like this:
0379     // SaE()->removeAnimFinished()->next3Balls()->bornAnimFinished()->
0380     // SaE()->removeAnimFinished()->next3Balls()->...
0381     // etc etc
0382     m_placeBalls = false;
0383     // after placing new balls new 5-in-a-row chunks can occur
0384     // so we need to check for them
0385     //
0386     // And because of that we check for gameOver in removeAnimFinished()
0387     // rather than here - there's a chance that searchAndErase() will remove
0388     // balls making some free cells to play in
0389     searchAndErase();
0390 }
0391 
0392 void KLinesScene::searchAndErase()
0393 {
0394     // FIXME dimsuz: put more comments about bounds in for loops
0395 
0396     // QSet - to exclude adding duplicates
0397     QSet<FieldPos> positionsToDelete;
0398 
0399     // horizontal chunks searching
0400     for(int x=0; x<FIELD_SIZE-4; ++x)
0401         for(int y=0;y<FIELD_SIZE; ++y)
0402         {
0403             if(m_field[x][y] == nullptr)
0404                 continue;
0405 
0406             BallColor col = m_field[x][y]->color();
0407             int tmpx = x+1;
0408             while(tmpx < FIELD_SIZE && m_field[tmpx][y] && m_field[tmpx][y]->color() == col)
0409                 tmpx++;
0410             // tmpx-x will be: how much balls of the same color we found
0411             if(tmpx-x >= 5)
0412             {
0413                 for(int i=x; i<tmpx;++i)
0414                 {
0415                     positionsToDelete.insert( FieldPos(i,y) );
0416                 }
0417             }
0418             else
0419                 continue;
0420         }
0421 
0422     // vertical chunks searching
0423     for(int y=0; y<FIELD_SIZE-4; ++y)
0424         for(int x=0;x<FIELD_SIZE; ++x)
0425         {
0426             if(m_field[x][y] == nullptr)
0427                 continue;
0428 
0429             BallColor col = m_field[x][y]->color();
0430             int tmpy = y+1;
0431             while(tmpy < FIELD_SIZE && m_field[x][tmpy] && m_field[x][tmpy]->color() == col)
0432                 tmpy++;
0433             // tmpy-y will be: how much balls of the same color we found
0434             if(tmpy-y >= 5)
0435             {
0436                 for(int j=y; j<tmpy;++j)
0437                 {
0438                     positionsToDelete.insert( FieldPos(x,j) );
0439                 }
0440             }
0441             else
0442                 continue;
0443         }
0444 
0445     // down-right diagonal
0446     for(int x=0; x<FIELD_SIZE-4; ++x)
0447         for(int y=0;y<FIELD_SIZE-4; ++y)
0448         {
0449             if(m_field[x][y] == nullptr)
0450                 continue;
0451 
0452             BallColor col = m_field[x][y]->color();
0453             int tmpx = x+1;
0454             int tmpy = y+1;
0455             while(tmpx < FIELD_SIZE && tmpy < FIELD_SIZE &&
0456                     m_field[tmpx][tmpy] && m_field[tmpx][tmpy]->color() == col)
0457             {
0458                 tmpx++;
0459                 tmpy++;
0460             }
0461             // tmpx-x (and tmpy-y too) will be: how much balls of the same color we found
0462             if(tmpx-x >= 5)
0463             {
0464                 for(int i=x,j=y; i<tmpx;++i,++j)
0465                 {
0466                     positionsToDelete.insert( FieldPos(i,j) );
0467                 }
0468             }
0469             else
0470                 continue;
0471         }
0472 
0473     // up-right diagonal
0474     for(int x=0; x<FIELD_SIZE-4; ++x)
0475         for(int y=4; y<FIELD_SIZE; ++y)
0476         {
0477             if(m_field[x][y] == nullptr)
0478                 continue;
0479 
0480             BallColor col = m_field[x][y]->color();
0481             int tmpx = x+1;
0482             int tmpy = y-1;
0483             while(tmpx < FIELD_SIZE && tmpy >=0 &&
0484                     m_field[tmpx][tmpy] && m_field[tmpx][tmpy]->color() == col)
0485             {
0486                 tmpx++;
0487                 tmpy--;
0488             }
0489             // tmpx-x (and tmpy-y too) will be: how much balls of the same color we found
0490             if(tmpx-x >= 5)
0491             {
0492                 for(int i=x,j=y; i<tmpx;++i,--j)
0493                 {
0494                     positionsToDelete.insert( FieldPos(i,j) );
0495                 }
0496             }
0497             else
0498                 continue;
0499         }
0500 
0501     for (const FieldPos& pos : std::as_const(positionsToDelete)) {
0502         m_itemsToDelete.append(m_field[pos.x][pos.y]);
0503         m_field[pos.x][pos.y] = nullptr;
0504         m_numFreeCells++;
0505     }
0506 
0507     // after it finishes slot removeAnimFinished() will be called
0508     // if m_itemsToDelete is empty removeAnimFinished() will be called immediately
0509     m_animator->animateRemove( m_itemsToDelete );
0510 }
0511 
0512 void KLinesScene::moveFocusLeft()
0513 {
0514     if( !m_focusItem->isVisible() )
0515     {
0516         m_focusItem->show();
0517         // no action for the first time
0518         return;
0519     }
0520     FieldPos focusPos = pixToField( m_focusItem->pos() );
0521     focusPos.x--;
0522     if (focusPos.x < 0) // rotate on the torus
0523         focusPos.x = FIELD_SIZE - 1;
0524 
0525     m_focusItem->setPos ( fieldToPix( focusPos ) );
0526 }
0527 
0528 void KLinesScene::moveFocusRight()
0529 {
0530     if( !m_focusItem->isVisible() )
0531     {
0532         m_focusItem->show();
0533         // no action for the first time
0534         return;
0535     }
0536     FieldPos focusPos = pixToField( m_focusItem->pos() );
0537     focusPos.x++;
0538     if (focusPos.x >= FIELD_SIZE) // rotate on the torus
0539         focusPos.x = 0;
0540 
0541     m_focusItem->setPos ( fieldToPix( focusPos ) );
0542 }
0543 
0544 void KLinesScene::moveFocusUp()
0545 {
0546     if( !m_focusItem->isVisible() )
0547     {
0548         m_focusItem->show();
0549         // no action for the first time
0550         return;
0551     }
0552     FieldPos focusPos = pixToField( m_focusItem->pos() );
0553     focusPos.y--;
0554     if (focusPos.y < 0) // rotate on the torus
0555         focusPos.y = FIELD_SIZE - 1;
0556 
0557     m_focusItem->setPos ( fieldToPix( focusPos ) );
0558 }
0559 
0560 void KLinesScene::moveFocusDown()
0561 {
0562     if( !m_focusItem->isVisible() )
0563     {
0564         m_focusItem->show();
0565         // no action for the first time
0566         return;
0567     }
0568 
0569     FieldPos focusPos = pixToField( m_focusItem->pos() );
0570     focusPos.y++;
0571     if (focusPos.y >= FIELD_SIZE) // rotate on the torus
0572         focusPos.y = 0;
0573 
0574     m_focusItem->setPos ( fieldToPix( focusPos ) );
0575 }
0576 
0577 void KLinesScene::cellSelected()
0578 {
0579     if( !m_focusItem->isVisible() )
0580         m_focusItem->show();
0581 
0582     // we're taking the center of the cell
0583     selectOrMove( pixToField( m_focusItem->pos() + QPointF(m_cellSize/2,m_cellSize/2) ) );
0584 }
0585 
0586 void KLinesScene::saveUndoInfo()
0587 {
0588     // save field state to undoInfo
0589     for(int x=0;x<FIELD_SIZE;++x)
0590         for(int y=0; y<FIELD_SIZE;++y)
0591             // NumColors represents no color
0592             m_undoInfo.fcolors[x][y] = ( m_field[x][y] ? m_field[x][y]->color() : NumColors );
0593     m_undoInfo.numFreeCells = m_numFreeCells;
0594     m_undoInfo.score = m_score;
0595     m_undoInfo.nextColors = m_nextColors;
0596 
0597     Q_EMIT stateChanged(QStringLiteral( "undoable" ));
0598 }
0599 
0600 // Brings m_field and some other vars to the state it was before last turn
0601 void KLinesScene::undo()
0602 {
0603     // do not allow undo during animation
0604     if(m_animator->isAnimating())
0605         return;
0606 
0607     if( m_selPos.isValid() )
0608         m_field[m_selPos.x][m_selPos.y]->stopAnimation();
0609 
0610     BallColor col;
0611     for(int x=0;x<FIELD_SIZE;++x)
0612         for(int y=0; y<FIELD_SIZE;++y)
0613         {
0614             col = m_undoInfo.fcolors[x][y];
0615             if(col == NumColors) // no ball
0616             {
0617                 if( m_field[x][y] )
0618                 {
0619                     removeItem( m_field[x][y] );
0620                     delete m_field[x][y];
0621                     m_field[x][y] = nullptr;
0622                 }
0623                 continue;
0624             }
0625 
0626             if( m_field[x][y] )
0627             {
0628                 if( m_field[x][y]->color() != col )
0629                     m_field[x][y]->setColor(col);
0630                 //else live it as it is
0631             }
0632             else
0633             {
0634                 BallItem *item = new BallItem(this);
0635                 item->setColor(col);
0636                 item->setPos( fieldToPix( FieldPos(x,y) ) );
0637                 item->show();
0638                 item->setRenderSize(KLinesRenderer::cellExtent());
0639                 m_field[x][y] = item;
0640             }
0641         }
0642     m_numFreeCells = m_undoInfo.numFreeCells;
0643     m_score = m_undoInfo.score;
0644     m_nextColors = m_undoInfo.nextColors;
0645 
0646     m_selPos = FieldPos();
0647 
0648     m_previewItem->setPreviewColors( m_nextColors );
0649 
0650     Q_EMIT scoreChanged(m_score);
0651 
0652     Q_EMIT stateChanged(QStringLiteral( "not_undoable" ));
0653 }
0654 
0655 void KLinesScene::drawBackground(QPainter *p, const QRectF&)
0656 {
0657     QPixmap tile = KLinesRenderer::backgroundTilePixmap();
0658     p->drawPixmap( 0, 0, KLinesRenderer::backgroundPixmap(sceneRect().size().toSize()) );
0659     p->drawPixmap( m_playFieldRect.x(), m_playFieldRect.y(),
0660                    KLinesRenderer::backgroundBorderPixmap( m_playFieldRect.size() ) );
0661 
0662     int startX = m_playFieldRect.x()+m_playFieldBorderSize;
0663     int maxX = m_playFieldRect.x()+m_cellSize*FIELD_SIZE;
0664     int startY = m_playFieldRect.y()+m_playFieldBorderSize;
0665     int maxY = m_playFieldRect.y()+m_cellSize*FIELD_SIZE;
0666 
0667     for(int x=startX; x<maxX; x+=m_cellSize)
0668         for(int y=startY; y<maxY; y+=m_cellSize)
0669             p->drawPixmap( x, y, tile );
0670 }
0671 
0672 void KLinesScene::gameOverHandler()
0673 {
0674     if( m_gameOver )
0675         return; // don't emit twice
0676     m_gameOver = true;
0677     qCDebug(KLINES_LOG) << "GAME OVER";
0678     Q_EMIT stateChanged(QStringLiteral( "not_undoable" ));
0679     //Q_EMIT enableUndo(false);
0680     Q_EMIT gameOver(m_score);
0681 
0682     // disable auto-hide
0683     m_popupItem->setMessageTimeout(0);
0684     m_popupItem->showMessage(i18n("<h1>Game over</h1>"), KGamePopupItem::Center);
0685 }
0686 
0687 #include "moc_scene.cpp"