|
|
|
/*
|
|
|
|
* 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 <tqpainter.h>
|
|
|
|
#include <tqpixmap.h>
|
|
|
|
#include <tqwhatsthis.h>
|
|
|
|
#include <tqlayout.h>
|
|
|
|
#include <tqwidgetstack.h>
|
|
|
|
#include <tqtextedit.h>
|
|
|
|
#include <tqtimer.h>
|
|
|
|
|
|
|
|
#include <tdeapplication.h>
|
|
|
|
#include <tdelocale.h>
|
|
|
|
#include <tdeconfig.h>
|
|
|
|
#include <tdemessagebox.h>
|
|
|
|
#include <tdeaction.h>
|
|
|
|
#include <kdebug.h>
|
|
|
|
#include <tdefiledialog.h>
|
|
|
|
#include <tdetempfile.h>
|
|
|
|
#include <tdeio/netaccess.h>
|
|
|
|
#include <knotifyclient.h>
|
|
|
|
#include <highscore/kexthighscore.h>
|
|
|
|
|
|
|
|
#include "settings.h"
|
|
|
|
#include "solver/solver.h"
|
|
|
|
#include "dialogs.h"
|
|
|
|
#include "version.h"
|
|
|
|
|
|
|
|
|
|
|
|
Status::Status(TQWidget *parent)
|
|
|
|
: TQWidget(parent, "status"), _oldLevel(Level::Easy)
|
|
|
|
{
|
|
|
|
_timer = new TQTimer(this);
|
|
|
|
connect(_timer, TQ_SIGNAL(timeout()), TQ_SLOT(replayStep()));
|
|
|
|
|
|
|
|
_solver = new Solver(this);
|
|
|
|
connect(_solver, TQ_SIGNAL(solvingDone(bool)), TQ_SLOT(solvingDone(bool)));
|
|
|
|
|
|
|
|
// top layout
|
|
|
|
TQGridLayout *top = new TQGridLayout(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(TQFrame::Panel | TQFrame::Sunken);
|
|
|
|
left->setDefaultBackgroundColor(black);
|
|
|
|
left->setDefaultColor(white);
|
|
|
|
TQWhatsThis::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, TQ_SIGNAL(clicked()), TQ_SLOT(smileyClicked()));
|
|
|
|
smiley->setFocusPolicy(TQWidget::NoFocus);
|
|
|
|
TQWhatsThis::add(smiley, i18n("Press to start a new game"));
|
|
|
|
top->addWidget(smiley, 0, 2);
|
|
|
|
|
|
|
|
// digital clock LCD
|
|
|
|
dg = new DigitalClock(this);
|
|
|
|
TQWhatsThis::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 TQWidget(this);
|
|
|
|
TQGridLayout *g = new TQGridLayout(_fieldContainer, 1, 1);
|
|
|
|
_field = new Field(_fieldContainer);
|
|
|
|
_field->readSettings();
|
|
|
|
g->addWidget(_field, 0, 0, AlignCenter);
|
|
|
|
connect( _field, TQ_SIGNAL(updateStatus(bool)), TQ_SLOT(updateStatus(bool)) );
|
|
|
|
connect(_field, TQ_SIGNAL(gameStateChanged(GameState)),
|
|
|
|
TQ_SLOT(gameStateChangedSlot(GameState)) );
|
|
|
|
connect(_field, TQ_SIGNAL(setMood(Mood)), smiley, TQ_SLOT(setMood(Mood)));
|
|
|
|
connect(_field, TQ_SIGNAL(setCheating()), dg, TQ_SLOT(setCheating()));
|
|
|
|
connect(_field,TQ_SIGNAL(addAction(const KGrid2D::Coord &, Field::ActionType)),
|
|
|
|
TQ_SLOT(addAction(const KGrid2D::Coord &, Field::ActionType)));
|
|
|
|
TQWhatsThis::add(_field, i18n("Mines field."));
|
|
|
|
|
|
|
|
// resume button
|
|
|
|
_resumeContainer = new TQWidget(this);
|
|
|
|
g = new TQGridLayout(_resumeContainer, 1, 1);
|
|
|
|
TQFont f = font();
|
|
|
|
f.setBold(true);
|
|
|
|
TQPushButton *pb
|
|
|
|
= new TQPushButton(i18n("Press to Resume"), _resumeContainer);
|
|
|
|
pb->setFont(f);
|
|
|
|
connect(pb, TQ_SIGNAL(clicked()), TQ_SIGNAL(pause()));
|
|
|
|
g->addWidget(pb, 0, 0, AlignCenter);
|
|
|
|
|
|
|
|
_stack = new TQWidgetStack(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();
|
|
|
|
TQColor 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");
|
|
|
|
TQString sa = "none";
|
|
|
|
if ( _field->solvingState()==Solved ) sa = "solving";
|
|
|
|
else if ( _field->solvingState()==Advised ) sa = "advising";
|
|
|
|
_logRoot.setAttribute("solver", sa);
|
|
|
|
|
|
|
|
TQDomElement f = _log.createElement("Field");
|
|
|
|
_logRoot.appendChild(f);
|
|
|
|
TQDomText 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 = TQDomDocument("kmineslog");
|
|
|
|
_logRoot = _log.createElement("kmineslog");
|
|
|
|
_logRoot.setAttribute("version", SHORT_VERSION);
|
|
|
|
TQDateTime date = TQDateTime::currentDateTime();
|
|
|
|
_logRoot.setAttribute("date", date.toString(TQt::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)
|
|
|
|
{
|
|
|
|
TQWidget *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)
|
|
|
|
{
|
|
|
|
TQDomElement 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."),
|
|
|
|
TQString(), TQString(), "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);
|
|
|
|
TQTextEdit *view = new TQTextEdit(&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(TQString(), TQString(), this);
|
|
|
|
if ( url.isEmpty() ) return;
|
|
|
|
if ( TDEIO::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();
|
|
|
|
TDEIO::NetAccess::upload(tmp.name(), url, this);
|
|
|
|
tmp.unlink();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Status::loadLog()
|
|
|
|
{
|
|
|
|
KURL url = KFileDialog::getOpenURL(TQString(), TQString(), this);
|
|
|
|
if ( url.isEmpty() ) return;
|
|
|
|
TQString tmpFile;
|
|
|
|
bool success = false;
|
|
|
|
TQDomDocument doc;
|
|
|
|
if( TDEIO::NetAccess::download(url, tmpFile, this) ) {
|
|
|
|
TQFile 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;
|
|
|
|
}
|
|
|
|
TDEIO::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 TQDomDocument &doc)
|
|
|
|
{
|
|
|
|
// check root element
|
|
|
|
if ( doc.doctype().name()!="kmineslog" ) return false;
|
|
|
|
TQDomElement 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
|
|
|
|
TQDomElement field = root.namedItem("Field").toElement();
|
|
|
|
if ( field.isNull() ) return false;
|
|
|
|
TQString ftext = field.text();
|
|
|
|
if ( !BaseField::checkField(w, h, nb, ftext) ) return false;
|
|
|
|
|
|
|
|
// check action list
|
|
|
|
TQDomElement list = root.namedItem("ActionList").toElement();
|
|
|
|
if ( list.isNull() ) return false;
|
|
|
|
TQDomNodeList actions = list.elementsByTagName("Action");
|
|
|
|
if ( actions.count()==0 ) return false;
|
|
|
|
for (uint i=0; i<actions.count(); i++) {
|
|
|
|
TQDomElement 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;
|
|
|
|
TQString 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);
|
|
|
|
TQDomNode f = _logRoot.namedItem("Field");
|
|
|
|
_oldLevel = _field->level();
|
|
|
|
newGame(level);
|
|
|
|
_field->setReplayField(f.toElement().text());
|
|
|
|
TQString 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 = TQDomNodeList();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
_timer->changeInterval(200);
|
|
|
|
TQDomElement a = _actions.item(_index).toElement();
|
|
|
|
dg->setTime(a.attribute("time"));
|
|
|
|
uint i = a.attribute("column").toUInt();
|
|
|
|
uint j = a.attribute("line").toUInt();
|
|
|
|
TQString 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++;
|
|
|
|
}
|