You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdegames/kmines/status.cpp

479 lines
14 KiB

/*
* 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++;
}