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 qsrand(QDateTime::currentDateTime().toTime_t());
55 scene_ = new QGraphicsScene;
59 introItem_ = new IntroItem;
60 introItem_->setText("Select new game from menu to play");
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 if(pieces.isEmpty()) {
99 qDebug() << "Empty list @ GameView::setPieces";
103 QList<QGraphicsItem *> previousItems = scene_->items();
104 if(!previousItems.isEmpty()) {
105 foreach(QGraphicsItem *item, previousItems) {
106 scene_->removeItem(item);
112 int horizontalCount = 0;
114 // Find out board size
115 if(pieces_.count() == EASY_PIECE_COUNT) {
116 horizontalCount = EASY_HORIZONTAL_COUNT;
118 else if(pieces_.count() == HARD_PIECE_COUNT) {
119 horizontalCount = HARD_HORIZONTAL_COUNT;
122 qDebug() << "Invalid piece count @ GameView::setPieces";
123 qDebug() << QString("Count was %1").arg(pieces_.count());
127 int verticalCount = pieces_.count() / horizontalCount;
128 horizontalStep_ = IMAGE_WIDTH / horizontalCount + 5;
129 verticalStep_ = IMAGE_HEIGHT / verticalCount + 5;
133 // Set pieces to their correct positions
134 for(int i = 0; i < verticalCount; ++i) {
135 for(int j = 0; j < horizontalCount; ++j) {
136 scene_->addItem(pieces_.at(pieceNumber));
137 QPointF point(j * horizontalStep_, i * verticalStep_);
138 pieces_.at(pieceNumber)->setPos(point);
139 pieces_.at(pieceNumber)->setCorrectPlace(point);
140 pieces_.at(pieceNumber)->setCurrentPlace(point);
141 pieces_.at(pieceNumber)->setDrawNumber(true);
146 // Wait and shuffle if desired
148 QTimer::singleShot(750, this, SLOT(shufflePieces()));
152 void GameView::shufflePieces()
154 if(pieces_.isEmpty()) {
155 qDebug() << "Empty list @ GameView::shufflePieces";
159 // Give pieces ramdom locations
160 hiddenIndex_ = qrand() % pieces_.count();
161 emptyPlace_ = pieces_.at(hiddenIndex_)->currentPlace();
163 QPointF topLeft = pieces_.at(0)->correctPlace();
164 QPointF bottomRight = pieces_.last()->correctPlace();
166 for(int i = 0; i < pieces_.count() * 10; ++i) {
167 int rand = qrand() % 4;
172 if(pieces_.at(hiddenIndex_)->currentPlace().y() > topLeft.y()) {
173 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
174 PuzzleItem *item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(0, -verticalStep_)));
175 emptyPlace_ = item->currentPlace();
176 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
177 item->setCurrentPlace(tmp);
182 if(pieces_.at(hiddenIndex_)->currentPlace().y() < bottomRight.y()) {
183 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
184 PuzzleItem *item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(0, verticalStep_)));
185 emptyPlace_ = item->currentPlace();
186 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
187 item->setCurrentPlace(tmp);
192 if(pieces_.at(hiddenIndex_)->currentPlace().x() > topLeft.x()) {
193 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
194 PuzzleItem *item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(-horizontalStep_, 0)));
195 emptyPlace_ = item->currentPlace();
196 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
197 item->setCurrentPlace(tmp);
202 if(pieces_.at(hiddenIndex_)->currentPlace().x() < bottomRight.x()) {
203 QPointF tmp = pieces_.at(hiddenIndex_)->currentPlace();
204 PuzzleItem *item = dynamic_cast<PuzzleItem *>(scene()->itemAt(tmp + QPointF(horizontalStep_, 0)));
205 emptyPlace_ = item->currentPlace();
206 pieces_.at(hiddenIndex_)->setCurrentPlace(item->currentPlace());
207 item->setCurrentPlace(tmp);
216 QParallelAnimationGroup *animationGroup = new QParallelAnimationGroup(this);
217 for(int i = 0; i < pieces_.count(); ++i) {
218 QPropertyAnimation *animation = new QPropertyAnimation(pieces_.at(i), "pos");
219 animation->setStartValue(pieces_.at(i)->correctPlace());
220 animation->setEndValue(pieces_.at(i)->currentPlace());
221 animation->setDuration(750);
222 animation->setEasingCurve(QEasingCurve::InOutCirc);
223 animationGroup->addAnimation(animation);
225 animationGroup->start();
228 pieces_.at(hiddenIndex_)->hide();
233 QPointF GameView::emptyPlace()
238 void GameView::setEmptyPlace(const QPointF &place)
243 bool GameView::areAllPiecesOk()
245 for(int i = 0; i < pieces_.count(); ++i) {
247 if(i == hiddenIndex_) {
250 // Id piece is not in it's place
251 else if(pieces_.at(i)->correctPlace() != pieces_.at(i)->currentPlace()) {
255 // Show hidden piece and move it to it's place
256 pieces_.at(hiddenIndex_)->show();
257 pieces_.at(hiddenIndex_)->moveMeTo(emptyPlace_);
259 // Set all pieces not movable and hide numbers
260 for(int i = 0; i < pieces_.count(); ++i) {
261 pieces_.at(i)->setMovable(false);
262 pieces_.at(i)->setDrawNumber(false);
265 // Show dialog with move count
266 QMessageBox::about(const_cast<GameView *>(this), tr("You won"), QString("Puzzle completed with %1 moves").arg(PuzzleItem::moveCount()));
272 void GameView::setMovingPieces()
274 if(pieces_.isEmpty()) {
275 qDebug() << "Empty list @ GameView::setMovingPieces";
279 QPointF point = QPointF();
280 for(int i = 0; i < pieces_.count(); ++i) {
281 point = pieces_.at(i)->currentPlace();
283 // Is piece on the left side of the empty space
284 if(emptyPlace_.y() == point.y() && point.x() + horizontalStep_ == emptyPlace_.x()) {
285 pieces_.at(i)->setMovable(true);
288 // Is piece on the right side of the empty space
289 else if(emptyPlace_.y() == point.y() && point.x() - horizontalStep_ == emptyPlace_.x()) {
290 pieces_.at(i)->setMovable(true);
293 // Is piece below the empty space
294 else if(emptyPlace_.x() == point.x() && point.y() - verticalStep_ == emptyPlace_.y()) {
295 pieces_.at(i)->setMovable(true);
298 // Is piece on top of the empty space
299 else if(emptyPlace_.x() == point.x() && point.y() + verticalStep_ == emptyPlace_.y()) {
300 pieces_.at(i)->setMovable(true);
303 // The piece is somewhere else
305 pieces_.at(i)->setMovable(false);
310 bool GameView::restoreGame()
312 // Read settings from file
313 QFile file(QString("%1/%2/%3")
314 .arg(QDir::homePath())
318 if(!file.open(QIODevice::ReadOnly)) {
319 qDebug() << "Failed to open restore file for reading";
323 QTextStream in(&file);
327 list = in.readLine().split(";;");
329 qDebug() << "restore list count: " << list.count();
331 if(!list.isEmpty()) {
333 int pieces = list.at(0).toInt(&ok);
338 QString im = list.at(1);
339 if(!QFile::exists(im) && im != "default") {
343 int moveCount = list.at(2).toInt(&ok);
348 Settings::instance()->setPieceCount(pieces);
349 PuzzleItem::setMoveCount(moveCount);
351 if(im == "default" || im.isEmpty()) {
352 Settings::instance()->setImage(0);
353 Settings::instance()->setImagePath("default");
356 Settings::instance()->setImagePath(im);
357 Settings::instance()->setImage(QPixmap(im));
360 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()), false);
362 qDebug() << "pieces_ count after restoring image: " << pieces_.count();
364 if(list.count() >= pieces_.count() + 3) {
365 for(int j = 0; j < pieces_.count(); ++j) {
366 if(!list.at(j + 3).isNull()) {
367 QStringList points = list.at(j + 3).split("#");
369 int x = points.at(0).toInt(&ok);
374 int y = points.at(1).toInt(&ok);
381 //qDebug() << "Setting piece " << pieces_.at(j)->pieceNumber();
382 //qDebug() << "x: " << point.x() << " y: " << point.y();
384 pieces_.at(j)->setCurrentPlace(point);
397 QStringList hidden = list.last().split("#");
399 if(hidden.count() == 3) {
400 for(int m = 0; m < pieces_.count(); ++m) {
401 pieces_.at(m)->setPos(pieces_.at(m)->currentPlace());
402 if(pieces_.at(m)->pieceNumber() == hidden.at(2).toInt()) {
403 //qDebug() << "Hiding piece number " << hidden;
408 setEmptyPlace(QPointF(hidden.at(0).toInt(), hidden.at(1).toInt()));
410 pieces_.at(hiddenIndex_)->setVisible(false);
415 setPieces(ImageImporter::instance()->newPieces(Settings::instance()->image(), Settings::instance()->pieceCount()));
422 qDebug() << "Invalid restore file";
428 QFileInfo fileInfo(file);
430 QDateTime created = fileInfo.created();
431 QString infoTxt = QString("Restored game state from %1")
432 .arg(created.toString(Qt::TextDate));
435 QMaemo5InformationBox::information(this, infoTxt);
444 void GameView::saveGame()
446 if(pieces_.isEmpty() || pieces_.count() < EASY_PIECE_COUNT) {
451 if(!dir.exists(QString("%1/%2")
452 .arg(QDir::homePath())
453 .arg(HOME_DIRECTORY))) {
454 dir.mkpath(QString("%1/%2")
455 .arg(QDir::homePath())
456 .arg(HOME_DIRECTORY));
459 QFile file(QString("%1/%2/%3")
460 .arg(QDir::homePath())
464 if(!file.open(QIODevice::WriteOnly)) {
465 qDebug() << "Failed to open restore file for writing";
469 QTextStream out(&file);
471 out << Settings::instance()->pieceCount();
472 out << QString(";;");
473 if(Settings::instance()->imagePath().isEmpty()) {
474 out << QString("default");
477 out << Settings::instance()->imagePath();
479 out << QString(";;");
480 out << PuzzleItem::moveCount();
481 out << QString(";;");
487 while(number != pieces_.count()) {
488 for(int i = 0; i < pieces_.count(); ++i) {
489 if(pieces_.at(i)->pieceNumber() == number + 1) {
490 out << pieces_.at(i)->currentPlace().x();
492 out << pieces_.at(i)->currentPlace().y();
493 out << QString(";;");
494 pieces_.at(i)->pieceNumber();
495 if(!pieces_.at(i)->isVisible()) {
496 hiddenNo = number + 1;
504 out << QString("%1#%2#%3").arg(emptyPlace().x()).arg(emptyPlace().y()).arg(hiddenNo);
513 void GameView::closeEvent(QCloseEvent *event)
515 int answer = QMessageBox::question(this, tr("Save game status?"),
516 tr("Saved status will be automatically loaded when you start the application next time"),
517 QMessageBox::Yes, QMessageBox::No);
519 if(answer == QMessageBox::Yes) {
526 int GameView::correctPlaces() const
530 for(int i = 0; i < pieces_.count(); ++i) {
531 if(pieces_.at(i)->currentPlace() == pieces_.at(i)->correctPlace()) {
539 QList<int> GameView::movingPlaces() const
543 for(int i = 0; i < pieces_.count(); ++i) {
544 if(pieces_.at(i)->movable()) {