1 /*
2     SPDX-FileCopyrightText: 2010 Ni Hui <shuizhuyuanluo@126.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #include "gamescene.h"
8 
9 #include "piece.h"
10 #include "settings.h"
11 #include "undo.h"
12 
13 #include <KConfigGroup>
14 #include <KGamePopupItem>
15 #include <KgTheme>
16 #include <KgThemeProvider>
17 #include <KLocalizedString>
18 
19 #include <QRandomGenerator>
20 #include <QEasingCurve>
21 #include <QGraphicsColorizeEffect>
22 #include <QPainter>
23 #include <QParallelAnimationGroup>
24 #include <QPropertyAnimation>
25 #include <QSequentialAnimationGroup>
26 #include <QStandardPaths>
27 
provider()28 static KgThemeProvider* provider()
29 {
30     //TODO: Do we want to store separate theme choices for Klickety and KSame?
31     const QLatin1String defaultTheme =
32         Settings::self()->config()->name() == QLatin1String("ksamerc")
33         ? QLatin1String("ksame") : QLatin1String("default");
34     KgThemeProvider* prov = new KgThemeProvider;
35     prov->discoverThemes("appdata", QStringLiteral("themes"), defaultTheme);
36     return prov;
37 }
38 
GameScene(QObject * parent)39 GameScene::GameScene( QObject* parent )
40 : QGraphicsScene(parent),
41 m_renderer(provider()),
42 m_messenger(new KGamePopupItem),
43 m_showBoundLines(true),
44 m_enableAnimation(true),
45 m_enableHighlight(true),
46 m_backgroundType(0),
47 PWC(0),
48 PHC(0),
49 m_colorCount(0),
50 m_gameId(QRandomGenerator::global()->bounded(RAND_MAX)),
51 m_isPaused(false),
52 m_isFinished(false),
53 m_animation(new QSequentialAnimationGroup),
54 m_soundRemove(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/klickety/remove.ogg")), this),
55 m_soundGameFinished(QStandardPaths::locate(QStandardPaths::GenericDataLocation, QStringLiteral("sounds/klickety/game-finished.ogg")), this)
56 {
57     connect(&m_undoStack, &QUndoStack::canUndoChanged, this, &GameScene::canUndoChanged);
58     connect(&m_undoStack, &QUndoStack::canRedoChanged, this, &GameScene::canRedoChanged);
59     connect(this, &GameScene::sceneRectChanged, this, &GameScene::resize);
60 
61     connect(m_animation, &QSequentialAnimationGroup::finished, this, &GameScene::updateScene);
62 
63     // init messenger
64     m_messenger->setMessageOpacity( 0.8 );
65     m_messenger->setMessageTimeout( 0 );
66     m_messenger->setHideOnMouseClick( false );
67     addItem( m_messenger );
68     m_messenger->forceHide();
69 }
70 
~GameScene()71 GameScene::~GameScene()
72 {
73     qDeleteAll( m_pieces );
74     delete m_animation;
75 }
76 
startNewGame(int pwc,int phc,int colorCount,int gameId)77 void GameScene::startNewGame( int pwc, int phc, int colorCount, int gameId )
78 {
79     PWC = pwc;
80     PHC = phc;
81     m_colorCount = colorCount;
82     m_gameId = gameId;
83     m_isPaused = false;
84     m_isFinished = false;
85     m_undoStack.clear();
86 
87     for (auto & piece : std::as_const(m_pieces)) {
88         removeItem( piece );
89     }
90     qDeleteAll( m_pieces );
91     m_pieces.clear();
92 
93     // hide messenger if any
94     m_messenger->forceHide();
95 
96     // create a default pen
97     QPen pen;
98     pen.setStyle( Qt::SolidLine );
99     pen.setWidth( 1 );
100     pen.setBrush( Qt::black );
101     pen.setCapStyle( Qt::RoundCap );
102     pen.setJoinStyle( Qt::RoundJoin );
103 
104     QRandomGenerator s( gameId );
105     for ( int j = 0; j < PHC; ++j ) {
106         for ( int i = 0; i < PWC; ++i ) {
107             // piece item
108             Piece* item = new Piece( &m_renderer, i, j, s.bounded( m_colorCount ) );
109             connect(item, &Piece::pieceClicked, this, &GameScene::removePieces);
110             connect(item, &Piece::pieceHovered, this, &GameScene::highlightPieces);
111             connect(item, &Piece::pieceUnhovered, this, &GameScene::unhighlightPieces);
112             m_pieces << item;
113             addItem( item );
114 
115             // set up highlight effects
116             item->m_highlighter->setParentItem( item );
117             item->m_highlighter->hide();
118 
119             // bound line item
120             item->m_rightLine->setPen( pen );
121             item->m_bottomLine->setPen( pen );
122             item->m_rightLine->setParentItem( item );
123             item->m_bottomLine->setParentItem( item );
124             item->m_rightLine->hide();
125             item->m_bottomLine->hide();
126         }
127     }
128 
129     Q_EMIT remainCountChanged( pwc * phc );
130 
131     updateScene();
132 }
133 
loadGame(const KConfigGroup & config)134 void GameScene::loadGame( const KConfigGroup& config )
135 {
136     int pwc = config.readEntry( "PWC", -1 );
137     int phc = config.readEntry( "PHC", -1 );
138     int colorCount = config.readEntry( "ColorCount", -1 );
139     int gameId = config.readEntry( "GameId", -1 );
140     if ( pwc == -1 || phc == -1 || colorCount == -1 || gameId == -1 ) {
141         qWarning() << "Unexpected game parameters.";
142         return;
143     }
144     startNewGame( pwc, phc, colorCount, gameId );
145 
146     int moveCount = config.readEntry( "MoveCount", 0 );
147     int undoIndex = config.readEntry( "UndoIndex", 0 );
148     if ( undoIndex > moveCount ) {
149         qWarning() << "Unexpected undo history structure.";
150         return;
151     }
152 
153     // disable animation temporarily
154     bool enableAnimationOld = m_enableAnimation;
155     setEnableAnimation( false );
156 
157     // execute the history
158     for ( int i = 0; i < moveCount; ++i ) {
159         QList<int> move = config.readEntry( QStringLiteral( "Move_%1" ).arg( i ), QList<int>() );
160         if ( move.count() != 2 ) {
161             qWarning() << "Unexpected undo command structure.";
162             return;
163         }
164         int x = move.at( 0 );
165         int y = move.at( 1 );
166         if ( x < 0 || x >= pwc || y < 0 || y >= phc ) {
167             qWarning() << "Unexpected undo command logic. Skip it.";
168             continue;
169         }
170 
171         removePieces( x, y );
172     }
173 
174     // undo
175     for ( int i = 0; i < moveCount - undoIndex; ++i ) {
176         undoMove();
177     }
178 
179     setEnableAnimation( enableAnimationOld );
180 
181     Q_EMIT remainCountChanged( currentRemainCount() );
182 }
183 
saveGame(KConfigGroup & config) const184 void GameScene::saveGame( KConfigGroup& config ) const
185 {
186     config.writeEntry( "PWC", PWC );
187     config.writeEntry( "PHC", PHC );
188     config.writeEntry( "ColorCount", m_colorCount );
189     config.writeEntry( "GameId", m_gameId );
190 
191     // save undostack
192     int moveCount = m_undoStack.count();
193     int undoIndex = m_undoStack.index();
194     config.writeEntry( "MoveCount", moveCount );
195     config.writeEntry( "UndoIndex", undoIndex );
196     for ( int i = 0; i < moveCount; ++i ) {
197         const QUndoCommand* cmd = m_undoStack.command( i );
198         // the first child should be the user click
199         const QUndoCommand* click = cmd->child( 0 );
200         if ( click->id() != ID_HIDEPIECE ) {
201             qWarning() << "Unexpected command id.";
202             return;
203         }
204         Piece* clickPiece = static_cast<const HidePiece*>(click)->m_piece;
205         QList<int> move;
206         move << clickPiece->m_x << clickPiece->m_y;
207         config.writeEntry( QStringLiteral( "Move_%1" ).arg( i ), move );
208     }
209 }
210 
restartGame()211 void GameScene::restartGame()
212 {
213     startNewGame( PWC, PHC, m_colorCount, m_gameId );
214 }
215 
setPaused(bool isPaused)216 void GameScene::setPaused( bool isPaused )
217 {
218     if ( m_isPaused == isPaused ) {
219         return;
220     }
221 
222     m_isPaused = isPaused;
223 
224     // hide or unhide all the enabled pieces
225     for ( int j = 0; j < PHC; ++j ) {
226         for ( int i = 0; i < PWC; ++i ) {
227             Piece* item = m_pieces[j*PWC+i];
228             if ( item->isEnabled() ) {
229                 item->setVisible( !m_isPaused );
230             }
231         }
232     }
233 
234     if ( m_isPaused ) {
235         m_messenger->showMessage( i18n( "paused" ), KGamePopupItem::Center );
236         Q_EMIT canUndoChanged( false );
237         Q_EMIT canRedoChanged( false );
238     }
239     else {
240         m_messenger->forceHide();
241         Q_EMIT canUndoChanged( m_undoStack.canUndo() );
242         Q_EMIT canRedoChanged( m_undoStack.canRedo() );
243     }
244 }
245 
isGameFinished() const246 bool GameScene::isGameFinished() const
247 {
248     if ( m_pieces.isEmpty() || m_undoStack.isClean() ) {
249         return true;
250     }
251 
252     for ( int j = 0; j < PHC; ++j ) {
253         for ( int i = 0; i < PWC; ++i ) {
254             Piece* item = m_pieces[j*PWC+i];
255             // check same color neighbors, rightside and downside
256             if ( !item->isEnabled() ) {
257                 continue;
258             }
259             int rightX = i + 1;
260             int downY = j + 1;
261             if ( rightX < PWC && m_pieces[j*PWC+rightX]->isEnabled()
262                 && m_pieces[j*PWC+rightX]->m_color == item->m_color ) {
263                 return false;
264             }
265             if ( downY < PHC && m_pieces[downY*PWC+i]->isEnabled()
266                 && m_pieces[downY*PWC+i]->m_color == item->m_color ) {
267                 return false;
268             }
269         }
270     }
271     return true;
272 }
273 
themeProvider() const274 KgThemeProvider* GameScene::themeProvider() const
275 {
276     return m_renderer.themeProvider();
277 }
278 
setBackgroundType(int type)279 void GameScene::setBackgroundType( int type )
280 {
281     m_backgroundType = type;
282     // update background immediately
283     invalidate( sceneRect(), QGraphicsScene::BackgroundLayer );
284 }
285 
setShowBoundLines(bool isShowing)286 void GameScene::setShowBoundLines( bool isShowing )
287 {
288     if ( m_showBoundLines != isShowing ) {
289         m_showBoundLines = isShowing;
290         // update bound lines immediately
291         updateBoundLines();
292     }
293 }
294 
setEnableAnimation(bool isEnabled)295 void GameScene::setEnableAnimation( bool isEnabled )
296 {
297     m_enableAnimation = isEnabled;
298 }
299 
setEnableHighlight(bool isEnabled)300 void GameScene::setEnableHighlight( bool isEnabled )
301 {
302     m_enableHighlight = isEnabled;
303 }
304 
undoMove()305 void GameScene::undoMove()
306 {
307     unhighlightPieces( m_currentlyHoveredPieceX, m_currentlyHoveredPieceY );
308     m_undoStack.undo();
309     Q_EMIT remainCountChanged( currentRemainCount() );
310     updateScene();
311 }
312 
redoMove()313 void GameScene::redoMove()
314 {
315     m_undoStack.redo();
316     Q_EMIT remainCountChanged( currentRemainCount() );
317     updateScene();
318 }
319 
undoAllMove()320 void GameScene::undoAllMove()
321 {
322     while ( m_undoStack.canUndo() ) {
323         m_undoStack.undo();
324     }
325     Q_EMIT remainCountChanged( currentRemainCount() );
326     updateScene();
327 }
328 
redoAllMove()329 void GameScene::redoAllMove()
330 {
331     while ( m_undoStack.canRedo() ) {
332         m_undoStack.redo();
333     }
334     Q_EMIT remainCountChanged( currentRemainCount() );
335     updateScene();
336 }
337 
checkGameFinished()338 void GameScene::checkGameFinished()
339 {
340     int remain = currentRemainCount();
341     Q_EMIT remainCountChanged( remain );
342     bool finished = isGameFinished();
343     if ( finished && m_isFinished != finished ) {
344         if (Settings::enableSounds()) {
345             m_soundGameFinished.start();
346         }
347         m_messenger->showMessage( i18n( "Game finished" ) , KGamePopupItem::Center );
348         Q_EMIT canUndoChanged( false );
349         Q_EMIT canRedoChanged( false );
350         Q_EMIT gameFinished( remain );
351     }
352     m_isFinished = finished;
353 }
354 
traverseNeighbors(int x,int y,int color,bool (GameScene::* func)(Piece *))355 void GameScene::traverseNeighbors( int x, int y, int color, bool (GameScene::*func)(Piece*) )
356 {
357     if ( x < 0 || x >= PWC || y < 0 || y >= PHC ) {
358         return;
359     }
360 
361     int index = y * PWC + x;
362     if ( m_pieces[index]->m_color == color ) {
363         if ( (this->*func)( m_pieces[index] ) ) {
364             traverseNeighbors( x-1, y, color, func );// check left neighbor
365             traverseNeighbors( x, y-1, color, func );// check up neighbor
366             traverseNeighbors( x+1, y, color, func );// check right neighbor
367             traverseNeighbors( x, y+1, color, func );// check down neighbor
368         }
369     }
370 }
371 
highlightPiece(Piece * p)372 bool GameScene::highlightPiece( Piece* p )
373 {
374     if ( !p->isEnabled() || p->m_highlighter->isVisible() ) {
375         return false;
376     }
377     p->m_highlighter->show();
378     return true;
379 }
380 
unhighlightPiece(Piece * p)381 bool GameScene::unhighlightPiece( Piece* p )
382 {
383     if ( !p->isEnabled() || !p->m_highlighter->isVisible() ) {
384         return false;
385     }
386     p->m_highlighter->hide();
387     return true;
388 }
389 
removePiece(Piece * p)390 bool GameScene::removePiece( Piece* p )
391 {
392     if ( !p->isEnabled() ) {
393         return false;
394     }
395     m_undoStack.push( new HidePiece( p ) );
396     return true;
397 }
398 
canRemovePiece(int x,int y)399 bool GameScene::canRemovePiece( int x, int y )
400 {
401     int index = y * PWC + x;
402     int color = m_pieces[index]->m_color;
403 
404     int leftX = x - 1;
405     int rightX = x + 1;
406     int upY = y - 1;
407     int downY = y + 1;
408     return ( leftX >= 0 && m_pieces[y*PWC+leftX]->m_color == color && m_pieces[y*PWC+leftX]->isEnabled() )
409         || ( rightX < PWC && m_pieces[y*PWC+rightX]->m_color == color && m_pieces[y*PWC+rightX]->isEnabled() )
410         || ( upY >= 0 && m_pieces[upY*PWC+x]->m_color == color && m_pieces[upY*PWC+x]->isEnabled() )
411         || ( downY < PHC && m_pieces[downY*PWC+x]->m_color == color && m_pieces[downY*PWC+x]->isEnabled() );
412 }
413 
highlightPieces(int x,int y)414 void GameScene::highlightPieces( int x, int y )
415 {
416     m_currentlyHoveredPieceX = x;
417     m_currentlyHoveredPieceY = y;
418     if ( !m_enableHighlight ) {
419         return;
420     }
421 
422     if ( x < 0 || x >= PWC || y < 0 || y >= PHC ) {
423         return;
424     }
425 
426     if ( !canRemovePiece( x, y ) ) {
427         return;
428     }
429 
430     int index = y * PWC + x;
431     m_pieces[index]->m_highlighter->show();
432     traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::highlightPiece );// check left neighbor
433     traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::highlightPiece );// check up neighbor
434     traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::highlightPiece );// check right neighbor
435     traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::highlightPiece );// check down neighbor
436 
437     Q_EMIT markedCountChanged( currentMarkedCount() );
438 }
439 
unhighlightPieces(int x,int y)440 void GameScene::unhighlightPieces( int x, int y )
441 {
442     if ( x < 0 || x >= PWC || y < 0 || y >= PHC ) {
443         return;
444     }
445 
446     if ( !canRemovePiece( x, y ) ) {
447         return;
448     }
449 
450     int index = y * PWC + x;
451     m_pieces[index]->m_highlighter->hide();
452 
453     traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check left neighbor
454     traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check up neighbor
455     traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check right neighbor
456     traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::unhighlightPiece );// check down neighbor
457 
458     Q_EMIT markedCountChanged( currentMarkedCount() );
459 }
460 
removePieces(int x,int y)461 void GameScene::removePieces( int x, int y )
462 {
463     if ( x < 0 || x >= PWC || y < 0 || y >= PHC ) {
464         return;
465     }
466 
467     if ( !canRemovePiece( x, y ) ) {
468         return;
469     }
470 
471     // unhighlight pieces
472     unhighlightPieces( x, y );
473 
474     int index = y * PWC + x;
475     m_undoStack.beginMacro( QStringLiteral( "Remove pieces" ) );
476     m_undoStack.push( new HidePiece( m_pieces[index] ) );
477 
478     traverseNeighbors( x-1, y, m_pieces[index]->m_color, &GameScene::removePiece );// check left neighbor
479     traverseNeighbors( x, y-1, m_pieces[index]->m_color, &GameScene::removePiece );// check up neighbor
480     traverseNeighbors( x+1, y, m_pieces[index]->m_color, &GameScene::removePiece );// check right neighbor
481     traverseNeighbors( x, y+1, m_pieces[index]->m_color, &GameScene::removePiece );// check down neighbor
482 
483     const int elementsSize1 = sceneRect().width() / PWC;
484     const int elementsSize2 = sceneRect().height() / PHC;
485     const int elementsSize = qMin( elementsSize1, elementsSize2 );
486 
487     // horizontal center
488     int gameAreaWidth = PWC * elementsSize;
489     int xShift = ( sceneRect().width() - gameAreaWidth ) / 2;
490     // vertical center
491     int gameAreaHeight = PHC * elementsSize;
492     int yShift = ( sceneRect().height() - gameAreaHeight ) / 2;
493 
494     QParallelAnimationGroup* gravityAnimationGroup = nullptr;
495     QParallelAnimationGroup* removeColumnsAnimationGroup = nullptr;
496     if ( m_enableAnimation ) {
497         // Clearing next line will trigger checkGameFinished but we don't want that since the user just clicked
498         // on some more tiles to remove, we will trigger another check game finished at the end of this function
499         // either directly or via connection the animation // finished signal again to checkGameFinished
500         disconnect(m_animation, &QSequentialAnimationGroup::finished, this, &GameScene::checkGameFinished);
501         // remove previous animations if any
502         m_animation->clear();
503         gravityAnimationGroup = new QParallelAnimationGroup;
504         removeColumnsAnimationGroup = new QParallelAnimationGroup;
505     }
506 
507     // gravity
508     for ( int i = 0; i < PWC; ++i ) {
509         bool mayNeedMove = false;
510         int floorRow = PHC - 1;
511         for ( int j = PHC-1; j >= 0; --j ) {
512             if ( !m_pieces[ j * PWC + i ]->isEnabled() && !mayNeedMove ) {
513                 // found the hidden piece
514                 mayNeedMove = true;
515                 floorRow = j;
516             }
517             else if ( m_pieces[ j * PWC + i ]->isEnabled() && mayNeedMove ) {
518                 // swap the visible one down to floorRow
519                 Piece* visiblePiece = m_pieces[ j * PWC + i ];
520                 Piece* hiddenPiece = m_pieces[ floorRow * PWC + i ];
521                 const QPointF oldpos( xShift + visiblePiece->m_x * elementsSize, yShift + visiblePiece->m_y * elementsSize );
522                 const QPointF newpos = hiddenPiece->pos();
523 
524                 m_undoStack.push( new SwapPiece( &m_pieces[j*PWC+i], &m_pieces[floorRow*PWC+i], oldpos, newpos ) );
525 
526                 if ( m_enableAnimation ) {
527                     // restore old position for proper animation beginning state
528                     visiblePiece->setPos( oldpos );
529 
530                     // 300ms animation is fast enough to prevent a user's resizing during it
531                     QPropertyAnimation* animation = new QPropertyAnimation( visiblePiece, "pos", this );
532                     animation->setStartValue( oldpos );
533                     animation->setEndValue( newpos );
534                     animation->setDuration( 250 );
535                     animation->setEasingCurve( QEasingCurve::InQuad );
536                     gravityAnimationGroup->addAnimation( animation );
537                 }
538 
539                 --floorRow;
540             }
541         }
542     }
543 
544     // remove empty columns
545     bool mayNeedMove = false;
546     int floorCol = 0;
547     for ( int i = 0; i < PWC; ++i ) {
548         if ( !m_pieces[ (PHC-1) * PWC + i ]->isEnabled() && !mayNeedMove ) {
549             // found the empty column
550             mayNeedMove = true;
551             floorCol = i;
552         }
553         else if ( m_pieces[ (PHC-1) * PWC + i ]->isEnabled() && mayNeedMove ) {
554             // swap the visible column down to floorCol
555             for ( int j = PHC-1; j >= 0; --j ) {
556                 if ( m_pieces[ j * PWC + i ]->isEnabled() ) {
557                     Piece* visiblePiece = m_pieces[ j * PWC + i ];
558                     Piece* hiddenPiece = m_pieces[ j * PWC + floorCol ];
559                     const QPointF oldpos( xShift + visiblePiece->m_x * elementsSize, yShift + visiblePiece->m_y * elementsSize );
560                     const QPointF newpos = hiddenPiece->pos();
561 
562                     m_undoStack.push( new SwapPiece( &m_pieces[j*PWC+i], &m_pieces[j*PWC+floorCol], oldpos, newpos ) );
563 
564                     if ( m_enableAnimation ) {
565                         // restore old position for proper animation beginning state
566                         visiblePiece->setPos( oldpos );
567 
568                         // 300ms animation is fast enough to prevent a user's resizing during it
569                         QPropertyAnimation* animation = new QPropertyAnimation( visiblePiece, "pos", this );
570                         animation->setStartValue( oldpos );
571                         animation->setEndValue( newpos );
572                         animation->setDuration( 250 );
573                         animation->setEasingCurve( QEasingCurve::InOutQuad );
574                         removeColumnsAnimationGroup->addAnimation( animation );
575                     }
576                 }
577             }
578 
579             ++floorCol;
580         }
581     }
582 
583     m_undoStack.endMacro();
584     if (Settings::enableSounds()) {
585         m_soundRemove.start();
586     }
587 
588     if ( m_enableAnimation ) {
589         // after finishing the animation we want to check if the game has finished
590         connect(m_animation, &QSequentialAnimationGroup::finished, this, &GameScene::checkGameFinished);
591         // add new animations
592         m_animation->addAnimation( gravityAnimationGroup );
593         m_animation->addAnimation( removeColumnsAnimationGroup );
594         m_animation->start( QAbstractAnimation::KeepWhenStopped );
595         // update bound lines if there are no animations
596         if ( m_animation->totalDuration() == 0 ) {
597             updateBoundLines();
598             checkGameFinished();
599         }
600     }
601     else {
602         updateBoundLines();
603         checkGameFinished();
604     }
605 }
606 
currentMarkedCount() const607 int GameScene::currentMarkedCount() const
608 {
609     int marked = 0;
610     for ( const Piece* p : std::as_const(m_pieces) ) {
611         if ( p->m_highlighter->isVisible() ) {
612             ++marked;
613         }
614     }
615     return marked;
616 }
617 
currentRemainCount() const618 int GameScene::currentRemainCount() const
619 {
620     int remain = 0;
621     for ( const Piece* p : std::as_const(m_pieces) ) {
622         if ( p->isEnabled() ) {
623             ++remain;
624         }
625     }
626     return remain;
627 }
628 
resize(const QRectF & size)629 void GameScene::resize( const QRectF& size )
630 {
631     const int elementsSize1 = size.width() / PWC;
632     const int elementsSize2 = size.height() / PHC;
633     const int elementsSize = qMin( elementsSize1, elementsSize2 );
634 
635     // horizontal center pieces
636     int gameAreaWidth = PWC * elementsSize;
637     int xShift = ( size.width() - gameAreaWidth ) / 2;
638     // vertical center pieces
639     int gameAreaHeight = PHC * elementsSize;
640     int yShift = ( size.height() - gameAreaHeight ) / 2;
641 
642     for ( int j = 0; j < PHC; ++j ) {
643         for ( int i = 0; i < PWC; ++i ) {
644             Piece* item = m_pieces[j*PWC+i];
645             item->setRenderSize( QSize(elementsSize,elementsSize) );
646             const QPoint pos( xShift + item->m_x * elementsSize, yShift + item->m_y * elementsSize );
647             item->setPos( pos );
648             item->m_highlighter->setRenderSize( QSize(elementsSize,elementsSize) );
649             item->m_highlighter->setPos( 0, 0 );
650         }
651     }
652 
653     if ( m_showBoundLines ) {
654         updateBoundLines();
655     }
656 
657     // center the messenger
658     m_messenger->setPos( size.width() / 2 - m_messenger->boundingRect().width() / 2,
659                          size.height() / 2 - m_messenger->boundingRect().height() / 2 );
660 }
661 
updateScene()662 void GameScene::updateScene()
663 {
664     resize( sceneRect() );
665 }
666 
updateBoundLines()667 void GameScene::updateBoundLines()
668 {
669     const int elementsSize1 = sceneRect().width() / PWC;
670     const int elementsSize2 = sceneRect().height() / PHC;
671     const int elementsSize = qMin( elementsSize1, elementsSize2 );
672 
673     for ( int j = 0; j < PHC; ++j ) {
674         for ( int i = 0; i < PWC; ++i ) {
675             Piece* item = m_pieces[j*PWC+i];
676             QGraphicsLineItem* rightLine = item->m_rightLine;
677             QGraphicsLineItem* bottomLine = item->m_bottomLine;
678             // draw boarder if necessary, rightside and downside
679             if ( !item->isEnabled() || !m_showBoundLines ) {
680                 rightLine->hide();
681                 bottomLine->hide();
682                 continue;
683             }
684 
685             // shift one pixel, otherwise the next piece will overlap our lines
686             int rightX = i + 1;
687             int downY = j + 1;
688             if ( rightX < PWC && m_pieces[j*PWC+rightX]->isEnabled()
689                 && m_pieces[j*PWC+rightX]->m_color != item->m_color ) {
690                 rightLine->setLine( elementsSize-1, 0-1, elementsSize-1, elementsSize-1 );
691                 rightLine->show();
692             } else {
693                 rightLine->hide();
694             }
695             if ( downY < PHC && m_pieces[downY*PWC+i]->isEnabled()
696                 && m_pieces[downY*PWC+i]->m_color != item->m_color ) {
697                 bottomLine->setLine( 0-1, elementsSize-1, elementsSize-1, elementsSize-1 );
698                 bottomLine->show();
699             } else {
700                 bottomLine->hide();
701             }
702         }
703     }
704 }
705 
drawBackground(QPainter * painter,const QRectF & rect)706 void GameScene::drawBackground( QPainter* painter, const QRectF& rect )
707 {
708     if ( Settings::radioTheme() == true ) {
709         // NOTE: the following is a workaround for https://bugs.kde.org/show_bug.cgi?id=243573
710         // cache the background pixmap locally in order to reduce the spritePixmap traffic when resizing
711         static QByteArray theme_pre( m_renderer.theme()->identifier() );
712         static QSize size_pre( rect.toRect().size() );
713         static QPixmap pix( m_renderer.spritePixmap( QStringLiteral( "BACKGROUND" ), size_pre ) );
714         QSize size_offset = size_pre - rect.toRect().size();
715         if ( size_offset.width() < -100 || size_offset.height() < -100 || theme_pre != m_renderer.theme()->identifier() ) {
716             qWarning() << "export";
717             theme_pre = m_renderer.theme()->identifier();
718             size_pre = rect.toRect().size();
719             pix = m_renderer.spritePixmap( QStringLiteral( "BACKGROUND" ), size_pre );
720             painter->drawPixmap( rect.topLeft(), pix );
721         }
722         else {
723             painter->drawPixmap( rect.topLeft(), pix.scaled( rect.toRect().size() ) );
724         }
725     }
726     if ( Settings::radioColor() == true ) {
727         painter->fillRect( rect, Settings::bgColor() );
728     }
729     if ( Settings::radioImage() == true ) {
730         // cache the background image locally in order to reduce the file opening traffic when resizing
731         static QString img_filepath( Settings::bgImage().path() );
732         static QImage img( img_filepath );
733         if ( img_filepath != Settings::bgImage().path() ) {
734             img_filepath = Settings::bgImage().path();
735             img = QImage( img_filepath );
736         }
737         if ( !img.isNull() ) {
738             painter->drawImage( rect, img );
739         } else {
740             qWarning() << "Null background image " << Settings::bgImage();
741         }
742     }
743 }
744