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