File indexing completed on 2024-04-14 03:59:18

0001 /*
0002     This file is part of the KDE project "KAtomic"
0003 
0004     SPDX-FileCopyrightText: 2006-2007 Dmitry Suzdalev <dimsuz@gmail.com>
0005     SPDX-FileCopyrightText: 2010 Brian Croom <brian.s.croom@gmail.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "playfield.h"
0011 
0012 #include "molecule.h"
0013 #include "fielditem.h"
0014 #include "levelset.h"
0015 #include "katomic_debug.h"
0016 
0017 #include <KGamePopupItem>
0018 #include <KGameTheme>
0019 
0020 #include <KConfig>
0021 #include <KConfigGroup>
0022 
0023 #include <QGraphicsSceneMouseEvent>
0024 #include <QTimeLine>
0025 #include <QPainter>
0026 #include <QStandardPaths>
0027 
0028 struct Theme : public KGameTheme
0029 {
0030     Theme() : KGameTheme("themes/default.desktop")
0031     {
0032         setGraphicsPath(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("themes/default.svgz")));
0033     }
0034 };
0035 
0036 PlayField::PlayField( QObject* parent )
0037     : QGraphicsScene(parent), m_renderer(new Theme), m_numMoves(0), m_levelData(nullptr),
0038     m_elemSize(MIN_ELEM_SIZE), m_selIdx(-1), m_animSpeed(120),
0039     m_levelFinished(false)
0040 {
0041     m_atomTimeLine = new QTimeLine(300, this);
0042     connect(m_atomTimeLine, &QTimeLine::frameChanged, this, &PlayField::atomAnimFrameChanged);
0043 
0044     m_upArrow = new ArrowFieldItem(&m_renderer, Up, this);
0045     m_downArrow = new ArrowFieldItem(&m_renderer, Down, this);
0046     m_leftArrow = new ArrowFieldItem(&m_renderer, Left, this);
0047     m_rightArrow = new ArrowFieldItem(&m_renderer, Right, this);
0048 
0049     m_messageItem = new KGamePopupItem();
0050     m_messageItem->setMessageOpacity(0.9);
0051     addItem(m_messageItem); // it hides itself by default
0052 
0053     m_previewItem = new MoleculePreviewItem(this);
0054 
0055     updateArrows(true); // this will hide them
0056     updateBackground();
0057 }
0058 
0059 PlayField::~PlayField()
0060 {
0061     //FIXME? Letting the contents of this list be destroyed as the scene's children
0062     //results in seg faults due to their having their own child objects and a
0063     //bug(?) in KGameRenderer's deletion code
0064     qDeleteAll(m_atoms);
0065     m_atoms.clear();
0066 }
0067 
0068 void PlayField::setLevelData(const LevelData* level)
0069 {
0070     if (!level)
0071     {
0072         //qCDebug(KATOMIC_LOG) << "level data is null!";
0073         return;
0074     }
0075 
0076     qDeleteAll(m_atoms);
0077     m_atoms.clear();
0078     m_numMoves = 0;
0079     m_levelFinished = false;
0080     m_atomTimeLine->stop();
0081     m_levelData = level;
0082 
0083     m_undoStack.clear();
0084     m_redoStack.clear();
0085     Q_EMIT enableUndo(false);
0086     Q_EMIT enableRedo(false);
0087 
0088     m_previewItem->setMolecule(m_levelData->molecule());
0089 
0090     const auto atomElements = m_levelData->atomElements();
0091     m_atoms.reserve(atomElements.size());
0092     for (const LevelData::Element& element : atomElements)
0093     {
0094         AtomFieldItem* atom = new AtomFieldItem(&m_renderer, m_levelData->molecule()->getAtom(element.atom), this);
0095         atom->setFieldXY(element.x, element.y);
0096         atom->setAtomNum(element.atom);
0097         m_atoms.append(atom);
0098     }
0099 
0100     m_selIdx = -1;
0101     updateArrows(true); // this will hide them (no atom selected)
0102     updateFieldItems();
0103     nextAtom();
0104 
0105     update();
0106 }
0107 
0108 void PlayField::updateFieldItems()
0109 {
0110     if (!m_levelData || !m_levelData->molecule())
0111     {
0112         //qCDebug(KATOMIC_LOG) << "level or molecule data is null!";
0113         return;
0114     }
0115 
0116     for ( AtomFieldItem *item : std::as_const(m_atoms) )
0117     {
0118         item->setRenderSize( QSize(m_elemSize, m_elemSize) );
0119 
0120         // this may be true if resize happens during animation
0121         if( isAnimating() && m_selIdx != -1 && item == m_atoms.at(m_selIdx) )
0122             continue; // its position will be taken care of in atomAnimFrameChanged()
0123 
0124         item->setPos( toPixX( item->fieldX() ), toPixY( item->fieldY() ) );
0125         item->show();
0126     }
0127 
0128     m_upArrow->setRenderSize(QSize(m_elemSize, m_elemSize));
0129     m_upArrow->setPos( toPixX(m_upArrow->fieldX()), toPixY(m_upArrow->fieldY()) );
0130 
0131     m_downArrow->setRenderSize(QSize(m_elemSize, m_elemSize));
0132     m_downArrow->setPos( toPixX(m_downArrow->fieldX()), toPixY(m_downArrow->fieldY()) );
0133 
0134     m_leftArrow->setRenderSize(QSize(m_elemSize, m_elemSize));
0135     m_leftArrow->setPos( toPixX(m_leftArrow->fieldX()), toPixY(m_leftArrow->fieldY()) );
0136 
0137     m_rightArrow->setRenderSize(QSize(m_elemSize, m_elemSize));
0138     m_rightArrow->setPos( toPixX(m_rightArrow->fieldX()), toPixY(m_rightArrow->fieldY()) );
0139 }
0140 
0141 void PlayField::updateBackground()
0142 {
0143     setBackgroundBrush(m_renderer.spritePixmap(QStringLiteral("background"), sceneRect().size().toSize()));
0144 }
0145 
0146 void PlayField::resize( int width, int height)
0147 {
0148     //qCDebug(KATOMIC_LOG) << "resize:" << width << "," << height;
0149     setSceneRect( 0, 0, width, height );
0150 
0151     // we take 1/4 of width for displaying preview
0152     int previewWidth = width/4;
0153     m_previewItem->setPos( width-previewWidth+2, 2 );
0154     m_previewItem->setWidth( previewWidth-4 );
0155 
0156     width -= previewWidth;
0157 
0158     int oldSize = m_elemSize;
0159     m_elemSize = qMin(width, height) / FIELD_SIZE;
0160     m_previewItem->setMaxAtomSize( m_elemSize );
0161 
0162     // if atom animation is running we need to rescale timeline
0163     if( isAnimating() )
0164     {
0165         //qCDebug(KATOMIC_LOG) << "restarting animation";
0166         int curTime = m_atomTimeLine->currentTime();
0167         // calculate numCells to move using oldSize
0168         int numCells = m_atomTimeLine->endFrame()/oldSize;
0169         m_atomTimeLine->stop();
0170         // recalculate this with new m_elemSize
0171         m_atomTimeLine->setFrameRange( 0, numCells*m_elemSize );
0172         m_atomTimeLine->setCurrentTime(curTime);
0173         m_atomTimeLine->start();
0174     }
0175     updateFieldItems();
0176     updateBackground();
0177 }
0178 
0179 void PlayField::nextAtom()
0180 {
0181     if ( m_levelFinished || isAnimating() )
0182         return;
0183 
0184     if(m_selIdx == -1)
0185     {
0186         m_selIdx = 0;
0187         updateArrows();
0188         return;
0189     }
0190 
0191     int xs = m_atoms.at(m_selIdx)->fieldX();
0192     int ys = m_atoms.at(m_selIdx)->fieldY()+1;
0193 
0194     int x = xs;
0195 
0196     while(1)
0197     {
0198         for(int y=ys; y<FIELD_SIZE; ++y )
0199         {
0200             int px = toPixX(x)+m_elemSize/2;
0201             int py = toPixY(y)+m_elemSize/2;
0202             const QList<QGraphicsItem *> itemsAtPoint = items(QPointF(px, py));
0203             if( !itemsAtPoint.isEmpty() )
0204             {
0205                 AtomFieldItem* item = qgraphicsitem_cast<AtomFieldItem*>( itemsAtPoint[0] );
0206                 m_selIdx = m_atoms.indexOf(item);
0207                 updateArrows();
0208                 // if this atom can't move, we won't return - we'll search further
0209                 // until we found moveable one
0210                 if( m_upArrow->isVisible() || m_rightArrow->isVisible()
0211                         || m_downArrow->isVisible() || m_leftArrow->isVisible() )
0212                     return;
0213             }
0214         }
0215         x++;
0216         if(x==FIELD_SIZE)
0217             x = 0;
0218         ys=0;
0219     }
0220 }
0221 
0222 void PlayField::previousAtom()
0223 {
0224     if ( m_levelFinished || isAnimating() )
0225         return;
0226 
0227     if(m_selIdx == -1)
0228     {
0229         m_selIdx = 0;
0230         updateArrows();
0231         return;
0232     }
0233 
0234     int xs = m_atoms.at(m_selIdx)->fieldX();
0235     int ys = m_atoms.at(m_selIdx)->fieldY()-1;
0236 
0237     int x = xs;
0238 
0239     while(1)
0240     {
0241         for(int y=ys; y>=0; --y )
0242         {
0243             int px = toPixX(x)+m_elemSize/2;
0244             int py = toPixY(y)+m_elemSize/2;
0245             const QList<QGraphicsItem *> itemsAtPoint = items(QPointF(px, py));
0246             if( !itemsAtPoint.isEmpty() )
0247             {
0248                 AtomFieldItem* item = qgraphicsitem_cast<AtomFieldItem*>( itemsAtPoint[0] );
0249                 if ( item && item->atomNum() != -1 )
0250                 {
0251                     m_selIdx = m_atoms.indexOf(item);
0252                     updateArrows();
0253                     // if this atom can't move, we won't return - we'll search further
0254                     // until we found moveable one
0255                     if( m_upArrow->isVisible() || m_rightArrow->isVisible()
0256                             || m_downArrow->isVisible() || m_leftArrow->isVisible() )
0257                         return;
0258                 }
0259             }
0260         }
0261         x--;
0262         if(x==0)
0263             x = FIELD_SIZE-1;
0264         ys=FIELD_SIZE-1;
0265     }
0266 }
0267 
0268 void PlayField::undo()
0269 {
0270     if( isAnimating() || m_undoStack.isEmpty())
0271         return;
0272 
0273     AtomMove am = m_undoStack.pop();
0274     if(m_redoStack.isEmpty())
0275         Q_EMIT enableRedo(true);
0276 
0277     m_redoStack.push(am);
0278 
0279     if(m_undoStack.isEmpty())
0280         Q_EMIT enableUndo(false);
0281 
0282     m_numMoves--;
0283     Q_EMIT updateMoves(m_numMoves);
0284 
0285     m_selIdx = am.atomIdx;
0286     switch( am.dir )
0287     {
0288         case Up:
0289             moveSelectedAtom(Down, am.numCells);
0290             break;
0291         case Down:
0292             moveSelectedAtom(Up, am.numCells);
0293             break;
0294         case Left:
0295             moveSelectedAtom(Right, am.numCells);
0296             break;
0297         case Right:
0298             moveSelectedAtom(Left, am.numCells);
0299             break;
0300     }
0301 }
0302 
0303 void PlayField::redo()
0304 {
0305     if( isAnimating() || m_redoStack.isEmpty() )
0306         return;
0307 
0308     AtomMove am = m_redoStack.pop();
0309     if(m_undoStack.isEmpty())
0310         Q_EMIT enableUndo(true);
0311 
0312     if(!m_redoStack.isEmpty()) //otherwise it will be pushed at the end of the move
0313         m_undoStack.push(am);
0314 
0315     if(m_redoStack.isEmpty())
0316         Q_EMIT enableRedo(false);
0317 
0318     m_numMoves++;
0319     Q_EMIT updateMoves(m_numMoves);
0320 
0321     m_selIdx = am.atomIdx;
0322     moveSelectedAtom(am.dir, am.numCells);
0323 }
0324 
0325 void PlayField::undoAll()
0326 {
0327     while( !m_undoStack.isEmpty() )
0328     {
0329         AtomMove am = m_undoStack.pop();
0330         m_redoStack.push( am );
0331 
0332         // adjust atom pos
0333         AtomFieldItem *atom = m_atoms.at(am.atomIdx);
0334         int xdelta = 0, ydelta = 0;
0335         switch(am.dir)
0336         {
0337             case Up:
0338                 ydelta = am.numCells;
0339                 break;
0340             case Down:
0341                 ydelta = -am.numCells;
0342                 break;
0343             case Right:
0344                 xdelta = -am.numCells;
0345                 break;
0346             case Left:
0347                 xdelta = am.numCells;
0348                 break;
0349         }
0350         atom->setFieldXY( atom->fieldX()+xdelta, atom->fieldY()+ydelta );
0351     }
0352     // update pixel positions
0353     for ( AtomFieldItem* atom : std::as_const(m_atoms) )
0354         atom->setPos( toPixX(atom->fieldX()), toPixY(atom->fieldY()));
0355 
0356     m_numMoves = 0;
0357     Q_EMIT updateMoves(m_numMoves);
0358     Q_EMIT enableUndo(false);
0359     Q_EMIT enableRedo(!m_redoStack.isEmpty());
0360     m_selIdx = m_redoStack.last().atomIdx;
0361     updateArrows();
0362 }
0363 
0364 void PlayField::redoAll()
0365 {
0366     while( !m_redoStack.isEmpty() )
0367     {
0368         AtomMove am = m_redoStack.pop();
0369         m_undoStack.push( am );
0370 
0371         // adjust atom pos
0372         AtomFieldItem *atom = m_atoms.at(am.atomIdx);
0373         int xdelta = 0, ydelta = 0;
0374         switch(am.dir)
0375         {
0376             case Up:
0377                 ydelta = -am.numCells;
0378                 break;
0379             case Down:
0380                 ydelta = am.numCells;
0381                 break;
0382             case Right:
0383                 xdelta = am.numCells;
0384                 break;
0385             case Left:
0386                 xdelta = -am.numCells;
0387                 break;
0388         }
0389         atom->setFieldXY( atom->fieldX()+xdelta, atom->fieldY()+ydelta );
0390     }
0391     // update pixel positions
0392     for ( AtomFieldItem * atom : std::as_const(m_atoms) )
0393         atom->setPos( toPixX(atom->fieldX()), toPixY(atom->fieldY()));
0394 
0395     m_numMoves = m_undoStack.count();
0396     Q_EMIT updateMoves(m_numMoves);
0397     Q_EMIT enableUndo(!m_undoStack.isEmpty());
0398     Q_EMIT enableRedo(false);
0399     m_selIdx = m_undoStack.last().atomIdx;
0400     updateArrows();
0401 }
0402 
0403 void PlayField::mousePressEvent( QGraphicsSceneMouseEvent* ev )
0404 {
0405     QGraphicsScene::mousePressEvent(ev);
0406 
0407     if( isAnimating() || m_levelFinished )
0408         return;
0409 
0410     const QList<QGraphicsItem *> itemsAtPoint = items(ev->scenePos());
0411     if(itemsAtPoint.isEmpty())
0412         return;
0413 
0414     AtomFieldItem *atomItem = qgraphicsitem_cast<AtomFieldItem*>(itemsAtPoint[0]);
0415     if( atomItem ) // that is: atom selected
0416     {
0417         m_selIdx = m_atoms.indexOf( atomItem );
0418         updateArrows();
0419         return;
0420     }
0421 
0422     ArrowFieldItem *arrowItem = qgraphicsitem_cast<ArrowFieldItem*>(itemsAtPoint[0]);
0423     if( arrowItem == m_upArrow )
0424     {
0425         moveSelectedAtom( Up );
0426     }
0427     else if( arrowItem == m_downArrow )
0428     {
0429         moveSelectedAtom( Down );
0430     }
0431     else if( arrowItem == m_rightArrow )
0432     {
0433         moveSelectedAtom( Right );
0434     }
0435     else if( arrowItem == m_leftArrow )
0436     {
0437         moveSelectedAtom( Left );
0438     }
0439 }
0440 
0441 void PlayField::moveSelectedAtom( Direction dir, int numCells )
0442 {
0443     if( isAnimating() )
0444         return;
0445 
0446 
0447     int numEmptyCells=0;
0448     m_dir = dir;
0449 
0450     // numCells is also a kind of indicator whether this
0451     // function was called interactively (=0) or from  undo/redo functions(!=0)
0452     if(numCells == 0) // then we'll calculate
0453     {
0454         // helpers
0455         int x = 0, y = 0;
0456         int selX = m_atoms.at(m_selIdx)->fieldX();
0457         int selY = m_atoms.at(m_selIdx)->fieldY();
0458         switch( dir )
0459         {
0460             case Up:
0461                 y = selY;
0462                 while( cellIsEmpty(selX, --y) )
0463                     numEmptyCells++;
0464                 break;
0465             case Down:
0466                 y = selY;
0467                 while( cellIsEmpty(selX, ++y) )
0468                     numEmptyCells++;
0469                 break;
0470             case Left:
0471                 x = selX;
0472                 while( cellIsEmpty(--x, selY) )
0473                     numEmptyCells++;
0474                 break;
0475             case Right:
0476                 x = selX;
0477                 while( cellIsEmpty(++x, selY) )
0478                     numEmptyCells++;
0479                 break;
0480         }
0481         // and clear the redo stack. we do it here
0482         // because if this function is called with numCells=0
0483         // this indicates it is called not from undo()/redo(),
0484         // but as a result of mouse/keyb input from player
0485         // so this is just a place to drop redo history :-)
0486         m_redoStack.clear();
0487         Q_EMIT enableRedo(false);
0488         // only count it as move if we actually move :-)
0489         if(numEmptyCells)
0490             m_numMoves++;
0491     }
0492     else
0493         numEmptyCells = numCells;
0494 
0495     if( numEmptyCells == 0)
0496         return;
0497 
0498     // put undo info
0499     // don't put if we in the middle of series of undos
0500     if(m_redoStack.isEmpty())
0501     {
0502         if(m_undoStack.isEmpty())
0503             Q_EMIT enableUndo(true);
0504         m_undoStack.push( AtomMove(m_selIdx, m_dir, numEmptyCells) );
0505     }
0506 
0507     m_atomTimeLine->setCurrentTime(0); // reset
0508     m_atomTimeLine->setDuration( numEmptyCells * m_animSpeed ); // 1cell/m_animSpeed speed
0509     m_atomTimeLine->setFrameRange( 0, numEmptyCells*m_elemSize ); // 1frame=1pixel
0510     updateArrows(true); // hide them
0511     m_atomTimeLine->start();
0512 }
0513 
0514 void PlayField::atomAnimFrameChanged(int frame)
0515 {
0516     AtomFieldItem *selAtom = m_atoms.at(m_selIdx);
0517     int posx= toPixX(selAtom->fieldX());
0518     int posy= toPixY(selAtom->fieldY());
0519 
0520     switch( m_dir )
0521     {
0522         case Up:
0523             posy = toPixY(selAtom->fieldY()) - frame;
0524             break;
0525         case Down:
0526             posy = toPixY(selAtom->fieldY()) + frame;
0527             break;
0528         case Left:
0529             posx = toPixX(selAtom->fieldX()) - frame;
0530             break;
0531         case Right:
0532             posx = toPixX(selAtom->fieldX()) + frame;
0533             break;
0534     }
0535 
0536     selAtom->setPos(posx, posy);
0537 
0538     if(frame == m_atomTimeLine->endFrame()) // that is: move finished
0539     {
0540         // NOTE: consider moving this to separate function (something like moveFinished())
0541         // to improve code readablility
0542         selAtom->setFieldX( toFieldX((int)selAtom->pos().x()) );
0543         selAtom->setFieldY( toFieldY((int)selAtom->pos().y()) );
0544         updateArrows();
0545 
0546         Q_EMIT updateMoves(m_numMoves);
0547 
0548         if(checkDone() && !m_levelFinished)
0549         {
0550             m_levelFinished = true;
0551             // hide arrows
0552             updateArrows(true);
0553             m_selIdx = -1;
0554             Q_EMIT gameOver(m_numMoves);
0555         }
0556     }
0557 }
0558 
0559 // most complicated algorithm ;-)
0560 bool PlayField::checkDone() const
0561 {
0562     if (!m_levelData || !m_levelData->molecule())
0563     {
0564         //qCDebug(KATOMIC_LOG) << "level or molecule data is null!";
0565         return false;
0566     }
0567     // let's assume that molecule is done
0568     // and see if we can break assumption
0569     //
0570     // first we find molecule origin in field coords
0571     // by finding minimum fieldX, fieldY through all atoms
0572     int minX = FIELD_SIZE+1;
0573     int minY = FIELD_SIZE+1;
0574     for ( AtomFieldItem* atom : std::as_const(m_atoms) )
0575     {
0576         if(atom->fieldX() < minX)
0577             minX = atom->fieldX();
0578         if(atom->fieldY() < minY)
0579             minY = atom->fieldY();
0580     }
0581     // so origin is (minX,minY)
0582     // we'll subtract this origin from each atom's coords and check
0583     // if the resulting position is the same as this atom has in molecule
0584     for ( AtomFieldItem* atom : std::as_const(m_atoms) )
0585     {
0586         uint atomNum = atom->atomNum();
0587         int molecCoordX = atom->fieldX() - minX;
0588         int molecCoordY = atom->fieldY() - minY;
0589         if( m_levelData->molecule()->getAtom( molecCoordX, molecCoordY ) != atomNum )
0590             return false; // nope. not there
0591     }
0592     return true;
0593 }
0594 
0595 bool PlayField::cellIsEmpty(int x, int y) const
0596 {
0597     if (!m_levelData)
0598     {
0599         //qCDebug(KATOMIC_LOG) << "level data is null!";
0600         return true;
0601     }
0602 
0603     if(m_levelData->containsWallAt(x,y))
0604         return false; // it is a wall
0605 
0606     for ( AtomFieldItem *atom : std::as_const(m_atoms) )
0607     {
0608         if( atom->fieldX() == x && atom->fieldY() == y )
0609             return false;
0610     }
0611     return true;
0612 }
0613 
0614 void PlayField::setAnimationSpeed(int speed)
0615 {
0616     if(speed == 0) // slow
0617         m_animSpeed = 300;
0618     else if (speed == 1) //normal
0619         m_animSpeed = 120;
0620     else
0621         m_animSpeed = 60;
0622 }
0623 
0624 void PlayField::updateArrows(bool justHide)
0625 {
0626     m_upArrow->hide();
0627     m_downArrow->hide();
0628     m_leftArrow->hide();
0629     m_rightArrow->hide();
0630 
0631     if(justHide || m_selIdx == -1 || m_levelFinished || m_atoms.isEmpty())
0632         return;
0633 
0634     int selX = m_atoms.at(m_selIdx)->fieldX();
0635     int selY = m_atoms.at(m_selIdx)->fieldY();
0636 
0637     if(cellIsEmpty(selX-1, selY))
0638     {
0639         m_leftArrow->show();
0640         m_leftArrow->setFieldXY( selX-1, selY );
0641         m_leftArrow->setPos( toPixX(selX-1), toPixY(selY) );
0642     }
0643     if(cellIsEmpty(selX+1, selY))
0644     {
0645         m_rightArrow->show();
0646         m_rightArrow->setFieldXY( selX+1, selY );
0647         m_rightArrow->setPos( toPixX(selX+1), toPixY(selY) );
0648     }
0649     if(cellIsEmpty(selX, selY-1))
0650     {
0651         m_upArrow->show();
0652         m_upArrow->setFieldXY( selX, selY-1 );
0653         m_upArrow->setPos( toPixX(selX), toPixY(selY-1) );
0654     }
0655     if(cellIsEmpty(selX, selY+1))
0656     {
0657         m_downArrow->show();
0658         m_downArrow->setFieldXY( selX, selY+1 );
0659         m_downArrow->setPos( toPixX(selX), toPixY(selY+1) );
0660     }
0661 }
0662 
0663 void PlayField::drawForeground( QPainter *p, const QRectF&)
0664 {
0665     if (!m_levelData)
0666     {
0667         //qCDebug(KATOMIC_LOG) << "level data is null!";
0668         return;
0669     }
0670 
0671     QPixmap aPix = m_renderer.spritePixmap(QStringLiteral("wall"), QSize(m_elemSize, m_elemSize));
0672     for (int i = 0; i < FIELD_SIZE; i++)
0673         for (int j = 0; j < FIELD_SIZE; j++)
0674             if(m_levelData->containsWallAt(i,j))
0675                 p->drawPixmap(toPixX(i), toPixY(j), aPix);
0676 }
0677 
0678 bool PlayField::isAnimating() const
0679 {
0680     return (m_atomTimeLine->state() == QTimeLine::Running);
0681 }
0682 
0683 void PlayField::saveGame( KConfigGroup& config ) const
0684 {
0685     // REMEMBER: while saving use atom indexes within m_atoms, not atom's atomNum()'s.
0686     // atomNum()'s arent unique, there can be several atoms
0687     // in molecule which represent same atomNum
0688 
0689     for(int idx=0; idx<m_atoms.count(); ++idx)
0690     {
0691         // we'll write pos through using QPoint
0692         // I'd use QPair but it isn't supported by QVariant
0693         QPoint pos(m_atoms.at(idx)->fieldX(), m_atoms.at(idx)->fieldY());
0694         config.writeEntry( QStringLiteral("Atom_%1").arg(idx), pos);
0695     }
0696 
0697     // save undo history
0698     int moveCount = m_undoStack.count();
0699     config.writeEntry( "MoveCount", moveCount );
0700     AtomMove mv;
0701     for(int i=0;i<moveCount;++i)
0702     {
0703         mv = m_undoStack.at(i);
0704         // atomIdx, direction, numCells
0705         const QList<int> move {
0706             mv.atomIdx,
0707             static_cast<int>(mv.dir),
0708             mv.numCells,
0709         };
0710         config.writeEntry( QStringLiteral("Move_%1").arg(i), move );
0711     }
0712     config.writeEntry("SelectedAtom", m_selIdx);
0713     config.writeEntry("LevelFinished", m_levelFinished );
0714 }
0715 
0716 void PlayField::loadGame( const KConfigGroup& config )
0717 {
0718     // it is assumed that this method is called right after setLevelData() so
0719     // level itself is already loaded at this point
0720 
0721     // read atom positions
0722     for(int idx=0; idx<m_atoms.count(); ++idx)
0723     {
0724         QPoint pos = config.readEntry( QStringLiteral("Atom_%1").arg(idx), QPoint() );
0725         m_atoms.at(idx)->setFieldXY(pos.x(), pos.y());
0726         m_atoms.at(idx)->setPos( toPixX(pos.x()), toPixY(pos.y()) );
0727     }
0728     // fill undo history
0729     m_numMoves = config.readEntry("MoveCount", 0);
0730 
0731     AtomMove mv;
0732     for(int i=0;i<m_numMoves;++i)
0733     {
0734         QList<int> move = config.readEntry( QStringLiteral("Move_%1").arg(i), QList<int>() );
0735         mv.atomIdx = move.at(0);
0736         mv.dir = static_cast<Direction>(move.at(1));
0737         mv.numCells = move.at(2);
0738         m_undoStack.push(mv);
0739     }
0740     if(m_numMoves)
0741     {
0742         Q_EMIT enableUndo(true);
0743         Q_EMIT updateMoves(m_numMoves);
0744     }
0745 
0746     m_selIdx = config.readEntry("SelectedAtom", 0);
0747     m_levelFinished = config.readEntry("LevelFinished", false);
0748     updateArrows();
0749 }
0750 
0751 void PlayField::showMessage( const QString& message )
0752 {
0753     m_messageItem->setMessageTimeout( 4000 );
0754     m_messageItem->showMessage( message, KGamePopupItem::BottomLeft );
0755 }
0756 
0757 QString PlayField::moleculeName() const
0758 {
0759     if (!m_levelData || !m_levelData->molecule())
0760     {
0761         //qCDebug(KATOMIC_LOG) << "level or molecule data is null!";
0762         return QString();
0763     }
0764 
0765     return m_levelData->molecule()->moleculeName();
0766 }
0767 
0768 #include "moc_playfield.cpp"