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