1 | /******************************************************************* |
2 | * |
3 | * Copyright 2006-2007 Dmitry Suzdalev <dimsuz@gmail.com> |
4 | * Copyright 2010 Brian Croom <brian.s.croom@gmail.com> |
5 | * |
6 | * This file is part of the KDE project "KAtomic" |
7 | * |
8 | * KAtomic is free software; you can redistribute it and/or modify |
9 | * it under the terms of the GNU General Public License as published by |
10 | * the Free Software Foundation; either version 2, or (at your option) |
11 | * any later version. |
12 | * |
13 | * KAtomic is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
16 | * GNU General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU General Public License |
19 | * along with KAtomic; see the file COPYING. If not, write to |
20 | * the Free Software Foundation, 51 Franklin Street, Fifth Floor, |
21 | * Boston, MA 02110-1301, USA. |
22 | * |
23 | ********************************************************************/ |
24 | |
25 | #include "playfield.h" |
26 | |
27 | #include <QGraphicsSceneMouseEvent> |
28 | #include <QTimeLine> |
29 | #include <QPainter> |
30 | |
31 | #include <KStandardDirs> |
32 | #include <KConfig> |
33 | #include <KDebug> |
34 | #include <kconfiggroup.h> |
35 | |
36 | #include <KGamePopupItem> |
37 | #include <KgTheme> |
38 | |
39 | #include "molecule.h" |
40 | #include "fielditem.h" |
41 | #include "levelset.h" |
42 | |
43 | static const int MIN_INFO_SIZE=10; |
44 | |
45 | struct Theme : public KgTheme |
46 | { |
47 | Theme() : KgTheme("pics/default_theme.desktop" ) |
48 | { |
49 | setGraphicsPath(KStandardDirs::locate("appdata" , "pics/default_theme.svgz" )); |
50 | } |
51 | }; |
52 | |
53 | PlayField::PlayField( QObject* parent ) |
54 | : QGraphicsScene(parent), m_renderer(new Theme), m_numMoves(0), m_levelData(0), |
55 | m_elemSize(MIN_ELEM_SIZE), m_selIdx(-1), m_animSpeed(120), |
56 | m_levelFinished(false) |
57 | { |
58 | m_atomTimeLine = new QTimeLine(300, this); |
59 | connect(m_atomTimeLine, SIGNAL(frameChanged(int)), SLOT(atomAnimFrameChanged(int)) ); |
60 | |
61 | m_upArrow = new ArrowFieldItem(&m_renderer, Up, this); |
62 | m_downArrow = new ArrowFieldItem(&m_renderer, Down, this); |
63 | m_leftArrow = new ArrowFieldItem(&m_renderer, Left, this); |
64 | m_rightArrow = new ArrowFieldItem(&m_renderer, Right, this); |
65 | |
66 | m_messageItem = new KGamePopupItem(); |
67 | m_messageItem->setMessageOpacity(0.9); |
68 | addItem(m_messageItem); // it hides itself by default |
69 | |
70 | m_previewItem = new MoleculePreviewItem(this); |
71 | |
72 | updateArrows(true); // this will hide them |
73 | updateBackground(); |
74 | } |
75 | |
76 | PlayField::~PlayField() |
77 | { |
78 | //FIXME? Letting the contents of this list be destroyed as the scene's children |
79 | //results in seg faults due to their having their own child objects and a |
80 | //bug(?) in KGameRenderer's deletion code |
81 | qDeleteAll(m_atoms); |
82 | m_atoms.clear(); |
83 | } |
84 | |
85 | void PlayField::setLevelData(const LevelData* level) |
86 | { |
87 | if (!level) |
88 | { |
89 | kDebug() << "level data is null!" ; |
90 | return; |
91 | } |
92 | |
93 | qDeleteAll(m_atoms); |
94 | m_atoms.clear(); |
95 | m_numMoves = 0; |
96 | m_levelFinished = false; |
97 | m_atomTimeLine->stop(); |
98 | m_levelData = level; |
99 | |
100 | m_undoStack.clear(); |
101 | m_redoStack.clear(); |
102 | emit enableUndo(false); |
103 | emit enableRedo(false); |
104 | |
105 | m_previewItem->setMolecule(m_levelData->molecule()); |
106 | |
107 | foreach (const LevelData::Element& element, m_levelData->atomElements()) |
108 | { |
109 | AtomFieldItem* atom = new AtomFieldItem(&m_renderer, m_levelData->molecule()->getAtom(element.atom), this); |
110 | atom->setFieldXY(element.x, element.y); |
111 | atom->setAtomNum(element.atom); |
112 | m_atoms.append(atom); |
113 | } |
114 | |
115 | m_selIdx = -1; |
116 | updateArrows(true); // this will hide them (no atom selected) |
117 | updateFieldItems(); |
118 | nextAtom(); |
119 | |
120 | update(); |
121 | } |
122 | |
123 | void PlayField::updateFieldItems() |
124 | { |
125 | if (!m_levelData || !m_levelData->molecule()) |
126 | { |
127 | kDebug() << "level or molecule data is null!" ; |
128 | return; |
129 | } |
130 | |
131 | foreach( AtomFieldItem *item, m_atoms ) |
132 | { |
133 | item->setRenderSize( QSize(m_elemSize, m_elemSize) ); |
134 | |
135 | // this may be true if resize happens during animation |
136 | if( isAnimating() && m_selIdx != -1 && item == m_atoms.at(m_selIdx) ) |
137 | continue; // its position will be taken care of in atomAnimFrameChanged() |
138 | |
139 | item->setPos( toPixX( item->fieldX() ), toPixY( item->fieldY() ) ); |
140 | item->show(); |
141 | } |
142 | |
143 | m_upArrow->setRenderSize(QSize(m_elemSize, m_elemSize)); |
144 | m_upArrow->setPos( toPixX(m_upArrow->fieldX()), toPixY(m_upArrow->fieldY()) ); |
145 | |
146 | m_downArrow->setRenderSize(QSize(m_elemSize, m_elemSize)); |
147 | m_downArrow->setPos( toPixX(m_downArrow->fieldX()), toPixY(m_downArrow->fieldY()) ); |
148 | |
149 | m_leftArrow->setRenderSize(QSize(m_elemSize, m_elemSize)); |
150 | m_leftArrow->setPos( toPixX(m_leftArrow->fieldX()), toPixY(m_leftArrow->fieldY()) ); |
151 | |
152 | m_rightArrow->setRenderSize(QSize(m_elemSize, m_elemSize)); |
153 | m_rightArrow->setPos( toPixX(m_rightArrow->fieldX()), toPixY(m_rightArrow->fieldY()) ); |
154 | } |
155 | |
156 | void PlayField::updateBackground() |
157 | { |
158 | setBackgroundBrush(m_renderer.spritePixmap("background" , sceneRect().size().toSize())); |
159 | } |
160 | |
161 | void PlayField::resize( int width, int height) |
162 | { |
163 | kDebug() << "resize:" << width << "," << height; |
164 | setSceneRect( 0, 0, width, height ); |
165 | |
166 | // we take 1/4 of width for displaying preview |
167 | int previewWidth = width/4; |
168 | m_previewItem->setPos( width-previewWidth+2, 2 ); |
169 | m_previewItem->setWidth( previewWidth-4 ); |
170 | |
171 | width -= previewWidth; |
172 | |
173 | int oldSize = m_elemSize; |
174 | m_elemSize = qMin(width, height) / FIELD_SIZE; |
175 | m_previewItem->setMaxAtomSize( m_elemSize ); |
176 | |
177 | // if atom animation is running we need to rescale timeline |
178 | if( isAnimating() ) |
179 | { |
180 | kDebug() << "restarting animation" ; |
181 | int curTime = m_atomTimeLine->currentTime(); |
182 | // calculate numCells to move using oldSize |
183 | int numCells = m_atomTimeLine->endFrame()/oldSize; |
184 | m_atomTimeLine->stop(); |
185 | // recalculate this with new m_elemSize |
186 | m_atomTimeLine->setFrameRange( 0, numCells*m_elemSize ); |
187 | m_atomTimeLine->setCurrentTime(curTime); |
188 | m_atomTimeLine->start(); |
189 | } |
190 | updateFieldItems(); |
191 | updateBackground(); |
192 | } |
193 | |
194 | void PlayField::nextAtom() |
195 | { |
196 | if ( m_levelFinished || isAnimating() ) |
197 | return; |
198 | |
199 | if(m_selIdx == -1) |
200 | { |
201 | m_selIdx = 0; |
202 | updateArrows(); |
203 | return; |
204 | } |
205 | |
206 | int xs = m_atoms.at(m_selIdx)->fieldX(); |
207 | int ys = m_atoms.at(m_selIdx)->fieldY()+1; |
208 | |
209 | int x = xs; |
210 | |
211 | while(1) |
212 | { |
213 | AtomFieldItem* item = 0; |
214 | for(int y=ys; y<FIELD_SIZE; ++y ) |
215 | { |
216 | int px = toPixX(x)+m_elemSize/2; |
217 | int py = toPixY(y)+m_elemSize/2; |
218 | item = qgraphicsitem_cast<AtomFieldItem*>( itemAt(px, py) ); |
219 | if( item != 0 ) |
220 | { |
221 | m_selIdx = m_atoms.indexOf(item); |
222 | updateArrows(); |
223 | // if this atom can't move, we won't return - we'll search further |
224 | // until we found moveable one |
225 | if( m_upArrow->isVisible() || m_rightArrow->isVisible() |
226 | || m_downArrow->isVisible() || m_leftArrow->isVisible() ) |
227 | return; |
228 | } |
229 | } |
230 | x++; |
231 | if(x==FIELD_SIZE) |
232 | x = 0; |
233 | ys=0; |
234 | } |
235 | } |
236 | |
237 | void PlayField::previousAtom() |
238 | { |
239 | if ( m_levelFinished || isAnimating() ) |
240 | return; |
241 | |
242 | if(m_selIdx == -1) |
243 | { |
244 | m_selIdx = 0; |
245 | updateArrows(); |
246 | return; |
247 | } |
248 | |
249 | int xs = m_atoms.at(m_selIdx)->fieldX(); |
250 | int ys = m_atoms.at(m_selIdx)->fieldY()-1; |
251 | |
252 | int x = xs; |
253 | |
254 | while(1) |
255 | { |
256 | AtomFieldItem* item = 0; |
257 | for(int y=ys; y>=0; --y ) |
258 | { |
259 | int px = toPixX(x)+m_elemSize/2; |
260 | int py = toPixY(y)+m_elemSize/2; |
261 | item = qgraphicsitem_cast<AtomFieldItem*>( itemAt(px, py) ); |
262 | if( item != 0 && item->atomNum() != -1 ) |
263 | { |
264 | m_selIdx = m_atoms.indexOf(item); |
265 | updateArrows(); |
266 | // if this atom can't move, we won't return - we'll search further |
267 | // until we found moveable one |
268 | if( m_upArrow->isVisible() || m_rightArrow->isVisible() |
269 | || m_downArrow->isVisible() || m_leftArrow->isVisible() ) |
270 | return; |
271 | } |
272 | } |
273 | x--; |
274 | if(x==0) |
275 | x = FIELD_SIZE-1; |
276 | ys=FIELD_SIZE-1; |
277 | } |
278 | } |
279 | |
280 | void PlayField::undo() |
281 | { |
282 | if( isAnimating() || m_undoStack.isEmpty()) |
283 | return; |
284 | |
285 | AtomMove am = m_undoStack.pop(); |
286 | if(m_redoStack.isEmpty()) |
287 | emit enableRedo(true); |
288 | |
289 | m_redoStack.push(am); |
290 | |
291 | if(m_undoStack.isEmpty()) |
292 | emit enableUndo(false); |
293 | |
294 | m_numMoves--; |
295 | emit updateMoves(m_numMoves); |
296 | |
297 | m_selIdx = am.atomIdx; |
298 | switch( am.dir ) |
299 | { |
300 | case Up: |
301 | moveSelectedAtom(Down, am.numCells); |
302 | break; |
303 | case Down: |
304 | moveSelectedAtom(Up, am.numCells); |
305 | break; |
306 | case Left: |
307 | moveSelectedAtom(Right, am.numCells); |
308 | break; |
309 | case Right: |
310 | moveSelectedAtom(Left, am.numCells); |
311 | break; |
312 | } |
313 | } |
314 | |
315 | void PlayField::redo() |
316 | { |
317 | if( isAnimating() || m_redoStack.isEmpty() ) |
318 | return; |
319 | |
320 | AtomMove am = m_redoStack.pop(); |
321 | if(m_undoStack.isEmpty()) |
322 | emit enableUndo(true); |
323 | |
324 | if(!m_redoStack.isEmpty()) //otherwise it will be pushed at the end of the move |
325 | m_undoStack.push(am); |
326 | |
327 | if(m_redoStack.isEmpty()) |
328 | emit enableRedo(false); |
329 | |
330 | m_numMoves++; |
331 | emit updateMoves(m_numMoves); |
332 | |
333 | m_selIdx = am.atomIdx; |
334 | moveSelectedAtom(am.dir, am.numCells); |
335 | } |
336 | |
337 | void PlayField::undoAll() |
338 | { |
339 | while( !m_undoStack.isEmpty() ) |
340 | { |
341 | AtomMove am = m_undoStack.pop(); |
342 | m_redoStack.push( am ); |
343 | |
344 | // adjust atom pos |
345 | AtomFieldItem *atom = m_atoms.at(am.atomIdx); |
346 | int xdelta = 0, ydelta = 0; |
347 | switch(am.dir) |
348 | { |
349 | case Up: |
350 | ydelta = am.numCells; |
351 | break; |
352 | case Down: |
353 | ydelta = -am.numCells; |
354 | break; |
355 | case Right: |
356 | xdelta = -am.numCells; |
357 | break; |
358 | case Left: |
359 | xdelta = am.numCells; |
360 | break; |
361 | } |
362 | atom->setFieldXY( atom->fieldX()+xdelta, atom->fieldY()+ydelta ); |
363 | } |
364 | // update pixel positions |
365 | foreach( AtomFieldItem* atom, m_atoms ) |
366 | atom->setPos( toPixX(atom->fieldX()), toPixY(atom->fieldY())); |
367 | |
368 | m_numMoves = 0; |
369 | emit updateMoves(m_numMoves); |
370 | emit enableUndo(false); |
371 | emit enableRedo(!m_redoStack.isEmpty()); |
372 | m_selIdx = m_redoStack.last().atomIdx; |
373 | updateArrows(); |
374 | } |
375 | |
376 | void PlayField::redoAll() |
377 | { |
378 | while( !m_redoStack.isEmpty() ) |
379 | { |
380 | AtomMove am = m_redoStack.pop(); |
381 | m_undoStack.push( am ); |
382 | |
383 | // adjust atom pos |
384 | AtomFieldItem *atom = m_atoms.at(am.atomIdx); |
385 | int xdelta = 0, ydelta = 0; |
386 | switch(am.dir) |
387 | { |
388 | case Up: |
389 | ydelta = -am.numCells; |
390 | break; |
391 | case Down: |
392 | ydelta = am.numCells; |
393 | break; |
394 | case Right: |
395 | xdelta = am.numCells; |
396 | break; |
397 | case Left: |
398 | xdelta = -am.numCells; |
399 | break; |
400 | } |
401 | atom->setFieldXY( atom->fieldX()+xdelta, atom->fieldY()+ydelta ); |
402 | } |
403 | // update pixel positions |
404 | foreach( AtomFieldItem * atom, m_atoms ) |
405 | atom->setPos( toPixX(atom->fieldX()), toPixY(atom->fieldY())); |
406 | |
407 | m_numMoves = m_undoStack.count(); |
408 | emit updateMoves(m_numMoves); |
409 | emit enableUndo(!m_undoStack.isEmpty()); |
410 | emit enableRedo(false); |
411 | m_selIdx = m_undoStack.last().atomIdx; |
412 | updateArrows(); |
413 | } |
414 | |
415 | void PlayField::mousePressEvent( QGraphicsSceneMouseEvent* ev ) |
416 | { |
417 | QGraphicsScene::mousePressEvent(ev); |
418 | |
419 | if( isAnimating() || m_levelFinished ) |
420 | return; |
421 | |
422 | QGraphicsItem* clickedItem = itemAt(ev->scenePos()); |
423 | if(!clickedItem) |
424 | return; |
425 | |
426 | AtomFieldItem *atomItem = qgraphicsitem_cast<AtomFieldItem*>(clickedItem); |
427 | if( atomItem ) // that is: atom selected |
428 | { |
429 | m_selIdx = m_atoms.indexOf( atomItem ); |
430 | updateArrows(); |
431 | return; |
432 | } |
433 | |
434 | ArrowFieldItem *arrowItem = qgraphicsitem_cast<ArrowFieldItem*>(clickedItem); |
435 | if( arrowItem == m_upArrow ) |
436 | { |
437 | moveSelectedAtom( Up ); |
438 | } |
439 | else if( arrowItem == m_downArrow ) |
440 | { |
441 | moveSelectedAtom( Down ); |
442 | } |
443 | else if( arrowItem == m_rightArrow ) |
444 | { |
445 | moveSelectedAtom( Right ); |
446 | } |
447 | else if( arrowItem == m_leftArrow ) |
448 | { |
449 | moveSelectedAtom( Left ); |
450 | } |
451 | } |
452 | |
453 | void PlayField::moveSelectedAtom( Direction dir, int numCells ) |
454 | { |
455 | if( isAnimating() ) |
456 | return; |
457 | |
458 | |
459 | int numEmptyCells=0; |
460 | m_dir = dir; |
461 | |
462 | // numCells is also a kind of indicator whether this |
463 | // function was called interactively (=0) or from undo/redo functions(!=0) |
464 | if(numCells == 0) // then we'll calculate |
465 | { |
466 | // helpers |
467 | int x = 0, y = 0; |
468 | int selX = m_atoms.at(m_selIdx)->fieldX(); |
469 | int selY = m_atoms.at(m_selIdx)->fieldY(); |
470 | switch( dir ) |
471 | { |
472 | case Up: |
473 | y = selY; |
474 | while( cellIsEmpty(selX, --y) ) |
475 | numEmptyCells++; |
476 | break; |
477 | case Down: |
478 | y = selY; |
479 | while( cellIsEmpty(selX, ++y) ) |
480 | numEmptyCells++; |
481 | break; |
482 | case Left: |
483 | x = selX; |
484 | while( cellIsEmpty(--x, selY) ) |
485 | numEmptyCells++; |
486 | break; |
487 | case Right: |
488 | x = selX; |
489 | while( cellIsEmpty(++x, selY) ) |
490 | numEmptyCells++; |
491 | break; |
492 | } |
493 | // and clear the redo stack. we do it here |
494 | // because if this function is called with numCells=0 |
495 | // this indicates it is called not from undo()/redo(), |
496 | // but as a result of mouse/keyb input from player |
497 | // so this is just a place to drop redo history :-) |
498 | m_redoStack.clear(); |
499 | emit enableRedo(false); |
500 | // only count it as move if we actually move :-) |
501 | if(numEmptyCells) |
502 | m_numMoves++; |
503 | } |
504 | else |
505 | numEmptyCells = numCells; |
506 | |
507 | if( numEmptyCells == 0) |
508 | return; |
509 | |
510 | // put undo info |
511 | // don't put if we in the middle of series of undos |
512 | if(m_redoStack.isEmpty()) |
513 | { |
514 | if(m_undoStack.isEmpty()) |
515 | emit enableUndo(true); |
516 | m_undoStack.push( AtomMove(m_selIdx, m_dir, numEmptyCells) ); |
517 | } |
518 | |
519 | m_atomTimeLine->setCurrentTime(0); // reset |
520 | m_atomTimeLine->setDuration( numEmptyCells * m_animSpeed ); // 1cell/m_animSpeed speed |
521 | m_atomTimeLine->setFrameRange( 0, numEmptyCells*m_elemSize ); // 1frame=1pixel |
522 | updateArrows(true); // hide them |
523 | m_atomTimeLine->start(); |
524 | } |
525 | |
526 | void PlayField::atomAnimFrameChanged(int frame) |
527 | { |
528 | AtomFieldItem *selAtom = m_atoms.at(m_selIdx); |
529 | int posx= toPixX(selAtom->fieldX()); |
530 | int posy= toPixY(selAtom->fieldY()); |
531 | |
532 | switch( m_dir ) |
533 | { |
534 | case Up: |
535 | posy = toPixY(selAtom->fieldY()) - frame; |
536 | break; |
537 | case Down: |
538 | posy = toPixY(selAtom->fieldY()) + frame; |
539 | break; |
540 | case Left: |
541 | posx = toPixX(selAtom->fieldX()) - frame; |
542 | break; |
543 | case Right: |
544 | posx = toPixX(selAtom->fieldX()) + frame; |
545 | break; |
546 | } |
547 | |
548 | selAtom->setPos(posx, posy); |
549 | |
550 | if(frame == m_atomTimeLine->endFrame()) // that is: move finished |
551 | { |
552 | // NOTE: consider moving this to separate function (something like moveFinished()) |
553 | // to improve code readablility |
554 | selAtom->setFieldX( toFieldX((int)selAtom->pos().x()) ); |
555 | selAtom->setFieldY( toFieldY((int)selAtom->pos().y()) ); |
556 | updateArrows(); |
557 | |
558 | emit updateMoves(m_numMoves); |
559 | |
560 | if(checkDone() && !m_levelFinished) |
561 | { |
562 | m_levelFinished = true; |
563 | // hide arrows |
564 | updateArrows(true); |
565 | m_selIdx = -1; |
566 | emit gameOver(m_numMoves); |
567 | } |
568 | } |
569 | } |
570 | |
571 | // most complicated algorithm ;-) |
572 | bool PlayField::checkDone() const |
573 | { |
574 | if (!m_levelData || !m_levelData->molecule()) |
575 | { |
576 | kDebug() << "level or molecule data is null!" ; |
577 | return false; |
578 | } |
579 | // let's assume that molecule is done |
580 | // and see if we can break assumtion |
581 | // |
582 | // first we find molecule origin in field coords |
583 | // by finding minimum fieldX, fieldY through all atoms |
584 | int minX = FIELD_SIZE+1; |
585 | int minY = FIELD_SIZE+1; |
586 | foreach( AtomFieldItem* atom, m_atoms ) |
587 | { |
588 | if(atom->fieldX() < minX) |
589 | minX = atom->fieldX(); |
590 | if(atom->fieldY() < minY) |
591 | minY = atom->fieldY(); |
592 | } |
593 | // so origin is (minX,minY) |
594 | // we'll substract this origin from each atom's coords and check |
595 | // if the resulting position is the same as this atom has in molecule |
596 | foreach( AtomFieldItem* atom, m_atoms ) |
597 | { |
598 | uint atomNum = atom->atomNum(); |
599 | int molecCoordX = atom->fieldX() - minX; |
600 | int molecCoordY = atom->fieldY() - minY; |
601 | if( m_levelData->molecule()->getAtom( molecCoordX, molecCoordY ) != atomNum ) |
602 | return false; // nope. not there |
603 | } |
604 | return true; |
605 | } |
606 | |
607 | bool PlayField::cellIsEmpty(int x, int y) const |
608 | { |
609 | if (!m_levelData) |
610 | { |
611 | kDebug() << "level data is null!" ; |
612 | return true; |
613 | } |
614 | |
615 | if(m_levelData->containsWallAt(x,y)) |
616 | return false; // it is a wall |
617 | |
618 | foreach( AtomFieldItem *atom, m_atoms ) |
619 | { |
620 | if( atom->fieldX() == x && atom->fieldY() == y ) |
621 | return false; |
622 | } |
623 | return true; |
624 | } |
625 | |
626 | void PlayField::setAnimationSpeed(int speed) |
627 | { |
628 | if(speed == 0) // slow |
629 | m_animSpeed = 300; |
630 | else if (speed == 1) //normal |
631 | m_animSpeed = 120; |
632 | else |
633 | m_animSpeed = 60; |
634 | } |
635 | |
636 | void PlayField::updateArrows(bool justHide) |
637 | { |
638 | m_upArrow->hide(); |
639 | m_downArrow->hide(); |
640 | m_leftArrow->hide(); |
641 | m_rightArrow->hide(); |
642 | |
643 | if(justHide || m_selIdx == -1 || m_levelFinished || m_atoms.isEmpty()) |
644 | return; |
645 | |
646 | int selX = m_atoms.at(m_selIdx)->fieldX(); |
647 | int selY = m_atoms.at(m_selIdx)->fieldY(); |
648 | |
649 | if(cellIsEmpty(selX-1, selY)) |
650 | { |
651 | m_leftArrow->show(); |
652 | m_leftArrow->setFieldXY( selX-1, selY ); |
653 | m_leftArrow->setPos( toPixX(selX-1), toPixY(selY) ); |
654 | } |
655 | if(cellIsEmpty(selX+1, selY)) |
656 | { |
657 | m_rightArrow->show(); |
658 | m_rightArrow->setFieldXY( selX+1, selY ); |
659 | m_rightArrow->setPos( toPixX(selX+1), toPixY(selY) ); |
660 | } |
661 | if(cellIsEmpty(selX, selY-1)) |
662 | { |
663 | m_upArrow->show(); |
664 | m_upArrow->setFieldXY( selX, selY-1 ); |
665 | m_upArrow->setPos( toPixX(selX), toPixY(selY-1) ); |
666 | } |
667 | if(cellIsEmpty(selX, selY+1)) |
668 | { |
669 | m_downArrow->show(); |
670 | m_downArrow->setFieldXY( selX, selY+1 ); |
671 | m_downArrow->setPos( toPixX(selX), toPixY(selY+1) ); |
672 | } |
673 | } |
674 | |
675 | void PlayField::drawForeground( QPainter *p, const QRectF&) |
676 | { |
677 | if (!m_levelData) |
678 | { |
679 | kDebug() << "level data is null!" ; |
680 | return; |
681 | } |
682 | |
683 | QPixmap aPix = m_renderer.spritePixmap("wall" , QSize(m_elemSize, m_elemSize)); |
684 | for (int i = 0; i < FIELD_SIZE; i++) |
685 | for (int j = 0; j < FIELD_SIZE; j++) |
686 | if(m_levelData->containsWallAt(i,j)) |
687 | p->drawPixmap(toPixX(i), toPixY(j), aPix); |
688 | } |
689 | |
690 | bool PlayField::isAnimating() const |
691 | { |
692 | return (m_atomTimeLine->state() == QTimeLine::Running); |
693 | } |
694 | |
695 | void PlayField::saveGame( KConfigGroup& config ) const |
696 | { |
697 | // REMEMBER: while saving use atom indexes within m_atoms, not atom's atomNum()'s. |
698 | // atomNum()'s arent unique, there can be several atoms |
699 | // in molecule which represent same atomNum |
700 | |
701 | for(int idx=0; idx<m_atoms.count(); ++idx) |
702 | { |
703 | // we'll write pos through using QPoint |
704 | // I'd use QPair but it isn't supported by QVariant |
705 | QPoint pos(m_atoms.at(idx)->fieldX(), m_atoms.at(idx)->fieldY()); |
706 | config.writeEntry( QString("Atom_%1" ).arg(idx), pos); |
707 | } |
708 | |
709 | // save undo history |
710 | int moveCount = m_undoStack.count(); |
711 | config.writeEntry( "MoveCount" , moveCount ); |
712 | AtomMove mv; |
713 | for(int i=0;i<moveCount;++i) |
714 | { |
715 | mv = m_undoStack.at(i); |
716 | // atomIdx, direction, numCells |
717 | QList<int> move; |
718 | move << mv.atomIdx << static_cast<int>(mv.dir) << mv.numCells; |
719 | config.writeEntry( QString("Move_%1" ).arg(i), move ); |
720 | } |
721 | config.writeEntry("SelectedAtom" , m_selIdx); |
722 | config.writeEntry("LevelFinished" , m_levelFinished ); |
723 | } |
724 | |
725 | void PlayField::loadGame( const KConfigGroup& config ) |
726 | { |
727 | // it is assumed that this method is called right after setLevelData() so |
728 | // level itself is already loaded at this point |
729 | |
730 | // read atom positions |
731 | for(int idx=0; idx<m_atoms.count(); ++idx) |
732 | { |
733 | QPoint pos = config.readEntry( QString("Atom_%1" ).arg(idx), QPoint() ); |
734 | m_atoms.at(idx)->setFieldXY(pos.x(), pos.y()); |
735 | m_atoms.at(idx)->setPos( toPixX(pos.x()), toPixY(pos.y()) ); |
736 | } |
737 | // fill undo history |
738 | m_numMoves = config.readEntry("MoveCount" , 0); |
739 | |
740 | AtomMove mv; |
741 | for(int i=0;i<m_numMoves;++i) |
742 | { |
743 | QList<int> move = config.readEntry( QString("Move_%1" ).arg(i), QList<int>() ); |
744 | mv.atomIdx = move.at(0); |
745 | mv.dir = static_cast<Direction>(move.at(1)); |
746 | mv.numCells = move.at(2); |
747 | m_undoStack.push(mv); |
748 | } |
749 | if(m_numMoves) |
750 | { |
751 | emit enableUndo(true); |
752 | emit updateMoves(m_numMoves); |
753 | } |
754 | |
755 | m_selIdx = config.readEntry("SelectedAtom" , 0); |
756 | m_levelFinished = config.readEntry("LevelFinished" , false); |
757 | updateArrows(); |
758 | } |
759 | |
760 | void PlayField::showMessage( const QString& message ) |
761 | { |
762 | m_messageItem->setMessageTimeout( 4000 ); |
763 | m_messageItem->showMessage( message, KGamePopupItem::BottomLeft ); |
764 | } |
765 | |
766 | QString PlayField::moleculeName() const |
767 | { |
768 | if (!m_levelData || !m_levelData->molecule()) |
769 | { |
770 | kDebug() << "level or molecule data is null!" ; |
771 | return QString(); |
772 | } |
773 | |
774 | return m_levelData->molecule()->moleculeName(); |
775 | } |
776 | |
777 | #include "playfield.moc" |
778 | |