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"