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