907292c190ea2eabc3ca0d3150a70e6df61de6b8
[evilplumber] / src / game.cpp
1 /* Evil Plumber is a small puzzle game.
2    Copyright (C) 2010 Marja Hassinen
3
4    This program is free software: you can redistribute it and/or modify
5    it under the terms of the GNU General Public License as published by
6    the Free Software Foundation, either version 3 of the License, or
7    (at your option) any later version.
8
9    This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13
14    You should have received a copy of the GNU General Public License
15    along with this program.  If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 #include "game.h"
19
20 #include <QTableWidget>
21 #include <QListWidget>
22 #include <QLabel>
23 #include <QPushButton>
24 #include <QApplication>
25 #include <QFile>
26 #include <QDir>
27 #include <QDebug>
28
29 const Piece* findPiece(PieceType type, int rotation)
30 {
31     static QHash<QPair<PieceType, int>, const Piece*> pieceCache;
32
33     // Fill the cache on the first run
34     if (pieceCache.size() == 0) {
35         for (int i = 0; ppieces[i].type != PiecesEnd; ++i)
36             pieceCache.insert(QPair<PieceType, int>(ppieces[i].type, ppieces[i].rotation), &ppieces[i]);
37     }
38     QPair<PieceType, int> key(type, rotation);
39     if (pieceCache.contains(key))
40         return pieceCache[key];
41     return 0;
42        
43 }
44
45 QString pieceToIconId(const Piece* piece, bool flow1 = false, bool flow2 = false)
46 {
47     QString fileName = QString(IMGDIR) + "/" + QString::number(piece->type) + "_" + QString::number(piece->rotation);
48     if (flow1 || flow2) {
49         fileName += (QString("_flow_") + (flow1? "1" : "0") + (flow2? "1" : "0"));
50     }
51
52
53     qDebug() << "need: " << fileName;
54     return fileName + ".png";
55 }
56
57 int flowCount(const Piece* piece)
58 {
59     // How many times the liquid can flow through this pre-placed
60     // pipe.
61     int flowCount = 0;
62     for (int i = 0; i < 4; ++i) {
63         if (piece->flows[i] != DirNone) {
64             ++flowCount;
65         }
66     }
67     return flowCount / 2;
68 }
69
70 Direction flowsTo(const Piece* piece, Direction flowFrom)
71 {
72     //qDebug() << piece->flows[0];
73     //qDebug() << piece->flows[1];
74     //qDebug() << piece->flows[2];
75     //qDebug() << piece->flows[3];
76     //qDebug() << "check" << flowFrom;
77     if (piece->flows[0] == flowFrom)
78         return piece->flows[1];
79     if (piece->flows[1] == flowFrom)
80         return piece->flows[0];
81     if (piece->flows[2] == flowFrom)
82         return piece->flows[3];
83     if (piece->flows[3] == flowFrom)
84         return piece->flows[2];
85     return DirNone;
86 }
87
88 GameField::GameField(QTableWidget* ui)
89 : fieldUi(ui), field(0), rows(0), cols(0)
90 {
91     connect(fieldUi, SIGNAL(cellClicked(int, int)), this, SIGNAL(cellClicked(int, int)));
92     fieldUi->setContentsMargins(0, 0, 0, 0);
93 }
94
95 void GameField::initGame(int rows_, int cols_, int count, PrePlacedPiece* prePlaced)
96 {
97     fieldUi->clear();
98
99     rows = rows_;
100     cols = cols_;
101
102     delete[] field;
103     field = new PlacedPiece[rows*cols];
104
105     for (int i = 0; i < rows*cols; ++i) {
106         field[i].piece = ppieces;
107         field[i].fixed = false;
108         field[i].flow[0] = false;
109         field[i].flow[1] = false;
110     }
111
112     // Setup ui  
113     fieldUi->setRowCount(rows);
114     fieldUi->setColumnCount(cols);
115
116     for (int i = 0; i < rows; ++i)
117         fieldUi->setRowHeight(i, 72);
118
119     for (int c = 0; c < cols; ++c) {
120         fieldUi->setColumnWidth(c, 72);
121         for (int r = 0; r < rows; ++r) {
122             QModelIndex index = fieldUi->model()->index(r, c);
123             fieldUi->setIndexWidget(index, new QLabel(""));
124         }
125     }
126
127     // Set pre-placed pieces
128     for (int i = 0; i < count; ++i) {
129         setPiece(prePlaced[i].row, prePlaced[i].col, prePlaced[i].piece, true);
130     }
131 }
132
133 int GameField::toIndex(int row, int col)
134 {
135     return row * cols + col;
136 }
137
138 bool GameField::setPiece(int row, int col, const Piece* piece, bool fixed)
139 {
140     qDebug() << "set piece" << row << col;
141
142     if (row < 0 || row >= rows || col < 0 || col >= cols) {
143         qWarning() << "Invalid piece index";
144         return false;
145     }
146
147     int index = toIndex(row, col);
148     if (field[index].piece->type == PieceNone) {
149         qDebug() << "really setting";
150         field[index].piece = piece;
151         field[index].fixed = fixed;
152
153         QString iconId = pieceToIconId(piece);
154         QModelIndex index = fieldUi->model()->index(row, col);
155         QLabel* label = (QLabel*)fieldUi->indexWidget(index);
156         label->setPixmap(QPixmap(iconId));
157
158         return true;
159     }
160     return false;
161 }
162
163 const Piece* GameField::pieceAt(int row, int col)
164 {
165     if (row < 0 || row >= rows || col < 0 || col >= cols) {
166         qWarning() << "Invalid piece index";
167         return ppieces;
168     }
169
170     int index = toIndex(row, col);
171     return field[index].piece;
172 }
173
174 bool GameField::isPrePlaced(int row, int col)
175 {
176     if (row < 0 || row >= rows || col < 0 || col >= cols) {
177         qWarning() << "Invalid piece index";
178         return false;
179     }
180
181     int index = toIndex(row, col);
182     return field[index].fixed;
183 }
184
185 void GameField::indicateFlow(int row, int col, Direction dir)
186 {
187     // Indicate the flow: fill the piece in question with the
188     // liquid. (The piece can also be an empty one, or an illegal
189     // one.)
190     qDebug() << "ind flow" << row << col << dir;
191     if (row < 0 || col < 0 || row >= rows || col >= cols) {
192         return;
193     }
194     if (dir == DirDone || dir == DirFailed || dir == DirPassed) {
195         // No need to indicate these pseudo-directions
196         return;
197     }
198
199     int index = toIndex(row, col);
200     if (dir != DirNone && (field[index].piece->flows[0] == dir || field[index].piece->flows[1] == dir)) {
201         field[index].flow[0] = true;
202     }
203     else if (dir != DirNone && (field[index].piece->flows[2] == dir || field[index].piece->flows[3] == dir)) {
204         field[index].flow[1] = true;
205     }
206     else if (dir == DirNone) {
207         // Flowing to a pipe from a wrong direction -> A hack to get
208         // the correct icon (same as an empty square flooded)
209         field[index].piece = ppieces;
210         field[index].flow[0] = true;
211         field[index].flow[1] = false;
212     }
213     else {
214         qWarning() << "Indicate flow: illegal direction" << row << col << dir;
215         return;
216     }
217
218     QString iconId = pieceToIconId(field[index].piece, field[index].flow[0], field[index].flow[1]);
219     qDebug() << "icon id" << iconId;
220     QModelIndex mIndex = fieldUi->model()->index(row, col);
221     QLabel* label = (QLabel*)fieldUi->indexWidget(mIndex);
222
223     label->setPixmap(QPixmap(iconId));
224 }
225
226 AvailablePieces::AvailablePieces(QTableWidget* ui)
227   : pieceUi(ui)
228 {
229     connect(pieceUi, SIGNAL(itemClicked(QTableWidgetItem*)), this, SLOT(onItemClicked(QTableWidgetItem*)));
230
231     // Setup ui
232
233     qDebug() << pieceUi->rowCount() << pieceUi->columnCount();
234
235     for (int i = 0; i < 2; ++i)
236         pieceUi->setColumnWidth(i, 120);
237
238     for (int i = 0; i < 4; ++i)
239         pieceUi->setRowHeight(i, 70);
240
241     for (int i = 0; ppieces[i].type != PiecesEnd; ++i) {
242         if (ppieces[i].userCanAdd == false) continue;
243
244         //qDebug() << ppieces[i].type << ppieces[i].rotation;
245         QString fileName = pieceToIconId(&(ppieces[i]));
246
247         QTableWidgetItem* item = new QTableWidgetItem(QIcon(fileName), "0", QTableWidgetItem::UserType + pieceToId(&(ppieces[i])));
248
249         pieceUi->setItem(ppieces[i].uiRow, ppieces[i].uiColumn, item);
250     }
251 }
252
253 int AvailablePieces::pieceToId(const Piece* piece)
254 {
255     return piece->type * 4 + piece->rotation/90;
256 }
257
258 const Piece* AvailablePieces::idToPiece(int id)
259 {
260     int rotation = (id % 4)*90;
261     PieceType type = (PieceType)(id / 4);
262     return findPiece(type, rotation);
263 }
264
265 void AvailablePieces::initGame(int count, AvailablePiece* pieces)
266 {
267     for (int i = 0; ppieces[i].type != PiecesEnd; ++i) {
268         if (ppieces[i].userCanAdd == false) continue;
269         pieceCounts.insert(&ppieces[i], 0);
270         pieceUi->item(ppieces[i].uiRow, ppieces[i].uiColumn)->setText(QString::number(0));
271     }
272
273     for (int i = 0; i < count; ++i) {
274         pieceCounts.insert(pieces[i].piece, pieces[i].count);
275         pieceUi->item(pieces[i].piece->uiRow, pieces[i].piece->uiColumn)->setText(QString::number(pieces[i].count));
276     }
277     pieceUi->clearSelection();
278 }
279
280 void AvailablePieces::onItemClicked(QTableWidgetItem* item)
281 {
282     qDebug() << "piece clicked";
283     int id =  item->type() - QTableWidgetItem::UserType;
284
285     const Piece* piece = idToPiece(id);
286     if (piece->type != PieceNone && pieceCounts[piece] > 0) {
287          emit validPieceSelected(piece);
288     }
289     else
290         emit invalidPieceSelected();
291 }
292
293 void AvailablePieces::onPieceUsed(const Piece* piece)
294 {
295     pieceCounts[piece]--;
296     pieceUi->item(piece->uiRow, piece->uiColumn)->setText(QString::number(pieceCounts[piece]));
297
298     // TODO: perhaps clear the selection
299     if (pieceCounts[piece] == 0)
300         emit invalidPieceSelected();
301 }
302
303 GameController::GameController(AvailablePieces* pieceUi, GameField* fieldUi, 
304                                QLabel* timeLabel, QPushButton* doneButton)
305     : pieceUi(pieceUi), fieldUi(fieldUi), 
306       timeLabel(timeLabel), doneButton(doneButton),
307       currentPiece(ppieces), rows(0), cols(0), timeLeft(0), levelRunning(false), neededFlow(0),
308       startRow(0), startCol(0), startDir(DirNone), flowRow(0), flowCol(0), flowDir(DirNone), flowPreplaced(0), flowScore(0)
309 {
310     connect(fieldUi, SIGNAL(cellClicked(int, int)), this, SLOT(onCellClicked(int, int)));
311     connect(pieceUi, SIGNAL(invalidPieceSelected()), 
312             this, SLOT(onInvalidPieceSelected()));
313     connect(pieceUi, SIGNAL(validPieceSelected(const Piece*)), 
314             this, SLOT(onValidPieceSelected(const Piece*)));
315
316     connect(this, SIGNAL(pieceUsed(const Piece*)), pieceUi, SLOT(onPieceUsed(const Piece*)));
317
318     connect(doneButton, SIGNAL(clicked()), this, SLOT(levelEnds()));
319
320     // Setup the timer, but don't start it yet
321     timer.setInterval(1000);
322     timer.setSingleShot(false);
323     connect(&timer, SIGNAL(timeout()), this, SLOT(onTimeout()));
324     timeLabel->setText("");
325
326     flowTimer.setInterval(500);
327     flowTimer.setSingleShot(false);
328     connect(&flowTimer, SIGNAL(timeout()), this, SLOT(computeFlow()));
329 }
330
331 void GameController::startLevel(QString fileName)
332 {
333     // TODO: read the data while the user is reading the
334     // instructions...
335
336     // Read data about pre-placed pieces and available pieces from a
337     // text file.
338     QFile file(fileName);
339     if (!file.exists())
340         qFatal("Error reading game file: doesn't exist");
341
342     file.open(QIODevice::ReadOnly);
343     QTextStream gameData(&file);
344
345     gameData >> rows;
346     gameData >> cols;
347     qDebug() << rows << cols;
348     if (rows < 2 || rows > 10 || cols < 2 || cols > 10)
349         qFatal("Error reading game file: rows and cols");
350
351     neededFlow = 0;
352     int prePlacedCount = 0;
353     gameData >> prePlacedCount;
354     if (prePlacedCount < 2 || prePlacedCount > 100)
355         qFatal("Error reading game file: piece count");
356
357     PrePlacedPiece* prePlaced = new PrePlacedPiece[prePlacedCount];
358     for (int i = 0; i < prePlacedCount; ++i) {
359         int type = 0;
360         gameData >> type;
361         if (type < 0 || type >= PiecesEnd)
362             qFatal("Error reading game file: type of pre-placed piece");
363
364         int rotation = 0;
365         gameData >> rotation;
366         if (rotation != 0 && rotation != 90 && rotation != 180 && rotation != 270)
367             qFatal("Error reading game file: rotation of pre-placed piece");
368
369         prePlaced[i].piece = findPiece((PieceType)type, rotation);
370         if (!prePlaced[i].piece)
371             qFatal("Error reading game file: invalid pre-placed piece");
372
373         // Record that the liquid must flow through this pre-placed
374         // piece (if it can)
375         neededFlow += flowCount(prePlaced[i].piece);
376
377         gameData >> prePlaced[i].row;
378         gameData >> prePlaced[i].col;
379         if (prePlaced[i].row < 0 || prePlaced[i].row >= rows || 
380             prePlaced[i].col < 0 || prePlaced[i].col >= cols)
381             qFatal("Error reading game file: piece position");
382
383         if (prePlaced[i].piece->type == PieceStart) {
384             startRow = prePlaced[i].row;
385             startCol = prePlaced[i].col;
386             startDir = prePlaced[i].piece->flows[0];
387         }
388     }
389     fieldUi->initGame(rows, cols, prePlacedCount, prePlaced);
390     delete[] prePlaced;
391
392     int availableCount = 0;
393     gameData >> availableCount;
394     if (availableCount < 2 || availableCount >= noPieces)
395         qFatal("Error reading game file: no of pieeces");
396
397     AvailablePiece* availablePieces = new AvailablePiece[availableCount];
398     for (int i = 0; i < availableCount; ++i) {
399         int ix = 0;
400         gameData >> ix;
401         if (ix < 0 || ix >= noPieces)
402             qFatal("Error reading game file: piece index");
403         availablePieces[i].piece = &ppieces[ix];
404         gameData >> availablePieces[i].count;
405         if (availablePieces[i].count < 0 || availablePieces[i].count > 100)
406             qFatal("Error reading game file: piece count");
407     }
408     pieceUi->initGame(availableCount, availablePieces);
409     delete[] availablePieces;
410
411     gameData >> timeLeft;
412     if (timeLeft < 0) 
413         qFatal("Error reading game file: time left");
414     timeLabel->setText(QString::number(timeLeft));
415
416     // Clear piece selection
417     onInvalidPieceSelected();
418
419     doneButton->setEnabled(true);
420     timer.start();
421     levelRunning = true;
422     file.close();
423 }
424
425 void GameController::onTimeout()
426 {
427     --timeLeft;
428     timeLabel->setText(QString::number(timeLeft));
429     if (timeLeft <= 0) {
430         timer.stop();
431         levelEnds();
432     }
433 }
434
435 void GameController::onCellClicked(int row, int column)
436 {
437   qDebug() << "clicked: " << row << column;
438   if (!levelRunning) return;
439   if (currentPiece->type == PieceNone) return;
440   if (fieldUi->setPiece(row, column, currentPiece))
441       emit pieceUsed(currentPiece);
442 }
443
444 void GameController::onValidPieceSelected(const Piece* piece)
445 {
446     qDebug() << "selected: " << piece->type << piece->rotation;
447     currentPiece = piece;
448 }
449
450 void GameController::onInvalidPieceSelected()
451 {
452     currentPiece = ppieces;
453 }
454
455 void GameController::levelEnds()
456 {
457     if (!levelRunning) return;
458
459     doneButton->setEnabled(false);
460     levelRunning = false;
461     timer.stop();
462
463     // Initiate computing the flow
464     flowRow = startRow;
465     flowCol = startCol;
466     flowDir = startDir;
467     flowPreplaced = 0;
468     flowScore = 0;
469     flowTimer.setInterval(500);
470     flowTimer.start();
471 }
472
473 void GameController::computeFlow()
474 {
475     // We know:
476     // Where the flow currently is
477     // and which direction the flow goes after that piece
478     fieldUi->indicateFlow(flowRow, flowCol, flowDir);
479
480     if (flowDir == DirFailed) {
481         flowTimer.stop();
482         emit levelFailed();
483         return;
484     }
485
486     if (flowDir == DirPassed) {
487         flowTimer.stop();
488         emit levelPassed(flowScore);
489     }
490
491     if (flowDir == DirNone) {
492         // This square contained no pipe or an incompatible pipe. Get
493         // some more time, so that the user sees the failure before we
494         // emit levelFailed.
495         flowDir = DirFailed;
496         flowTimer.setInterval(1000);
497         return;
498     }
499     flowScore += 10;
500
501     if (flowDir == DirDone) {
502         // Again, give the user some time...
503         if (flowPreplaced < neededFlow) {
504             flowDir = DirFailed;
505             // TODO: indicate which pipes were missing
506         }
507         else
508             flowDir = DirPassed;
509
510         flowTimer.setInterval(1000);
511         return;
512     }
513
514     // Compute where it flows next
515     if (flowDir == DirRight) {
516         ++flowCol;
517         flowDir = DirLeft;
518     }
519     else if (flowDir == DirLeft) {
520         --flowCol;
521         flowDir = DirRight;
522     }
523     else if (flowDir == DirUp) {
524         --flowRow;
525         flowDir = DirDown;
526     }
527     else if (flowDir == DirDown) {
528         ++flowRow;
529         flowDir = DirUp;
530     }
531
532     if (flowRow < 0 || flowCol < 0 || flowRow >= rows || flowCol >= cols) {
533         // Out of bounds
534         flowDir = DirFailed;
535         flowTimer.setInterval(1000);
536         return;
537     }
538
539     // Now we know the next piece and where the flow comes *from*
540     qDebug() << "flow to" << flowRow << flowCol;
541
542     // Check which piece is there
543     const Piece* piece = fieldUi->pieceAt(flowRow, flowCol);
544     qDebug() << "there is" << piece->type << piece->rotation;
545     flowDir = flowsTo(piece, flowDir);
546     // If the piece was pre-placed, record that the liquid has
547     // flown through it once
548     if (fieldUi->isPrePlaced(flowRow, flowCol))
549         flowPreplaced += 1;
550 }
551
552 LevelSwitcher::LevelSwitcher(GameController* gameController,
553                              QWidget* levelWidget, QListWidget* levelList, 
554                              QPushButton* levelStartButton,
555                              QWidget* startWidget, QLabel* startTitle, 
556                              QLabel* startLabel, QPushButton* startButton,
557                              QLabel* levelLabel, QLabel* scoreLabel,
558                              QStringList levelCollections)
559     : gameController(gameController),
560       levelWidget(levelWidget), levelList(levelList), levelStartButton(levelStartButton),
561       startWidget(startWidget), startTitle(startTitle), startLabel(startLabel), startButton(startButton),
562       levelLabel(levelLabel), scoreLabel(scoreLabel),
563       levelCollections(levelCollections), curColl(0), level(0), totalScore(0)
564 {
565     connect(levelStartButton, SIGNAL(clicked()), this, SLOT(onLevelCollectionChosen()));
566
567     connect(startButton, SIGNAL(clicked()), this, SLOT(onStartClicked()));
568     connect(gameController, SIGNAL(levelPassed(int)), this, SLOT(onLevelPassed(int)));
569     connect(gameController, SIGNAL(levelFailed()), this, SLOT(onLevelFailed()));
570     readSavedGames();
571     chooseLevelCollection();
572 }
573
574 void LevelSwitcher::chooseLevelCollection()
575 {
576     levelList->clear();
577     bool first = true;
578     foreach (const QString& collection, levelCollections) {
579         QListWidgetItem *newItem = new QListWidgetItem();
580
581         // Check how many levels the user has already passed
582         int passed = 0;
583         if (savedGames.contains(collection)) {
584             passed = savedGames[collection];
585         }
586
587         newItem->setText(collection + " " + QString::number(passed) + " / " /*+ y*/);
588         levelList->addItem(newItem); // transfers ownership
589         if (first) { // TODO: and passed < total
590             levelList->setCurrentItem(newItem);
591             first = false;
592         }
593     }
594     levelWidget->show();
595 }
596
597 void LevelSwitcher::onLevelCollectionChosen()
598 {
599     levelWidget->hide();
600     curColl = levelList->currentItem()->text().split(" ").first();
601     QFile file(QString(LEVDIR) + "/" + curColl + ".dat");
602     qDebug() << "Trying to read" << file.fileName();
603     if (!file.exists())
604         qFatal("Error reading game file: doesn't exist");
605     file.open(QIODevice::ReadOnly);
606     QTextStream levelData(&file);
607     levels.clear();
608     
609     while (!levelData.atEnd())
610         levels << levelData.readLine();
611
612     level = 0;
613     // Go to the level the user has not yet passed
614     if (savedGames.contains(curColl)) {
615         level = savedGames[curColl];
616         if (level >= levels.size() - 1) {
617             level = 0;
618         }
619     }
620     
621     totalScore = 0;
622     startTitle->setText("Starting a new game.");
623     scoreLabel->setText("0");
624     file.close();
625     initiateLevel();
626 }
627
628 void LevelSwitcher::onStartClicked()
629 {
630     startWidget->hide();
631     levelLabel->setText(QString::number(level+1));
632     gameController->startLevel(QString(LEVDIR) + "/" + levels[level] + ".dat");
633 }
634
635 void LevelSwitcher::initiateLevel()
636 {
637     if (level >= levels.size()) {
638         qWarning() << "Level index too large";
639         return;
640     }
641
642     QFile file(QString(LEVDIR) + "/" + levels[level] + ".leg");
643     if (!file.exists())
644         qFatal("Error reading game file: doesn't exist");
645     file.open(QIODevice::ReadOnly);
646     QTextStream gameData(&file);
647
648     QString introText = gameData.readLine();
649     introText.replace("IMGDIR", IMGDIR);
650
651     // The start button might be connected to "chooseLevelCollection"
652     startButton->disconnect();
653     connect(startButton, SIGNAL(clicked()), this, SLOT(onStartClicked()));
654     startLabel->setText(introText);
655     startWidget->show();
656 }
657
658 void LevelSwitcher::onLevelPassed(int score)
659 {
660     totalScore += score;
661     scoreLabel->setText(QString::number(score));
662
663     if (level < levels.size() - 1) {
664         ++ level;
665         startTitle->setText(QString("Level ") + QString::number(level) + QString(" passed, proceeding to level ") + QString::number(level+1));
666         // Record that the level has been passed, so that the user can
667         // start again
668         savedGames.insert(curColl, level);
669         writeSavedGames();
670
671         initiateLevel();
672     }
673     else {
674         startTitle->setText(QString("All levels passed. Score: ") + QString::number(score));
675         startLabel->setText("Start a new game?");
676         startButton->disconnect();
677         connect(startButton, SIGNAL(clicked()), this, SLOT(chooseLevelCollection()));
678         // Record that all levels have been passed
679         savedGames.insert(curColl, levels.size() - 1);
680         writeSavedGames();
681
682         level = 0;
683         startWidget->show();
684     }
685 }
686
687 void LevelSwitcher::onLevelFailed()
688 {
689     startTitle->setText(QString("Level ") + QString::number(level+1) + QString(" failed, try again!"));
690     initiateLevel();
691 }
692
693 void LevelSwitcher::readSavedGames()
694 {
695     QFile file(QDir::homePath() + "/.evilplumber");
696     if (!file.exists()) {
697         qWarning() << "Save file doesn't exist";
698         return;
699     }
700     file.open(QIODevice::ReadOnly);
701     QTextStream saveData(&file);
702     QString collection = 0;
703     int level = 0;
704     while (!saveData.atEnd()) {
705         saveData >> collection;
706         saveData >> level;
707         qDebug() << "Got saved game: " << collection << level;
708         savedGames.insert(collection, level);
709     }
710     file.close();
711 }
712
713 void LevelSwitcher::writeSavedGames()
714 {
715     QFile file(QDir::homePath() + "/.evilplumber");
716     file.open(QIODevice::Truncate | QIODevice::WriteOnly);
717     QTextStream saveData(&file);
718     foreach (const QString& collection, savedGames.keys()) {
719         saveData << collection << " " << savedGames[collection] << endl;
720     }
721     file.close();
722 }
723
724 // Todo next:
725 // better graphics
726 // save & load
727 // level collections: introduction + basic
728 // more levels
729 // make fixed pipes look different than non-fixed ones
730 // color theme
731 // --------------
732 // re-placing pieces
733 // graphical hints on what to do next
734 // graphical help, showing the ui elements: demo
735 // "done" animation
736 // level editor