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"