2 Image Puzzle - A set your pieces straight game
3 Copyright (C) 2009 Timo Härkönen
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
20 #include "puzzleitem.h"
22 #include "introitem.h"
23 #include "imageimporter.h"
26 #include <QGraphicsScene>
29 #include <QPropertyAnimation>
30 #include <QParallelAnimationGroup>
32 #include <QMessageBox>
35 #include <QTextStream>
39 #include <QApplication>
43 #include <QMaemo5InformationBox>
48 GameView *GameView::instance_ = 0;
50 GameView::GameView(QWidget *parent) :
53 setBackgroundBrush(Qt::black);
54 qsrand(QDateTime::currentDateTime().toTime_t());
55 scene_ = new QGraphicsScene;
59 introItem_ = new IntroItem;
60 introItem_->setText("- ImPuzzle -");
65 qsrand(QDateTime::currentDateTime().toTime_t());
67 if(QFile::exists(QString("%1/%2/%3")
68 .arg(QDir::homePath()).arg(HOME_DIRECTORY).arg(RESTORE_FILE))) {
70 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()));
71 PuzzleItem::setMoveCount(0);
74 QTimer::singleShot(0, this, SIGNAL(gameRestored()));
78 scene_->addItem(introItem_);
82 GameView *GameView::instance()
85 instance_ = new GameView;
91 QList<PuzzleItem *> GameView::pieces() const
96 void GameView::setPieces(const QList<PuzzleItem *> pieces, bool shuffle)
98 PuzzleItem::setManuallyMovable(false);
100 if(pieces.isEmpty()) {
101 qDebug() << "Empty list @ GameView::setPieces";
105 QList<QGraphicsItem *> previousItems = scene_->items();
106 if(!previousItems.isEmpty()) {
107 foreach(QGraphicsItem *item, previousItems) {
108 scene_->removeItem(item);
114 int horizontalCount = 0;
116 // Find out board size
117 if(pieces_.count() == EASY_PIECE_COUNT) {
118 horizontalCount = EASY_HORIZONTAL_COUNT;
120 else if(pieces_.count() == HARD_PIECE_COUNT) {
121 horizontalCount = HARD_HORIZONTAL_COUNT;
124 qDebug() << "Invalid piece count @ GameView::setPieces";
125 qDebug() << QString("Count was %1").arg(pieces_.count());
129 int verticalCount = pieces_.count() / horizontalCount;
130 horizontalStep_ = IMAGE_WIDTH / horizontalCount + 5;
131 verticalStep_ = IMAGE_HEIGHT / verticalCount + 5;
135 // Set pieces to their correct positions
136 for(int i = 0; i < verticalCount; ++i) {
137 for(int j = 0; j < horizontalCount; ++j) {
138 scene_->addItem(pieces_.at(pieceNumber));
139 QPointF point(j * horizontalStep_, i * verticalStep_);
140 pieces_.at(pieceNumber)->setPos(point);
141 pieces_.at(pieceNumber)->setCorrectPlace(point);
142 pieces_.at(pieceNumber)->setCurrentPlace(point);
143 pieces_.at(pieceNumber)->setDrawNumber(true);
148 // Wait and shuffle if desired
150 QTimer::singleShot(750, this, SLOT(shufflePieces()));
153 PuzzleItem::setManuallyMovable(true);
158 void GameView::shufflePieces()
160 if(pieces_.isEmpty()) {
161 qDebug() << "Empty list @ GameView::shufflePieces";
165 // Give pieces ramdom locations
166 hiddenIndex_ = qrand() % pieces_.count();
167 emptyPlace_ = pieces_.at(hiddenIndex_)->currentPlace();
169 QPointF topLeft = pieces_.at(0)->correctPlace();
170 QPointF bottomRight = pieces_.last()->correctPlace();
172 int moveCount = pieces_.count() * 10;
175 PuzzleItem *item = 0;
177 for(int i = 0; i < moveCount; ++i) {
178 int rand = qrand() % 4;
183 if(pieces_.at(hiddenIndex_)->currentPlace().y() > topLeft.y()) {
184 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
185 QGraphicsItem *graphicsItem = scene()->itemAt(tmp + QPointF(0, -verticalStep_));
187 item = dynamic_cast<PuzzleItem *>(graphicsItem);
188 if(item->movable()) {
189 emptyPlace_ = item->currentPlace();
190 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
191 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
192 item->setCurrentPlace(tmp);
200 qDebug() << "Item right of hidden piece not movable";
210 if(pieces_.at(hiddenIndex_)->currentPlace().y() < bottomRight.y()) {
211 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
212 QGraphicsItem *graphicsItem = scene()->itemAt(tmp + QPointF(0, verticalStep_));
214 item = dynamic_cast<PuzzleItem *>(graphicsItem);
215 if(item->movable()) {
216 emptyPlace_ = item->currentPlace();
217 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
218 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
219 item->setCurrentPlace(tmp);
225 qDebug() << "Item down of hidden piece not movable";
235 if(pieces_.at(hiddenIndex_)->currentPlace().x() > topLeft.x()) {
236 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
237 QGraphicsItem *graphicsItem = scene()->itemAt(tmp + QPointF(-horizontalStep_, 0));
239 item = dynamic_cast<PuzzleItem *>(graphicsItem);
240 if(item->movable()) {
241 emptyPlace_ = item->currentPlace();
242 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
243 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
244 item->setCurrentPlace(tmp);
250 qDebug() << "Item left of hidden piece not movable";
260 if(pieces_.at(hiddenIndex_)->currentPlace().x() < bottomRight.x()) {
261 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
262 QGraphicsItem *graphicsItem = scene()->itemAt(tmp + QPointF(horizontalStep_, 0));
264 item = dynamic_cast<PuzzleItem *>(graphicsItem);
265 if(item->movable()) {
266 emptyPlace_ = item->currentPlace();
267 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
268 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
269 item->setCurrentPlace(tmp);
275 qDebug() << "Item up of hidden piece not movable";
289 qDebug() << QString("Shuffle moves: %1/%2").arg(movesMade).arg(moveCount);
291 QParallelAnimationGroup *animationGroup = new QParallelAnimationGroup(this);
292 connect(animationGroup, SIGNAL(finished()), this, SLOT(shuffleAnimationFinished()));
293 for(int i = 0; i < pieces_.count(); ++i) {
294 QPropertyAnimation *animation = new QPropertyAnimation(pieces_.at(i), "pos");
295 animation->setStartValue(pieces_.at(i)->correctPlace());
296 animation->setEndValue(pieces_.at(i)->currentPlace());
297 animation->setDuration(750);
298 animation->setEasingCurve(QEasingCurve::InOutCirc);
299 animationGroup->addAnimation(animation);
301 animationGroup->start();
302 pieces_.at(hiddenIndex_)->hide();
305 void GameView::shuffleAnimationFinished()
308 PuzzleItem::setManuallyMovable(true);
311 QPointF GameView::emptyPlace()
316 void GameView::setEmptyPlace(const QPointF &place)
321 bool GameView::areAllPiecesOk()
323 for(int i = 0; i < pieces_.count(); ++i) {
325 if(i == hiddenIndex_) {
328 // Id piece is not in it's place
329 else if(pieces_.at(i)->correctPlace() != pieces_.at(i)->currentPlace()) {
333 // Show hidden piece and move it to it's place
334 pieces_.at(hiddenIndex_)->show();
335 pieces_.at(hiddenIndex_)->moveMeTo(emptyPlace_);
337 // Set all pieces not movable and hide numbers
338 for(int i = 0; i < pieces_.count(); ++i) {
339 pieces_.at(i)->setMovable(false);
340 pieces_.at(i)->setDrawNumber(false);
343 // Show dialog with move count
344 QMessageBox::about(const_cast<GameView *>(this), tr("You won"), QString("Puzzle completed with %1 moves").arg(PuzzleItem::moveCount()));
350 void GameView::setMovingPieces()
352 if(pieces_.isEmpty()) {
353 qDebug() << "Empty list @ GameView::setMovingPieces";
357 QPointF point = QPointF();
358 for(int i = 0; i < pieces_.count(); ++i) {
359 point = pieces_.at(i)->currentPlace();
361 // Is piece on the left side of the empty space
362 if(emptyPlace_.y() == point.y() && point.x() + horizontalStep_ == emptyPlace_.x()) {
363 pieces_.at(i)->setMovable(true);
366 // Is piece on the right side of the empty space
367 else if(emptyPlace_.y() == point.y() && point.x() - horizontalStep_ == emptyPlace_.x()) {
368 pieces_.at(i)->setMovable(true);
371 // Is piece below the empty space
372 else if(emptyPlace_.x() == point.x() && point.y() - verticalStep_ == emptyPlace_.y()) {
373 pieces_.at(i)->setMovable(true);
376 // Is piece on top of the empty space
377 else if(emptyPlace_.x() == point.x() && point.y() + verticalStep_ == emptyPlace_.y()) {
378 pieces_.at(i)->setMovable(true);
381 // The piece is somewhere else
383 pieces_.at(i)->setMovable(false);
388 bool GameView::restoreGame()
390 // Read settings from file
391 QFile file(QString("%1/%2/%3")
392 .arg(QDir::homePath())
396 if(!file.open(QIODevice::ReadOnly)) {
397 qDebug() << "Failed to open restore file for reading";
401 QTextStream in(&file);
405 list = in.readLine().split(";;");
407 qDebug() << "restore list count: " << list.count();
409 if(!list.isEmpty()) {
411 int pieces = list.at(0).toInt(&ok);
416 QString im = list.at(1);
417 if(!QFile::exists(im) && im != "default") {
421 int moveCount = list.at(2).toInt(&ok);
426 Settings::instance()->setPieceCount(pieces);
427 PuzzleItem::setMoveCount(moveCount);
429 if(im == "default" || im.isEmpty()) {
430 Settings::instance()->setImage(0);
431 Settings::instance()->setImagePath("default");
434 Settings::instance()->setImagePath(im);
435 Settings::instance()->setImage(QPixmap(im));
438 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()), false);
440 qDebug() << "pieces_ count after restoring image: " << pieces_.count();
442 if(list.count() >= pieces_.count() + 3) {
443 for(int j = 0; j < pieces_.count(); ++j) {
444 if(!list.at(j + 3).isNull()) {
445 QStringList points = list.at(j + 3).split("#");
447 int x = points.at(0).toInt(&ok);
452 int y = points.at(1).toInt(&ok);
459 //qDebug() << "Setting piece " << pieces_.at(j)->pieceNumber();
460 //qDebug() << "x: " << point.x() << " y: " << point.y();
462 pieces_.at(j)->setCurrentPlace(point);
475 QStringList hidden = list.last().split("#");
477 if(hidden.count() == 3) {
478 for(int m = 0; m < pieces_.count(); ++m) {
479 pieces_.at(m)->setPos(pieces_.at(m)->currentPlace());
480 if(pieces_.at(m)->pieceNumber() == hidden.at(2).toInt()) {
481 //qDebug() << "Hiding piece number " << hidden;
486 setEmptyPlace(QPointF(hidden.at(0).toInt(), hidden.at(1).toInt()));
488 pieces_.at(hiddenIndex_)->setVisible(false);
493 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()));
500 qDebug() << "Invalid restore file";
506 QFileInfo fileInfo(file);
508 QDateTime created = fileInfo.created();
509 QString infoTxt = QString("Restored game state from %1")
510 .arg(created.toString(Qt::TextDate));
513 QMaemo5InformationBox::information(this, infoTxt);
522 void GameView::saveGame()
524 if(pieces_.isEmpty() || pieces_.count() < EASY_PIECE_COUNT) {
529 if(!dir.exists(QString("%1/%2")
530 .arg(QDir::homePath())
531 .arg(HOME_DIRECTORY))) {
532 dir.mkpath(QString("%1/%2")
533 .arg(QDir::homePath())
534 .arg(HOME_DIRECTORY));
537 QFile file(QString("%1/%2/%3")
538 .arg(QDir::homePath())
542 if(!file.open(QIODevice::WriteOnly)) {
543 qDebug() << "Failed to open restore file for writing";
547 QTextStream out(&file);
549 out << Settings::instance()->pieceCount();
550 out << QString(";;");
551 if(Settings::instance()->imagePath().isEmpty()) {
552 out << QString("default");
555 out << Settings::instance()->imagePath();
557 out << QString(";;");
558 out << PuzzleItem::moveCount();
559 out << QString(";;");
565 while(number != pieces_.count()) {
566 for(int i = 0; i < pieces_.count(); ++i) {
567 if(pieces_.at(i)->pieceNumber() == number + 1) {
568 out << pieces_.at(i)->currentPlace().x();
570 out << pieces_.at(i)->currentPlace().y();
571 out << QString(";;");
572 pieces_.at(i)->pieceNumber();
573 if(!pieces_.at(i)->isVisible()) {
574 hiddenNo = number + 1;
582 out << QString("%1#%2#%3").arg(emptyPlace().x()).arg(emptyPlace().y()).arg(hiddenNo);
591 int GameView::correctPlaces() const
595 for(int i = 0; i < pieces_.count(); ++i) {
596 if(pieces_.at(i)->currentPlace() == pieces_.at(i)->correctPlace()) {
604 QList<int> GameView::movingPlaces() const
608 for(int i = 0; i < pieces_.count(); ++i) {
609 if(pieces_.at(i)->movable()) {