File indexing completed on 2024-05-05 04:02:56
0001 /* 0002 SPDX-FileCopyrightText: 2010 Ni Hui <shuizhuyuanluo@126.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "gamescene.h" 0008 0009 #include "piece.h" 0010 #include "settings.h" 0011 #include "undo.h" 0012 // KDEGames 0013 #include <KGamePopupItem> 0014 #include <KGameTheme> 0015 #include <KGameThemeProvider> 0016 // KF 0017 #include <KConfigGroup> 0018 #include <KLocalizedString> 0019 // Qt 0020 #include <QRandomGenerator> 0021 #include <QEasingCurve> 0022 #include <QGraphicsColorizeEffect> 0023 #include <QPainter> 0024 #include <QParallelAnimationGroup> 0025 #include <QPropertyAnimation> 0026 #include <QSequentialAnimationGroup> 0027 #include <QStandardPaths> 0028 0029 static KGameThemeProvider* provider() 0030 { 0031 //TODO: Do we want to store separate theme choices for Klickety and KSame? 0032 const QLatin1String defaultTheme = 0033 Settings::self()->config()->name() == QLatin1String("ksamerc") 0034 ? QLatin1String("ksame") : QLatin1String("default"); 0035 KGameThemeProvider* prov = new KGameThemeProvider; 0036 prov->discoverThemes(QStringLiteral("themes"), defaultTheme); 0037 return prov; 0038 } 0039 0040 GameScene::GameScene( QObject* parent ) 0041 : QGraphicsScene(parent), 0042 m_renderer(provider()), 0043 m_messenger(new KGamePopupItem), 0044 m_showBoundLines(true), 0045 m_enableAnimation(true), 0046 m_enableHighlight(true), 0047 m_backgroundType(0), 0048 PWC(0), 0049 PHC(0), 0050 m_colorCount(0), 0051 m_gameId(QRandomGenerator::global()->bounded(RAND_MAX)), 0052 m_isPaused(false), 0053 m_isFinished(false), 0054 m_animation(new QSequentialAnimationGroup), 0055 m_soundRemove(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/klickety/remove.ogg")), this), 0056 m_soundGameFinished(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/klickety/game-finished.ogg")), this) 0057 { 0058 connect(&m_undoStack, &QUndoStack::canUndoChanged, this, &GameScene::canUndoChanged); 0059 connect(&m_undoStack, &QUndoStack::canRedoChanged, this, &GameScene::canRedoChanged); 0060 connect(this, &GameScene::sceneRectChanged, this, &GameScene::resize); 0061 0062 connect(m_animation, &QSequentialAnimationGroup::finished, this, &GameScene::updateScene); 0063 0064 // init messenger 0065 m_messenger->setMessageOpacity( 0.8 ); 0066 m_messenger->setMessageTimeout( 0 ); 0067 m_messenger->setHideOnMouseClick( false ); 0068 addItem( m_messenger ); 0069 m_messenger->forceHide(); 0070 } 0071 0072 GameScene::~GameScene() 0073 { 0074 qDeleteAll( m_pieces ); 0075 delete m_animation; 0076 } 0077 0078 void GameScene::startNewGame( int pwc, int phc, int colorCount, int gameId ) 0079 { 0080 PWC = pwc; 0081 PHC = phc; 0082 m_colorCount = colorCount; 0083 m_gameId = gameId; 0084 m_isPaused = false; 0085 m_isFinished = false; 0086 m_undoStack.clear(); 0087 0088 for (auto & piece : std::as_const(m_pieces)) { 0089 removeItem( piece ); 0090 } 0091 qDeleteAll( m_pieces ); 0092 m_pieces.clear(); 0093 0094 // hide messenger if any 0095 m_messenger->forceHide(); 0096 0097 // create a default pen 0098 QPen pen; 0099 pen.setStyle( Qt::SolidLine ); 0100 pen.setWidth( 1 ); 0101 pen.setBrush( Qt::black ); 0102 pen.setCapStyle( Qt::RoundCap ); 0103 pen.setJoinStyle( Qt::RoundJoin ); 0104 0105 QRandomGenerator s( gameId ); 0106 for ( int j = 0; j < PHC; ++j ) { 0107 for ( int i = 0; i < PWC; ++i ) { 0108 // piece item 0109 Piece* item = new Piece( &m_renderer, i, j, s.bounded( m_colorCount ) ); 0110 connect(item, &Piece::pieceClicked, this, &GameScene::removePieces); 0111 connect(item, &Piece::pieceHovered, this, &GameScene::highlightPieces); 0112 connect(item, &Piece::pieceUnhovered, this, &GameScene::unhighlightPieces); 0113 m_pieces << item; 0114 addItem( item ); 0115 0116 // set up highlight effects 0117 item->m_highlighter->setParentItem( item ); 0118 item->m_highlighter->hide(); 0119 0120 // bound line item 0121 item->m_rightLine->setPen( pen ); 0122 item->m_bottomLine->setPen( pen ); 0123 item->m_rightLine->setParentItem( item ); 0124 item->m_bottomLine->setParentItem( item ); 0125 item->m_rightLine->hide(); 0126 item->m_bottomLine->hide(); 0127 } 0128 } 0129 0130 Q_EMIT remainCountChanged( pwc * phc ); 0131 0132 updateScene(); 0133 } 0134 0135 void GameScene::loadGame( const KConfigGroup& config ) 0136 { 0137 int pwc = config.readEntry( "PWC", -1 ); 0138 int phc = config.readEntry( "PHC", -1 ); 0139 int colorCount = config.readEntry( "ColorCount", -1 ); 0140 int gameId = config.readEntry( "GameId", -1 ); 0141 if ( pwc == -1 || phc == -1 || colorCount == -1 || gameId == -1 ) { 0142 qWarning() << "Unexpected game parameters."; 0143 return; 0144 } 0145 startNewGame( pwc, phc, colorCount, gameId ); 0146 0147 int moveCount = config.readEntry( "MoveCount", 0 ); 0148 int undoIndex = config.readEntry( "UndoIndex", 0 ); 0149 if ( undoIndex > moveCount ) { 0150 qWarning() << "Unexpected undo history structure."; 0151 return; 0152 } 0153 0154 // disable animation temporarily 0155 bool enableAnimationOld = m_enableAnimation; 0156 setEnableAnimation( false ); 0157 0158 // execute the history 0159 for ( int i = 0; i < moveCount; ++i ) { 0160 QList<int> move = config.readEntry( QStringLiteral( "Move_%1" ).arg( i ), QList<int>() ); 0161 if ( move.count() != 2 ) { 0162 qWarning() << "Unexpected undo command structure."; 0163 return; 0164 } 0165 int x = move.at( 0 ); 0166 int y = move.at( 1 ); 0167 if ( x < 0 || x >= pwc || y < 0 || y >= phc ) { 0168 qWarning() << "Unexpected undo command logic. Skip it."; 0169 continue; 0170 } 0171 0172 removePieces( x, y ); 0173 } 0174 0175 // undo 0176 for ( int i = 0; i < moveCount - undoIndex; ++i ) { 0177 undoMove(); 0178 } 0179 0180 setEnableAnimation( enableAnimationOld ); 0181 0182 Q_EMIT remainCountChanged( currentRemainCount() ); 0183 } 0184 0185 void GameScene::saveGame( KConfigGroup& config ) const 0186 { 0187 config.writeEntry( "PWC", PWC ); 0188 config.writeEntry( "PHC", PHC ); 0189 config.writeEntry( "ColorCount", m_colorCount ); 0190 config.writeEntry( "GameId", m_gameId ); 0191 0192 // save undostack 0193 int moveCount = m_undoStack.count(); 0194 int undoIndex = m_undoStack.index(); 0195 config.writeEntry( "MoveCount", moveCount ); 0196 config.writeEntry( "UndoIndex", undoIndex ); 0197 for ( int i = 0; i < moveCount; ++i ) { 0198 const QUndoCommand* cmd = m_undoStack.command( i ); 0199 // the first child should be the user click 0200 const QUndoCommand* click = cmd->child( 0 ); 0201 if ( click->id() != ID_HIDEPIECE ) { 0202 qWarning() << "Unexpected command id."; 0203 return; 0204 } 0205 Piece* clickPiece = static_cast<const HidePiece*>(click)->m_piece; 0206 QList<int> move; 0207 move << clickPiece->m_x << clickPiece->m_y; 0208 config.writeEntry( QStringLiteral( "Move_%1" ).arg( i ), move ); 0209 } 0210 } 0211 0212 void GameScene::restartGame() 0213 { 0214 startNewGame( PWC, PHC, m_colorCount, m_gameId ); 0215 } 0216 0217 void GameScene::setPaused( bool isPaused ) 0218 { 0219 if ( m_isPaused == isPaused ) { 0220 return; 0221 } 0222 0223 m_isPaused = isPaused; 0224 0225 // hide or unhide all the enabled pieces 0226 for ( int j = 0; j < PHC; ++j ) { 0227 for ( int i = 0; i < PWC; ++i ) { 0228 Piece* item = m_pieces[j*PWC+i]; 0229 if ( item->isEnabled() ) { 0230 item->setVisible( !m_isPaused ); 0231 } 0232 } 0233 } 0234 0235 if ( m_isPaused ) { 0236 m_messenger->showMessage( i18n( "paused" ), KGamePopupItem::Center ); 0237 Q_EMIT canUndoChanged( false ); 0238 Q_EMIT canRedoChanged( false ); 0239 } 0240 else { 0241 m_messenger->forceHide(); 0242 Q_EMIT canUndoChanged( m_undoStack.canUndo() ); 0243 Q_EMIT canRedoChanged( m_undoStack.canRedo() ); 0244 } 0245 } 0246 0247 bool GameScene::isGameFinished() const 0248 { 0249 if ( m_pieces.isEmpty() || m_undoStack.isClean() ) { 0250 return true; 0251 } 0252 0253 for ( int j = 0; j < PHC; ++j ) { 0254 for ( int i = 0; i < PWC; ++i ) { 0255 Piece* item = m_pieces[j*PWC+i]; 0256 // check same color neighbors, rightside and downside 0257 if ( !item->isEnabled() ) { 0258 continue; 0259 } 0260 int rightX = i + 1; 0261 int downY = j + 1; 0262 if ( rightX < PWC && m_pieces[j*PWC+rightX]->isEnabled() 0263 && m_pieces[j*PWC+rightX]->m_color == item->m_color ) { 0264 return false; 0265 } 0266 if ( downY < PHC && m_pieces[downY*PWC+i]->isEnabled() 0267 && m_pieces[downY*PWC+i]->m_color == item->m_color ) { 0268 return false; 0269 } 0270 } 0271 } 0272 return true; 0273 } 0274 0275 KGameThemeProvider* GameScene::themeProvider() const 0276 { 0277 return m_renderer.themeProvider(); 0278 } 0279 0280 void GameScene::setBackgroundType( int type ) 0281 { 0282 m_backgroundType = type; 0283 // update background immediately 0284 invalidate( sceneRect(), QGraphicsScene::BackgroundLayer ); 0285 } 0286 0287 void GameScene::setShowBoundLines( bool isShowing ) 0288 { 0289 if ( m_showBoundLines != isShowing ) { 0290 m_showBoundLines = isShowing; 0291 // update bound lines immediately 0292 updateBoundLines(); 0293 } 0294 } 0295 0296 void GameScene::setEnableAnimation( bool isEnabled ) 0297 { 0298 m_enableAnimation = isEnabled; 0299 } 0300 0301 void GameScene::setEnableHighlight( bool isEnabled ) 0302 { 0303 m_enableHighlight = isEnabled; 0304 } 0305 0306 void GameScene::undoMove() 0307 { 0308 unhighlightPieces( m_currentlyHoveredPieceX, m_currentlyHoveredPieceY ); 0309 m_undoStack.undo(); 0310 Q_EMIT remainCountChanged( currentRemainCount() ); 0311 updateScene(); 0312 } 0313 0314 void GameScene::redoMove() 0315 { 0316 m_undoStack.redo(); 0317 Q_EMIT remainCountChanged( currentRemainCount() ); 0318 updateScene(); 0319 } 0320 0321 void GameScene::undoAllMove() 0322 { 0323 while ( m_undoStack.canUndo() ) { 0324 m_undoStack.undo(); 0325 } 0326 Q_EMIT remainCountChanged( currentRemainCount() ); 0327 updateScene(); 0328 } 0329 0330 void GameScene::redoAllMove() 0331 { 0332 while ( m_undoStack.canRedo() ) { 0333 m_undoStack.redo(); 0334 } 0335 Q_EMIT remainCountChanged( currentRemainCount() ); 0336 updateScene(); 0337 } 0338 0339 void GameScene::checkGameFinished() 0340 { 0341 int remain = currentRemainCount(); 0342 Q_EMIT remainCountChanged( remain ); 0343 bool finished = isGameFinished(); 0344 if ( finished && m_isFinished != finished ) { 0345 if (Settings::enableSounds()) { 0346 m_soundGameFinished.start(); 0347 } 0348 m_messenger->showMessage( i18n( "Game finished" ) , KGamePopupItem::Center ); 0349 Q_EMIT canUndoChanged( false ); 0350 Q_EMIT canRedoChanged( false ); 0351 Q_EMIT gameFinished( remain ); 0352 } 0353 m_isFinished = finished; 0354 } 0355 0356 void GameScene::traverseNeighbors( int x, int y, int color, bool (GameScene::*func)(Piece*) ) 0357 { 0358 if ( x < 0 || x >= PWC || y < 0 || y >= PHC ) { 0359 return; 0360 } 0361 0362 int index = y * PWC + x; 0363 if ( m_pieces[index]->m_color == color ) { 0364 if ( (this->*func)( m_pieces[index] ) ) { 0365 traverseNeighbors( x-1, y, color, func );// check left neighbor 0366 traverseNeighbors( x, y-1, color, func );// check up neighbor 0367 traverseNeighbors( x+1, y, color, func );// check right neighbor 0368 traverseNeighbors( x, y+1, color, func );// check down neighbor 0369 } 0370 } 0371 } 0372 0373 bool GameScene::highlightPiece( Piece* p ) 0374 { 0375 if ( !p->isEnabled() || p->m_highlighter->isVisible() ) { 0376 return false; 0377 } 0378 p->m_highlighter->show(); 0379 return true; 0380 } 0381 0382 bool GameScene::unhighlightPiece( Piece* p ) 0383 { 0384 if ( !p->isEnabled() || !p->m_highlighter->isVisible() ) { 0385 return false; 0386 } 0387 p->m_highlighter->hide(); 0388 return true; 0389 } 0390 0391 bool GameScene::removePiece( Piece* p ) 0392 { 0393 if ( !p->isEnabled() ) { 0394 return false; 0395 } 0396 m_undoStack.push( new HidePiece( p ) ); 0397 return true; 0398 } 0399 0400 bool GameScene::canRemovePiece( int x, int y ) 0401 { 0402 int index = y * PWC + x; 0403 int color = m_pieces[index]->m_color; 0404 0405 int leftX = x - 1; 0406 int rightX = x + 1; 0407 int upY = y - 1; 0408 int downY = y + 1; 0409 return ( leftX >= 0 && m_pieces[y*PWC+leftX]->m_color == color && m_pieces[y*PWC+leftX]->isEnabled() ) 0410 || ( rightX < PWC && m_pieces[y*PWC+rightX]->m_color == color && m_pieces[y*PWC+rightX]->isEnabled() ) 0411 || ( upY >= 0 && m_pieces[upY*PWC+x]->m_color == color && m_pieces[upY*PWC+x]->isEnabled() ) 0412 || ( downY < PHC && m_pieces[downY*PWC+x]->m_color == color && m_pieces[downY*PWC+x]->isEnabled() ); 0413 } 0414 0415 void GameScene::highlightPieces( int x, int y ) 0416 { 0417 m_currentlyHoveredPieceX = x; 0418 m_currentlyHoveredPieceY = y; 0419 if ( !m_enableHighlight ) { 0420 return; 0421 } 0422 0423 if ( x < 0 || x >= PWC || y < 0 || y >= PHC ) { 0424 return; 0425 } 0426 0427 if ( !canRemovePiece( x, y ) ) { 0428 return; 0429 } 0430 0431 int index = y * PWC + x; 0432 m_pieces[index]->m_highlighter->show(); 0433 traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::highlightPiece );// check left neighbor 0434 traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::highlightPiece );// check up neighbor 0435 traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::highlightPiece );// check right neighbor 0436 traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::highlightPiece );// check down neighbor 0437 0438 Q_EMIT markedCountChanged( currentMarkedCount() ); 0439 } 0440 0441 void GameScene::unhighlightPieces( int x, int y ) 0442 { 0443 if ( x < 0 || x >= PWC || y < 0 || y >= PHC ) { 0444 return; 0445 } 0446 0447 if ( !canRemovePiece( x, y ) ) { 0448 return; 0449 } 0450 0451 int index = y * PWC + x; 0452 m_pieces[index]->m_highlighter->hide(); 0453 0454 traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check left neighbor 0455 traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check up neighbor 0456 traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check right neighbor 0457 traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check down neighbor 0458 0459 Q_EMIT markedCountChanged( currentMarkedCount() ); 0460 } 0461 0462 void GameScene::removePieces( int x, int y ) 0463 { 0464 if ( x < 0 || x >= PWC || y < 0 || y >= PHC ) { 0465 return; 0466 } 0467 0468 if ( !canRemovePiece( x, y ) ) { 0469 return; 0470 } 0471 0472 // If the animation is running we need to emit here the "old" 0473 // remaining count otherwise the remainCountChanged won't 0474 // be emitted until the "next" animation finishes meaning 0475 // that the scores get calculated wrongly because it thinks 0476 // you removed more pieces in one go that you really did 0477 if (m_animation->state() == QAbstractAnimation::Running) { 0478 Q_EMIT remainCountChanged( currentRemainCount() ); 0479 } 0480 // unhighlight pieces 0481 unhighlightPieces( x, y ); 0482 0483 int index = y * PWC + x; 0484 m_undoStack.beginMacro( QStringLiteral( "Remove pieces" ) ); 0485 m_undoStack.push( new HidePiece( m_pieces[index] ) ); 0486 0487 traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::removePiece );// check left neighbor 0488 traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::removePiece );// check up neighbor 0489 traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::removePiece );// check right neighbor 0490 traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::removePiece );// check down neighbor 0491 0492 const int elementsSize1 = sceneRect().width() / PWC; 0493 const int elementsSize2 = sceneRect().height() / PHC; 0494 const int elementsSize = qMin( elementsSize1, elementsSize2 ); 0495 0496 // horizontal center 0497 int gameAreaWidth = PWC * elementsSize; 0498 int xShift = ( sceneRect().width() - gameAreaWidth ) / 2; 0499 // vertical center 0500 int gameAreaHeight = PHC * elementsSize; 0501 int yShift = ( sceneRect().height() - gameAreaHeight ) / 2; 0502 0503 QParallelAnimationGroup* gravityAnimationGroup = nullptr; 0504 QParallelAnimationGroup* removeColumnsAnimationGroup = nullptr; 0505 if ( m_enableAnimation ) { 0506 // Clearing next line will trigger checkGameFinished but we don't want that since the user just clicked 0507 // on some more tiles to remove, we will trigger another check game finished at the end of this function 0508 // either directly or via connection the animation // finished signal again to checkGameFinished 0509 disconnect(m_animation, &QSequentialAnimationGroup::finished, this, &GameScene::checkGameFinished); 0510 // remove previous animations if any 0511 m_animation->clear(); 0512 gravityAnimationGroup = new QParallelAnimationGroup; 0513 removeColumnsAnimationGroup = new QParallelAnimationGroup; 0514 } 0515 0516 // gravity 0517 for ( int i = 0; i < PWC; ++i ) { 0518 bool mayNeedMove = false; 0519 int floorRow = PHC - 1; 0520 for ( int j = PHC-1; j >= 0; --j ) { 0521 if ( !m_pieces[ j * PWC + i ]->isEnabled() && !mayNeedMove ) { 0522 // found the hidden piece 0523 mayNeedMove = true; 0524 floorRow = j; 0525 } 0526 else if ( m_pieces[ j * PWC + i ]->isEnabled() && mayNeedMove ) { 0527 // swap the visible one down to floorRow 0528 Piece* visiblePiece = m_pieces[ j * PWC + i ]; 0529 Piece* hiddenPiece = m_pieces[ floorRow * PWC + i ]; 0530 const QPointF oldpos( xShift + visiblePiece->m_x * elementsSize, yShift + visiblePiece->m_y * elementsSize ); 0531 const QPointF newpos = hiddenPiece->pos(); 0532 0533 m_undoStack.push( new SwapPiece( &m_pieces[j*PWC+i], &m_pieces[floorRow*PWC+i], oldpos, newpos ) ); 0534 0535 if ( m_enableAnimation ) { 0536 // restore old position for proper animation beginning state 0537 visiblePiece->setPos( oldpos ); 0538 0539 // 300ms animation is fast enough to prevent a user's resizing during it 0540 QPropertyAnimation* animation = new QPropertyAnimation( visiblePiece, "pos", this ); 0541 animation->setStartValue( oldpos ); 0542 animation->setEndValue( newpos ); 0543 animation->setDuration( 250 ); 0544 animation->setEasingCurve( QEasingCurve::InQuad ); 0545 gravityAnimationGroup->addAnimation( animation ); 0546 } 0547 0548 --floorRow; 0549 } 0550 } 0551 } 0552 0553 // remove empty columns 0554 bool mayNeedMove = false; 0555 int floorCol = 0; 0556 for ( int i = 0; i < PWC; ++i ) { 0557 if ( !m_pieces[ (PHC-1) * PWC + i ]->isEnabled() && !mayNeedMove ) { 0558 // found the empty column 0559 mayNeedMove = true; 0560 floorCol = i; 0561 } 0562 else if ( m_pieces[ (PHC-1) * PWC + i ]->isEnabled() && mayNeedMove ) { 0563 // swap the visible column down to floorCol 0564 for ( int j = PHC-1; j >= 0; --j ) { 0565 if ( m_pieces[ j * PWC + i ]->isEnabled() ) { 0566 Piece* visiblePiece = m_pieces[ j * PWC + i ]; 0567 Piece* hiddenPiece = m_pieces[ j * PWC + floorCol ]; 0568 const QPointF oldpos( xShift + visiblePiece->m_x * elementsSize, yShift + visiblePiece->m_y * elementsSize ); 0569 const QPointF newpos = hiddenPiece->pos(); 0570 0571 m_undoStack.push( new SwapPiece( &m_pieces[j*PWC+i], &m_pieces[j*PWC+floorCol], oldpos, newpos ) ); 0572 0573 if ( m_enableAnimation ) { 0574 // restore old position for proper animation beginning state 0575 visiblePiece->setPos( oldpos ); 0576 0577 // 300ms animation is fast enough to prevent a user's resizing during it 0578 QPropertyAnimation* animation = new QPropertyAnimation( visiblePiece, "pos", this ); 0579 animation->setStartValue( oldpos ); 0580 animation->setEndValue( newpos ); 0581 animation->setDuration( 250 ); 0582 animation->setEasingCurve( QEasingCurve::InOutQuad ); 0583 removeColumnsAnimationGroup->addAnimation( animation ); 0584 } 0585 } 0586 } 0587 0588 ++floorCol; 0589 } 0590 } 0591 0592 m_undoStack.endMacro(); 0593 if (Settings::enableSounds()) { 0594 m_soundRemove.start(); 0595 } 0596 0597 if ( m_enableAnimation ) { 0598 // after finishing the animation we want to check if the game has finished 0599 connect(m_animation, &QSequentialAnimationGroup::finished, this, &GameScene::checkGameFinished); 0600 // add new animations 0601 m_animation->addAnimation( gravityAnimationGroup ); 0602 m_animation->addAnimation( removeColumnsAnimationGroup ); 0603 m_animation->start( QAbstractAnimation::KeepWhenStopped ); 0604 // update bound lines if there are no animations 0605 if ( m_animation->totalDuration() == 0 ) { 0606 updateBoundLines(); 0607 checkGameFinished(); 0608 } 0609 } 0610 else { 0611 updateBoundLines(); 0612 checkGameFinished(); 0613 } 0614 } 0615 0616 int GameScene::currentMarkedCount() const 0617 { 0618 int marked = 0; 0619 for ( const Piece* p : std::as_const(m_pieces) ) { 0620 if ( p->m_highlighter->isVisible() ) { 0621 ++marked; 0622 } 0623 } 0624 return marked; 0625 } 0626 0627 int GameScene::currentRemainCount() const 0628 { 0629 int remain = 0; 0630 for ( const Piece* p : std::as_const(m_pieces) ) { 0631 if ( p->isEnabled() ) { 0632 ++remain; 0633 } 0634 } 0635 return remain; 0636 } 0637 0638 void GameScene::resize( const QRectF& size ) 0639 { 0640 const int elementsSize1 = size.width() / PWC; 0641 const int elementsSize2 = size.height() / PHC; 0642 const int elementsSize = qMin( elementsSize1, elementsSize2 ); 0643 0644 // horizontal center pieces 0645 int gameAreaWidth = PWC * elementsSize; 0646 int xShift = ( size.width() - gameAreaWidth ) / 2; 0647 // vertical center pieces 0648 int gameAreaHeight = PHC * elementsSize; 0649 int yShift = ( size.height() - gameAreaHeight ) / 2; 0650 0651 for ( int j = 0; j < PHC; ++j ) { 0652 for ( int i = 0; i < PWC; ++i ) { 0653 Piece* item = m_pieces[j*PWC+i]; 0654 item->setRenderSize( QSize(elementsSize,elementsSize) ); 0655 const QPoint pos( xShift + item->m_x * elementsSize, yShift + item->m_y * elementsSize ); 0656 item->setPos( pos ); 0657 item->m_highlighter->setRenderSize( QSize(elementsSize,elementsSize) ); 0658 item->m_highlighter->setPos( 0, 0 ); 0659 } 0660 } 0661 0662 if ( m_showBoundLines ) { 0663 updateBoundLines(); 0664 } 0665 0666 // center the messenger 0667 m_messenger->setPos( size.width() / 2 - m_messenger->boundingRect().width() / 2, 0668 size.height() / 2 - m_messenger->boundingRect().height() / 2 ); 0669 } 0670 0671 void GameScene::updateScene() 0672 { 0673 resize( sceneRect() ); 0674 } 0675 0676 void GameScene::updateBoundLines() 0677 { 0678 const int elementsSize1 = sceneRect().width() / PWC; 0679 const int elementsSize2 = sceneRect().height() / PHC; 0680 const int elementsSize = qMin( elementsSize1, elementsSize2 ); 0681 0682 for ( int j = 0; j < PHC; ++j ) { 0683 for ( int i = 0; i < PWC; ++i ) { 0684 Piece* item = m_pieces[j*PWC+i]; 0685 QGraphicsLineItem* rightLine = item->m_rightLine; 0686 QGraphicsLineItem* bottomLine = item->m_bottomLine; 0687 // draw boarder if necessary, rightside and downside 0688 if ( !item->isEnabled() || !m_showBoundLines ) { 0689 rightLine->hide(); 0690 bottomLine->hide(); 0691 continue; 0692 } 0693 0694 // shift one pixel, otherwise the next piece will overlap our lines 0695 int rightX = i + 1; 0696 int downY = j + 1; 0697 if ( rightX < PWC && m_pieces[j*PWC+rightX]->isEnabled() 0698 && m_pieces[j*PWC+rightX]->m_color != item->m_color ) { 0699 rightLine->setLine( elementsSize-1, 0-1, elementsSize-1, elementsSize-1 ); 0700 rightLine->show(); 0701 } else { 0702 rightLine->hide(); 0703 } 0704 if ( downY < PHC && m_pieces[downY*PWC+i]->isEnabled() 0705 && m_pieces[downY*PWC+i]->m_color != item->m_color ) { 0706 bottomLine->setLine( 0-1, elementsSize-1, elementsSize-1, elementsSize-1 ); 0707 bottomLine->show(); 0708 } else { 0709 bottomLine->hide(); 0710 } 0711 } 0712 } 0713 } 0714 0715 void GameScene::drawBackground( QPainter* painter, const QRectF& rect ) 0716 { 0717 if ( Settings::radioTheme() == true ) { 0718 // NOTE: the following is a workaround for https://bugs.kde.org/show_bug.cgi?id=243573 0719 // cache the background pixmap locally in order to reduce the spritePixmap traffic when resizing 0720 static QByteArray theme_pre( m_renderer.theme()->identifier() ); 0721 static QSize size_pre( rect.toRect().size() ); 0722 static QPixmap pix( m_renderer.spritePixmap( QStringLiteral( "BACKGROUND" ), size_pre ) ); 0723 QSize size_offset = size_pre - rect.toRect().size(); 0724 if ( size_offset.width() < -100 || size_offset.height() < -100 || theme_pre != m_renderer.theme()->identifier() ) { 0725 qWarning() << "export"; 0726 theme_pre = m_renderer.theme()->identifier(); 0727 size_pre = rect.toRect().size(); 0728 pix = m_renderer.spritePixmap( QStringLiteral( "BACKGROUND" ), size_pre ); 0729 painter->drawPixmap( rect.topLeft(), pix ); 0730 } 0731 else { 0732 painter->drawPixmap( rect.topLeft(), pix.scaled( rect.toRect().size() ) ); 0733 } 0734 } 0735 if ( Settings::radioColor() == true ) { 0736 painter->fillRect( rect, Settings::bgColor() ); 0737 } 0738 if ( Settings::radioImage() == true ) { 0739 // cache the background image locally in order to reduce the file opening traffic when resizing 0740 static QString img_filepath( Settings::bgImage().path() ); 0741 static QImage img( img_filepath ); 0742 if ( img_filepath != Settings::bgImage().path() ) { 0743 img_filepath = Settings::bgImage().path(); 0744 img = QImage( img_filepath ); 0745 } 0746 if ( !img.isNull() ) { 0747 painter->drawImage( rect, img ); 0748 } else { 0749 qWarning() << "Null background image " << Settings::bgImage(); 0750 } 0751 } 0752 } 0753 0754 #include "moc_gamescene.cpp"