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()));
153 void GameView::shufflePieces()
155 if(pieces_.isEmpty()) {
156 qDebug() << "Empty list @ GameView::shufflePieces";
160 // Give pieces ramdom locations
161 hiddenIndex_ = qrand() % pieces_.count();
162 emptyPlace_ = pieces_.at(hiddenIndex_)->currentPlace();
164 QPointF topLeft = pieces_.at(0)->correctPlace();
165 QPointF bottomRight = pieces_.last()->correctPlace();
167 for(int i = 0; i < pieces_.count() * 10; ++i) {
168 int rand = qrand() % 4;
173 if(pieces_.at(hiddenIndex_)->currentPlace().y() > topLeft.y()) {
174 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
175 PuzzleItem *item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(0, -verticalStep_)));
176 emptyPlace_ = item->currentPlace();
177 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
178 item->setCurrentPlace(tmp);
183 if(pieces_.at(hiddenIndex_)->currentPlace().y() < bottomRight.y()) {
184 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
185 PuzzleItem *item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(0, verticalStep_)));
186 emptyPlace_ = item->currentPlace();
187 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
188 item->setCurrentPlace(tmp);
193 if(pieces_.at(hiddenIndex_)->currentPlace().x() > topLeft.x()) {
194 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
195 PuzzleItem *item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(-horizontalStep_, 0)));
196 emptyPlace_ = item->currentPlace();
197 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
198 item->setCurrentPlace(tmp);
203 if(pieces_.at(hiddenIndex_)->currentPlace().x() < bottomRight.x()) {
204 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
205 PuzzleItem *item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(horizontalStep_, 0)));
206 emptyPlace_ = item->currentPlace();
207 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
208 item->setCurrentPlace(tmp);
217 QParallelAnimationGroup *animationGroup = new QParallelAnimationGroup(this);
218 for(int i = 0; i < pieces_.count(); ++i) {
219 QPropertyAnimation *animation = new QPropertyAnimation(pieces_.at(i), "pos");
220 animation->setStartValue(pieces_.at(i)->correctPlace());
221 animation->setEndValue(pieces_.at(i)->currentPlace());
222 animation->setDuration(750);
223 animation->setEasingCurve(QEasingCurve::InOutCirc);
224 animationGroup->addAnimation(animation);
226 animationGroup->start();
229 pieces_.at(hiddenIndex_)->hide();
234 QPointF GameView::emptyPlace()
239 void GameView::setEmptyPlace(const QPointF &place)
244 bool GameView::areAllPiecesOk()
246 for(int i = 0; i < pieces_.count(); ++i) {
248 if(i == hiddenIndex_) {
251 // Id piece is not in it's place
252 else if(pieces_.at(i)->correctPlace() != pieces_.at(i)->currentPlace()) {
256 // Show hidden piece and move it to it's place
257 pieces_.at(hiddenIndex_)->show();
258 pieces_.at(hiddenIndex_)->moveMeTo(emptyPlace_);
260 // Set all pieces not movable and hide numbers
261 for(int i = 0; i < pieces_.count(); ++i) {
262 pieces_.at(i)->setMovable(false);
263 pieces_.at(i)->setDrawNumber(false);
266 // Show dialog with move count
267 QMessageBox::about(const_cast<GameView *>(this), tr("You won"), QString("Puzzle completed with %1 moves").arg(PuzzleItem::moveCount()));
273 void GameView::setMovingPieces()
275 if(pieces_.isEmpty()) {
276 qDebug() << "Empty list @ GameView::setMovingPieces";
280 QPointF point = QPointF();
281 for(int i = 0; i < pieces_.count(); ++i) {
282 point = pieces_.at(i)->currentPlace();
284 // Is piece on the left side of the empty space
285 if(emptyPlace_.y() == point.y() && point.x() + horizontalStep_ == emptyPlace_.x()) {
286 pieces_.at(i)->setMovable(true);
289 // Is piece on the right side of the empty space
290 else if(emptyPlace_.y() == point.y() && point.x() - horizontalStep_ == emptyPlace_.x()) {
291 pieces_.at(i)->setMovable(true);
294 // Is piece below the empty space
295 else if(emptyPlace_.x() == point.x() && point.y() - verticalStep_ == emptyPlace_.y()) {
296 pieces_.at(i)->setMovable(true);
299 // Is piece on top of the empty space
300 else if(emptyPlace_.x() == point.x() && point.y() + verticalStep_ == emptyPlace_.y()) {
301 pieces_.at(i)->setMovable(true);
304 // The piece is somewhere else
306 pieces_.at(i)->setMovable(false);
311 bool GameView::restoreGame()
313 // Read settings from file
314 QFile file(QString("%1/%2/%3")
315 .arg(QDir::homePath())
319 if(!file.open(QIODevice::ReadOnly)) {
320 qDebug() << "Failed to open restore file for reading";
324 QTextStream in(&file);
328 list = in.readLine().split(";;");
330 qDebug() << "restore list count: " << list.count();
332 if(!list.isEmpty()) {
334 int pieces = list.at(0).toInt(&ok);
339 QString im = list.at(1);
340 if(!QFile::exists(im) && im != "default") {
344 int moveCount = list.at(2).toInt(&ok);
349 Settings::instance()->setPieceCount(pieces);
350 PuzzleItem::setMoveCount(moveCount);
352 if(im == "default" || im.isEmpty()) {
353 Settings::instance()->setImage(0);
354 Settings::instance()->setImagePath("default");
357 Settings::instance()->setImagePath(im);
358 Settings::instance()->setImage(QPixmap(im));
361 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()), false);
363 qDebug() << "pieces_ count after restoring image: " << pieces_.count();
365 if(list.count() >= pieces_.count() + 3) {
366 for(int j = 0; j < pieces_.count(); ++j) {
367 if(!list.at(j + 3).isNull()) {
368 QStringList points = list.at(j + 3).split("#");
370 int x = points.at(0).toInt(&ok);
375 int y = points.at(1).toInt(&ok);
382 //qDebug() << "Setting piece " << pieces_.at(j)->pieceNumber();
383 //qDebug() << "x: " << point.x() << " y: " << point.y();
385 pieces_.at(j)->setCurrentPlace(point);
398 QStringList hidden = list.last().split("#");
400 if(hidden.count() == 3) {
401 for(int m = 0; m < pieces_.count(); ++m) {
402 pieces_.at(m)->setPos(pieces_.at(m)->currentPlace());
403 if(pieces_.at(m)->pieceNumber() == hidden.at(2).toInt()) {
404 //qDebug() << "Hiding piece number " << hidden;
409 setEmptyPlace(QPointF(hidden.at(0).toInt(), hidden.at(1).toInt()));
411 pieces_.at(hiddenIndex_)->setVisible(false);
416 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()));
423 qDebug() << "Invalid restore file";
429 QFileInfo fileInfo(file);
431 QDateTime created = fileInfo.created();
432 QString infoTxt = QString("Restored game state from %1")
433 .arg(created.toString(Qt::TextDate));
436 QMaemo5InformationBox::information(this, infoTxt);
445 void GameView::saveGame()
447 if(pieces_.isEmpty() || pieces_.count() < EASY_PIECE_COUNT) {
452 if(!dir.exists(QString("%1/%2")
453 .arg(QDir::homePath())
454 .arg(HOME_DIRECTORY))) {
455 dir.mkpath(QString("%1/%2")
456 .arg(QDir::homePath())
457 .arg(HOME_DIRECTORY));
460 QFile file(QString("%1/%2/%3")
461 .arg(QDir::homePath())
465 if(!file.open(QIODevice::WriteOnly)) {
466 qDebug() << "Failed to open restore file for writing";
470 QTextStream out(&file);
472 out << Settings::instance()->pieceCount();
473 out << QString(";;");
474 if(Settings::instance()->imagePath().isEmpty()) {
475 out << QString("default");
478 out << Settings::instance()->imagePath();
480 out << QString(";;");
481 out << PuzzleItem::moveCount();
482 out << QString(";;");
488 while(number != pieces_.count()) {
489 for(int i = 0; i < pieces_.count(); ++i) {
490 if(pieces_.at(i)->pieceNumber() == number + 1) {
491 out << pieces_.at(i)->currentPlace().x();
493 out << pieces_.at(i)->currentPlace().y();
494 out << QString(";;");
495 pieces_.at(i)->pieceNumber();
496 if(!pieces_.at(i)->isVisible()) {
497 hiddenNo = number + 1;
505 out << QString("%1#%2#%3").arg(emptyPlace().x()).arg(emptyPlace().y()).arg(hiddenNo);
514 void GameView::closeEvent(QCloseEvent *event)
516 int answer = QMessageBox::question(this, tr("Save game status?"),
517 tr("Saved status will be automatically loaded when you start the application next time"),
518 QMessageBox::Yes, QMessageBox::No);
520 if(answer == QMessageBox::Yes) {
527 int GameView::correctPlaces() const
531 for(int i = 0; i < pieces_.count(); ++i) {
532 if(pieces_.at(i)->currentPlace() == pieces_.at(i)->correctPlace()) {
540 QList<int> GameView::movingPlaces() const
544 for(int i = 0; i < pieces_.count(); ++i) {
545 if(pieces_.at(i)->movable()) {