#include <QTableWidget>
#include <QListWidget>
#include <QLabel>
-#include <QFile>
#include <QPushButton>
#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)
{
fileName += (QString("_flow_") + (flow1? "1" : "0") + (flow2? "1" : "0"));
}
-
- qDebug() << "need: " << fileName;
return fileName + ".png";
}
Direction flowsTo(const Piece* piece, Direction flowFrom)
{
- //qDebug() << piece->flows[0];
- //qDebug() << piece->flows[1];
- //qDebug() << piece->flows[2];
- //qDebug() << piece->flows[3];
- //qDebug() << "check" << flowFrom;
if (piece->flows[0] == flowFrom)
return piece->flows[1];
if (piece->flows[1] == flowFrom)
bool GameField::setPiece(int row, int col, const Piece* piece, bool fixed)
{
- qDebug() << "set piece" << row << col;
-
if (row < 0 || row >= rows || col < 0 || col >= cols) {
qWarning() << "Invalid piece index";
return false;
int index = toIndex(row, col);
if (field[index].piece->type == PieceNone) {
- qDebug() << "really setting";
field[index].piece = piece;
field[index].fixed = fixed;
QLabel* label = (QLabel*)fieldUi->indexWidget(index);
label->setPixmap(QPixmap(iconId));
+ if (fixed) {
+ label->setStyleSheet("background-color: #263d49");
+ }
+
return true;
}
return false;
// Indicate the flow: fill the piece in question with the
// liquid. (The piece can also be an empty one, or an illegal
// one.)
- qDebug() << "ind flow" << row << col << 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;
field[index].piece = ppieces;
field[index].flow[0] = true;
field[index].flow[1] = false;
-
}
else {
qWarning() << "Indicate flow: illegal direction" << row << col << dir;
}
QString iconId = pieceToIconId(field[index].piece, field[index].flow[0], field[index].flow[1]);
- qDebug() << "icon id" << iconId;
QModelIndex mIndex = fieldUi->model()->index(row, col);
QLabel* label = (QLabel*)fieldUi->indexWidget(mIndex);
label->setPixmap(QPixmap(iconId));
}
-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();
-
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) {
if (ppieces[i].userCanAdd == false) continue;
- //qDebug() << ppieces[i].type << ppieces[i].rotation;
QString fileName = pieceToIconId(&(ppieces[i]));
QTableWidgetItem* item = new QTableWidgetItem(QIcon(fileName), "0", QTableWidgetItem::UserType + pieceToId(&(ppieces[i])));
{
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)
void AvailablePieces::onItemClicked(QTableWidgetItem* item)
{
- qDebug() << "piece clicked";
int id = item->type() - QTableWidgetItem::UserType;
const Piece* piece = idToPiece(id);
gameData >> rows;
gameData >> cols;
- qDebug() << rows << cols;
if (rows < 2 || rows > 10 || cols < 2 || cols > 10)
qFatal("Error reading game file: rows and cols");
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)
doneButton->setEnabled(true);
timer.start();
levelRunning = true;
+ file.close();
}
void GameController::onTimeout()
void GameController::onCellClicked(int row, int column)
{
- qDebug() << "clicked: " << row << column;
- if (!levelRunning) return;
- if (currentPiece->type == PieceNone) return;
- if (fieldUi->setPiece(row, column, currentPiece))
- emit pieceUsed(currentPiece);
+ if (!levelRunning) return;
+ if (currentPiece->type == PieceNone) return;
+ if (fieldUi->setPiece(row, column, currentPiece))
+ emit pieceUsed(currentPiece);
}
void GameController::onValidPieceSelected(const Piece* piece)
{
- qDebug() << "selected: " << piece->type << piece->rotation;
currentPiece = piece;
}
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
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;
}
}
// Now we know the next piece and where the flow comes *from*
- qDebug() << "flow to" << flowRow << flowCol;
// Check which piece is there
const Piece* piece = fieldUi->pieceAt(flowRow, flowCol);
- qDebug() << "there is" << piece->type << piece->rotation;
flowDir = flowsTo(piece, flowDir);
// If the piece was pre-placed, record that the liquid has
// flown through it once
QPushButton* levelStartButton,
QWidget* startWidget, QLabel* startTitle,
QLabel* startLabel, QPushButton* startButton,
- QLabel* levelLabel, QLabel* scoreLabel,
- QStringList levelCollections)
+ QWidget* gameWidget, 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),
- levelCollections(levelCollections), level(0), totalScore(0)
+ gameWidget(gameWidget), 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()));
- startTitle->setText("Starting a new game.");
- scoreLabel->setText("0");
+ readSavedGames();
+ readLevelCollections(collections);
chooseLevelCollection();
}
void LevelSwitcher::chooseLevelCollection()
{
levelList->clear();
- foreach (const QString& collection, levelCollections) {
+ bool first = true;
+ foreach (const QString& collection, levelCollections.keys()) {
QListWidgetItem *newItem = new QListWidgetItem();
- newItem->setText(collection);
+
+ // 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 + ", passed: " +
+ QString::number(passed) + " / " + QString::number(total));
levelList->addItem(newItem); // transfers ownership
+ if (first && passed < total) {
+ levelList->setCurrentItem(newItem);
+ first = false;
+ }
}
+ gameWidget->hide();
+ startWidget->hide();
levelWidget->show();
}
void LevelSwitcher::onLevelCollectionChosen()
{
levelWidget->hide();
- QString collection = levelList->currentItem()->text();
- QFile file(QString(LEVDIR) + "/" + collection + ".dat");
- if (!file.exists())
- qFatal("Error reading game file: doesn't exist");
- file.open(QIODevice::ReadOnly);
- QTextStream levelData(&file);
- levels.clear();
-
- while (!levelData.atEnd())
- levels << levelData.readLine();
+ 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)) {
+ level = savedGames[curColl];
+ if (level >= levels.size()) {
+ level = 0;
+ }
+ }
+
totalScore = 0;
+ if (level == 0)
+ startTitle->setText("Starting a new game.");
+ else
+ startTitle->setText(QString("Continuing a game from level ") + QString::number(level) + QString("."));
+
+ scoreLabel->setText("0");
initiateLevel();
}
void LevelSwitcher::onStartClicked()
{
- startWidget->hide();
levelLabel->setText(QString::number(level+1));
gameController->startLevel(QString(LEVDIR) + "/" + levels[level] + ".dat");
+ startWidget->hide();
+ gameWidget->show();
}
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);
+ gameWidget->hide();
startWidget->show();
}
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;
+ gameWidget->hide();
startWidget->show();
}
}
initiateLevel();
}
-// Todo next:
+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;
+
+ if (collection != "")
+ savedGames.insert(collection, level);
+ }
+ file.close();
+}
+
+void LevelSwitcher::readLevelCollections(QStringList collections)
+{
+ foreach (const QString& coll, collections) {
+ QFile file(QString(LEVDIR) + "/" + coll + ".dat");
+
+ 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()) {
+ saveData << collection << " " << savedGames[collection] << endl;
+ }
+ file.close();
+}
+
+// TODO:
+// --- 0.1 ---
+// more levels to the basic collection
+// --- 0.2 ---
+// ability to install level sets as different packages
// better graphics
-// save & load
-// level collections: introduction + basic
-// more levels
-// make fixed pipes look different than non-fixed ones
-// color theme
-// transparency
-// --------------
// re-placing pieces
// graphical hints on what to do next
// graphical help, showing the ui elements: demo