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