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"