diff options
Diffstat (limited to 'kmines/status.cpp')
-rw-r--r-- | kmines/status.cpp | 478 |
1 files changed, 478 insertions, 0 deletions
diff --git a/kmines/status.cpp b/kmines/status.cpp new file mode 100644 index 00000000..a3d5f061 --- /dev/null +++ b/kmines/status.cpp @@ -0,0 +1,478 @@ +/* + * Copyright (c) 1996-2002 Nicolas HADACEK (hadacek@kde.org) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include "status.h" +#include "status.moc" + +#include <qpainter.h> +#include <qpixmap.h> +#include <qwhatsthis.h> +#include <qlayout.h> +#include <qwidgetstack.h> +#include <qtextedit.h> +#include <qtimer.h> + +#include <kapplication.h> +#include <klocale.h> +#include <kconfig.h> +#include <kmessagebox.h> +#include <kaction.h> +#include <kdebug.h> +#include <kfiledialog.h> +#include <ktempfile.h> +#include <kio/netaccess.h> +#include <knotifyclient.h> +#include <kexthighscore.h> + +#include "settings.h" +#include "solver/solver.h" +#include "dialogs.h" +#include "version.h" + + +Status::Status(QWidget *parent) + : QWidget(parent, "status"), _oldLevel(Level::Easy) +{ + _timer = new QTimer(this); + connect(_timer, SIGNAL(timeout()), SLOT(replayStep())); + + _solver = new Solver(this); + connect(_solver, SIGNAL(solvingDone(bool)), SLOT(solvingDone(bool))); + +// top layout + QGridLayout *top = new QGridLayout(this, 2, 5, 10, 10); + top->setColStretch(1, 1); + top->setColStretch(3, 1); + +// status bar + // mines left LCD + left = new KGameLCD(5, this); + left->setFrameStyle(QFrame::Panel | QFrame::Sunken); + left->setDefaultBackgroundColor(black); + left->setDefaultColor(white); + QWhatsThis::add(left, i18n("<qt>Mines left.<br/>" + "It turns <font color=\"red\">red</font> " + "when you have flagged more cases than " + "present mines.</qt>")); + top->addWidget(left, 0, 0); + + // smiley + smiley = new Smiley(this); + connect(smiley, SIGNAL(clicked()), SLOT(smileyClicked())); + smiley->setFocusPolicy(QWidget::NoFocus); + QWhatsThis::add(smiley, i18n("Press to start a new game")); + top->addWidget(smiley, 0, 2); + + // digital clock LCD + dg = new DigitalClock(this); + QWhatsThis::add(dg, i18n("<qt>Time elapsed.<br/>" + "It turns <font color=\"blue\">blue</font> " + "if it is a highscore " + "and <font color=\"red\">red</font> " + "if it is the best time.</qt>")); + top->addWidget(dg, 0, 4); + +// mines field + _fieldContainer = new QWidget(this); + QGridLayout *g = new QGridLayout(_fieldContainer, 1, 1); + _field = new Field(_fieldContainer); + _field->readSettings(); + g->addWidget(_field, 0, 0, AlignCenter); + connect( _field, SIGNAL(updateStatus(bool)), SLOT(updateStatus(bool)) ); + connect(_field, SIGNAL(gameStateChanged(GameState)), + SLOT(gameStateChangedSlot(GameState)) ); + connect(_field, SIGNAL(setMood(Mood)), smiley, SLOT(setMood(Mood))); + connect(_field, SIGNAL(setCheating()), dg, SLOT(setCheating())); + connect(_field,SIGNAL(addAction(const KGrid2D::Coord &, Field::ActionType)), + SLOT(addAction(const KGrid2D::Coord &, Field::ActionType))); + QWhatsThis::add(_field, i18n("Mines field.")); + +// resume button + _resumeContainer = new QWidget(this); + g = new QGridLayout(_resumeContainer, 1, 1); + QFont f = font(); + f.setBold(true); + QPushButton *pb + = new QPushButton(i18n("Press to Resume"), _resumeContainer); + pb->setFont(f); + connect(pb, SIGNAL(clicked()), SIGNAL(pause())); + g->addWidget(pb, 0, 0, AlignCenter); + + _stack = new QWidgetStack(this); + _stack->addWidget(_fieldContainer); + _stack->addWidget(_resumeContainer); + _stack->raiseWidget(_fieldContainer); + top->addMultiCellWidget(_stack, 1, 1, 0, 4); +} + +void Status::smileyClicked() +{ + if ( _field->gameState()==Paused ) emit pause(); + else restartGame(); +} + +void Status::newGame(int t) +{ + if ( _field->gameState()==Paused ) emit pause(); + Level::Type type = (Level::Type)t; + Settings::setLevel(type); + if ( type!=Level::Custom ) newGame( Level(type) ); + else newGame( Settings::customLevel() ); +} + +void Status::newGame(const Level &level) +{ + _timer->stop(); + if ( level.type()!=Level::Custom ) + KExtHighscore::setGameType(level.type()); + _field->setLevel(level); +} + +bool Status::checkBlackMark() +{ + bool bm = ( _field->gameState()==Playing ); + if (bm) KExtHighscore::submitScore(KExtHighscore::Lost, this); + return bm; +} + +void Status::restartGame() +{ + if ( _field->gameState()==Paused ) emit pause(); + else if ( _field->gameState()==Replaying ) { + _timer->stop(); + _field->setLevel(_oldLevel); + } else { + bool bm = checkBlackMark(); + _field->reset(bm); + } +} + +void Status::settingsChanged() +{ + _field->readSettings(); + + if ( Settings::level()!=Level::Custom ) return; + Level l = Settings::customLevel(); + if ( l==_field->level() ) return; + if ( _field->gameState()==Paused ) emit pause(); + newGame(l); +} + +void Status::updateStatus(bool mine) +{ + int r = _field->nbMines() - _field->nbMarked(); + QColor color = (r<0 && !_field->isSolved() ? red : white); + left->setColor(color); + left->display(r); + + if ( _field->isSolved() && !mine ) + gameStateChanged(GameOver, true); // ends only for wins +} + +void Status::setGameOver(bool won) +{ + if ( !won ) + KNotifyClient::event(winId(), "explosion", i18n("Explosion!")); + _field->showAllMines(won); + smiley->setMood(won ? Happy : Sad); + if ( _field->gameState()==Replaying ) return; + + _field->setGameOver(); + dg->stop(); + if ( _field->level().type()!=Level::Custom && !dg->cheating() ) { + if (won) KExtHighscore::submitScore(dg->score(), this); + else KExtHighscore::submitScore(KExtHighscore::Lost, this); + } + + KNotifyClient::event(winId(), won ? "won" : "lost", + won ? i18n("Game won!") : i18n("Game lost!")); + + // game log + _logRoot.setAttribute("count", dg->nbActions()); + + if ( Settings::magicReveal() ) + _logRoot.setAttribute("complete_reveal", "true"); + QString sa = "none"; + if ( _field->solvingState()==Solved ) sa = "solving"; + else if ( _field->solvingState()==Advised ) sa = "advising"; + _logRoot.setAttribute("solver", sa); + + QDomElement f = _log.createElement("Field"); + _logRoot.appendChild(f); + QDomText data = _log.createTextNode(_field->string()); + f.appendChild(data); +} + +void Status::setStopped() +{ + smiley->setMood(Normal); + updateStatus(false); + bool custom = ( _field->level().type()==Level::Custom ); + dg->reset(custom); + _field->setSolvingState(Regular); +} + +void Status::setPlaying() +{ + smiley->setMood(Normal); + dg->start(); + if ( _field->gameState()==Paused ) return; // do not restart game log... + + // game log + const Level &level = _field->level(); + _log = QDomDocument("kmineslog"); + _logRoot = _log.createElement("kmineslog"); + _logRoot.setAttribute("version", SHORT_VERSION); + QDateTime date = QDateTime::currentDateTime(); + _logRoot.setAttribute("date", date.toString(Qt::ISODate)); + _logRoot.setAttribute("width", level.width()); + _logRoot.setAttribute("height", level.height()); + _logRoot.setAttribute("mines", level.nbMines()); + _log.appendChild(_logRoot); + _logList = _log.createElement("ActionList"); + _logRoot.appendChild(_logList); +} + +void Status::gameStateChanged(GameState state, bool won) +{ + QWidget *w = _fieldContainer; + + switch (state) { + case Playing: + setPlaying(); + break; + case GameOver: + setGameOver(won); + break; + case Paused: + smiley->setMood(Sleeping); + dg->stop(); + w = _resumeContainer; + break; + case Stopped: + case Init: + setStopped(); + break; + case Replaying: + smiley->setMood(Normal); + break; + case NB_STATES: + Q_ASSERT(false); + break; + } + + _stack->raiseWidget(w); + emit gameStateChangedSignal(state); +} + +void Status::addAction(const KGrid2D::Coord &c, Field::ActionType type) +{ + QDomElement action = _log.createElement("Action"); + action.setAttribute("time", dg->pretty()); + action.setAttribute("column", c.first); + action.setAttribute("line", c.second); + action.setAttribute("type", Field::ACTION_DATA[type].name); + _logList.appendChild(action); + dg->addAction(); +} + +void Status::advise() +{ + int res = KMessageBox::warningContinueCancel(this, + i18n("When the solver gives " + "you advice, your score will not be added to the highscores."), + QString::null, QString::null, "advice_warning"); + if ( res==KMessageBox::Cancel ) return; + dg->setCheating(); + float probability; + KGrid2D::Coord c = _solver->advise(*_field, probability); + _field->setAdvised(c, probability); +} + +void Status::solve() +{ + dg->setCheating(); + _solver->solve(*_field, false); + _field->setSolvingState(Solved); +} + +void Status::solvingDone(bool success) +{ + if ( !success ) gameStateChanged(GameOver, false); +} + +void Status::solveRate() +{ + SolvingRateDialog sd(*_field, this); + sd.exec(); +} + +void Status::viewLog() +{ + KDialogBase d(this, "view_log", true, i18n("View Game Log"), + KDialogBase::Close, KDialogBase::Close); + QTextEdit *view = new QTextEdit(&d); + view->setReadOnly(true); + view->setTextFormat(PlainText); + view->setText(_log.toString()); + d.setMainWidget(view); + d.resize(500, 400); + d.exec(); +} + +void Status::saveLog() +{ + KURL url = KFileDialog::getSaveURL(QString::null, QString::null, this); + if ( url.isEmpty() ) return; + if ( KIO::NetAccess::exists(url, false, this) ) { + KGuiItem gi = KStdGuiItem::save(); + gi.setText(i18n("Overwrite")); + int res = KMessageBox::warningYesNo(this, + i18n("The file already exists. Overwrite?"), + i18n("File Exists"), gi, KStdGuiItem::cancel()); + if ( res==KMessageBox::No ) return; + } + KTempFile tmp; + (*tmp.textStream()) << _log.toString(); + tmp.close(); + KIO::NetAccess::upload(tmp.name(), url, this); + tmp.unlink(); +} + +void Status::loadLog() +{ + KURL url = KFileDialog::getOpenURL(QString::null, QString::null, this); + if ( url.isEmpty() ) return; + QString tmpFile; + bool success = false; + QDomDocument doc; + if( KIO::NetAccess::download(url, tmpFile, this) ) { + QFile file(tmpFile); + if ( file.open(IO_ReadOnly) ) { + int errorLine; + bool ok = doc.setContent(&file, 0, &errorLine); + if ( !ok ) { + KMessageBox::sorry(this, i18n("Cannot read XML file on line %1") + .arg(errorLine)); + return; + } + success = true; + } + KIO::NetAccess::removeTempFile(tmpFile); + + } + if ( !success ) { + KMessageBox::sorry(this, i18n("Cannot load file.")); + return; + } + + if ( !checkLog(doc) ) + KMessageBox::sorry(this, i18n("Log file not recognized.")); + else { + _log = doc; + _logRoot = doc.namedItem("kmineslog").toElement(); + emit gameStateChangedSignal(GameOver); + } +} + +bool Status::checkLog(const QDomDocument &doc) +{ + // check root element + if ( doc.doctype().name()!="kmineslog" ) return false; + QDomElement root = doc.namedItem("kmineslog").toElement(); + if ( root.isNull() ) return false; + bool ok; + uint w = root.attribute("width").toUInt(&ok); + if ( !ok || w>CustomConfig::maxWidth || w<CustomConfig::minWidth ) + return false; + uint h = root.attribute("height").toUInt(&ok); + if ( !ok || h>CustomConfig::maxHeight || h<CustomConfig::minHeight ) + return false; + uint nb = root.attribute("mines").toUInt(&ok); + if ( !ok || nb==0 || nb>Level::maxNbMines(w, h) ) return false; + + // check field + QDomElement field = root.namedItem("Field").toElement(); + if ( field.isNull() ) return false; + QString ftext = field.text(); + if ( !BaseField::checkField(w, h, nb, ftext) ) return false; + + // check action list + QDomElement list = root.namedItem("ActionList").toElement(); + if ( list.isNull() ) return false; + QDomNodeList actions = list.elementsByTagName("Action"); + if ( actions.count()==0 ) return false; + for (uint i=0; i<actions.count(); i++) { + QDomElement a = actions.item(i).toElement(); + if ( a.isNull() ) return false; + uint i0 = a.attribute("line").toUInt(&ok); + if ( !ok || i0>=h ) return false; + uint j = a.attribute("column").toUInt(&ok); + if ( !ok || j>=w ) return false; + QString type = a.attribute("type"); + uint k = 0; + for (; k<Field::Nb_Actions; k++) + if ( type==Field::ACTION_DATA[k].name ) break; + if ( k==Field::Nb_Actions ) return false; + } + + return true; +} + + +void Status::replayLog() +{ + uint w = _logRoot.attribute("width").toUInt(); + uint h = _logRoot.attribute("height").toUInt(); + uint n = _logRoot.attribute("mines").toUInt(); + Level level(w, h, n); + QDomNode f = _logRoot.namedItem("Field"); + _oldLevel = _field->level(); + newGame(level); + _field->setReplayField(f.toElement().text()); + QString s = _logRoot.attribute("complete_reveal"); + _completeReveal = ( s=="true" ); + + f = _logRoot.namedItem("ActionList"); + _actions = f.toElement().elementsByTagName("Action"); + _index = 0; + _timer->start(500); +} + +void Status::replayStep() +{ + if ( _index>=_actions.count() ) { + _timer->stop(); + _actions = QDomNodeList(); + return; + } + + _timer->changeInterval(200); + QDomElement a = _actions.item(_index).toElement(); + dg->setTime(a.attribute("time")); + uint i = a.attribute("column").toUInt(); + uint j = a.attribute("line").toUInt(); + QString type = a.attribute("type"); + for (uint k=0; k<Field::Nb_Actions; k++) + if ( type==Field::ACTION_DATA[k].name ) { + _field->doAction((Field::ActionType)k, + KGrid2D::Coord(i, j), _completeReveal); + break; + } + _index++; +} |