Ui fixes.
[evilplumber] / src / game.cpp
index df29387..62da042 100644 (file)
 #include "game.h"
 
 #include <QTableWidget>
-#include <QDebug>
+#include <QListWidget>
 #include <QLabel>
-#include <QFile>
 #include <QPushButton>
-#include <QFrame>
 #include <QApplication>
+#include <QFile>
+#include <QDir>
+#include <QDebug>
+
+const Piece* findPiece(PieceType type, int rotation)
+{
+    static QHash<QPair<PieceType, int>, const Piece*> pieceCache;
 
+    // Fill the cache on the first run
+    if (pieceCache.size() == 0) {
+        for (int i = 0; ppieces[i].type != PiecesEnd; ++i)
+            pieceCache.insert(QPair<PieceType, int>(ppieces[i].type, ppieces[i].rotation), &ppieces[i]);
+    }
+    QPair<PieceType, int> key(type, rotation);
+    if (pieceCache.contains(key))
+        return pieceCache[key];
+    return 0;
+       
+}
 
 QString pieceToIconId(const Piece* piece, bool flow1 = false, bool flow2 = false)
 {
@@ -33,8 +49,7 @@ QString pieceToIconId(const Piece* piece, bool flow1 = false, bool flow2 = false
         fileName += (QString("_flow_") + (flow1? "1" : "0") + (flow2? "1" : "0"));
     }
 
-
-    qDebug() << "need: " << fileName;
+    //qDebug() << "need: " << fileName;
     return fileName + ".png";
 }
 
@@ -79,7 +94,6 @@ GameField::GameField(QTableWidget* ui)
 void GameField::initGame(int rows_, int cols_, int count, PrePlacedPiece* prePlaced)
 {
     fieldUi->clear();
-    // FIXME: Does the table widget call the destructors of its items...
 
     rows = rows_;
     cols = cols_;
@@ -140,6 +154,10 @@ bool GameField::setPiece(int row, int col, const Piece* piece, bool fixed)
         QLabel* label = (QLabel*)fieldUi->indexWidget(index);
         label->setPixmap(QPixmap(iconId));
 
+        if (fixed) {
+            label->setStyleSheet("background-color: #263d49");
+        }
+
         return true;
     }
     return false;
@@ -176,6 +194,11 @@ void GameField::indicateFlow(int row, int col, Direction dir)
     if (row < 0 || col < 0 || row >= rows || col >= cols) {
         return;
     }
+    if (dir == DirFailed || dir == DirPassed) {
+        // No need to indicate these pseudo-directions
+        return;
+    }
+
     int index = toIndex(row, col);
     if (dir != DirNone && (field[index].piece->flows[0] == dir || field[index].piece->flows[1] == dir)) {
         field[index].flow[0] = true;
@@ -189,7 +212,6 @@ void GameField::indicateFlow(int row, int col, Direction dir)
         field[index].piece = ppieces;
         field[index].flow[0] = true;
         field[index].flow[1] = false;
-        
     }
     else {
         qWarning() << "Indicate flow: illegal direction" << row << col << dir;
@@ -202,19 +224,13 @@ void GameField::indicateFlow(int row, int col, Direction dir)
     QLabel* label = (QLabel*)fieldUi->indexWidget(mIndex);
 
     label->setPixmap(QPixmap(iconId));
-    // The pixmap won't show nicely if we're just sleeping...
-    QApplication::processEvents();
 }
 
-QHash<QPair<PieceType, int>, const Piece*> AvailablePieces::pieceCache;
-
 AvailablePieces::AvailablePieces(QTableWidget* ui)
   : pieceUi(ui)
 {
     connect(pieceUi, SIGNAL(itemClicked(QTableWidgetItem*)), this, SLOT(onItemClicked(QTableWidgetItem*)));
 
-    initPieceCache();
-
     // Setup ui
 
     qDebug() << pieceUi->rowCount() << pieceUi->columnCount();
@@ -222,7 +238,7 @@ AvailablePieces::AvailablePieces(QTableWidget* ui)
     for (int i = 0; i < 2; ++i)
         pieceUi->setColumnWidth(i, 120);
 
-    for (int i = 0; i < 4; ++i)
+    for (int i = 0; i < 5; ++i)
         pieceUi->setRowHeight(i, 70);
 
     for (int i = 0; ppieces[i].type != PiecesEnd; ++i) {
@@ -246,16 +262,7 @@ const Piece* AvailablePieces::idToPiece(int id)
 {
     int rotation = (id % 4)*90;
     PieceType type = (PieceType)(id / 4);
-    QPair<PieceType, int> key(type, rotation);
-    if (!pieceCache.contains(key))
-        return ppieces;
-    return pieceCache[key];
-}
-
-void AvailablePieces::initPieceCache()
-{
-    for (int i = 0; ppieces[i].type != PiecesEnd; ++i)
-        pieceCache.insert(QPair<PieceType, int>(ppieces[i].type, ppieces[i].rotation), &ppieces[i]);
+    return findPiece(type, rotation);
 }
 
 void AvailablePieces::initGame(int count, AvailablePiece* pieces)
@@ -347,17 +354,24 @@ void GameController::startLevel(QString fileName)
     neededFlow = 0;
     int prePlacedCount = 0;
     gameData >> prePlacedCount;
-    qDebug() << rows << cols;
     if (prePlacedCount < 2 || prePlacedCount > 100)
-        qFatal("Error reading game file: piece count000");
+        qFatal("Error reading game file: piece count");
 
     PrePlacedPiece* prePlaced = new PrePlacedPiece[prePlacedCount];
     for (int i = 0; i < prePlacedCount; ++i) {
-        int ix = 0;
-        gameData >> ix;
-        if (ix < 0 || ix >= noPieces)
-            qFatal("Error reading game file: no of pieces");
-        prePlaced[i].piece = &ppieces[ix];
+        int type = 0;
+        gameData >> type;
+        if (type < 0 || type >= PiecesEnd)
+            qFatal("Error reading game file: type of pre-placed piece");
+
+        int rotation = 0;
+        gameData >> rotation;
+        if (rotation != 0 && rotation != 90 && rotation != 180 && rotation != 270)
+            qFatal("Error reading game file: rotation of pre-placed piece");
+
+        prePlaced[i].piece = findPiece((PieceType)type, rotation);
+        if (!prePlaced[i].piece)
+            qFatal("Error reading game file: invalid pre-placed piece");
 
         // Record that the liquid must flow through this pre-placed
         // piece (if it can)
@@ -408,6 +422,7 @@ void GameController::startLevel(QString fileName)
     doneButton->setEnabled(true);
     timer.start();
     levelRunning = true;
+    file.close();
 }
 
 void GameController::onTimeout()
@@ -471,6 +486,11 @@ void GameController::computeFlow()
         return;
     }
 
+    if (flowDir == DirPassed) {
+        flowTimer.stop();
+        emit levelPassed(flowScore);
+    }
+
     if (flowDir == DirNone) {
         // This square contained no pipe or an incompatible pipe. Get
         // some more time, so that the user sees the failure before we
@@ -482,15 +502,15 @@ void GameController::computeFlow()
     flowScore += 10;
 
     if (flowDir == DirDone) {
+        // Again, give the user some time...
         if (flowPreplaced < neededFlow) {
             flowDir = DirFailed;
             // TODO: indicate which pipes were missing
-            flowTimer.setInterval(1000);
-        }
-        else {
-            flowTimer.stop();
-            emit levelPassed(flowScore);
         }
+        else
+            flowDir = DirPassed;
+
+        flowTimer.setInterval(1000);
         return;
     }
 
@@ -532,19 +552,79 @@ void GameController::computeFlow()
         flowPreplaced += 1;
 }
 
-LevelSwitcher::LevelSwitcher(GameController* gameController, QLabel* levelLabel, 
-                             QFrame* startFrame, QLabel* startTitle, QLabel* startLabel, QPushButton* startButton,
-                             QLabel* scoreLabel,
-
-                             QStringList levels)
-    : gameController(gameController), levelLabel(levelLabel), 
-      startFrame(startFrame), startTitle(startTitle), startLabel(startLabel), startButton(startButton),
-      scoreLabel(scoreLabel),
-      levels(levels), level(0), totalScore(0)
+LevelSwitcher::LevelSwitcher(GameController* gameController,
+                             QWidget* levelWidget, QListWidget* levelList, 
+                             QPushButton* levelStartButton,
+                             QWidget* startWidget, QLabel* startTitle, 
+                             QLabel* startLabel, QPushButton* startButton,
+                             QLabel* levelLabel, QLabel* scoreLabel,
+                             QStringList collections)
+    : gameController(gameController),
+      levelWidget(levelWidget), levelList(levelList), levelStartButton(levelStartButton),
+      startWidget(startWidget), startTitle(startTitle), startLabel(startLabel), startButton(startButton),
+      levelLabel(levelLabel), scoreLabel(scoreLabel),
+      curColl(""), level(0), totalScore(0)
 {
+    connect(levelStartButton, SIGNAL(clicked()), this, SLOT(onLevelCollectionChosen()));
+
     connect(startButton, SIGNAL(clicked()), this, SLOT(onStartClicked()));
     connect(gameController, SIGNAL(levelPassed(int)), this, SLOT(onLevelPassed(int)));
     connect(gameController, SIGNAL(levelFailed()), this, SLOT(onLevelFailed()));
+    readSavedGames();
+    readLevelCollections(collections);
+    chooseLevelCollection();
+}
+
+void LevelSwitcher::chooseLevelCollection()
+{
+    levelList->clear();
+    bool first = true;
+    foreach (const QString& collection, levelCollections.keys()) {
+        QListWidgetItem *newItem = new QListWidgetItem();
+
+        // Check how many levels the user has already passed
+        int passed = 0;
+        if (savedGames.contains(collection)) {
+            passed = savedGames[collection];
+        }
+        int total = 0;
+        if (levelCollections.contains(collection)) {
+            total = levelCollections[collection].size();
+        }
+
+        newItem->setText(collection + "   \tPassed: " + 
+                         QString::number(passed) + " / " + QString::number(total));
+        levelList->addItem(newItem); // transfers ownership
+        if (first && passed < total) {
+            levelList->setCurrentItem(newItem);
+            first = false;
+        }
+    }
+    levelWidget->show();
+}
+
+void LevelSwitcher::onLevelCollectionChosen()
+{
+    levelWidget->hide();
+    curColl = levelList->currentItem()->text().split(" ").first();
+
+    if (levelCollections.contains(curColl)) {
+        levels = levelCollections[curColl];
+    }
+    else
+        qFatal("Error choosing a level collection: unrecognized");
+
+    level = 0;
+    // Go to the level the user has not yet passed
+    if (savedGames.contains(curColl)) {
+        qDebug() << "going to saved level" << savedGames[curColl];
+        level = savedGames[curColl];
+        if (level >= levels.size()) {
+            level = 0;
+        }
+    }
+    
+    totalScore = 0;
     startTitle->setText("Starting a new game.");
     scoreLabel->setText("0");
     initiateLevel();
@@ -552,13 +632,18 @@ LevelSwitcher::LevelSwitcher(GameController* gameController, QLabel* levelLabel,
 
 void LevelSwitcher::onStartClicked()
 {
-    startFrame->hide();
+    startWidget->hide();
     levelLabel->setText(QString::number(level+1));
     gameController->startLevel(QString(LEVDIR) + "/" + levels[level] + ".dat");
 }
 
 void LevelSwitcher::initiateLevel()
 {
+    if (level >= levels.size()) {
+        qWarning() << "Level index too large";
+        return;
+    }
+
     QFile file(QString(LEVDIR) + "/" + levels[level] + ".leg");
     if (!file.exists())
         qFatal("Error reading game file: doesn't exist");
@@ -567,8 +652,12 @@ void LevelSwitcher::initiateLevel()
 
     QString introText = gameData.readLine();
     introText.replace("IMGDIR", IMGDIR);
+
+    // The start button might be connected to "chooseLevelCollection"
+    startButton->disconnect();
+    connect(startButton, SIGNAL(clicked()), this, SLOT(onStartClicked()));
     startLabel->setText(introText);
-    startFrame->show();
+    startWidget->show();
 }
 
 void LevelSwitcher::onLevelPassed(int score)
@@ -579,14 +668,24 @@ void LevelSwitcher::onLevelPassed(int score)
     if (level < levels.size() - 1) {
         ++ level;
         startTitle->setText(QString("Level ") + QString::number(level) + QString(" passed, proceeding to level ") + QString::number(level+1));
+        // Record that the level has been passed, so that the user can
+        // start again
+        savedGames.insert(curColl, level);
+        writeSavedGames();
+
         initiateLevel();
     }
     else {
         startTitle->setText(QString("All levels passed. Score: ") + QString::number(score));
         startLabel->setText("Start a new game?");
-        // TODO: go to the level set selection screen
+        startButton->disconnect();
+        connect(startButton, SIGNAL(clicked()), this, SLOT(chooseLevelCollection()));
+        // Record that all levels have been passed
+        savedGames.insert(curColl, levels.size());
+        writeSavedGames();
+
         level = 0;
-        startFrame->show();
+        startWidget->show();
     }
 }
 
@@ -596,16 +695,66 @@ void LevelSwitcher::onLevelFailed()
     initiateLevel();
 }
 
-// Todo next:
-// desktop stuff
-// icon for app manager
-// install all graphics
-// better graphics
-// save & load
-// level collections: introduction + basic
-// more levels
+void LevelSwitcher::readSavedGames()
+{
+    QFile file(QDir::homePath() + "/.evilplumber");
+    if (!file.exists()) {
+        qWarning() << "Save file doesn't exist";
+        return;
+    }
+    file.open(QIODevice::ReadOnly);
+    QTextStream saveData(&file);
+    QString collection = 0;
+    int level = 0;
+    while (!saveData.atEnd()) {
+        saveData >> collection;
+        saveData >> level;
+        qDebug() << "Got saved game: " << collection << level;
+        if (collection != "")
+            savedGames.insert(collection, level);
+    }
+    file.close();
+}
+
+void LevelSwitcher::readLevelCollections(QStringList collections)
+{
+    foreach (const QString& coll, collections) {
+        QFile file(QString(LEVDIR) + "/" + coll + ".dat");
+        qDebug() << "Trying to read" << file.fileName();
+        if (!file.exists())
+            qFatal("Error reading level collection: doesn't exist");
+        file.open(QIODevice::ReadOnly);
+        QTextStream levelData(&file);
+        QStringList readLevels;
+        while (!levelData.atEnd())
+            readLevels << levelData.readLine();
+
+        levelCollections.insert(coll, readLevels);
+        file.close();
+    }
+}
+
+void LevelSwitcher::writeSavedGames()
+{
+    QFile file(QDir::homePath() + "/.evilplumber");
+    file.open(QIODevice::Truncate | QIODevice::WriteOnly);
+    QTextStream saveData(&file);
+    foreach (const QString& collection, savedGames.keys()) {
+        qDebug() << "writing" << collection << savedGames[collection];
+        saveData << collection << " " << savedGames[collection] << endl;
+    }
+    file.close();
+}
+
+// TODO:
+// --- 0.1 ---
+// more levels to the basic collection
 // make fixed pipes look different than non-fixed ones
-// --------------
+// get rid of debug prints
+// --- 0.2 ---
+// ability to install level sets as different packages
+// better graphics
+// color theme
 // re-placing pieces
 // graphical hints on what to do next
 // graphical help, showing the ui elements: demo