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 if(pieces.isEmpty()) {
100 qDebug() << "Empty list @ GameView::setPieces";
104 QList<QGraphicsItem *> previousItems = scene_->items();
105 if(!previousItems.isEmpty()) {
106 foreach(QGraphicsItem *item, previousItems) {
107 scene_->removeItem(item);
113 int horizontalCount = 0;
115 // Find out board size
116 if(pieces_.count() == EASY_PIECE_COUNT) {
117 horizontalCount = EASY_HORIZONTAL_COUNT;
119 else if(pieces_.count() == HARD_PIECE_COUNT) {
120 horizontalCount = HARD_HORIZONTAL_COUNT;
123 qDebug() << "Invalid piece count @ GameView::setPieces";
124 qDebug() << QString("Count was %1").arg(pieces_.count());
128 int verticalCount = pieces_.count() / horizontalCount;
129 horizontalStep_ = IMAGE_WIDTH / horizontalCount + 5;
130 verticalStep_ = IMAGE_HEIGHT / verticalCount + 5;
134 // Set pieces to their correct positions
135 for(int i = 0; i < verticalCount; ++i) {
136 for(int j = 0; j < horizontalCount; ++j) {
137 scene_->addItem(pieces_.at(pieceNumber));
138 QPointF point(j * horizontalStep_, i * verticalStep_);
139 pieces_.at(pieceNumber)->setPos(point);
140 pieces_.at(pieceNumber)->setCorrectPlace(point);
141 pieces_.at(pieceNumber)->setCurrentPlace(point);
142 pieces_.at(pieceNumber)->setDrawNumber(true);
147 // Wait and shuffle if desired
149 QTimer::singleShot(750, this, SLOT(shufflePieces()));
154 void GameView::shufflePieces()
156 if(pieces_.isEmpty()) {
157 qDebug() << "Empty list @ GameView::shufflePieces";
161 // Give pieces ramdom locations
162 hiddenIndex_ = qrand() % pieces_.count();
163 emptyPlace_ = pieces_.at(hiddenIndex_)->currentPlace();
165 QPointF topLeft = pieces_.at(0)->correctPlace();
166 QPointF bottomRight = pieces_.last()->correctPlace();
168 int moveCount = pieces_.count() * 10;
171 PuzzleItem *item = 0;
173 for(int i = 0; i < moveCount; ++i) {
174 int rand = qrand() % 4;
179 if(pieces_.at(hiddenIndex_)->currentPlace().y() > topLeft.y()) {
180 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
181 item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(0, -verticalStep_)));
182 if(item->movable()) {
183 emptyPlace_ = item->currentPlace();
184 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
185 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
186 item->setCurrentPlace(tmp);
194 qDebug() << "Item right of hidden piece not movable";
203 if(pieces_.at(hiddenIndex_)->currentPlace().y() < bottomRight.y()) {
204 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
205 item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(0, verticalStep_)));
206 if(item->movable()) {
207 emptyPlace_ = item->currentPlace();
208 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
209 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
210 item->setCurrentPlace(tmp);
216 qDebug() << "Item down of hidden piece not movable";
225 if(pieces_.at(hiddenIndex_)->currentPlace().x() > topLeft.x()) {
226 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
227 item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(-horizontalStep_, 0)));
228 if(item->movable()) {
229 emptyPlace_ = item->currentPlace();
230 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
231 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
232 item->setCurrentPlace(tmp);
238 qDebug() << "Item left of hidden piece not movable";
247 if(pieces_.at(hiddenIndex_)->currentPlace().x() < bottomRight.x()) {
248 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
249 item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(horizontalStep_, 0)));
250 if(item->movable()) {
251 emptyPlace_ = item->currentPlace();
252 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
253 pieces_.at(hiddenIndex_)->setPos(item->currentPlace());
254 item->setCurrentPlace(tmp);
260 qDebug() << "Item up of hidden piece not movable";
273 qDebug() << QString("Shuffle moves: %1/%2").arg(movesMade).arg(moveCount);
275 QParallelAnimationGroup *animationGroup = new QParallelAnimationGroup(this);
276 for(int i = 0; i < pieces_.count(); ++i) {
277 QPropertyAnimation *animation = new QPropertyAnimation(pieces_.at(i), "pos");
278 animation->setStartValue(pieces_.at(i)->correctPlace());
279 animation->setEndValue(pieces_.at(i)->currentPlace());
280 animation->setDuration(750);
281 animation->setEasingCurve(QEasingCurve::InOutCirc);
282 animationGroup->addAnimation(animation);
284 animationGroup->start();
287 pieces_.at(hiddenIndex_)->hide();
292 QPointF GameView::emptyPlace()
297 void GameView::setEmptyPlace(const QPointF &place)
302 bool GameView::areAllPiecesOk()
304 for(int i = 0; i < pieces_.count(); ++i) {
306 if(i == hiddenIndex_) {
309 // Id piece is not in it's place
310 else if(pieces_.at(i)->correctPlace() != pieces_.at(i)->currentPlace()) {
314 // Show hidden piece and move it to it's place
315 pieces_.at(hiddenIndex_)->show();
316 pieces_.at(hiddenIndex_)->moveMeTo(emptyPlace_);
318 // Set all pieces not movable and hide numbers
319 for(int i = 0; i < pieces_.count(); ++i) {
320 pieces_.at(i)->setMovable(false);
321 pieces_.at(i)->setDrawNumber(false);
324 // Show dialog with move count
325 QMessageBox::about(const_cast<GameView *>(this), tr("You won"), QString("Puzzle completed with %1 moves").arg(PuzzleItem::moveCount()));
331 void GameView::setMovingPieces()
333 if(pieces_.isEmpty()) {
334 qDebug() << "Empty list @ GameView::setMovingPieces";
338 QPointF point = QPointF();
339 for(int i = 0; i < pieces_.count(); ++i) {
340 point = pieces_.at(i)->currentPlace();
342 // Is piece on the left side of the empty space
343 if(emptyPlace_.y() == point.y() && point.x() + horizontalStep_ == emptyPlace_.x()) {
344 pieces_.at(i)->setMovable(true);
347 // Is piece on the right side of the empty space
348 else if(emptyPlace_.y() == point.y() && point.x() - horizontalStep_ == emptyPlace_.x()) {
349 pieces_.at(i)->setMovable(true);
352 // Is piece below the empty space
353 else if(emptyPlace_.x() == point.x() && point.y() - verticalStep_ == emptyPlace_.y()) {
354 pieces_.at(i)->setMovable(true);
357 // Is piece on top of the empty space
358 else if(emptyPlace_.x() == point.x() && point.y() + verticalStep_ == emptyPlace_.y()) {
359 pieces_.at(i)->setMovable(true);
362 // The piece is somewhere else
364 pieces_.at(i)->setMovable(false);
369 bool GameView::restoreGame()
371 // Read settings from file
372 QFile file(QString("%1/%2/%3")
373 .arg(QDir::homePath())
377 if(!file.open(QIODevice::ReadOnly)) {
378 qDebug() << "Failed to open restore file for reading";
382 QTextStream in(&file);
386 list = in.readLine().split(";;");
388 qDebug() << "restore list count: " << list.count();
390 if(!list.isEmpty()) {
392 int pieces = list.at(0).toInt(&ok);
397 QString im = list.at(1);
398 if(!QFile::exists(im) && im != "default") {
402 int moveCount = list.at(2).toInt(&ok);
407 Settings::instance()->setPieceCount(pieces);
408 PuzzleItem::setMoveCount(moveCount);
410 if(im == "default" || im.isEmpty()) {
411 Settings::instance()->setImage(0);
412 Settings::instance()->setImagePath("default");
415 Settings::instance()->setImagePath(im);
416 Settings::instance()->setImage(QPixmap(im));
419 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()), false);
421 qDebug() << "pieces_ count after restoring image: " << pieces_.count();
423 if(list.count() >= pieces_.count() + 3) {
424 for(int j = 0; j < pieces_.count(); ++j) {
425 if(!list.at(j + 3).isNull()) {
426 QStringList points = list.at(j + 3).split("#");
428 int x = points.at(0).toInt(&ok);
433 int y = points.at(1).toInt(&ok);
440 //qDebug() << "Setting piece " << pieces_.at(j)->pieceNumber();
441 //qDebug() << "x: " << point.x() << " y: " << point.y();
443 pieces_.at(j)->setCurrentPlace(point);
456 QStringList hidden = list.last().split("#");
458 if(hidden.count() == 3) {
459 for(int m = 0; m < pieces_.count(); ++m) {
460 pieces_.at(m)->setPos(pieces_.at(m)->currentPlace());
461 if(pieces_.at(m)->pieceNumber() == hidden.at(2).toInt()) {
462 //qDebug() << "Hiding piece number " << hidden;
467 setEmptyPlace(QPointF(hidden.at(0).toInt(), hidden.at(1).toInt()));
469 pieces_.at(hiddenIndex_)->setVisible(false);
474 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()));
481 qDebug() << "Invalid restore file";
487 QFileInfo fileInfo(file);
489 QDateTime created = fileInfo.created();
490 QString infoTxt = QString("Restored game state from %1")
491 .arg(created.toString(Qt::TextDate));
494 QMaemo5InformationBox::information(this, infoTxt);
503 void GameView::saveGame()
505 if(pieces_.isEmpty() || pieces_.count() < EASY_PIECE_COUNT) {
510 if(!dir.exists(QString("%1/%2")
511 .arg(QDir::homePath())
512 .arg(HOME_DIRECTORY))) {
513 dir.mkpath(QString("%1/%2")
514 .arg(QDir::homePath())
515 .arg(HOME_DIRECTORY));
518 QFile file(QString("%1/%2/%3")
519 .arg(QDir::homePath())
523 if(!file.open(QIODevice::WriteOnly)) {
524 qDebug() << "Failed to open restore file for writing";
528 QTextStream out(&file);
530 out << Settings::instance()->pieceCount();
531 out << QString(";;");
532 if(Settings::instance()->imagePath().isEmpty()) {
533 out << QString("default");
536 out << Settings::instance()->imagePath();
538 out << QString(";;");
539 out << PuzzleItem::moveCount();
540 out << QString(";;");
546 while(number != pieces_.count()) {
547 for(int i = 0; i < pieces_.count(); ++i) {
548 if(pieces_.at(i)->pieceNumber() == number + 1) {
549 out << pieces_.at(i)->currentPlace().x();
551 out << pieces_.at(i)->currentPlace().y();
552 out << QString(";;");
553 pieces_.at(i)->pieceNumber();
554 if(!pieces_.at(i)->isVisible()) {
555 hiddenNo = number + 1;
563 out << QString("%1#%2#%3").arg(emptyPlace().x()).arg(emptyPlace().y()).arg(hiddenNo);
572 void GameView::closeEvent(QCloseEvent *event)
574 int answer = QMessageBox::question(this, tr("Save game status?"),
575 tr("Saved status will be automatically loaded when you start the application next time"),
576 QMessageBox::Yes, QMessageBox::No);
578 if(answer == QMessageBox::Yes) {
585 int GameView::correctPlaces() const
589 for(int i = 0; i < pieces_.count(); ++i) {
590 if(pieces_.at(i)->currentPlace() == pieces_.at(i)->correctPlace()) {
598 QList<int> GameView::movingPlaces() const
602 for(int i = 0; i < pieces_.count(); ++i) {
603 if(pieces_.at(i)->movable()) {