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>
36 #include <QCloseEvent>
40 #include <QApplication>
44 #include <QMaemo5InformationBox>
49 GameView *GameView::instance_ = 0;
51 GameView::GameView(QWidget *parent) :
54 setBackgroundBrush(Qt::black);
55 qsrand(QDateTime::currentDateTime().toTime_t());
56 scene_ = new QGraphicsScene;
60 introItem_ = new IntroItem;
61 introItem_->setText("- ImPuzzle -");
66 qsrand(QDateTime::currentDateTime().toTime_t());
68 if(QFile::exists(QString("%1/%2/%3")
69 .arg(QDir::homePath()).arg(HOME_DIRECTORY).arg(RESTORE_FILE))) {
71 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()));
72 PuzzleItem::setMoveCount(0);
75 QTimer::singleShot(0, this, SIGNAL(gameRestored()));
79 scene_->addItem(introItem_);
83 GameView *GameView::instance()
86 instance_ = new GameView;
92 QList<PuzzleItem *> GameView::pieces() const
97 void GameView::setPieces(const QList<PuzzleItem *> pieces, bool shuffle)
99 PuzzleItem::setManuallyMovable(false);
101 if(pieces.isEmpty()) {
102 qDebug() << "Empty list @ GameView::setPieces";
106 QList<QGraphicsItem *> previousItems = scene_->items();
107 if(!previousItems.isEmpty()) {
108 foreach(QGraphicsItem *item, previousItems) {
109 scene_->removeItem(item);
115 int horizontalCount = 0;
117 // Find out board size
118 if(pieces_.count() == EASY_PIECE_COUNT) {
119 horizontalCount = EASY_HORIZONTAL_COUNT;
121 else if(pieces_.count() == HARD_PIECE_COUNT) {
122 horizontalCount = HARD_HORIZONTAL_COUNT;
125 qDebug() << "Invalid piece count @ GameView::setPieces";
126 qDebug() << QString("Count was %1").arg(pieces_.count());
130 int verticalCount = pieces_.count() / horizontalCount;
131 horizontalStep_ = IMAGE_WIDTH / horizontalCount + 5;
132 verticalStep_ = IMAGE_HEIGHT / verticalCount + 5;
136 // Set pieces to their correct positions
137 for(int i = 0; i < verticalCount; ++i) {
138 for(int j = 0; j < horizontalCount; ++j) {
139 scene_->addItem(pieces_.at(pieceNumber));
140 QPointF point(j * horizontalStep_, i * verticalStep_);
141 pieces_.at(pieceNumber)->setPos(point);
142 pieces_.at(pieceNumber)->setCorrectPlace(point);
143 pieces_.at(pieceNumber)->setCurrentPlace(point);
144 pieces_.at(pieceNumber)->setDrawNumber(true);
149 // Wait and shuffle if desired
151 QTimer::singleShot(750, this, SLOT(shufflePieces()));
154 PuzzleItem::setManuallyMovable(true);
159 void GameView::shufflePieces()
161 if(pieces_.isEmpty()) {
162 qDebug() << "Empty list @ GameView::shufflePieces";
166 // Give pieces ramdom locations
167 hiddenIndex_ = qrand() % pieces_.count();
168 emptyPlace_ = pieces_.at(hiddenIndex_)->currentPlace();
170 QPointF topLeft = pieces_.at(0)->correctPlace();
171 QPointF bottomRight = pieces_.last()->correctPlace();
173 int moveCount = pieces_.count() * 10;
176 PuzzleItem *item = 0;
178 for(int i = 0; i < moveCount; ++i) {
179 int rand = qrand() % 4;
184 if(pieces_.at(hiddenIndex_)->currentPlace().y() > topLeft.y()) {
185 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
186 QGraphicsItem *graphicsItem = scene()->itemAt(tmp + QPointF(0, -verticalStep_));
188 item = dynamic_cast<PuzzleItem *>(graphicsItem);
189 if(item->movable()) {
190 emptyPlace_ = item->currentPlace();
191 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
192 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
193 item->setCurrentPlace(tmp);
201 qDebug() << "Item right of hidden piece not movable";
211 if(pieces_.at(hiddenIndex_)->currentPlace().y() < bottomRight.y()) {
212 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
213 QGraphicsItem *graphicsItem = scene()->itemAt(tmp + QPointF(0, verticalStep_));
215 item = dynamic_cast<PuzzleItem *>(graphicsItem);
216 if(item->movable()) {
217 emptyPlace_ = item->currentPlace();
218 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
219 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
220 item->setCurrentPlace(tmp);
226 qDebug() << "Item down of hidden piece not movable";
236 if(pieces_.at(hiddenIndex_)->currentPlace().x() > topLeft.x()) {
237 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
238 QGraphicsItem *graphicsItem = scene()->itemAt(tmp + QPointF(-horizontalStep_, 0));
240 item = dynamic_cast<PuzzleItem *>(graphicsItem);
241 if(item->movable()) {
242 emptyPlace_ = item->currentPlace();
243 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
244 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
245 item->setCurrentPlace(tmp);
251 qDebug() << "Item left of hidden piece not movable";
261 if(pieces_.at(hiddenIndex_)->currentPlace().x() < bottomRight.x()) {
262 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
263 QGraphicsItem *graphicsItem = scene()->itemAt(tmp + QPointF(horizontalStep_, 0));
265 item = dynamic_cast<PuzzleItem *>(graphicsItem);
266 if(item->movable()) {
267 emptyPlace_ = item->currentPlace();
268 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
269 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
270 item->setCurrentPlace(tmp);
276 qDebug() << "Item up of hidden piece not movable";
290 qDebug() << QString("Shuffle moves: %1/%2").arg(movesMade).arg(moveCount);
292 QParallelAnimationGroup *animationGroup = new QParallelAnimationGroup(this);
293 connect(animationGroup, SIGNAL(finished()), this, SLOT(shuffleAnimationFinished()));
294 for(int i = 0; i < pieces_.count(); ++i) {
295 QPropertyAnimation *animation = new QPropertyAnimation(pieces_.at(i), "pos");
296 animation->setStartValue(pieces_.at(i)->correctPlace());
297 animation->setEndValue(pieces_.at(i)->currentPlace());
298 animation->setDuration(750);
299 animation->setEasingCurve(QEasingCurve::InOutCirc);
300 animationGroup->addAnimation(animation);
302 animationGroup->start();
303 pieces_.at(hiddenIndex_)->hide();
306 void GameView::shuffleAnimationFinished()
309 PuzzleItem::setManuallyMovable(true);
312 QPointF GameView::emptyPlace()
317 void GameView::setEmptyPlace(const QPointF &place)
322 bool GameView::areAllPiecesOk()
324 for(int i = 0; i < pieces_.count(); ++i) {
326 if(i == hiddenIndex_) {
329 // Id piece is not in it's place
330 else if(pieces_.at(i)->correctPlace() != pieces_.at(i)->currentPlace()) {
334 // Show hidden piece and move it to it's place
335 pieces_.at(hiddenIndex_)->show();
336 pieces_.at(hiddenIndex_)->moveMeTo(emptyPlace_);
338 // Set all pieces not movable and hide numbers
339 for(int i = 0; i < pieces_.count(); ++i) {
340 pieces_.at(i)->setMovable(false);
341 pieces_.at(i)->setDrawNumber(false);
344 // Show dialog with move count
345 QMessageBox::about(const_cast<GameView *>(this), tr("You won"), QString("Puzzle completed with %1 moves").arg(PuzzleItem::moveCount()));
351 void GameView::setMovingPieces()
353 if(pieces_.isEmpty()) {
354 qDebug() << "Empty list @ GameView::setMovingPieces";
358 QPointF point = QPointF();
359 for(int i = 0; i < pieces_.count(); ++i) {
360 point = pieces_.at(i)->currentPlace();
362 // Is piece on the left side of the empty space
363 if(emptyPlace_.y() == point.y() && point.x() + horizontalStep_ == emptyPlace_.x()) {
364 pieces_.at(i)->setMovable(true);
367 // Is piece on the right side of the empty space
368 else if(emptyPlace_.y() == point.y() && point.x() - horizontalStep_ == emptyPlace_.x()) {
369 pieces_.at(i)->setMovable(true);
372 // Is piece below the empty space
373 else if(emptyPlace_.x() == point.x() && point.y() - verticalStep_ == emptyPlace_.y()) {
374 pieces_.at(i)->setMovable(true);
377 // Is piece on top of the empty space
378 else if(emptyPlace_.x() == point.x() && point.y() + verticalStep_ == emptyPlace_.y()) {
379 pieces_.at(i)->setMovable(true);
382 // The piece is somewhere else
384 pieces_.at(i)->setMovable(false);
389 bool GameView::restoreGame()
391 // Read settings from file
392 QFile file(QString("%1/%2/%3")
393 .arg(QDir::homePath())
397 if(!file.open(QIODevice::ReadOnly)) {
398 qDebug() << "Failed to open restore file for reading";
402 QTextStream in(&file);
406 list = in.readLine().split(";;");
408 qDebug() << "restore list count: " << list.count();
410 if(!list.isEmpty()) {
412 int pieces = list.at(0).toInt(&ok);
417 QString im = list.at(1);
418 if(!QFile::exists(im) && im != "default") {
422 int moveCount = list.at(2).toInt(&ok);
427 Settings::instance()->setPieceCount(pieces);
428 PuzzleItem::setMoveCount(moveCount);
430 if(im == "default" || im.isEmpty()) {
431 Settings::instance()->setImage(0);
432 Settings::instance()->setImagePath("default");
435 Settings::instance()->setImagePath(im);
436 Settings::instance()->setImage(QPixmap(im));
439 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()), false);
441 qDebug() << "pieces_ count after restoring image: " << pieces_.count();
443 if(list.count() >= pieces_.count() + 3) {
444 for(int j = 0; j < pieces_.count(); ++j) {
445 if(!list.at(j + 3).isNull()) {
446 QStringList points = list.at(j + 3).split("#");
448 int x = points.at(0).toInt(&ok);
453 int y = points.at(1).toInt(&ok);
460 //qDebug() << "Setting piece " << pieces_.at(j)->pieceNumber();
461 //qDebug() << "x: " << point.x() << " y: " << point.y();
463 pieces_.at(j)->setCurrentPlace(point);
476 QStringList hidden = list.last().split("#");
478 if(hidden.count() == 3) {
479 for(int m = 0; m < pieces_.count(); ++m) {
480 pieces_.at(m)->setPos(pieces_.at(m)->currentPlace());
481 if(pieces_.at(m)->pieceNumber() == hidden.at(2).toInt()) {
482 //qDebug() << "Hiding piece number " << hidden;
487 setEmptyPlace(QPointF(hidden.at(0).toInt(), hidden.at(1).toInt()));
489 pieces_.at(hiddenIndex_)->setVisible(false);
494 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()));
501 qDebug() << "Invalid restore file";
507 QFileInfo fileInfo(file);
509 QDateTime created = fileInfo.created();
510 QString infoTxt = QString("Restored game state from %1")
511 .arg(created.toString(Qt::TextDate));
514 QMaemo5InformationBox::information(this, infoTxt);
523 void GameView::saveGame()
525 if(pieces_.isEmpty() || pieces_.count() < EASY_PIECE_COUNT) {
530 if(!dir.exists(QString("%1/%2")
531 .arg(QDir::homePath())
532 .arg(HOME_DIRECTORY))) {
533 dir.mkpath(QString("%1/%2")
534 .arg(QDir::homePath())
535 .arg(HOME_DIRECTORY));
538 QFile file(QString("%1/%2/%3")
539 .arg(QDir::homePath())
543 if(!file.open(QIODevice::WriteOnly)) {
544 qDebug() << "Failed to open restore file for writing";
548 QTextStream out(&file);
550 out << Settings::instance()->pieceCount();
551 out << QString(";;");
552 if(Settings::instance()->imagePath().isEmpty()) {
553 out << QString("default");
556 out << Settings::instance()->imagePath();
558 out << QString(";;");
559 out << PuzzleItem::moveCount();
560 out << QString(";;");
566 while(number != pieces_.count()) {
567 for(int i = 0; i < pieces_.count(); ++i) {
568 if(pieces_.at(i)->pieceNumber() == number + 1) {
569 out << pieces_.at(i)->currentPlace().x();
571 out << pieces_.at(i)->currentPlace().y();
572 out << QString(";;");
573 pieces_.at(i)->pieceNumber();
574 if(!pieces_.at(i)->isVisible()) {
575 hiddenNo = number + 1;
583 out << QString("%1#%2#%3").arg(emptyPlace().x()).arg(emptyPlace().y()).arg(hiddenNo);
592 void GameView::closeEvent(QCloseEvent *event)
594 int answer = QMessageBox::question(this, tr("Save game status?"),
595 tr("Saved status will be automatically loaded when you start the application next time"),
596 QMessageBox::Yes, QMessageBox::No);
598 if(answer == QMessageBox::Yes) {
605 int GameView::correctPlaces() const
609 for(int i = 0; i < pieces_.count(); ++i) {
610 if(pieces_.at(i)->currentPlace() == pieces_.at(i)->correctPlace()) {
618 QList<int> GameView::movingPlaces() const
622 for(int i = 0; i < pieces_.count(); ++i) {
623 if(pieces_.at(i)->movable()) {