move game logic to libqnono::CNonogram

This commit is contained in:
Stefan Bühler 2012-04-23 18:04:51 +02:00
parent 281109d1e4
commit 0aa2842b95
25 changed files with 674 additions and 842 deletions

View File

@ -7,14 +7,13 @@ include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_S
include(${QT_USE_FILE})
set(SOURCES_MOC_H
cnonogramsolver.h
cnonogram.h
ccrosspackagemodel.h
ccrosspackagelistmodel.h
)
set(SOURCES_CPP
cnonogram.cpp
cnonogramsolver.cpp
ccrosspackage.cpp
ccrosspackagemodel.cpp
ccrosspackagelistmodel.cpp

View File

@ -21,6 +21,7 @@
#include "nonogramproblem.h"
#include <QDataStream>
#include <QFile>
#include <QDir>
namespace libqnono {
//public:
@ -71,10 +72,12 @@ namespace libqnono {
}
in >> pictureCount;
QString id(identifier());
int i;
for (i = 0; i < pictureCount && !in.atEnd(); i++) {
m_PictureList.append(NonogramProblem());
if (!m_PictureList.last().readFromStream(in)) {
if (!m_PictureList.last().readFromStream(in, id, i)) {
m_PictureList.pop_back();
qCritical("invalid package file - invalid picture");
return false;
@ -174,4 +177,10 @@ namespace libqnono {
}
return p;
}
QString CCrossPackage::identifierFromPath(const QString &fileName) {
QString id(QDir::toNativeSeparators(fileName).section(QDir::separator(), -1, -1, QString::SectionSkipEmpty));
while (id.endsWith(".cpk", Qt::CaseInsensitive) || id.endsWith(".hsc", Qt::CaseInsensitive)) id.chop(4);
return id;
}
}

View File

@ -37,6 +37,7 @@ namespace libqnono {
void setFileName(QString & value) { m_FileName = value; }
QString fileName() const { return m_FileName; }
QString identifier() const { return identifierFromPath(m_FileName); }
void setName(QString value) { m_Name = value; }
QString name() const { return m_Name; }
@ -52,6 +53,9 @@ namespace libqnono {
static CCrossPackage * read(QString fileName);
static CCrossPackage * readHeader(QString fileName);
/* identifier is basename without extension; the function is idempotent and can be used on identifiers and highscore paths too */
static QString identifierFromPath(const QString &fileName);
protected:
bool doReadHeader(QDataStream & in);
bool doReadData(QDataStream & in);

View File

@ -21,8 +21,8 @@
#include <QIcon>
#include "ccrosspackage.h"
#include "cnonogram.h"
#include "ccrosspackagemodel.h"
#include "nonogramproblem.h"
#include "constants.h"
#define COL_NAME 0
@ -37,23 +37,15 @@ namespace libqnono {
QString result;
if (hours) {
if (hours < 10)
result += '0';
result += QString::number(hours);
result += ':';
result = QString("%1:").arg(hours, 2, 10, QLatin1Char('0'));
}
minutes %= 60;
if (minutes < 10)
result += '0';
result += QString::number(minutes);
result += QString("%1").arg(minutes, 2, 10, QLatin1Char('0'));
if (showSeconds) {
result += ':';
if (strippedSeconds < 10)
result += '0';
result += QString::number(strippedSeconds);
result += QString(":%1").arg(strippedSeconds, 2, 10, QLatin1Char('0'));
}
return result;
}
@ -93,19 +85,19 @@ namespace libqnono {
case COL_NAME:
switch (role) {
case Qt::DecorationRole:
return static_cast<CNonogram *>(index.internalPointer())->timeout() ?
return static_cast<NonogramProblem *>(index.internalPointer())->timeout() ?
QIcon(LIBQCROSS_ICON_TIMEOUT) :
QIcon(LIBQCROSS_ICON_TIMETRIAL);
case Qt::EditRole:
case Qt::DisplayRole:
return static_cast<CNonogram *>(index.internalPointer())->name();
return static_cast<NonogramProblem *>(index.internalPointer())->name();
default:
break;
}
break;
case COL_TIME:
if (role == Qt::DisplayRole)
return formatedTime(static_cast<CNonogram *>(index.internalPointer())->timeout());
return formatedTime(static_cast<NonogramProblem *>(index.internalPointer())->timeout());
break;
default:
break;

View File

@ -18,46 +18,336 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/
#include "cnonogram.h"
#include "nonogramsolver.h"
#include <QImage>
#include <QFile>
#include <QDataStream>
namespace libqnono {
static const quint64 CNonogram_MAGIC = Q_UINT64_C(0x35a8bca32006c5a9);
static const char CNonogram_HEADER[] = {'C', 'R', 'S', 'V'}; /* readable magic */
static const quint64 CNonogram_MAGIC = Q_UINT64_C(0x35a8bca32006c5a9); /* binary magic */
CNonogram::CNonogram() {
CNonogram::CNonogram(QObject *parent) : QObject(parent), m_markedPixels(0), m_time(0), m_errorCount(0), m_preventErrors(true), m_finished(true), m_paused(true) {
}
CNonogram::CNonogram(const NonogramProblem & problem)
: m_problem(problem), m_marker(problem.size()) {
void CNonogram::start(const NonogramProblem & problem) {
m_problem = problem;
m_marker.reset(problem.size());
m_markedPixels = 0;
m_time = problem.timeout();
m_errorCount = 0;
m_preventErrors = problem.timeout() > 0;
m_finished = true;
m_paused = true;
if (!valid()) {
emit loaded();
emit tick(0);
emit timeup();
return;
}
internRestart(true);
emit loaded();
}
bool CNonogram::readFromStream(QDataStream & stream) {
quint64 magic;
stream >> magic;
if (CNonogram_MAGIC != magic || QDataStream::Ok != stream.status()) {
if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData);
void CNonogram::solve() {
if (!valid() || m_finished || m_paused) return;
QList<NonogramImage> solutions = libqnono::solve(m_problem.numbers());
if (!solutions.empty()) {
NonogramImage &sol(solutions.first());
m_errorCount = 0;
//m_time = -1;
m_markedPixels = sol.blackPixels();
for (int i = 0; i < width(); ++i) {
for (int j = 0; j < height(); ++j) {
NonogramMarker::Mark mark = sol.pixel(i, j) ? NonogramMarker::MARKED : NonogramMarker::CROSSED;
if (mark != m_marker.pixel(i, j)) {
m_marker.setPixel(i, j, mark);
emit changedMark(i, j);
}
}
}
m_finished = true;
m_timer.stop();
emit won(m_time);
}
}
bool CNonogram::saveGame(QString fileName) {
qDebug("saving game state file: %s", fileName.toAscii().data());
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
qDebug("couldn't open file for writing");
return false;
}
NonogramProblem problem;
NonogramMarker marker;
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_0);
if (!problem.readFromStream(stream)) return false;
if (!marker.readFromStream(stream)) return false;
writeToStream(out);
if (problem.size() != marker.size()) {
return true;
}
bool CNonogram::loadGame(QString fileName) {
qDebug("opening game state file: %s", fileName.toAscii().data());
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
qDebug("couldn't open file for writing: %s", file.errorString().toAscii().data());
return false;
}
QDataStream in(&file);
in.setVersion(QDataStream::Qt_4_0);
if (!readFromStream(in)) {
switch (in.status()) {
case QDataStream::ReadPastEnd:
qDebug("Unexpected end of file");
break;
case QDataStream::ReadCorruptData:
default:
qDebug("Corrupted save game");
break;
}
return false;
}
return true;
}
bool CNonogram::readFromStream(QDataStream & stream) {
char magicHeader[sizeof(CNonogram_HEADER)];
stream.readRawData(magicHeader, sizeof(CNonogram_HEADER));
if (QDataStream::Ok != stream.status()) return false;
if (0 != memcmp(magicHeader, CNonogram_HEADER, sizeof(CNonogram_HEADER))) {
qDebug("Invalid text magic");
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
quint64 magic;
stream >> magic;
if (QDataStream::Ok != stream.status()) return false;
if (CNonogram_MAGIC != magic) {
qDebug("Invalid binary magic");
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
QString packageIdentifier;
int packageIndex;
stream >> packageIdentifier >> packageIndex;
if (QDataStream::Ok != stream.status()) return false;
NonogramProblem problem;
NonogramMarker marker;
if (!problem.readFromStream(stream, packageIdentifier, packageIndex)) return false;
if (!marker.readFromStream(stream)) return false;
if (problem.size() != marker.size()) {
qDebug("Problem size doesn't match maker size");
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
int time, errorCount;
bool preventErrors;
stream >> time >> errorCount >> preventErrors;
if (QDataStream::Ok != stream.status()) return false;
if (preventErrors && (0 == problem.timeout())) {
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
NonogramImage markerImage(marker);
const NonogramImage &solution(problem.solution());
int markedPixels = markerImage.blackPixels();
bool finished;
if (preventErrors) {
/* check no wrong mark is set */
for (int x = 0; x < problem.width(); ++x) {
for (int y = 0; y < problem.height(); ++y) {
if (markerImage.pixel(x, y) && !solution.pixel(x, y)) {
qDebug("Wrong marked pixel and preventErrors is on: %i, %i", x, y);
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
}
}
finished = (markedPixels == solution.blackPixels());
} else {
finished = (markedPixels == solution.blackPixels() && problem.numbers().check(markerImage));
}
m_problem = problem;
m_marker = marker;
m_markedPixels = markedPixels;
m_time = time;
m_errorCount = errorCount;
m_preventErrors = preventErrors;
m_finished = finished;
m_paused = false;
if (time == 0) {
m_finished = true;
emit loaded();
emit tick(m_time);
emit timeup();
} else if (finished) {
emit loaded();
emit tick(m_time);
emit won(m_time);
} else {
m_timer.start(1000, this);
emit tick(m_time);
emit loaded();
}
return true;
}
void CNonogram::writeToStream(QDataStream & stream) const {
stream << CNonogram_MAGIC << m_problem << m_marker;
stream.writeRawData(CNonogram_HEADER, sizeof(CNonogram_HEADER));
stream << CNonogram_MAGIC;
/* problem meta data; not stored by m_problem itself */
stream << m_problem.packageIdentifier() << m_problem.packageIndex();
stream << m_problem << m_marker;
stream << m_time << m_errorCount << m_preventErrors;
}
// public slots
void CNonogram::setMark(int x, int y, NonogramMarker::Mark mark) {
if (x < 0 || x >= width() || y < 0 || y >= height()) return;
if (m_finished || m_paused) return;
if (m_preventErrors) {
// if pixel already is marked it must be correct - no reason to undo it
if (m_marker.pixel(x, y) == NonogramMarker::MARKED) return;
if (m_marker.pixel(x, y) == mark) return; /* nothing to change */
if (mark == NonogramMarker::MARKED && !m_problem.solution().pixel(x, y)) {
int penalty;
m_marker.setPixel(x, y, NonogramMarker::CROSSED);
++m_errorCount;
switch (m_errorCount) {
case 1: penalty = 2*60; break;
case 2: penalty = 4*60; break;
default: penalty = 8*60; break;
}
if (penalty >= m_time) {
// lost
m_finished = true;
m_timer.stop();
m_time = 0;
emit wrongMark(x, y, penalty);
emit changedMark(x, y);
emit tick(m_time);
emit timeup();
} else {
m_time -= penalty;
emit wrongMark(x, y, penalty);
emit changedMark(x, y);
emit tick(m_time);
}
} else {
// unmarking is prevented above
// if (m_marker.pixel(x, y) == NonogramMarker::MARKED) --m_markedPixels;
m_marker.setPixel(x, y, mark);
if (mark == NonogramMarker::MARKED) ++m_markedPixels;
if (m_markedPixels == m_problem.solution().blackPixels()) {
m_finished = true;
m_timer.stop();
emit changedMark(x, y);
emit won(m_time);
return;
}
emit changedMark(x, y);
}
} else {
if (m_marker.pixel(x, y) == mark) return; /* nothing to change */
if (m_marker.pixel(x, y) == NonogramMarker::MARKED) --m_markedPixels;
m_marker.setPixel(x, y, mark);
if (mark == NonogramMarker::MARKED) ++m_markedPixels;
// blackPixels is an invariant on all solutions for a set of numbers
if (m_markedPixels == m_problem.solution().blackPixels() && m_problem.numbers().check(NonogramImage(m_marker))) {
m_finished = true;
m_timer.stop();
emit changedMark(x, y);
emit won(m_time);
return;
}
emit changedMark(x, y);
}
}
void CNonogram::restart() {
internRestart(false);
}
void CNonogram::pause() {
if (m_finished || m_paused) return;
m_paused = true;
m_timer.stop();
emit paused(m_time);
}
void CNonogram::resume() {
if (m_finished || !m_paused) return;
m_paused = false;
m_timer.start(1000, this);
emit resumed(m_time);
}
// protected
void CNonogram::timerEvent(QTimerEvent * event) {
if (m_finished) {
m_timer.stop();
return;
}
m_time = m_time + (timeout() ? -1 : 1);
if (m_time <= 0) m_time = 0;
if (0 == m_time) {
// lost
m_finished = true;
m_timer.stop();
emit tick(m_time);
emit timeup();
} else {
emit tick(m_time);
}
}
void CNonogram::internRestart(bool newGame) {
if (!valid()) {
emit tick(0);
emit timeup();
return;
}
m_finished = false;
m_errorCount = 0;
m_time = timeout();
m_marker.reset();
m_markedPixels = 0;
m_paused = false;
m_timer.start(1000, this);
emit tick(m_time);
if (!newGame) emit restarted();
}
QDataStream & operator<<(QDataStream& stream, const libqnono::CNonogram& nonogram) {

View File

@ -20,9 +20,11 @@
#ifndef LIBQCROSS_CMONOPICTURE_H
#define LIBQCROSS_CMONOPICTURE_H
#include <QObject>
#include <QSize>
#include <QVector>
#include <QString>
#include <QBasicTimer>
#include "nonogramproblem.h"
#include "nonogrammarker.h"
@ -30,12 +32,14 @@
class QImage;
namespace libqnono {
class CNonogram {
class CNonogram : public QObject {
Q_OBJECT
public:
typedef QVector<quint16> NumbersVector;
CNonogram();
CNonogram(const NonogramProblem & problem);
explicit CNonogram(QObject *parent = 0);
void start(const NonogramProblem & problem);
const NonogramProblem & problem() const { return m_problem; }
QString name() const { return m_problem.name(); }
@ -45,19 +49,59 @@ namespace libqnono {
const NonogramMarker & marker() const { return m_marker; }
bool valid() const { return (!size().isEmpty()) && (0 != m_problem.solution().blackPixels()); }
QSize size() const { return m_problem.size(); }
int width() const { return size().width(); }
int height() const { return size().height(); }
/* returns false if set MARKED on white solution pixel (it still sets the mark) */
bool setMark(int x, int y, NonogramMarker::Mark mark);
int time() const { return m_time; }
bool isPaused() const { return m_paused; }
bool isFinished() const { return m_finished; }
bool saveGame(QString fileName);
bool loadGame(QString fileName);
bool readFromStream(QDataStream & stream);
void writeToStream(QDataStream& stream) const;
public slots:
void setMark(int x, int y, NonogramMarker::Mark mark);
void restart();
void pause();
void resume();
void solve();
signals:
void loaded(); /* new problem/loaded savegame */
void won(int score); /* score is the current time */
void timeup();
void wrongMark(int x, int y, int penaltyTime);
void changedMark(int x, int y);
void restarted();
void paused(int time);
void resumed(int time);
void tick(int time);
protected:
virtual void timerEvent(QTimerEvent * event);
void internRestart(bool newGame);
QBasicTimer m_timer;
NonogramProblem m_problem;
NonogramMarker m_marker;
int m_markedPixels;
int m_time; /* timeout() == 0: used time, timeout() > 0: remaining time */
int m_errorCount;
bool m_preventErrors; /* (timeout() > 0) for now - must be false if timeout() == 0 */
bool m_finished, m_paused;
};
QDataStream & operator<<(QDataStream & stream, const CNonogram & nonogram);

View File

@ -1,37 +0,0 @@
#include "cnonogramsolver.h"
#include "cnonogram.h"
#include "nonogramsolver.h"
#include <QString>
#include <QDebug>
namespace libqnono {
CNonogramSolver::CNonogramSolver(QObject * parent)
: QObject(parent), m_Nonogram(0) {
}
CNonogramSolver::~CNonogramSolver() {
}
void CNonogramSolver::setNonogram(CNonogram * nonogram) {
m_Nonogram = nonogram;
}
bool CNonogramSolver::solve() {
if (!m_Nonogram) return false;
QList<NonogramImage> solutions = libqnono::solve(m_Nonogram->numbers());
if (!solutions.empty()) {
NonogramImage &sol(solutions.first());
for (int i = 0; i < m_Nonogram->width(); ++i) {
for (int j = 0; j < m_Nonogram->height(); ++j) {
emit markRequested(i, j, sol.pixel(i, j) ? CMT_MARKED : CMT_CROSSED);
}
}
return TRUE;
}
return FALSE;
}
}

View File

@ -1,29 +0,0 @@
#ifndef LIBQCROSS_CNONOGRAMSOLVER_H
#define LIBQCROSS_CNONOGRAMSOLVER_H
#include <QObject>
#include <QSize>
#include <QList>
namespace libqnono {
class CNonogram;
class CNonogramSolver : public QObject {
Q_OBJECT
public:
CNonogramSolver(QObject * parent = 0);
~CNonogramSolver();
void setNonogram(CNonogram * nonogram);
public slots:
bool solve();
signals:
void markRequested(int x, int y, int type);
protected:
enum MarkerType {CMT_UNMARKED = 0, CMT_MARKED = 1, CMT_CROSSED = 2, CMT_NONE = 3};
CNonogram * m_Nonogram;
};
}
#endif

View File

@ -24,34 +24,6 @@
namespace libqnono {
static const quint64 NonogramImage_DataStream_MAGIC = Q_UINT64_C(0xe47028650d925b33);
class NonogramImageViewRowColumn : public NonogramImageView {
public:
NonogramImageViewRowColumn(NonogramImage & img) : m_img(img) { }
virtual bool pixel(int row, int col) const {
return m_img.pixel(col, row);
}
virtual bool & pixel(int row, int col) {
return m_img.pixel(col, row);
}
private:
NonogramImage &m_img;
};
class NonogramImageViewColumnRow : public NonogramImageView {
public:
NonogramImageViewColumnRow(NonogramImage & img) : m_img(img) { }
virtual bool pixel(int col, int row) const {
return m_img.pixel(col, row);
}
virtual bool & pixel(int col, int row) {
return m_img.pixel(col, row);
}
private:
NonogramImage &m_img;
};
NonogramImage::NonogramImage() : m_size(0,0), m_data(0), m_blackPixels(0) {
}
@ -74,6 +46,11 @@ namespace libqnono {
load(image);
}
NonogramImage::NonogramImage(const NonogramMarker & marker)
: m_size(0, 0), m_data(0), m_blackPixels(0) {
load(marker);
}
NonogramImage::~NonogramImage() {
delete [] m_data; m_data = 0;
}
@ -127,14 +104,6 @@ namespace libqnono {
m_size = size;
}
NonogramImageView* NonogramImage::viewRowColumn() {
return new NonogramImageViewRowColumn(*this);
}
NonogramImageView* NonogramImage::viewColumnRow() {
return new NonogramImageViewColumnRow(*this);
}
bool NonogramImage::readFromStream(QDataStream & stream) {
quint64 magic;
stream >> magic;

View File

@ -25,24 +25,18 @@
namespace libqnono {
class NonogramMarker;
class NonogramImageView {
public:
virtual bool pixel(int coord1, int coord2) const = 0;
virtual bool & pixel(int coord1, int coord2) = 0;
};
class NonogramImage {
public:
NonogramImage();
NonogramImage(QSize size);
explicit NonogramImage(QSize size);
NonogramImage(const NonogramImage & other);
NonogramImage(const QImage & image);
explicit NonogramImage(const QImage & image);
explicit NonogramImage(const NonogramMarker & marker);
~NonogramImage();
NonogramImage& operator=(const NonogramImage & other);
bool operator==(const NonogramImage & other) const;
bool pixel(int x, int y) const { return m_data[y*m_size.width()+x]; }
bool & pixel(int x, int y) { return m_data[y*m_size.width()+x]; }
void setPixel(int x, int y, bool value) { m_data[y*m_size.width()+x] = value; }
void fill(bool value);
@ -53,9 +47,6 @@ namespace libqnono {
int height() const { return m_size.height(); }
void resize(QSize size);
NonogramImageView* viewRowColumn();
NonogramImageView* viewColumnRow();
bool readFromStream(QDataStream & stream);
void writeToStream(QDataStream & stream) const;

View File

@ -62,6 +62,20 @@ namespace libqnono {
return true;
}
void NonogramMarker::reset() {
int n = m_size.width() * m_size.height();
for (int i = 0; i < n; ++i) m_data[i] = NONE;
}
void NonogramMarker::reset(QSize size) {
delete [] m_data; m_data = 0;
m_size = size;
int n = m_size.width() * m_size.height();
m_data = new Mark[n];
for (int i = 0; i < n; ++i) m_data[i] = NONE;
}
bool NonogramMarker::readFromStream(QDataStream & stream) {
quint64 magic;
stream >> magic;

View File

@ -29,7 +29,7 @@ namespace libqnono {
enum Mark { NONE = 0, MARKED = 1, CROSSED = 2 };
NonogramMarker();
NonogramMarker(QSize size);
explicit NonogramMarker(QSize size);
NonogramMarker(const NonogramMarker& other);
~NonogramMarker();
NonogramMarker& operator=(const NonogramMarker& other);
@ -43,6 +43,9 @@ namespace libqnono {
int width() const { return m_size.width(); }
int height() const { return m_size.height(); }
void reset();
void reset(QSize size);
bool readFromStream(QDataStream & stream);
void writeToStream(QDataStream & stream) const;

View File

@ -33,8 +33,8 @@ namespace libqnono {
NonogramNumbers();
NonogramNumbers(const vv_num & rows, const vv_num & columns);
NonogramNumbers(const NonogramImage & image);
NonogramNumbers(const QImage & image);
explicit NonogramNumbers(const NonogramImage & image);
explicit NonogramNumbers(const QImage & image);
void calcFromImage(const NonogramImage & image);

View File

@ -52,7 +52,7 @@ namespace libqnono {
m_numbers.calcFromImage(m_solution);
}
bool NonogramProblem::readFromStream(QDataStream & stream) {
bool NonogramProblem::readFromStream(QDataStream & stream, QString packageIdentifier, int packageIndex) {
quint64 magic;
stream >> magic;
if (NonogramProblem_MAGIC != magic || QDataStream::Ok != stream.status()) {
@ -70,6 +70,8 @@ namespace libqnono {
m_name = name;
m_timeout = timeout;
m_numbers.calcFromImage(m_solution);
m_PackageIdentifier = packageIdentifier;
m_PackageIndex = packageIndex;
return true;
}

View File

@ -36,6 +36,9 @@ namespace libqnono {
const NonogramImage & solution() const { return m_solution; }
const NonogramNumbers & numbers() const { return m_numbers; }
QString packageIdentifier() const { return m_PackageIdentifier; }
int packageIndex() const { return m_PackageIndex; }
QSize size() const { return m_solution.size(); }
int width() const { return size().width(); }
int height() const { return size().height(); }
@ -50,7 +53,7 @@ namespace libqnono {
void fill(bool value);
void loadFromImage(const NonogramImage & solution);
bool readFromStream(QDataStream & stream);
bool readFromStream(QDataStream & stream, QString packageIdentifier = QString(), int packageIndex = -1);
void writeToStream(QDataStream & stream) const;
private:
@ -59,6 +62,10 @@ namespace libqnono {
QString m_name;
quint16 m_timeout;
/* not saved directly */
QString m_PackageIdentifier;
int m_PackageIndex;
};
QDataStream & operator<<(QDataStream & stream, const NonogramProblem & problem);

View File

@ -20,7 +20,6 @@
#include "ccrossfieldwidget.h"
#include <libqnono/cnonogram.h>
#include <libqnono/cnonogramsolver.h>
#include <QPainter>
#include <QMouseEvent>
@ -44,19 +43,13 @@ namespace qcross {
}
//public
CCrossFieldWidget::CCrossFieldWidget(CNonogram * picture, QWidget * parent) : QWidget(parent),
m_Picture(picture),
m_OverlayData(NULL),
m_MouseMark(CMT_NONE),
m_ErrorAware(false),
m_NumbersMarkable(true),
m_Time(0),
m_Paused(true),
m_Solved(false),
m_TimerId(-1),
CCrossFieldWidget::CCrossFieldWidget(QWidget * parent) : QWidget(parent),
m_MessageTimeoutId(-1),
m_Clock(NULL),
m_ErrorCount(0),
m_Nonogram(new CNonogram(this)),
m_MouseMark(NonogramMarker::NONE),
m_MouseDown(false),
m_NumbersMarkable(true),
m_Clock(new QLCDNumber(8, this)),
m_LastErrorMark(-1, -1)
{
// m_Notifier = new QFrame(this);
@ -80,51 +73,27 @@ namespace qcross {
// m_Notifier->hide();
m_Clock = new QLCDNumber(8, this);
m_Clock->setVisible(m_Picture);
if (m_Picture) {
initialize();
m_RemainingPixels = m_Picture->solution().blackPixels();
updateTimeDisplay();
}
m_Clock->setVisible(false);
setEnabled(false);
connect(m_Nonogram, SIGNAL(loaded()), SLOT(loaded()));
connect(m_Nonogram, SIGNAL(won(int)), SLOT(won(int)));
connect(m_Nonogram, SIGNAL(timeup()), SLOT(timeup()));
connect(m_Nonogram, SIGNAL(wrongMark(int,int,int)), SLOT(wrongMark(int,int,int)));
connect(m_Nonogram, SIGNAL(changedMark(int,int)), SLOT(changedMark(int,int)));
connect(m_Nonogram, SIGNAL(restarted()), SLOT(restarted()));
connect(m_Nonogram, SIGNAL(paused(int)), SLOT(paused(int)));
connect(m_Nonogram, SIGNAL(resumed(int)), SLOT(resumed(int)));
connect(m_Nonogram, SIGNAL(tick(int)), SLOT(tick(int)));
}
CCrossFieldWidget::~CCrossFieldWidget() {
cleanup();
// delete m_Clock;
}
void CCrossFieldWidget::setPicture(CNonogram * picture) {
if (m_Picture)
cleanup();
m_Picture = picture;
if (m_Picture) {
m_LastErrorMark.setX(-1);
m_LastErrorMark.setY(-1);
m_ErrorCount = 0;
m_Time = picture->timeout();
m_ErrorAware = picture->timeout();
m_Solved = false;
updateTimeDisplay();
emit timeChanged(m_Time);
initialize();
m_Messages.clear();
nextMessage();
updateMetrics();
m_RemainingPixels = m_Picture->solution().blackPixels();
qDebug("m_MessageTimeoutId = %i", m_MessageTimeoutId);
if (m_MessageTimeoutId != -1) {
killTimer(m_MessageTimeoutId);
m_MessageTimeoutId = -1;
}
m_Clock->setVisible(m_Picture);
}
void CCrossFieldWidget::showMessage(const QString message, int timeout, MessageType type) {
@ -140,134 +109,54 @@ namespace qcross {
nextMessage();
}
void CCrossFieldWidget::applyState(QDataStream & stream) {
qDebug("trying to apply state...");
qDebug("reading basic stuff:");
// error state
stream >> m_ErrorAware; qDebug("m_ErrorAware = %i", int(m_ErrorAware));
stream >> m_ErrorCount; qDebug("m_ErrorCount = %i", m_ErrorCount);
// time(out)
stream >> m_Time; qDebug("m_Time = %i", m_Time);
updateTimeDisplay();
emit timeChanged(m_Time);
// paused
stream >> m_Paused; qDebug("m_Paused = %i", int(m_Paused));
setDisabled(m_Paused);
// messages
int messageCount;
stream >> messageCount; qDebug("messageCount = %i", messageCount);
m_Messages.clear();
unsigned char byteBuffer;
for (int i = 0; i < messageCount; i++) {
qDebug("message #%i:", i);
stream >> byteBuffer; qDebug("type = %i", byteBuffer);
m_Message.type = MessageType(byteBuffer);
if (m_Message.type != Invalid) {
stream >> m_Message.timeout; qDebug("timeout = %i", m_Message.timeout);
stream >> m_Message.text; qDebug("text = %s", qPrintable(m_Message.text));
m_Messages.enqueue(m_Message);
}
}
nextMessage();
// overlaydata
for (int iy = 0; iy < m_Picture->height(); iy++) {
for (int ix = 0; ix < m_Picture->width(); ix++) {
stream >> byteBuffer;
m_OverlayData[ix][iy] = MarkerType(byteBuffer);
}
}
}
void CCrossFieldWidget::dumpState(QDataStream & stream) {
if (!m_OverlayData || !m_Picture)
return;
// error state
stream << m_ErrorAware;
stream << m_ErrorCount;
// time(out)
stream << m_Time;
// paused state
stream << m_Paused;
// messages
// actually we should dump the message queue
// for now only dump last displayed
// TODO dump message queue
stream << 1; // dummy message count
stream << (unsigned char)(m_Message.type);
if (m_Message.type != Invalid) {
stream << m_Message.timeout;
stream << m_Message.text;
}
// overlaydata
for (int iy = 0; iy < m_Picture->height(); iy++) {
for (int ix = 0; ix < m_Picture->width(); ix++)
stream << (unsigned char)(m_OverlayData[ix][iy]);
}
}
//public slots
void CCrossFieldWidget::mark(int x, int y, int type) {
if (type == CMT_NONE || x < 0 || x >= m_Picture->width() || y < 0 || y >= m_Picture->height())
return;
MarkerType marker = static_cast<qcross::MarkerType>(type);
execMark(x, y, marker);
}
void CCrossFieldWidget::setTime(int value) {
if (value < 0)
value = 0;
if (m_Time != value) {
m_Time = value;
if (m_ErrorAware && !m_Time) {
killTimer(m_TimerId);
m_TimerId = -1;
m_Paused = true;
emit timeUp();
}
updateTimeDisplay();
emit timeChanged(m_Time);
}
}
void CCrossFieldWidget::setErrorAware(bool value) {
m_ErrorAware = value;
}
void CCrossFieldWidget::setNumbersMarkable(bool value) {
m_NumbersMarkable = value;
}
void CCrossFieldWidget::start() {
reset();
resume();
//protected slots:
void CCrossFieldWidget::loaded() {
m_LastErrorMark = QPoint(-1, -1);
initialize();
updateTimeDisplay();
m_Messages.clear();
if (m_Nonogram->valid()) {
if (m_Nonogram->time() == m_Nonogram->timeout()) {
showMessage(tr("Game started!"), 1000);
} else {
showMessage(tr("Save game loaded!"), 1000);
}
setEnabled(true);
} else {
nextMessage();
setEnabled(false);
}
}
void CCrossFieldWidget::pause() {
if (m_Solved)
return;
killTimer(m_TimerId);
m_TimerId = -1;
void CCrossFieldWidget::won(int score) {
showMessage(tr("Congratulations! You've solved the puzzle."));
}
void CCrossFieldWidget::timeup() {
showMessage(tr("Too bad! Time's up."), 0, CCrossFieldWidget::Critical);
}
void CCrossFieldWidget::wrongMark(int x, int y, int penaltyTime) {
m_LastErrorMark = QPoint(x, y);
showMessage(tr("Sorry this was not correct: -%1min").arg(penaltyTime/60), 1000, CCrossFieldWidget::Warning);
}
void CCrossFieldWidget::changedMark(int x, int y) {
update();
}
void CCrossFieldWidget::restarted() {
m_Messages.clear();
m_LastErrorMark = QPoint(-1, -1);
showMessage(tr("Game restarted."), 1000);
}
void CCrossFieldWidget::paused(int time) {
if (m_Message.type != Invalid) {
m_Messages.enqueue(m_Message);
@ -276,19 +165,18 @@ namespace qcross {
m_MessageTimeoutId = -1;
}
}
m_Paused = true;
setEnabled(false);
showMessage(tr("Game paused."));
}
void CCrossFieldWidget::resume() {
if (m_Solved)
return;
m_Paused = false;
void CCrossFieldWidget::resumed(int time) {
setEnabled(true);
showMessage(tr("Game resumed."), 1000);
update();
m_TimerId = startTimer(1000);
}
void CCrossFieldWidget::tick(int time) {
updateTimeDisplay();
}
//protected
@ -333,78 +221,34 @@ namespace qcross {
}
void CCrossFieldWidget::initialize() {
m_OverlayData = new MarkerType *[m_Picture->width()];
for (int i = 0; i < m_Picture->width(); i++) {
m_OverlayData[i] = new MarkerType[m_Picture->height()];
for (int j = 0; j < m_Picture->height(); j++)
m_OverlayData[i][j] = CMT_UNMARKED;
}
int minimumW = max(m_Picture->numbers().maximumNumberCount()+1, 5);
int minimumW = max(m_Nonogram->numbers().maximumNumberCount()+1, 5);
int minimumH = minimumW;
minimumW += m_Picture->width();
minimumH += m_Picture->height();
minimumW += m_Nonogram->width();
minimumH += m_Nonogram->height();
int minimumBoxSize = max(fontMetrics().width(QString::number(m_Picture->height())), fontMetrics().width(QString::number(m_Picture->width())));
int minimumBoxSize = max(fontMetrics().width(QString::number(m_Nonogram->height())), fontMetrics().width(QString::number(m_Nonogram->width())));
minimumBoxSize = max(minimumBoxSize, fontInfo().pixelSize() * 1.5);
minimumW *= minimumBoxSize;
minimumH *= minimumBoxSize;
setMinimumSize(minimumW, minimumH);
}
void CCrossFieldWidget::cleanup() {
qDebug("m_TimerId = %i m_MessageTimeoutId = %i", m_TimerId, m_MessageTimeoutId);
if (m_TimerId != -1) {
killTimer(m_TimerId);
m_TimerId = -1;
}
if (m_MessageTimeoutId != -1) {
killTimer(m_MessageTimeoutId);
m_MessageTimeoutId = -1;
}
if (m_OverlayData) {
for (int i = 0; i < m_Picture->width(); i++)
delete[] m_OverlayData[i];
delete[] m_OverlayData;
m_OverlayData = NULL;
}
}
void CCrossFieldWidget::reset() {
m_Messages.clear();
nextMessage();
for (int i = 0; i < m_Picture->width(); i++) {
for (int j = 0; j < m_Picture->height(); j++)
m_OverlayData[i][j] = CMT_UNMARKED;
}
m_Solved = false;
m_ErrorCount = 0;
m_LastErrorMark.setX(-1);
m_LastErrorMark.setY(-1);
m_RemainingPixels = m_Picture->solution().blackPixels();
}
bool CCrossFieldWidget::checkNoError(int x, int y) {
return (m_OverlayData[x][y] == CMT_MARKED) || m_Picture->solution().pixel(x, y);
updateMetrics();
}
void CCrossFieldWidget::timerEvent(QTimerEvent * event) {
if (event->timerId() == m_TimerId)
setTime(m_Time + (m_ErrorAware ? -1 : +1));
else if (event->timerId() == m_MessageTimeoutId)
if (event->timerId() == m_MessageTimeoutId) {
nextMessage();
}
}
void CCrossFieldWidget::updateTimeDisplay() {
quint8 seconds = m_Time % 60;
quint8 minutes = m_Time / 60;
m_Clock->setVisible(m_Nonogram->valid());
int secs = m_Nonogram->time();
quint8 seconds = secs % 60;
quint8 minutes = secs / 60;
quint8 hours = minutes / 60;
minutes %= 60;
@ -426,59 +270,8 @@ namespace qcross {
m_Clock->display(time);
}
void CCrossFieldWidget::execMark(int x, int y, MarkerType & marker) {
if (m_Solved) return;
switch (marker) {
case CMT_MARKED:
if (m_Picture->solution().pixel(x, y)) {
m_RemainingPixels--;
m_OverlayData[x][y] = marker;
}
else {
m_ErrorCount++;
if (m_ErrorAware) {
m_OverlayData[x][y] = CMT_CROSSED;
marker = CMT_NONE;
m_LastErrorMark.setX(x);
m_LastErrorMark.setY(y);
emit markError();
}
else
m_OverlayData[x][y] = marker;
}
break;
case CMT_CROSSED:
case CMT_UNMARKED:
if (m_ErrorAware && x == m_LastErrorMark.x() && y == m_LastErrorMark.y()) {
m_LastErrorMark.setX(-1);
m_LastErrorMark.setY(-1);
}
if (m_OverlayData[x][y] == CMT_MARKED) {
if (m_Picture->solution().pixel(x, y))
m_RemainingPixels++;
else if (!m_ErrorAware)
m_ErrorCount--;
}
m_OverlayData[x][y] = marker;
break;
default:
break;
}
if (!m_RemainingPixels && (m_ErrorAware || !m_ErrorCount)) {
killTimer(m_TimerId);
m_TimerId = -1;
m_Paused = true;
m_Solved = true;
emit solved();
}
}
void CCrossFieldWidget::paintEvent(QPaintEvent *) {
if (!m_Picture) {
if (!m_Nonogram) {
return;
}
@ -541,23 +334,23 @@ namespace qcross {
errorColor.setAlpha(128);
painter.setBrush(markerColor);
for (int i = 0; i < m_Picture->width(); i++) {
for (int i = 0; i < m_Nonogram->width(); i++) {
originX = m_OffsetX + m_HeaderWidth + i * m_BoxSize;
markerRect.moveLeft(originX + m_MarkerOffset);
for (int j = 0; j < m_Picture->height(); j++) {
for (int j = 0; j < m_Nonogram->height(); j++) {
originY = m_OffsetY + m_HeaderHeight + j * m_BoxSize;
markerRect.moveTop(originY + m_MarkerOffset);
switch (m_OverlayData[i][j]) {
case CMT_MARKED:
switch (m_Nonogram->marker().pixel(i, j)) {
case NonogramMarker::MARKED:
painter.fillRect(markerRect, painter.brush());
break;
case CMT_CROSSED:
if (m_Solved)
case