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/field.cpp

463 lines
12 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 "field.h"
#include "field.moc"
#include <math.h>
#include <qlayout.h>
#include <qtimer.h>
#include <qpainter.h>
#include <klocale.h>
#include <knotifyclient.h>
#include "settings.h"
#include "solver/solver.h"
#include "dialogs.h"
using namespace KGrid2D;
const Field::ActionData Field::ACTION_DATA[Nb_Actions] = {
{ "Reveal", "reveal", I18N_NOOP("Case revealed") },
{ "AutoReveal", "autoreveal", I18N_NOOP("Case autorevealed") },
{ "SetFlag", "mark", I18N_NOOP("Flag set") },
{ "UnsetFlag", "unmark", I18N_NOOP("Flag unset") },
{ "SetUncertain", "set_uncertain", I18N_NOOP("Question mark set") },
{ "UnsetUncertain", "unset_uncertain", I18N_NOOP("Question mark unset") }
};
Field::Field(QWidget *parent)
: FieldFrame(parent), _state(Init), _solvingState(Regular), _level(Level::Easy)
{}
void Field::readSettings()
{
if ( inside(_cursor) ) {
QPainter p(this);
drawCase(p, _cursor);
}
if ( Settings::magicReveal() ) emit setCheating();
}
QSize Field::sizeHint() const
{
return QSize(2*frameWidth() + _level.width()*Settings::caseSize(),
2*frameWidth() + _level.height()*Settings::caseSize());
}
void Field::setLevel(const Level &level)
{
_level = level;
reset(false);
adjustSize();
}
void Field::setReplayField(const QString &field)
{
setState(Replaying);
initReplay(field);
}
void Field::setState(GameState state)
{
Q_ASSERT( state!=GameOver );
emit gameStateChanged(state);
_state = state;
}
void Field::reset(bool init)
{
BaseField::reset(_level.width(), _level.height(), _level.nbMines());
if ( init || _state==Init ) setState(Init);
else setState(Stopped);
if (Settings::magicReveal()) emit setCheating();
_currentAction = Settings::EnumMouseAction::None;
_reveal = false;
_cursor.first = _level.width()/2;
_cursor.second = _level.height()/2;
_advisedCoord = Coord(-1, -1);
update();
}
void Field::paintEvent(QPaintEvent *e)
{
QPainter painter(this);
drawFrame(&painter);
if ( _state==Paused ) return;
Coord min = fromPoint(e->rect().topLeft());
bound(min);
Coord max = fromPoint(e->rect().bottomRight());
bound(max);
for (short i=min.first; i<=max.first; i++)
for (short j=min.second; j<=max.second; j++)
drawCase(painter, Coord(i,j));
}
void Field::changeCase(const Coord &p, CaseState newState)
{
BaseField::changeCase(p, newState);
QPainter painter(this);
drawCase(painter, p);
if ( isActive() ) emit updateStatus( hasMine(p) );
}
QPoint Field::toPoint(const Coord &p) const
{
QPoint qp;
qp.setX( p.first*Settings::caseSize() + frameWidth() );
qp.setY( p.second*Settings::caseSize() + frameWidth() );
return qp;
}
Coord Field::fromPoint(const QPoint &qp) const
{
double i = (double)(qp.x() - frameWidth()) / Settings::caseSize();
double j = (double)(qp.y() - frameWidth()) / Settings::caseSize();
return Coord((int)floor(i), (int)floor(j));
}
int Field::mapMouseButton(QMouseEvent *e) const
{
switch (e->button()) {
case Qt::LeftButton: return Settings::mouseAction(Settings::EnumButton::left);
case Qt::MidButton: return Settings::mouseAction(Settings::EnumButton::mid);
case Qt::RightButton: return Settings::mouseAction(Settings::EnumButton::right);
default: return Settings::EnumMouseAction::ToggleFlag;
}
}
void Field::revealActions(bool press)
{
if ( _reveal==press ) return; // avoid flicker
_reveal = press;
switch (_currentAction) {
case Reveal:
pressCase(_cursor, press);
break;
case AutoReveal:
pressClearFunction(_cursor, press);
break;
default:
break;
}
}
void Field::mousePressEvent(QMouseEvent *e)
{
if ( !isActive() || (_currentAction!=Settings::EnumMouseAction::None) ) return;
emit setMood(Stressed);
_currentAction = mapMouseButton(e);
Coord p = fromPoint(e->pos());
if ( !inside(p) ) return;
placeCursor(p);
revealActions(true);
}
void Field::mouseReleaseEvent(QMouseEvent *e)
{
if ( !isActive() ) return;
int tmp = _currentAction;
emit setMood(Normal);
revealActions(false);
int ma = mapMouseButton(e);
_currentAction = Settings::EnumMouseAction::None;
if ( ma!=tmp ) return;
Coord p = fromPoint(e->pos());
if ( !inside(p) ) return;
placeCursor(p);
switch (ma) {
case Settings::EnumMouseAction::ToggleFlag: doMark(p); break;
case Settings::EnumMouseAction::ToggleUncertainFlag: doUmark(p); break;
case Settings::EnumMouseAction::Reveal: doReveal(p); break;
case Settings::EnumMouseAction::AutoReveal: doAutoReveal(p); break;
default: break;
}
}
void Field::mouseMoveEvent(QMouseEvent *e)
{
if ( !isActive() ) return;
Coord p = fromPoint(e->pos());
if ( p==_cursor ) return; // avoid flicker
revealActions(false);
if ( !inside(p) ) return;
placeCursor(p);
revealActions(true);
}
void Field::pressCase(const Coord &c, bool pressed)
{
if ( state(c)==Covered ) {
QPainter painter(this);
drawCase(painter, c, pressed);
}
}
void Field::pressClearFunction(const Coord &p, bool pressed)
{
pressCase(p, pressed);
CoordList n = coveredNeighbours(p);
QPainter painter(this);
for (CoordList::const_iterator it=n.begin(); it!=n.end(); ++it)
drawCase(painter, *it, pressed);
}
void Field::keyboardAutoReveal()
{
_cursor_back = _cursor;
pressClearFunction(_cursor_back, true);
QTimer::singleShot(50, this, SLOT(keyboardAutoRevealSlot()));
}
void Field::keyboardAutoRevealSlot()
{
pressClearFunction(_cursor_back, false);
doAutoReveal(_cursor_back);
}
void Field::doAutoReveal(const Coord &c)
{
if ( !isActive() ) return;
if ( state(c)!=Uncovered ) return;
emit addAction(c, AutoReveal);
resetAdvised();
doAction(AutoReveal, c, Settings::magicReveal());
}
void Field::pause()
{
switch (_state) {
case Paused: setState(Playing); break;
case Playing: setState(Paused); break;
default: return;
}
update();
}
void Field::moveCursor(Neighbour n)
{
Coord c = neighbour(_cursor, n);
if ( inside(c) ) placeCursor(c);
}
void Field::moveToEdge(Neighbour n)
{
Coord c = toEdge(_cursor, n);
if ( inside(c) ) placeCursor(c);
}
bool Field::doReveal(const Coord &c, CoordList *autorevealed,
bool *caseUncovered)
{
if ( !isActive() ) return true;
if ( state(c)!=Covered ) return true;
if ( firstReveal() ) setState(Playing);
CaseState state =
doAction(Reveal, c, Settings::magicReveal(), autorevealed, caseUncovered);
emit addAction(c, Reveal);
return ( state!=Error );
}
void Field::doMark(const Coord &c)
{
if ( !isActive() ) return;
ActionType action;
CaseState oldState = state(c);
switch (oldState) {
case Covered: action = SetFlag; break;
case Marked: action = (Settings::uncertainMark() ? SetUncertain : UnsetFlag); break;
case Uncertain: action = UnsetUncertain; break;
default: return;
}
CaseState newState = doAction(action, c, Settings::magicReveal());
addMarkAction(c, newState, oldState);
}
void Field::doUmark(const Coord &c)
{
if ( !isActive() ) return;
ActionType action;
CaseState oldState = state(c);
switch (oldState) {
case Covered:
case Marked: action = SetUncertain; break;
case Uncertain: action = UnsetUncertain; break;
default: return;
}
CaseState newState = doAction(action, c, Settings::magicReveal());
addMarkAction(c, newState, oldState);
}
KMines::CaseState Field::doAction(ActionType type, const Coord &c,
bool complete, CoordList *autorevealed,
bool *caseUncovered)
{
resetAdvised();
CaseState state = Error;
if ( _solvingState==Solved ) complete = false;
KNotifyClient::event(winId(), ACTION_DATA[type].event,
i18n(ACTION_DATA[type].eventMessage));
switch (type) {
case Reveal:
if ( !reveal(c, autorevealed, caseUncovered) )
emit gameStateChanged(GameOver);
else {
state = Uncovered;
if (complete) completeReveal();
}
break;
case AutoReveal:
if ( !autoReveal(c, caseUncovered) )
emit gameStateChanged(GameOver);
else {
state = Uncovered;
if (complete) completeReveal();
}
break;
case SetFlag:
state = Marked;
if (complete) completeReveal();
break;
case UnsetFlag:
case UnsetUncertain:
state = Covered;
break;
case SetUncertain:
state = Uncertain;
break;
case Nb_Actions:
Q_ASSERT(false);
break;
}
if ( state!=Error ) changeCase(c, state);
return state;
}
void Field::addMarkAction(const Coord &c, CaseState newS, CaseState oldS)
{
switch (newS) {
case Marked: emit addAction(c, SetFlag); return;
case Uncertain: emit addAction(c, SetUncertain); return;
default: break;
}
switch (oldS) {
case Marked: emit addAction(c, UnsetFlag); return;
case Uncertain: emit addAction(c, UnsetUncertain); return;
default: break;
}
}
void Field::placeCursor(const Coord &p)
{
if ( !isActive() ) return;
Q_ASSERT( inside(p) );
Coord old = _cursor;
_cursor = p;
if ( Settings::keyboardGame() ) {
QPainter painter(this);
drawCase(painter, old);
drawCase(painter, _cursor);
}
}
void Field::resetAdvised()
{
if ( !inside(_advisedCoord) ) return;
QPainter p(this);
Coord tmp = _advisedCoord;
_advisedCoord = Coord(-1, -1);
drawCase(p, tmp);
}
void Field::setAdvised(const Coord &c, double proba)
{
resetAdvised();
_solvingState = Advised;
_advisedCoord = c;
_advisedProba = proba;
if ( inside(c) ) {
QPainter p(this);
drawCase(p, c);
}
}
void Field::drawCase(QPainter &painter, const Coord &c, bool pressed) const
{
Q_ASSERT( inside(c) );
QString text;
uint nbMines = 0;
PixmapType type = NoPixmap;
switch ( state(c) ) {
case Covered:
break;
case Marked:
type = FlagPixmap;
pressed = false;
break;
case Error:
type = ErrorPixmap;
pressed = true;
break;
case Uncertain:
text = '?';
pressed = false;
break;
case Exploded:
type = ExplodedPixmap;
pressed = true;
break;
case Uncovered:
pressed = true;
if ( hasMine(c) ) type = MinePixmap;
else {
nbMines = nbMinesAround(c);
if (nbMines) text.setNum(nbMines);
}
}
int i = -1;
if ( c==_advisedCoord ) {
if ( _advisedProba==1 ) i = 0;
else if ( _advisedProba>0.75 ) i = 1;
else if ( _advisedProba>0.5 ) i = 2;
else if ( _advisedProba>0.25 ) i = 3;
else i = 4;
}
bool hasFocus = ( Settings::keyboardGame() && (c==_cursor) );
drawBox(painter, toPoint(c), pressed, type, text, nbMines, i, hasFocus);
}