1 | /******************************************************************* |
2 | * |
3 | * Copyright 2006-2008 Dmitry Suzdalev <dimsuz@gmail.com> |
4 | * |
5 | * This file is part of the KDE project "KLines" |
6 | * |
7 | * KLines is free software; you can redistribute it and/or modify |
8 | * it under the terms of the GNU General Public License as published by |
9 | * the Free Software Foundation; either version 2, or (at your option) |
10 | * any later version. |
11 | * |
12 | * KLines is distributed in the hope that it will be useful, |
13 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | * GNU General Public License for more details. |
16 | * |
17 | * You should have received a copy of the GNU General Public License |
18 | * along with KLines; see the file COPYING. If not, write to |
19 | * the Free Software Foundation, 51 Franklin Street, Fifth Floor, |
20 | * Boston, MA 02110-1301, USA. |
21 | * |
22 | ********************************************************************/ |
23 | #include "scene.h" |
24 | #include "ballitem.h" |
25 | #include "previewitem.h" |
26 | #include "animator.h" |
27 | #include "renderer.h" |
28 | |
29 | #include <QGraphicsSceneMouseEvent> |
30 | #include <QPainter> |
31 | #include <QSet> |
32 | |
33 | #include <KGamePopupItem> |
34 | #include <KLocale> |
35 | #include <KDebug> |
36 | |
37 | inline uint qHash( const FieldPos& pos ) |
38 | { |
39 | return qHash( QPair<int,int>(pos.x,pos.y) ); |
40 | } |
41 | |
42 | KLinesScene::KLinesScene( QObject* parent ) |
43 | : QGraphicsScene(parent), |
44 | m_playFieldBorderSize(0), m_numFreeCells(FIELD_SIZE*FIELD_SIZE), |
45 | m_score(0), m_bonusScore(0), m_cellSize(32), m_previewZoneVisible(true) |
46 | { |
47 | m_animator = new KLinesAnimator(this); |
48 | connect( m_animator, SIGNAL(moveFinished()), SLOT(moveAnimFinished()) ); |
49 | connect( m_animator, SIGNAL(removeFinished()), SLOT(removeAnimFinished()) ); |
50 | connect( m_animator, SIGNAL(bornFinished()), SLOT(bornAnimFinished()) ); |
51 | |
52 | m_focusItem = new QGraphicsRectItem( QRectF(0, 0, m_cellSize, m_cellSize), 0, this ); |
53 | m_focusItem->setZValue(1.0); |
54 | m_focusItem->setPen( Qt::DashLine ); |
55 | |
56 | m_previewItem = new PreviewItem(this); |
57 | m_previewItem->setPos( 0, 0 ); |
58 | |
59 | m_popupItem = new KGamePopupItem; |
60 | addItem(m_popupItem); |
61 | |
62 | startNewGame(); |
63 | } |
64 | |
65 | void KLinesScene::startNewGame() |
66 | { |
67 | if(m_animator->isAnimating()) |
68 | return; |
69 | |
70 | // reset all vars |
71 | m_selPos = FieldPos(); |
72 | m_numFreeCells = FIELD_SIZE*FIELD_SIZE; |
73 | m_score = 0; |
74 | m_bonusScore = 0; |
75 | m_placeBalls = true; |
76 | m_gameOver = false; |
77 | m_itemsToDelete.clear(); |
78 | m_nextColors.clear(); |
79 | m_focusItem->setPos(0, 0); |
80 | m_focusItem->hide(); |
81 | |
82 | m_popupItem->forceHide(); |
83 | |
84 | // remove all ball items from the scene leaving other items untouched |
85 | QList<QGraphicsItem*> itemlist = items(); |
86 | foreach( QGraphicsItem* item, itemlist ) |
87 | { |
88 | BallItem* ball = qgraphicsitem_cast<BallItem*>(item); |
89 | if( ball ) |
90 | { |
91 | removeItem(item); |
92 | delete item; |
93 | } |
94 | } |
95 | |
96 | for(int x=0; x<FIELD_SIZE; ++x) |
97 | for(int y=0; y<FIELD_SIZE; ++y) |
98 | m_field[x][y] = 0; |
99 | |
100 | // init m_nextColors |
101 | for(int i=0; i<3; i++) |
102 | { |
103 | // random color |
104 | BallColor c = static_cast<BallColor>(m_randomSeq.getLong(static_cast<int>(NumColors))); |
105 | m_nextColors.append(c); |
106 | } |
107 | |
108 | emit stateChanged(QLatin1String( "not_undoable" )); |
109 | |
110 | nextThreeBalls(); |
111 | } |
112 | |
113 | KLinesScene::~KLinesScene() |
114 | { |
115 | delete m_animator; |
116 | } |
117 | |
118 | void KLinesScene::resizeScene(int width,int height) |
119 | { |
120 | // store focus item field pos (calculated using old cellSize) |
121 | FieldPos focusRectFieldPos = pixToField( m_focusItem->pos() ); |
122 | |
123 | bool hasBorder = KLinesRenderer::hasBorderElement(); |
124 | |
125 | int minDim = qMin( width, height ); |
126 | // border width is hardcoded to be half of cell size. |
127 | // take it into account if it exists |
128 | m_cellSize = hasBorder ? minDim/(FIELD_SIZE+1) : minDim/FIELD_SIZE; |
129 | |
130 | // set it only if current theme supports it |
131 | m_playFieldBorderSize = hasBorder ? m_cellSize/2 : 0; |
132 | |
133 | int boardSize = m_cellSize * FIELD_SIZE; |
134 | if ( m_previewZoneVisible && boardSize +m_playFieldBorderSize*2 + m_cellSize > width) // No space enough for balls preview |
135 | { |
136 | minDim = width; |
137 | m_cellSize = hasBorder ? (minDim - m_cellSize - m_playFieldBorderSize*2)/FIELD_SIZE : (minDim - m_cellSize)/FIELD_SIZE; |
138 | boardSize = m_cellSize * FIELD_SIZE; |
139 | } |
140 | |
141 | |
142 | m_playFieldRect.setX( (width - (m_previewZoneVisible ? m_cellSize : 0))/2 - boardSize/2 - m_playFieldBorderSize ); |
143 | m_playFieldRect.setY( height/2 - boardSize/2 - m_playFieldBorderSize ); |
144 | |
145 | m_playFieldRect.setWidth( boardSize + m_playFieldBorderSize*2 ); |
146 | m_playFieldRect.setHeight( boardSize + m_playFieldBorderSize*2 ); |
147 | |
148 | setSceneRect( 0, 0, width, height ); |
149 | |
150 | // sets render sizes for cells |
151 | KLinesRenderer::setCellSize( m_cellSize ); |
152 | QSize cellSize(m_cellSize, m_cellSize); |
153 | |
154 | // re-render && recalc positions for all balls |
155 | for( int x=0; x<FIELD_SIZE; ++x) |
156 | for(int y=0; y< FIELD_SIZE; ++y) |
157 | { |
158 | if( m_field[x][y] ) |
159 | { |
160 | // this will update pixmap |
161 | m_field[x][y]->setRenderSize(cellSize); |
162 | m_field[x][y]->setPos( fieldToPix( FieldPos(x,y) ) ); |
163 | m_field[x][y]->setColor( m_field[x][y]->color() ); |
164 | } |
165 | } |
166 | |
167 | m_focusItem->setRect( QRect(0,0, m_cellSize, m_cellSize) ); |
168 | m_focusItem->setPos( fieldToPix( focusRectFieldPos ) ); |
169 | |
170 | int previewOriginY = height / 2 - (3 * m_cellSize) / 2; |
171 | int previewOriginX = m_playFieldRect.x() + m_playFieldRect.width(); |
172 | m_previewItem->setPos( previewOriginX, previewOriginY ); |
173 | m_previewItem->setPreviewColors( m_nextColors ); |
174 | |
175 | //kDebug() << "resize:" << width << "," << height << "; cellSize:" << m_cellSize; |
176 | } |
177 | |
178 | void KLinesScene::endTurn() |
179 | { |
180 | if( m_gameOver ) |
181 | return; |
182 | |
183 | saveUndoInfo(); |
184 | nextThreeBalls(); |
185 | } |
186 | |
187 | void KLinesScene::nextThreeBalls() |
188 | { |
189 | if( m_animator->isAnimating() ) |
190 | return; |
191 | |
192 | QList<BallItem*> newItems; |
193 | BallItem* newBall; |
194 | for(int i=0; i<3; i++) |
195 | { |
196 | newBall = randomlyPlaceBall( m_nextColors.at(i) ); |
197 | if( newBall ) |
198 | newItems.append(newBall); |
199 | else |
200 | break; // the field is filled :). |
201 | } |
202 | |
203 | for(int i=0; i<3; i++) |
204 | { |
205 | // random color |
206 | BallColor c = static_cast<BallColor>(m_randomSeq.getLong(static_cast<int>(NumColors))); |
207 | m_nextColors[i] = c; |
208 | } |
209 | |
210 | m_previewItem->setPreviewColors( m_nextColors ); |
211 | |
212 | m_animator->animateBorn( newItems ); |
213 | } |
214 | |
215 | void KLinesScene::setPreviewZoneVisible( bool visible ) |
216 | { |
217 | if (visible == m_previewZoneVisible) |
218 | return; |
219 | |
220 | m_previewZoneVisible = visible; |
221 | m_previewItem->setVisible( visible ); |
222 | resizeScene((int) width(), (int) height()); |
223 | invalidate( sceneRect() ); |
224 | } |
225 | |
226 | BallItem* KLinesScene::randomlyPlaceBall(BallColor c) |
227 | { |
228 | m_numFreeCells--; |
229 | if(m_numFreeCells < 0) |
230 | { |
231 | // restore m_numFreeCells value, it will trigger |
232 | // saveAndErase() after bornAnimFinished to check if |
233 | // we have 5-in-a-row to erase |
234 | m_numFreeCells = 0; |
235 | return 0; // game over, we won't create more balls |
236 | } |
237 | |
238 | int posx = -1, posy = -1; |
239 | // let's find random free cell |
240 | do |
241 | { |
242 | posx = m_randomSeq.getLong(FIELD_SIZE); |
243 | posy = m_randomSeq.getLong(FIELD_SIZE); |
244 | } while( m_field[posx][posy] != 0 ); |
245 | |
246 | BallItem* newBall = new BallItem( this); |
247 | newBall->setColor(c, false); // pixmap will be set by born animation |
248 | newBall->setPos( fieldToPix( FieldPos(posx,posy) ) ); |
249 | |
250 | m_field[posx][posy] = newBall; |
251 | return newBall; |
252 | } |
253 | |
254 | void KLinesScene::mousePressEvent( QGraphicsSceneMouseEvent* ev ) |
255 | { |
256 | QGraphicsScene::mousePressEvent(ev); |
257 | QRect boardRect = m_playFieldRect.adjusted( m_playFieldBorderSize, |
258 | m_playFieldBorderSize, |
259 | -m_playFieldBorderSize, |
260 | -m_playFieldBorderSize ); |
261 | |
262 | if ( !boardRect.contains( ev->scenePos().toPoint() ) ) |
263 | return; |
264 | |
265 | selectOrMove( pixToField(ev->scenePos()) ); |
266 | } |
267 | |
268 | void KLinesScene::selectOrMove( const FieldPos& fpos ) |
269 | { |
270 | if( m_animator->isAnimating() ) |
271 | return; |
272 | |
273 | if( m_field[fpos.x][fpos.y] ) // ball was selected |
274 | { |
275 | if( m_selPos.isValid() ) |
276 | { |
277 | m_field[m_selPos.x][m_selPos.y]->stopAnimation(); |
278 | |
279 | if ( m_selPos == fpos ) |
280 | { |
281 | m_selPos.x = m_selPos.y = -1; // invalidate position |
282 | return; |
283 | } |
284 | } |
285 | |
286 | m_field[fpos.x][fpos.y]->startSelectedAnimation(); |
287 | m_selPos = fpos; |
288 | } |
289 | else // move selected ball to new location |
290 | { |
291 | if( m_selPos.isValid() && m_field[fpos.x][fpos.y] == 0 ) |
292 | { |
293 | saveUndoInfo(); |
294 | // start move animation |
295 | // slot moveAnimFinished() will be called when it finishes |
296 | bool pathExists = m_animator->animateMove(m_selPos, fpos); |
297 | if(!pathExists) |
298 | { |
299 | m_popupItem->setMessageTimeout(2500); |
300 | m_popupItem->showMessage(i18n("There is no path from the selected piece to this cell" ), KGamePopupItem::BottomLeft); |
301 | } |
302 | } |
303 | } |
304 | } |
305 | |
306 | void KLinesScene::moveAnimFinished() |
307 | { |
308 | // m_field[m_selPos.x][m_selPos.y] still holds the ball pointer |
309 | // but animation placed it to new location. |
310 | // But it updated only pixel position, not the field one |
311 | // So let's do it here |
312 | BallItem *movedBall = m_field[m_selPos.x][m_selPos.y]; |
313 | // movedBall has new pixel position - let's find out corresponding field pos |
314 | FieldPos newpos = pixToField(movedBall->pos()); |
315 | |
316 | m_field[m_selPos.x][m_selPos.y] = 0; // no more ball here |
317 | m_field[newpos.x][newpos.y] = movedBall; |
318 | |
319 | m_selPos.x = m_selPos.y = -1; // invalidate position |
320 | |
321 | m_placeBalls = true; |
322 | // after anim finished, slot removeAnimFinished() |
323 | // will be called |
324 | searchAndErase(); |
325 | } |
326 | |
327 | void KLinesScene::removeAnimFinished() |
328 | { |
329 | if( m_itemsToDelete.isEmpty() && m_numFreeCells == 0 ) |
330 | { |
331 | // game over |
332 | gameOverHandler(); |
333 | return; |
334 | } |
335 | |
336 | if( m_itemsToDelete.isEmpty() && m_placeBalls) |
337 | { |
338 | // slot bornAnimFinished() will be called |
339 | // when born animation finishes |
340 | // NOTE: removeAnimFinished() will be called again |
341 | // after new balls will born (because searchAndErase() will be called) |
342 | // but other if branch will be taken |
343 | nextThreeBalls(); |
344 | } |
345 | else |
346 | { |
347 | // this is kind of 'things to do after one turn is finished' |
348 | // place in code :) |
349 | |
350 | int numBallsErased = m_itemsToDelete.count(); |
351 | if(numBallsErased) |
352 | { |
353 | // expression taked from previous code in klines.cpp |
354 | m_score += 2*numBallsErased*numBallsErased - 20*numBallsErased + 60 ; |
355 | m_score += m_bonusScore; |
356 | } |
357 | |
358 | foreach( BallItem* item, m_itemsToDelete ) |
359 | { |
360 | removeItem(item); |
361 | delete item; |
362 | } |
363 | m_itemsToDelete.clear(); |
364 | |
365 | if(numBallsErased) |
366 | emit scoreChanged(m_score); |
367 | } |
368 | } |
369 | |
370 | void KLinesScene::bornAnimFinished() |
371 | { |
372 | // note that if m_numFreeCells == 0, we still need to |
373 | // check for possible 5-in-a-row balls, i.e. call searchAndErase() |
374 | // So there's another gameOver-check in removeAnimFinished() |
375 | if( m_numFreeCells < 0 ) |
376 | { |
377 | gameOverHandler(); |
378 | return; |
379 | } |
380 | // There's a little trick here: |
381 | // searchAndErase() will cause m_animator to emit removeFinished() |
382 | // If there wasn't m_placeBalls var |
383 | // it would cause an infinite loop like this: |
384 | // SaE()->removeAnimFinished()->next3Balls()->bornAnimFinished()-> |
385 | // SaE()->removeAnimFinished()->next3Balls()->... |
386 | // etc etc |
387 | m_placeBalls = false; |
388 | // after placing new balls new 5-in-a-row chunks can occur |
389 | // so we need to check for them |
390 | // |
391 | // And because of that we check for gameOver in removeAnimFinished() |
392 | // rather than here - there's a chance that searchAndErase() will remove |
393 | // balls making some free cells to play in |
394 | searchAndErase(); |
395 | } |
396 | |
397 | void KLinesScene::searchAndErase() |
398 | { |
399 | // FIXME dimsuz: put more comments about bounds in for loops |
400 | |
401 | // QSet - to exclude adding duplicates |
402 | QSet<FieldPos> positionsToDelete; |
403 | |
404 | // horizontal chunks searching |
405 | for(int x=0; x<FIELD_SIZE-4; ++x) |
406 | for(int y=0;y<FIELD_SIZE; ++y) |
407 | { |
408 | if(m_field[x][y] == 0) |
409 | continue; |
410 | |
411 | BallColor col = m_field[x][y]->color(); |
412 | int tmpx = x+1; |
413 | while(tmpx < FIELD_SIZE && m_field[tmpx][y] && m_field[tmpx][y]->color() == col) |
414 | tmpx++; |
415 | // tmpx-x will be: how much balls of the same color we found |
416 | if(tmpx-x >= 5) |
417 | { |
418 | for(int i=x; i<tmpx;++i) |
419 | { |
420 | positionsToDelete.insert( FieldPos(i,y) ); |
421 | } |
422 | } |
423 | else |
424 | continue; |
425 | } |
426 | |
427 | // vertical chunks searching |
428 | for(int y=0; y<FIELD_SIZE-4; ++y) |
429 | for(int x=0;x<FIELD_SIZE; ++x) |
430 | { |
431 | if(m_field[x][y] == 0) |
432 | continue; |
433 | |
434 | BallColor col = m_field[x][y]->color(); |
435 | int tmpy = y+1; |
436 | while(tmpy < FIELD_SIZE && m_field[x][tmpy] && m_field[x][tmpy]->color() == col) |
437 | tmpy++; |
438 | // tmpy-y will be: how much balls of the same color we found |
439 | if(tmpy-y >= 5) |
440 | { |
441 | for(int j=y; j<tmpy;++j) |
442 | { |
443 | positionsToDelete.insert( FieldPos(x,j) ); |
444 | } |
445 | } |
446 | else |
447 | continue; |
448 | } |
449 | |
450 | // down-right diagonal |
451 | for(int x=0; x<FIELD_SIZE-4; ++x) |
452 | for(int y=0;y<FIELD_SIZE-4; ++y) |
453 | { |
454 | if(m_field[x][y] == 0) |
455 | continue; |
456 | |
457 | BallColor col = m_field[x][y]->color(); |
458 | int tmpx = x+1; |
459 | int tmpy = y+1; |
460 | while(tmpx < FIELD_SIZE && tmpy < FIELD_SIZE && |
461 | m_field[tmpx][tmpy] && m_field[tmpx][tmpy]->color() == col) |
462 | { |
463 | tmpx++; |
464 | tmpy++; |
465 | } |
466 | // tmpx-x (and tmpy-y too) will be: how much balls of the same color we found |
467 | if(tmpx-x >= 5) |
468 | { |
469 | for(int i=x,j=y; i<tmpx;++i,++j) |
470 | { |
471 | positionsToDelete.insert( FieldPos(i,j) ); |
472 | } |
473 | } |
474 | else |
475 | continue; |
476 | } |
477 | |
478 | // up-right diagonal |
479 | for(int x=0; x<FIELD_SIZE-4; ++x) |
480 | for(int y=4; y<FIELD_SIZE; ++y) |
481 | { |
482 | if(m_field[x][y] == 0) |
483 | continue; |
484 | |
485 | BallColor col = m_field[x][y]->color(); |
486 | int tmpx = x+1; |
487 | int tmpy = y-1; |
488 | while(tmpx < FIELD_SIZE && tmpy >=0 && |
489 | m_field[tmpx][tmpy] && m_field[tmpx][tmpy]->color() == col) |
490 | { |
491 | tmpx++; |
492 | tmpy--; |
493 | } |
494 | // tmpx-x (and tmpy-y too) will be: how much balls of the same color we found |
495 | if(tmpx-x >= 5) |
496 | { |
497 | for(int i=x,j=y; i<tmpx;++i,--j) |
498 | { |
499 | positionsToDelete.insert( FieldPos(i,j) ); |
500 | } |
501 | } |
502 | else |
503 | continue; |
504 | } |
505 | |
506 | foreach( const FieldPos& pos, positionsToDelete ) |
507 | { |
508 | m_itemsToDelete.append(m_field[pos.x][pos.y]); |
509 | m_field[pos.x][pos.y] = 0; |
510 | m_numFreeCells++; |
511 | } |
512 | |
513 | // after it finishes slot removeAnimFinished() will be called |
514 | // if m_itemsToDelete is empty removeAnimFinished() will be called immediately |
515 | m_animator->animateRemove( m_itemsToDelete ); |
516 | } |
517 | |
518 | void KLinesScene::moveFocusLeft() |
519 | { |
520 | if( !m_focusItem->isVisible() ) |
521 | { |
522 | m_focusItem->show(); |
523 | // no action for the first time |
524 | return; |
525 | } |
526 | FieldPos focusPos = pixToField( m_focusItem->pos() ); |
527 | focusPos.x--; |
528 | if (focusPos.x < 0) // rotate on the torus |
529 | focusPos.x = FIELD_SIZE - 1; |
530 | |
531 | m_focusItem->setPos ( fieldToPix( focusPos ) ); |
532 | } |
533 | |
534 | void KLinesScene::moveFocusRight() |
535 | { |
536 | if( !m_focusItem->isVisible() ) |
537 | { |
538 | m_focusItem->show(); |
539 | // no action for the first time |
540 | return; |
541 | } |
542 | FieldPos focusPos = pixToField( m_focusItem->pos() ); |
543 | focusPos.x++; |
544 | if (focusPos.x >= FIELD_SIZE) // rotate on the torus |
545 | focusPos.x = 0; |
546 | |
547 | m_focusItem->setPos ( fieldToPix( focusPos ) ); |
548 | } |
549 | |
550 | void KLinesScene::moveFocusUp() |
551 | { |
552 | if( !m_focusItem->isVisible() ) |
553 | { |
554 | m_focusItem->show(); |
555 | // no action for the first time |
556 | return; |
557 | } |
558 | FieldPos focusPos = pixToField( m_focusItem->pos() ); |
559 | focusPos.y--; |
560 | if (focusPos.y < 0) // rotate on the torus |
561 | focusPos.y = FIELD_SIZE - 1; |
562 | |
563 | m_focusItem->setPos ( fieldToPix( focusPos ) ); |
564 | } |
565 | |
566 | void KLinesScene::moveFocusDown() |
567 | { |
568 | if( !m_focusItem->isVisible() ) |
569 | { |
570 | m_focusItem->show(); |
571 | // no action for the first time |
572 | return; |
573 | } |
574 | |
575 | FieldPos focusPos = pixToField( m_focusItem->pos() ); |
576 | focusPos.y++; |
577 | if (focusPos.y >= FIELD_SIZE) // rotate on the torus |
578 | focusPos.y = 0; |
579 | |
580 | m_focusItem->setPos ( fieldToPix( focusPos ) ); |
581 | } |
582 | |
583 | void KLinesScene::cellSelected() |
584 | { |
585 | if( !m_focusItem->isVisible() ) |
586 | m_focusItem->show(); |
587 | |
588 | // we're taking the center of the cell |
589 | selectOrMove( pixToField( m_focusItem->pos() + QPointF(m_cellSize/2,m_cellSize/2) ) ); |
590 | } |
591 | |
592 | void KLinesScene::saveUndoInfo() |
593 | { |
594 | // save field state to undoInfo |
595 | for(int x=0;x<FIELD_SIZE;++x) |
596 | for(int y=0; y<FIELD_SIZE;++y) |
597 | // NumColors represents no color |
598 | m_undoInfo.fcolors[x][y] = ( m_field[x][y] ? m_field[x][y]->color() : NumColors ); |
599 | m_undoInfo.numFreeCells = m_numFreeCells; |
600 | m_undoInfo.score = m_score; |
601 | m_undoInfo.nextColors = m_nextColors; |
602 | |
603 | emit stateChanged(QLatin1String( "undoable" )); |
604 | } |
605 | |
606 | // Brings m_field and some other vars to the state it was before last turn |
607 | void KLinesScene::undo() |
608 | { |
609 | // do not allow undo during animation |
610 | if(m_animator->isAnimating()) |
611 | return; |
612 | |
613 | if( m_selPos.isValid() ) |
614 | m_field[m_selPos.x][m_selPos.y]->stopAnimation(); |
615 | |
616 | BallColor col; |
617 | for(int x=0;x<FIELD_SIZE;++x) |
618 | for(int y=0; y<FIELD_SIZE;++y) |
619 | { |
620 | col = m_undoInfo.fcolors[x][y]; |
621 | if(col == NumColors) // no ball |
622 | { |
623 | if( m_field[x][y] ) |
624 | { |
625 | removeItem( m_field[x][y] ); |
626 | delete m_field[x][y]; |
627 | m_field[x][y] = 0; |
628 | } |
629 | continue; |
630 | } |
631 | |
632 | if( m_field[x][y] ) |
633 | { |
634 | if( m_field[x][y]->color() != col ) |
635 | m_field[x][y]->setColor(col); |
636 | //else live it as it is |
637 | } |
638 | else |
639 | { |
640 | BallItem *item = new BallItem(this); |
641 | item->setColor(col); |
642 | item->setPos( fieldToPix( FieldPos(x,y) ) ); |
643 | item->show(); |
644 | item->setRenderSize(KLinesRenderer::cellExtent()); |
645 | m_field[x][y] = item; |
646 | } |
647 | } |
648 | m_numFreeCells = m_undoInfo.numFreeCells; |
649 | m_score = m_undoInfo.score; |
650 | m_nextColors = m_undoInfo.nextColors; |
651 | |
652 | m_selPos = FieldPos(); |
653 | |
654 | m_previewItem->setPreviewColors( m_nextColors ); |
655 | |
656 | emit scoreChanged(m_score); |
657 | |
658 | emit stateChanged(QLatin1String( "not_undoable" )); |
659 | } |
660 | |
661 | void KLinesScene::drawBackground(QPainter *p, const QRectF&) |
662 | { |
663 | QPixmap tile = KLinesRenderer::backgroundTilePixmap(); |
664 | p->drawPixmap( 0, 0, KLinesRenderer::backgroundPixmap(sceneRect().size().toSize()) ); |
665 | p->drawPixmap( m_playFieldRect.x(), m_playFieldRect.y(), |
666 | KLinesRenderer::backgroundBorderPixmap( m_playFieldRect.size() ) ); |
667 | |
668 | int startX = m_playFieldRect.x()+m_playFieldBorderSize; |
669 | int maxX = m_playFieldRect.x()+m_cellSize*FIELD_SIZE; |
670 | int startY = m_playFieldRect.y()+m_playFieldBorderSize; |
671 | int maxY = m_playFieldRect.y()+m_cellSize*FIELD_SIZE; |
672 | |
673 | for(int x=startX; x<maxX; x+=m_cellSize) |
674 | for(int y=startY; y<maxY; y+=m_cellSize) |
675 | p->drawPixmap( x, y, tile ); |
676 | } |
677 | |
678 | void KLinesScene::gameOverHandler() |
679 | { |
680 | if( m_gameOver ) |
681 | return; // don't emit twice |
682 | m_gameOver = true; |
683 | kDebug() << "GAME OVER" ; |
684 | emit stateChanged(QLatin1String( "not_undoable" )); |
685 | //emit enableUndo(false); |
686 | emit gameOver(m_score); |
687 | |
688 | // disable auto-hide |
689 | m_popupItem->setMessageTimeout(0); |
690 | m_popupItem->showMessage(i18n("<h1>Game over</h1>" ), KGamePopupItem::Center); |
691 | } |
692 | |
693 | #include "scene.moc" |
694 | |