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