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