Compare commits
No commits in common. "master" and "stefan-wip" have entirely different histories.
master
...
stefan-wip
@ -7,21 +7,17 @@ include_directories(${QT_INCLUDES} ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_S
|
|||||||
include(${QT_USE_FILE})
|
include(${QT_USE_FILE})
|
||||||
|
|
||||||
set(SOURCES_MOC_H
|
set(SOURCES_MOC_H
|
||||||
cnonogram.h
|
cnonogramsolver.h
|
||||||
ccrosspackagemodel.h
|
ccrosspackagemodel.h
|
||||||
ccrosspackagelistmodel.h
|
ccrosspackagelistmodel.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set(SOURCES_CPP
|
set(SOURCES_CPP
|
||||||
cnonogram.cpp
|
cnonogram.cpp
|
||||||
|
cnonogramsolver.cpp
|
||||||
ccrosspackage.cpp
|
ccrosspackage.cpp
|
||||||
ccrosspackagemodel.cpp
|
ccrosspackagemodel.cpp
|
||||||
ccrosspackagelistmodel.cpp
|
ccrosspackagelistmodel.cpp
|
||||||
nonogramimage.cpp
|
|
||||||
nonogrammarker.cpp
|
|
||||||
nonogramnumbers.cpp
|
|
||||||
nonogramproblem.cpp
|
|
||||||
nonogramsolver.cpp
|
|
||||||
)
|
)
|
||||||
|
|
||||||
qt4_wrap_cpp(SOURCES_MOC_CPP ${SOURCES_MOC_H})
|
qt4_wrap_cpp(SOURCES_MOC_CPP ${SOURCES_MOC_H})
|
||||||
|
@ -18,40 +18,78 @@
|
|||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
#include "ccrosspackage.h"
|
#include "ccrosspackage.h"
|
||||||
#include "nonogramproblem.h"
|
#include "cnonogram.h"
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QDir>
|
|
||||||
|
|
||||||
namespace libqnono {
|
namespace libqnono {
|
||||||
//public:
|
//public:
|
||||||
CCrossPackage::CCrossPackage() : m_headersOnly(false) {}
|
CCrossPackage::CCrossPackage() : m_File(NULL), m_headersOnly(false) {}
|
||||||
CCrossPackage::~CCrossPackage() {} /* destructor needs more includes than the header has */
|
|
||||||
|
|
||||||
bool CCrossPackage::doReadHeader(QDataStream & in) {
|
CCrossPackage::~CCrossPackage() {
|
||||||
|
close();
|
||||||
|
|
||||||
|
foreach (CNonogram * i, m_PictureList) {
|
||||||
|
delete i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CCrossPackage::open() {
|
||||||
|
close();
|
||||||
|
|
||||||
|
if (m_FileName.isEmpty())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
qDebug("opening file: %s", m_FileName.toAscii().data());
|
||||||
|
m_File = new QFile(m_FileName);
|
||||||
|
if (!m_File->open(QIODevice::ReadOnly)) {
|
||||||
|
delete m_File;
|
||||||
|
m_File = NULL;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCrossPackage::close() {
|
||||||
|
if (m_File) {
|
||||||
|
m_File->close();
|
||||||
|
delete m_File;
|
||||||
|
m_File = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CCrossPackage::doReadHeader() {
|
||||||
|
QDataStream in(m_File);
|
||||||
|
in.setVersion(QDataStream::Qt_4_0);
|
||||||
|
|
||||||
QString stringBuffer;
|
QString stringBuffer;
|
||||||
QSize sizeBuffer;
|
QSize sizeBuffer;
|
||||||
|
|
||||||
qDebug("reading header");
|
qDebug("reading header");
|
||||||
if (in.atEnd()) {
|
if (in.atEnd()) {
|
||||||
qCritical("invalid package file - no header");
|
qCritical("invalid package file - no header");
|
||||||
|
close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
in >> stringBuffer;
|
in >> stringBuffer;
|
||||||
|
|
||||||
if ((stringBuffer != "QCROSSPACKAGE")) {
|
if ((stringBuffer != "QCROSSPACKAGE")) {
|
||||||
qCritical("invalid package file - invalid header: %s", stringBuffer.toUtf8().data());
|
qCritical("invalid package file - invalid header: %s", stringBuffer.toAscii().data());
|
||||||
|
close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (in.atEnd()) {
|
if (in.atEnd()) {
|
||||||
qCritical("invalid package file - no package name");
|
qCritical("invalid package file - no package name");
|
||||||
|
close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
in >> stringBuffer;
|
in >> stringBuffer;
|
||||||
|
|
||||||
if (stringBuffer.isEmpty()) {
|
if (stringBuffer.isEmpty()) {
|
||||||
qCritical("invalid package file - invalid package name");
|
qCritical("invalid package file - invalid package name");
|
||||||
|
close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
m_Name = stringBuffer;
|
m_Name = stringBuffer;
|
||||||
@ -61,27 +99,36 @@ namespace libqnono {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCrossPackage::doReadData(QDataStream & in) {
|
bool CCrossPackage::doReadData() {
|
||||||
|
if (!m_File)
|
||||||
|
return false;
|
||||||
|
|
||||||
m_PictureList.clear();
|
m_PictureList.clear();
|
||||||
|
|
||||||
|
QDataStream in(m_File);
|
||||||
|
in.setVersion(QDataStream::Qt_4_0);
|
||||||
|
|
||||||
|
CNonogram * newPicture = NULL;
|
||||||
|
|
||||||
qint32 pictureCount = 0;
|
qint32 pictureCount = 0;
|
||||||
|
|
||||||
if (in.atEnd()) {
|
if (in.atEnd()) {
|
||||||
qCritical("invalid package file - no picture count");
|
qCritical("invalid package file - no picture count");
|
||||||
|
close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
in >> pictureCount;
|
in >> pictureCount;
|
||||||
|
|
||||||
QString id(identifier());
|
|
||||||
|
|
||||||
int i;
|
int i;
|
||||||
for (i = 0; i < pictureCount && !in.atEnd(); i++) {
|
for (i = 0; i < pictureCount && !in.atEnd(); i++) {
|
||||||
m_PictureList.append(NonogramProblem());
|
newPicture = new CNonogram();
|
||||||
if (!m_PictureList.last().readFromStream(in, id, i)) {
|
if (!newPicture->readFromStream(in)) {
|
||||||
m_PictureList.pop_back();
|
|
||||||
qCritical("invalid package file - invalid picture");
|
qCritical("invalid package file - invalid picture");
|
||||||
|
delete newPicture;
|
||||||
|
close();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
m_PictureList << newPicture;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i < pictureCount)
|
if (i < pictureCount)
|
||||||
@ -92,28 +139,28 @@ namespace libqnono {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const NonogramProblem & CCrossPackage::getPicture(int ndx) {
|
CNonogram * CCrossPackage::takePicture(int ndx) {
|
||||||
loadPictures();
|
loadPictures();
|
||||||
return m_PictureList[ndx];
|
CNonogram * result = m_PictureList.takeAt(ndx);
|
||||||
|
unloadPictures();
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CCrossPackage::loadPictures() {
|
bool CCrossPackage::loadPictures() {
|
||||||
if (!m_headersOnly) return true;
|
if (!m_headersOnly) return true;
|
||||||
|
if (!open() || !doReadHeader() || !doReadData()) {
|
||||||
QFile file(m_FileName);
|
close();
|
||||||
qDebug("opening file: %s", m_FileName.toUtf8().data());
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
close();
|
||||||
QDataStream in(&file);
|
return true;
|
||||||
in.setVersion(QDataStream::Qt_4_0);
|
|
||||||
|
|
||||||
return !doReadHeader(in) || !doReadData(in);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossPackage::unloadPictures() {
|
void CCrossPackage::unloadPictures() {
|
||||||
if (!m_PictureList.empty()) {
|
if (!m_PictureList.empty()) {
|
||||||
|
foreach (CNonogram * i, m_PictureList) {
|
||||||
|
delete i;
|
||||||
|
}
|
||||||
m_PictureList.clear();
|
m_PictureList.clear();
|
||||||
m_headersOnly = true;
|
m_headersOnly = true;
|
||||||
}
|
}
|
||||||
@ -122,7 +169,6 @@ namespace libqnono {
|
|||||||
bool CCrossPackage::save() {
|
bool CCrossPackage::save() {
|
||||||
Q_ASSERT(!m_headersOnly);
|
Q_ASSERT(!m_headersOnly);
|
||||||
QFile file(m_FileName);
|
QFile file(m_FileName);
|
||||||
qDebug("opening file for writing: %s", m_FileName.toUtf8().data());
|
|
||||||
if (!file.open(QIODevice::WriteOnly))
|
if (!file.open(QIODevice::WriteOnly))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -133,54 +179,32 @@ namespace libqnono {
|
|||||||
out << m_Name;
|
out << m_Name;
|
||||||
out << (qint32)(m_PictureList.size());
|
out << (qint32)(m_PictureList.size());
|
||||||
|
|
||||||
foreach (const NonogramProblem & i, m_PictureList)
|
foreach (CNonogram * i, m_PictureList)
|
||||||
i.writeToStream(out);
|
i->writeToStream(out);
|
||||||
|
|
||||||
file.close();
|
file.close();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
CCrossPackage * CCrossPackage::read(QString fileName) {
|
CCrossPackage * CCrossPackage::read(QString fileName) {
|
||||||
QFile file(fileName);
|
|
||||||
qDebug("opening file: %s", fileName.toUtf8().data());
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CCrossPackage *p = new CCrossPackage();
|
CCrossPackage *p = new CCrossPackage();
|
||||||
p->setFileName(fileName);
|
p->setFileName(fileName);
|
||||||
|
if (!p->open() || !p->doReadHeader() || !p->doReadData()) {
|
||||||
QDataStream in(&file);
|
|
||||||
in.setVersion(QDataStream::Qt_4_0);
|
|
||||||
if (!p->doReadHeader(in) || !p->doReadData(in)) {
|
|
||||||
delete p;
|
delete p;
|
||||||
return 0;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
p->close();
|
||||||
return p;
|
return p;
|
||||||
}
|
}
|
||||||
|
|
||||||
CCrossPackage * CCrossPackage::readHeader(QString fileName) {
|
CCrossPackage * CCrossPackage::readHeader(QString fileName) {
|
||||||
QFile file(fileName);
|
|
||||||
qDebug("opening file: %s", fileName.toUtf8().data());
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
CCrossPackage *p = new CCrossPackage();
|
CCrossPackage *p = new CCrossPackage();
|
||||||
p->setFileName(fileName);
|
p->setFileName(fileName);
|
||||||
|
if (!p->open() || !p->doReadHeader()) {
|
||||||
QDataStream in(&file);
|
|
||||||
in.setVersion(QDataStream::Qt_4_0);
|
|
||||||
if (!p->doReadHeader(in)) {
|
|
||||||
delete p;
|
delete p;
|
||||||
return 0;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
p->close();
|
||||||
return p;
|
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -26,9 +26,9 @@
|
|||||||
class QFile;
|
class QFile;
|
||||||
|
|
||||||
namespace libqnono {
|
namespace libqnono {
|
||||||
class NonogramProblem;
|
class CNonogram;
|
||||||
|
|
||||||
typedef QList<NonogramProblem> QMonoPictureList;
|
typedef QList<CNonogram *> QMonoPictureList;
|
||||||
|
|
||||||
class CCrossPackage {
|
class CCrossPackage {
|
||||||
public:
|
public:
|
||||||
@ -37,13 +37,12 @@ namespace libqnono {
|
|||||||
|
|
||||||
void setFileName(QString & value) { m_FileName = value; }
|
void setFileName(QString & value) { m_FileName = value; }
|
||||||
QString fileName() const { return m_FileName; }
|
QString fileName() const { return m_FileName; }
|
||||||
QString identifier() const { return identifierFromPath(m_FileName); }
|
|
||||||
|
|
||||||
void setName(QString value) { m_Name = value; }
|
void setName(QString value) { m_Name = value; }
|
||||||
QString name() const { return m_Name; }
|
QString name() const { return m_Name; }
|
||||||
|
|
||||||
QMonoPictureList & pictures() { Q_ASSERT(!m_headersOnly); return m_PictureList; }
|
QMonoPictureList & pictures() { Q_ASSERT(!m_headersOnly); return m_PictureList; }
|
||||||
const NonogramProblem & getPicture(int ndx);
|
CNonogram * takePicture(int ndx);
|
||||||
|
|
||||||
bool loadPictures();
|
bool loadPictures();
|
||||||
void unloadPictures();
|
void unloadPictures();
|
||||||
@ -53,17 +52,18 @@ namespace libqnono {
|
|||||||
static CCrossPackage * read(QString fileName);
|
static CCrossPackage * read(QString fileName);
|
||||||
static CCrossPackage * readHeader(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:
|
protected:
|
||||||
bool doReadHeader(QDataStream & in);
|
bool open();
|
||||||
bool doReadData(QDataStream & in);
|
void close();
|
||||||
|
|
||||||
|
bool doReadHeader();
|
||||||
|
bool doReadData();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QMonoPictureList m_PictureList;
|
QMonoPictureList m_PictureList;
|
||||||
QString m_FileName;
|
QString m_FileName;
|
||||||
QString m_Name;
|
QString m_Name;
|
||||||
|
QFile * m_File;
|
||||||
bool m_headersOnly; /** valid headers but no pictures */
|
bool m_headersOnly; /** valid headers but no pictures */
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -21,8 +21,8 @@
|
|||||||
#include <QIcon>
|
#include <QIcon>
|
||||||
|
|
||||||
#include "ccrosspackage.h"
|
#include "ccrosspackage.h"
|
||||||
|
#include "cnonogram.h"
|
||||||
#include "ccrosspackagemodel.h"
|
#include "ccrosspackagemodel.h"
|
||||||
#include "nonogramproblem.h"
|
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
|
|
||||||
#define COL_NAME 0
|
#define COL_NAME 0
|
||||||
@ -37,15 +37,23 @@ namespace libqnono {
|
|||||||
QString result;
|
QString result;
|
||||||
|
|
||||||
if (hours) {
|
if (hours) {
|
||||||
result = QString("%1:").arg(hours, 2, 10, QLatin1Char('0'));
|
if (hours < 10)
|
||||||
|
result += '0';
|
||||||
|
result += QString::number(hours);
|
||||||
|
result += ':';
|
||||||
}
|
}
|
||||||
|
|
||||||
minutes %= 60;
|
minutes %= 60;
|
||||||
|
|
||||||
result += QString("%1").arg(minutes, 2, 10, QLatin1Char('0'));
|
if (minutes < 10)
|
||||||
|
result += '0';
|
||||||
|
result += QString::number(minutes);
|
||||||
|
|
||||||
if (showSeconds) {
|
if (showSeconds) {
|
||||||
result += QString(":%1").arg(strippedSeconds, 2, 10, QLatin1Char('0'));
|
result += ':';
|
||||||
|
if (strippedSeconds < 10)
|
||||||
|
result += '0';
|
||||||
|
result += QString::number(strippedSeconds);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -73,7 +81,7 @@ namespace libqnono {
|
|||||||
|
|
||||||
QModelIndex CCrossPackageModel::index(int row, int column, const QModelIndex & /*parent*/) const {
|
QModelIndex CCrossPackageModel::index(int row, int column, const QModelIndex & /*parent*/) const {
|
||||||
return (m_Package) && (column >= 0) && (row >= 0) && (row < m_Package->pictures().size()) ?
|
return (m_Package) && (column >= 0) && (row >= 0) && (row < m_Package->pictures().size()) ?
|
||||||
createIndex(row, column, static_cast<void *>(&m_Package->pictures()[row])) :
|
createIndex(row, column, static_cast<void *>(m_Package->pictures()[row])) :
|
||||||
QModelIndex();
|
QModelIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,19 +93,19 @@ namespace libqnono {
|
|||||||
case COL_NAME:
|
case COL_NAME:
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case Qt::DecorationRole:
|
case Qt::DecorationRole:
|
||||||
return static_cast<NonogramProblem *>(index.internalPointer())->timeout() ?
|
return static_cast<CNonogram *>(index.internalPointer())->timeout() ?
|
||||||
QIcon(LIBQCROSS_ICON_TIMEOUT) :
|
QIcon(LIBQCROSS_ICON_TIMEOUT) :
|
||||||
QIcon(LIBQCROSS_ICON_TIMETRIAL);
|
QIcon(LIBQCROSS_ICON_TIMETRIAL);
|
||||||
case Qt::EditRole:
|
case Qt::EditRole:
|
||||||
case Qt::DisplayRole:
|
case Qt::DisplayRole:
|
||||||
return static_cast<NonogramProblem *>(index.internalPointer())->name();
|
return static_cast<CNonogram *>(index.internalPointer())->name();
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case COL_TIME:
|
case COL_TIME:
|
||||||
if (role == Qt::DisplayRole)
|
if (role == Qt::DisplayRole)
|
||||||
return formatedTime(static_cast<NonogramProblem *>(index.internalPointer())->timeout());
|
return formatedTime(static_cast<CNonogram *>(index.internalPointer())->timeout());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
@ -152,12 +160,12 @@ namespace libqnono {
|
|||||||
if (name.isEmpty())
|
if (name.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
foreach (const NonogramProblem & i, m_Package->pictures()) {
|
foreach (CNonogram * i, m_Package->pictures()) {
|
||||||
if (i.name() == name)
|
if (i->name() == name)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Package->pictures()[index.row()].setName(name);
|
m_Package->pictures()[index.row()]->setName(name);
|
||||||
emit dataChanged(index, index);
|
emit dataChanged(index, index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -170,8 +178,9 @@ namespace libqnono {
|
|||||||
|
|
||||||
beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1);
|
beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1);
|
||||||
|
|
||||||
NonogramProblem newNonogram(name, 60*60, NonogramImage(size));
|
CNonogram * newNonogram = new CNonogram(size);
|
||||||
newNonogram.fill(fillValue);
|
newNonogram->fill(fillValue);
|
||||||
|
newNonogram->setName(name);
|
||||||
|
|
||||||
m_Package->pictures() << newNonogram;
|
m_Package->pictures() << newNonogram;
|
||||||
|
|
||||||
@ -183,9 +192,12 @@ namespace libqnono {
|
|||||||
if (!m_Package)
|
if (!m_Package)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
NonogramProblem newNonogram(name, 60*60, NonogramImage(image));
|
CNonogram * newNonogram = new CNonogram();
|
||||||
|
newNonogram->loadFromImage(image);
|
||||||
|
newNonogram->setName(name);
|
||||||
|
newNonogram->setTimeout(60*60); // set an hour as default timeout
|
||||||
|
|
||||||
if (!newNonogram.size().isEmpty()) {
|
if (newNonogram->isValid()) {
|
||||||
beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1);
|
beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1);
|
||||||
|
|
||||||
m_Package->pictures() << newNonogram;
|
m_Package->pictures() << newNonogram;
|
||||||
@ -194,6 +206,7 @@ namespace libqnono {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
|
delete newNonogram;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,8 +217,12 @@ namespace libqnono {
|
|||||||
|
|
||||||
beginInsertRows(parent, row, count);
|
beginInsertRows(parent, row, count);
|
||||||
|
|
||||||
|
CNonogram * newNonogram;
|
||||||
|
|
||||||
for (int i = row; i < row+count; ++i) {
|
for (int i = row; i < row+count; ++i) {
|
||||||
m_Package->pictures().push_back(NonogramProblem());
|
newNonogram = new CNonogram();
|
||||||
|
newNonogram->fill(false);
|
||||||
|
m_Package->pictures() << newNonogram;
|
||||||
}
|
}
|
||||||
|
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
@ -219,7 +236,7 @@ namespace libqnono {
|
|||||||
beginRemoveRows(parent, row, row+count-1);
|
beginRemoveRows(parent, row, row+count-1);
|
||||||
|
|
||||||
for (int i = row; i < row+count; ++i)
|
for (int i = row; i < row+count; ++i)
|
||||||
m_Package->pictures().removeAt(i);
|
delete m_Package->pictures().takeAt(i);
|
||||||
|
|
||||||
endRemoveRows();
|
endRemoveRows();
|
||||||
return true;
|
return true;
|
||||||
|
@ -18,339 +18,343 @@
|
|||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
#include "cnonogram.h"
|
#include "cnonogram.h"
|
||||||
#include "nonogramsolver.h"
|
|
||||||
|
|
||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QFile>
|
|
||||||
#include <QDataStream>
|
|
||||||
|
|
||||||
namespace libqnono {
|
namespace libqnono {
|
||||||
static const char CNonogram_HEADER[] = {'C', 'R', 'S', 'V'}; /* readable magic */
|
static void boolsFromImage(bool ** & data, const QImage &image) {
|
||||||
static const quint64 CNonogram_MAGIC = Q_UINT64_C(0x35a8bca32006c5a9); /* binary magic */
|
int cols = image.width(), rows = image.height();
|
||||||
|
data = new bool*[cols];
|
||||||
CNonogram::CNonogram(QObject *parent) : QObject(parent), m_markedPixels(0), m_time(0), m_errorCount(0), m_preventErrors(true), m_finished(true), m_paused(true) {
|
for (int col = 0; col < cols; ++col) data[col] = new bool[rows];
|
||||||
}
|
for (int row = 0; row < rows; ++row) {
|
||||||
|
for (int col = 0; col < cols; ++col) {
|
||||||
void CNonogram::start(const NonogramProblem & problem) {
|
data[col][row] = 0 == (image.pixel(col, row) & 0x00FFFFFF);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNonogram::solve() {
|
static void freeBools(const QSize & size, bool ** data) {
|
||||||
if (!valid() || m_finished || m_paused) return;
|
int cols = size.width();
|
||||||
|
for (int col = 0; col < cols; ++col) delete [] data[col];
|
||||||
QList<NonogramImage> solutions = libqnono::solve(m_problem.numbers());
|
delete[] data;
|
||||||
if (!solutions.empty()) {
|
}
|
||||||
NonogramImage &sol(solutions.first());
|
|
||||||
m_errorCount = 0;
|
static const quint64 CNonogramNumbers_MAGIC = Q_UINT64_C(0xd33efcacc0050164);
|
||||||
//m_time = -1;
|
|
||||||
m_markedPixels = sol.blackPixels();
|
CNonogramNumbers::CNonogramNumbers(const vv_num & rows, const vv_num & columns)
|
||||||
for (int i = 0; i < width(); ++i) {
|
: m_rows(rows), m_columns(columns) {
|
||||||
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)) {
|
CNonogramNumbers::CNonogramNumbers(const CNonogram & source)
|
||||||
m_marker.setPixel(i, j, mark);
|
: m_rows(source.height()), m_columns(source.width()) {
|
||||||
emit changedMark(i, j);
|
// remove the "0" blocks (they are used for otherwise empty lines)
|
||||||
}
|
for (int i = 0; i < source.height(); ++i) {
|
||||||
|
m_rows[i] = source.rowNumbers(i);
|
||||||
|
if (1 == m_rows[i].count() && 0 == m_rows[i][0]) m_rows[i].clear();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < source.width(); ++i) {
|
||||||
|
m_columns[i] = source.columnNumbers(i);
|
||||||
|
if (1 == m_columns[i].count() && 0 == m_columns[i][0]) m_columns[i].clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogramNumbers::CNonogramNumbers(QSize size, bool **data) {
|
||||||
|
calcFromImage(size, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogramNumbers::CNonogramNumbers(const QImage & image) {
|
||||||
|
bool ** data = 0;
|
||||||
|
boolsFromImage(data, image);
|
||||||
|
calcFromImage(image.size(), data);
|
||||||
|
freeBools(image.size(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNonogramNumbers::calcFromImage(QSize size, bool **data) {
|
||||||
|
int rows = size.height(), cols = size.width();
|
||||||
|
m_rows.clear(); m_rows.resize(rows);
|
||||||
|
m_columns.clear(); m_columns.resize(cols);
|
||||||
|
for (int row = 0; row < rows; ++row) {
|
||||||
|
quint16 *val = 0;
|
||||||
|
for (int col = 0; col < cols; ++col) {
|
||||||
|
bool b = data[col][row];
|
||||||
|
if (!val && b) {
|
||||||
|
m_rows[row].append(1);
|
||||||
|
val = &m_rows[row].last();
|
||||||
|
} else if (b) {
|
||||||
|
++(*val);
|
||||||
|
} else {
|
||||||
|
val = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_finished = true;
|
|
||||||
m_timer.stop();
|
|
||||||
emit won(m_time);
|
|
||||||
}
|
}
|
||||||
}
|
for (int col = 0; col < cols; ++col) {
|
||||||
|
quint16 *val = 0;
|
||||||
bool CNonogram::saveGame(QString fileName) {
|
for (int row = 0; row < rows; ++row) {
|
||||||
qDebug("saving game state file: %s", fileName.toAscii().data());
|
bool b = data[col][row];
|
||||||
QFile file(fileName);
|
if (!val && b) {
|
||||||
if (!file.open(QIODevice::WriteOnly)) {
|
m_columns[col].append(1);
|
||||||
qDebug("couldn't open file for writing");
|
val = &m_columns[col].last();
|
||||||
return false;
|
} else if (b) {
|
||||||
}
|
++(*val);
|
||||||
|
} else {
|
||||||
QDataStream out(&file);
|
val = 0;
|
||||||
out.setVersion(QDataStream::Qt_4_0);
|
}
|
||||||
|
|
||||||
writeToStream(out);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNonogramNumbers::readFromStream(QDataStream & stream) {
|
||||||
|
quint64 magic;
|
||||||
|
stream >> magic;
|
||||||
|
if (CNonogramNumbers_MAGIC != magic || QDataStream::Ok != stream.status()) {
|
||||||
|
if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
stream >> m_rows >> m_columns;
|
||||||
|
return QDataStream::Ok == stream.status();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNonogramNumbers::writeToStream(QDataStream & stream) {
|
||||||
|
stream << CNonogramNumbers_MAGIC << m_rows << m_columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNonogramNumbers::operator==(const CNonogramNumbers &other) const {
|
||||||
|
return m_rows == other.m_rows && m_columns == other.m_columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNonogramNumbers::check(bool **data) const {
|
||||||
|
CNonogramNumbers tmp(size(), data);
|
||||||
|
return *this == tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNonogramNumbers::check(const QImage & image) const {
|
||||||
|
if (size() != image.size()) return FALSE;
|
||||||
|
CNonogramNumbers tmp(image);
|
||||||
|
return *this == tmp;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream & operator<<(QDataStream & stream, CNonogramNumbers & numbers) {
|
||||||
|
numbers.writeToStream(stream);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDataStream & operator>>(QDataStream & stream, CNonogramNumbers & numbers) {
|
||||||
|
numbers.readFromStream(stream);
|
||||||
|
return stream;
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogram::CNonogram()
|
||||||
|
: m_Size(0, 0),
|
||||||
|
m_Data(NULL),
|
||||||
|
m_Timeout(0),
|
||||||
|
m_MaximumNumberCount(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogram::CNonogram(QSize size) : m_Size(size), m_MaximumNumberCount(0) {
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogram::CNonogram(QImage & image) : m_Data(NULL), m_MaximumNumberCount(0) {
|
||||||
|
loadFromImage(image);
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogram::CNonogram(QDataStream & stream) : m_Data(NULL), m_MaximumNumberCount(0) {
|
||||||
|
readFromStream(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogram::~CNonogram() {
|
||||||
|
cleanup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNonogram::loadFromImage(QImage & image) {
|
||||||
|
if (image.isNull())
|
||||||
|
return;
|
||||||
|
|
||||||
return true;
|
resize(image.size());
|
||||||
|
|
||||||
|
for (int iy = 0; iy < m_Size.height(); iy++)
|
||||||
|
for (int ix = 0; ix < m_Size.width(); ix++)
|
||||||
|
m_Data[ix][iy] = !(image.pixel(ix, iy) & 0x00FFFFFF);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CNonogram::readFromStream(QDataStream & stream) {
|
bool CNonogram::readFromStream(QDataStream & stream) {
|
||||||
char magicHeader[sizeof(CNonogram_HEADER)];
|
unsigned char data;
|
||||||
stream.readRawData(magicHeader, sizeof(CNonogram_HEADER));
|
QString stringBuffer;
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
qint32 intBuffer;
|
||||||
if (0 != memcmp(magicHeader, CNonogram_HEADER, sizeof(CNonogram_HEADER))) {
|
QSize sizeBuffer;
|
||||||
qDebug("Invalid text magic");
|
|
||||||
stream.setStatus(QDataStream::ReadCorruptData);
|
stream >> stringBuffer; qDebug("reading image: %s", qPrintable(stringBuffer));
|
||||||
|
if (stream.atEnd()) {
|
||||||
|
qCritical("invalid nonogram");
|
||||||
|
cleanup();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 magic;
|
stream >> intBuffer; qDebug("width %i", intBuffer);
|
||||||
stream >> magic;
|
if (stream.atEnd()) {
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
qCritical("invalid nonogram");
|
||||||
if (CNonogram_MAGIC != magic) {
|
cleanup();
|
||||||
qDebug("Invalid binary magic");
|
|
||||||
stream.setStatus(QDataStream::ReadCorruptData);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString packageIdentifier;
|
sizeBuffer.setWidth(intBuffer);
|
||||||
int packageIndex;
|
|
||||||
|
|
||||||
stream >> packageIdentifier >> packageIndex;
|
stream >> intBuffer; qDebug("height %i", intBuffer);
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
if (stream.atEnd()) {
|
||||||
|
qCritical("invalid nonogram");
|
||||||
NonogramProblem problem;
|
cleanup();
|
||||||
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
int time, errorCount;
|
sizeBuffer.setHeight(intBuffer);
|
||||||
bool preventErrors;
|
|
||||||
stream >> time >> errorCount >> preventErrors;
|
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
|
||||||
|
|
||||||
if (preventErrors && (0 == problem.timeout())) {
|
m_Name = stringBuffer;
|
||||||
stream.setStatus(QDataStream::ReadCorruptData);
|
|
||||||
|
stream >> intBuffer; qDebug("timeout %i", intBuffer);
|
||||||
|
if (stream.atEnd()) {
|
||||||
|
qCritical("invalid nonogram");
|
||||||
|
cleanup();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
NonogramImage markerImage(marker);
|
m_Timeout = intBuffer;
|
||||||
const NonogramImage &solution(problem.solution());
|
|
||||||
int markedPixels = markerImage.blackPixels();
|
resize(sizeBuffer);
|
||||||
bool finished;
|
|
||||||
if (preventErrors) {
|
for (int x = 0; x < sizeBuffer.width(); x++) {
|
||||||
/* check no wrong mark is set */
|
int y = 0;
|
||||||
for (int x = 0; x < problem.width(); ++x) {
|
|
||||||
for (int y = 0; y < problem.height(); ++y) {
|
while (y < sizeBuffer.height()) {
|
||||||
if (markerImage.pixel(x, y) && !solution.pixel(x, y)) {
|
if (stream.atEnd()) {
|
||||||
qDebug("Wrong marked pixel and preventErrors is on: %i, %i", x, y);
|
qCritical("invalid nonogram");
|
||||||
stream.setStatus(QDataStream::ReadCorruptData);
|
cleanup();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
stream.readRawData((char *)&data, 1);
|
||||||
|
|
||||||
|
int b;
|
||||||
|
for (b = 0; b < 8; b++) {
|
||||||
|
if (y < sizeBuffer.height())
|
||||||
|
m_Data[x][y] = (bool)(data & 0x80);
|
||||||
|
data = data << 1;
|
||||||
|
y++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNonogram::writeToStream(QDataStream & stream) const {
|
void CNonogram::writeToStream(QDataStream & stream) {
|
||||||
stream.writeRawData(CNonogram_HEADER, sizeof(CNonogram_HEADER));
|
stream << m_Name;
|
||||||
stream << CNonogram_MAGIC;
|
stream << qint32(m_Size.width());
|
||||||
/* problem meta data; not stored by m_problem itself */
|
stream << qint32(m_Size.height());
|
||||||
stream << m_problem.packageIdentifier() << m_problem.packageIndex();
|
stream << qint32(m_Timeout);
|
||||||
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) {
|
unsigned char data;
|
||||||
// if pixel already is marked it must be correct - no reason to undo it
|
|
||||||
if (m_marker.pixel(x, y) == NonogramMarker::MARKED) return;
|
for (int x = 0; x < m_Size.width(); x++) {
|
||||||
|
data = 0x00;
|
||||||
if (m_marker.pixel(x, y) == mark) return; /* nothing to change */
|
int y = 0;
|
||||||
|
while (y < m_Size.height()) {
|
||||||
if (mark == NonogramMarker::MARKED && !m_problem.solution().pixel(x, y)) {
|
int b;
|
||||||
int penalty;
|
for (b = 0; b < 8; b++) {
|
||||||
m_marker.setPixel(x, y, NonogramMarker::CROSSED);
|
data = data << 1;
|
||||||
++m_errorCount;
|
if (y < m_Size.height() && m_Data[x][y])
|
||||||
switch (m_errorCount) {
|
data |= 0x01;
|
||||||
case 1: penalty = 2*60; break;
|
y++;
|
||||||
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);
|
stream.writeRawData((char *)&data, 1);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
if (m_marker.pixel(x, y) == mark) return; /* nothing to change */
|
}
|
||||||
|
|
||||||
if (m_marker.pixel(x, y) == NonogramMarker::MARKED) --m_markedPixels;
|
void CNonogram::resize(QSize size) {
|
||||||
|
if (m_Data)
|
||||||
m_marker.setPixel(x, y, mark);
|
cleanup();
|
||||||
if (mark == NonogramMarker::MARKED) ++m_markedPixels;
|
|
||||||
|
m_Size = size;
|
||||||
// blackPixels is an invariant on all solutions for a set of numbers
|
init();
|
||||||
if (m_markedPixels == m_problem.solution().blackPixels() && m_problem.numbers().check(NonogramImage(m_marker))) {
|
}
|
||||||
m_finished = true;
|
|
||||||
m_timer.stop();
|
void CNonogram::setPixel(int x, int y, bool value) {
|
||||||
emit changedMark(x, y);
|
m_Data[x][y] = value;
|
||||||
emit won(m_time);
|
}
|
||||||
return;
|
|
||||||
|
void CNonogram::updateNumbers() {
|
||||||
|
m_BlackPixels = 0;
|
||||||
|
int pixelCount;
|
||||||
|
|
||||||
|
m_MaximumNumberCount = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < m_Size.height(); i++) {
|
||||||
|
m_RowNumbers[i].clear();
|
||||||
|
pixelCount = 0;
|
||||||
|
for (int j = 0; j < m_Size.width(); j++) {
|
||||||
|
if (m_Data[j][i]) {
|
||||||
|
pixelCount++;
|
||||||
|
m_BlackPixels++;
|
||||||
|
}
|
||||||
|
else if (pixelCount) {
|
||||||
|
m_RowNumbers[i] << pixelCount;
|
||||||
|
pixelCount = 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if (pixelCount || m_RowNumbers[i].empty())
|
||||||
|
m_RowNumbers[i] << pixelCount;
|
||||||
|
|
||||||
emit changedMark(x, y);
|
if (m_RowNumbers[i].count() > m_MaximumNumberCount)
|
||||||
|
m_MaximumNumberCount = m_RowNumbers[i].count();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int j = 0; j < m_Size.width(); j++) {
|
||||||
|
m_ColumnNumbers[j].clear();
|
||||||
|
pixelCount = 0;
|
||||||
|
for (int i = 0; i < m_Size.height(); i++) {
|
||||||
|
if (m_Data[j][i]) {
|
||||||
|
pixelCount++;
|
||||||
|
}
|
||||||
|
else if (pixelCount) {
|
||||||
|
m_ColumnNumbers[j] << pixelCount;
|
||||||
|
pixelCount = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (pixelCount || m_ColumnNumbers[j].empty())
|
||||||
|
m_ColumnNumbers[j] << pixelCount;
|
||||||
|
|
||||||
|
if (m_ColumnNumbers[j].count() > m_MaximumNumberCount)
|
||||||
|
m_MaximumNumberCount = m_ColumnNumbers[j].count();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNonogram::restart() {
|
void CNonogram::fill(bool value) {
|
||||||
internRestart(false);
|
for (int i = 0; i < m_Size.width(); i++)
|
||||||
|
for (int j = 0; j < m_Size.height(); j++)
|
||||||
|
m_Data[i][j] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNonogram::pause() {
|
void CNonogram::cleanup() {
|
||||||
if (m_finished || m_paused) return;
|
delete[] m_RowNumbers;
|
||||||
m_paused = true;
|
delete[] m_ColumnNumbers;
|
||||||
m_timer.stop();
|
|
||||||
emit paused(m_time);
|
for (int i = 0; i < m_Size.width(); i++)
|
||||||
|
delete[] m_Data[i];
|
||||||
|
delete[] m_Data;
|
||||||
|
|
||||||
|
m_Data = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CNonogram::resume() {
|
void CNonogram::init() {
|
||||||
if (m_finished || !m_paused) return;
|
m_Data = new bool *[m_Size.width()];
|
||||||
m_paused = false;
|
for (int i = 0; i < m_Size.width(); i++)
|
||||||
m_timer.start(1000, this);
|
m_Data[i] = new bool[m_Size.height()];
|
||||||
emit resumed(m_time);
|
|
||||||
|
m_RowNumbers = new NumbersVector[m_Size.height()];
|
||||||
|
m_ColumnNumbers = new NumbersVector[m_Size.width()];
|
||||||
}
|
}
|
||||||
|
|
||||||
// protected
|
QDataStream & operator<<(QDataStream & stream, CNonogram & nonogram) {
|
||||||
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) {
|
|
||||||
nonogram.writeToStream(stream);
|
nonogram.writeToStream(stream);
|
||||||
return stream;
|
return stream;
|
||||||
}
|
}
|
||||||
|
@ -20,91 +20,110 @@
|
|||||||
#ifndef LIBQCROSS_CMONOPICTURE_H
|
#ifndef LIBQCROSS_CMONOPICTURE_H
|
||||||
#define LIBQCROSS_CMONOPICTURE_H
|
#define LIBQCROSS_CMONOPICTURE_H
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
#include <QVector>
|
#include <QVector>
|
||||||
#include <QString>
|
#include <QString>
|
||||||
#include <QBasicTimer>
|
|
||||||
|
|
||||||
#include "nonogramproblem.h"
|
|
||||||
#include "nonogrammarker.h"
|
|
||||||
|
|
||||||
class QImage;
|
class QImage;
|
||||||
|
|
||||||
namespace libqnono {
|
namespace libqnono {
|
||||||
class CNonogram : public QObject {
|
class CNonogram;
|
||||||
Q_OBJECT
|
class CNonogramNumbers;
|
||||||
|
|
||||||
|
class CNonogramNumbers {
|
||||||
|
public:
|
||||||
|
typedef QVector<quint16> v_num;
|
||||||
|
typedef QVector<v_num> vv_num;
|
||||||
|
|
||||||
|
CNonogramNumbers(const vv_num & rows, const vv_num & columns);
|
||||||
|
CNonogramNumbers(const CNonogram & source);
|
||||||
|
CNonogramNumbers(QSize size, bool **data);
|
||||||
|
CNonogramNumbers(const QImage & image);
|
||||||
|
|
||||||
|
void calcFromImage(QSize size, bool **data);
|
||||||
|
|
||||||
|
bool readFromStream(QDataStream & stream);
|
||||||
|
void writeToStream(QDataStream & stream);
|
||||||
|
|
||||||
|
bool operator==(const CNonogramNumbers &other) const;
|
||||||
|
|
||||||
|
inline const vv_num & rows() const { return m_rows; }
|
||||||
|
inline const vv_num & columns() const { return m_columns; }
|
||||||
|
|
||||||
|
inline QSize size() const { return QSize(m_columns.count(), m_rows.count()); }
|
||||||
|
inline int width() const { return m_columns.count(); }
|
||||||
|
inline int height() const { return m_rows.count(); }
|
||||||
|
|
||||||
|
bool check(bool **data) const;
|
||||||
|
bool check(const QImage & image) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
vv_num m_rows, m_columns;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
class CNonogram {
|
||||||
public:
|
public:
|
||||||
typedef QVector<quint16> NumbersVector;
|
typedef QVector<quint16> NumbersVector;
|
||||||
|
|
||||||
explicit CNonogram(QObject *parent = 0);
|
CNonogram();
|
||||||
|
CNonogram(QSize size);
|
||||||
|
CNonogram(QImage & image);
|
||||||
|
CNonogram(QDataStream & stream);
|
||||||
|
CNonogram(const CNonogram &other);
|
||||||
|
~CNonogram();
|
||||||
|
CNonogram& operator=(const CNonogram &other);
|
||||||
|
|
||||||
void start(const NonogramProblem & problem);
|
void loadFromImage(QImage & image);
|
||||||
|
void resize(QSize size);
|
||||||
const NonogramProblem & problem() const { return m_problem; }
|
|
||||||
QString name() const { return m_problem.name(); }
|
|
||||||
quint16 timeout() const { return m_problem.timeout(); }
|
|
||||||
const NonogramImage & solution() const { return m_problem.solution(); }
|
|
||||||
const NonogramNumbers & numbers() const { return m_problem.numbers(); }
|
|
||||||
|
|
||||||
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(); }
|
|
||||||
|
|
||||||
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);
|
bool readFromStream(QDataStream & stream);
|
||||||
void writeToStream(QDataStream& stream) const;
|
void writeToStream(QDataStream & stream);
|
||||||
|
|
||||||
public slots:
|
inline QString name() const { return m_Name; }
|
||||||
void setMark(int x, int y, NonogramMarker::Mark mark);
|
inline void setName(QString value) { m_Name = value; }
|
||||||
void restart();
|
|
||||||
void pause();
|
|
||||||
void resume();
|
|
||||||
void solve();
|
|
||||||
|
|
||||||
signals:
|
inline quint16 timeout() const { return m_Timeout; }
|
||||||
void loaded(); /* new problem/loaded savegame */
|
void setTimeout(quint16 value) { m_Timeout = value; }
|
||||||
|
|
||||||
void won(int score); /* score is the current time */
|
inline QSize size() const { return m_Size; }
|
||||||
void timeup();
|
inline int width() const { return m_Size.width(); }
|
||||||
|
inline int height() const { return m_Size.height(); }
|
||||||
|
|
||||||
void wrongMark(int x, int y, int penaltyTime);
|
inline bool pixel(int x, int y) const { return m_Data[x][y]; }
|
||||||
void changedMark(int x, int y);
|
void setPixel(int x, int y, bool value);
|
||||||
|
|
||||||
void restarted();
|
NumbersVector & rowNumbers(int index) const { return m_RowNumbers[index]; }
|
||||||
void paused(int time);
|
NumbersVector & columnNumbers(int index) const { return m_ColumnNumbers[index]; }
|
||||||
void resumed(int time);
|
|
||||||
void tick(int time);
|
inline int maximumNumberCount() const { return m_MaximumNumberCount; }
|
||||||
|
|
||||||
|
inline quint32 blackPixels() const { return m_BlackPixels; }
|
||||||
|
|
||||||
|
void updateNumbers();
|
||||||
|
|
||||||
|
void fill(bool value);
|
||||||
|
|
||||||
|
bool isValid() const { return m_Data; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual void timerEvent(QTimerEvent * event);
|
QSize m_Size;
|
||||||
|
|
||||||
void internRestart(bool newGame);
|
bool ** m_Data;
|
||||||
|
|
||||||
QBasicTimer m_timer;
|
QString m_Name;
|
||||||
|
quint16 m_Timeout;
|
||||||
|
|
||||||
NonogramProblem m_problem;
|
NumbersVector * m_RowNumbers;
|
||||||
NonogramMarker m_marker;
|
NumbersVector * m_ColumnNumbers;
|
||||||
int m_markedPixels;
|
quint32 m_BlackPixels;
|
||||||
|
int m_MaximumNumberCount;
|
||||||
|
|
||||||
int m_time; /* timeout() == 0: used time, timeout() > 0: remaining time */
|
void cleanup();
|
||||||
int m_errorCount;
|
void init();
|
||||||
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);
|
QDataStream & operator<<(QDataStream & stream, CNonogram & nonogram);
|
||||||
QDataStream & operator>>(QDataStream & stream, CNonogram & nonogram);
|
QDataStream & operator>>(QDataStream & stream, CNonogram & nonogram);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
523
libqnono/cnonogramsolver.cpp
Normal file
523
libqnono/cnonogramsolver.cpp
Normal file
@ -0,0 +1,523 @@
|
|||||||
|
#include "cnonogramsolver.h"
|
||||||
|
#include "cnonogram.h"
|
||||||
|
|
||||||
|
#include <QString>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
namespace libqnono {
|
||||||
|
|
||||||
|
struct Block {
|
||||||
|
int minFirst, maxFirst, length;
|
||||||
|
};
|
||||||
|
enum Mark { MARK_UNKNOWN = 0, MARK_BLACK, MARK_WHITE };
|
||||||
|
struct UndoOp {
|
||||||
|
union {
|
||||||
|
struct {
|
||||||
|
int *ptr, old;
|
||||||
|
} data_int;
|
||||||
|
struct {
|
||||||
|
Mark *ptr, old;
|
||||||
|
} data_mark;
|
||||||
|
};
|
||||||
|
enum { UNDO_INT, UNDO_MARK } type;
|
||||||
|
};
|
||||||
|
typedef QList<UndoOp> UndoState;
|
||||||
|
|
||||||
|
static void trackInt(UndoState *undo_state, bool &changed, int &ptr, int val) {
|
||||||
|
if (val == ptr) return;
|
||||||
|
changed = TRUE;
|
||||||
|
if (undo_state) {
|
||||||
|
UndoOp op;
|
||||||
|
op.type = UndoOp::UNDO_INT;
|
||||||
|
op.data_int.ptr = &ptr;
|
||||||
|
op.data_int.old = ptr;
|
||||||
|
undo_state->push_front(op);
|
||||||
|
}
|
||||||
|
ptr = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void trackMark(UndoState *undo_state, bool &changed, Mark &ptr, Mark val) {
|
||||||
|
if (val == ptr) return;
|
||||||
|
changed = TRUE;
|
||||||
|
if (undo_state) {
|
||||||
|
UndoOp op;
|
||||||
|
op.type = UndoOp::UNDO_MARK;
|
||||||
|
op.data_mark.ptr = &ptr;
|
||||||
|
op.data_mark.old = ptr;
|
||||||
|
undo_state->push_front(op);
|
||||||
|
}
|
||||||
|
ptr = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void undo(UndoState & undo_state) {
|
||||||
|
foreach (const UndoOp &op, undo_state) {
|
||||||
|
switch (op.type) {
|
||||||
|
case UndoOp::UNDO_INT:
|
||||||
|
*op.data_int.ptr = op.data_int.old;
|
||||||
|
break;
|
||||||
|
case UndoOp::UNDO_MARK:
|
||||||
|
*op.data_mark.ptr = op.data_mark.old;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
undo_state.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
int nrows, ncols;
|
||||||
|
QVector< QVector<Block> > rows, cols;
|
||||||
|
Mark **data;
|
||||||
|
|
||||||
|
State(const CNonogramNumbers & numbers)
|
||||||
|
: nrows(numbers.height()), ncols(numbers.width()) {
|
||||||
|
data = new Mark*[ncols];
|
||||||
|
for (int col = 0; col < ncols; ++col) {
|
||||||
|
data[col] = new Mark[nrows];
|
||||||
|
for (int row = 0; row < nrows; ++row) {
|
||||||
|
data[col][row] = MARK_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
rows.resize(nrows);
|
||||||
|
cols.resize(ncols);
|
||||||
|
for (int row = 0; row < nrows; ++row) {
|
||||||
|
foreach (quint16 len, numbers.rows()[row]) {
|
||||||
|
Block block = { 0, ncols - len, len };
|
||||||
|
rows[row] << block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int col = 0; col < ncols; ++col) {
|
||||||
|
foreach (quint16 len, numbers.columns()[col]) {
|
||||||
|
Block block = { 0, nrows - len, len };
|
||||||
|
cols[col] << block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
~State() {
|
||||||
|
for (int col = 0; col < ncols; ++col) delete [] data[col];
|
||||||
|
delete[] data;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool markHorizontal(UndoState *undo_state, bool &changed, int row, int from, int to) {
|
||||||
|
for (int i = from; i <= to; ++i) {
|
||||||
|
if (data[i][row] == MARK_WHITE) return FALSE;
|
||||||
|
trackMark(undo_state, changed, data[i][row], MARK_BLACK);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
bool clearHorizontal(UndoState *undo_state, bool &changed, int row, int from, int to) {
|
||||||
|
for (int i = from; i <= to; ++i) {
|
||||||
|
if (data[i][row] == MARK_BLACK) return FALSE;
|
||||||
|
trackMark(undo_state, changed, data[i][row], MARK_WHITE);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
bool markHorizontalBlock(UndoState *undo_state, bool &changed, int row, int minFirst, int maxFirst, int length) {
|
||||||
|
if (minFirst == maxFirst) {
|
||||||
|
if (minFirst > 0) {
|
||||||
|
if (data[minFirst-1][row] == MARK_BLACK) return FALSE;
|
||||||
|
trackMark(undo_state, changed, data[minFirst-1][row], MARK_WHITE);
|
||||||
|
}
|
||||||
|
if (minFirst + length < ncols) {
|
||||||
|
if (data[minFirst + length][row] == MARK_BLACK) return FALSE;
|
||||||
|
trackMark(undo_state, changed, data[minFirst + length][row], MARK_WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return markHorizontal(undo_state, changed, row, maxFirst, minFirst+length-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool markVertical(UndoState *undo_state, bool &changed, int col, int from, int to) {
|
||||||
|
for (int i = from; i <= to; ++i) {
|
||||||
|
if (data[col][i] == MARK_WHITE) return FALSE;
|
||||||
|
trackMark(undo_state, changed, data[col][i], MARK_BLACK);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
bool clearVertical(UndoState *undo_state, bool &changed, int col, int from, int to) {
|
||||||
|
for (int i = from; i <= to; ++i) {
|
||||||
|
if (data[col][i] == MARK_BLACK) return FALSE;
|
||||||
|
trackMark(undo_state, changed, data[col][i], MARK_WHITE);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
bool markVerticalBlock(UndoState *undo_state, bool &changed, int col, int minFirst, int maxFirst, int length) {
|
||||||
|
if (minFirst == maxFirst) {
|
||||||
|
if (minFirst > 0) {
|
||||||
|
if (data[col][minFirst-1] == MARK_BLACK) return FALSE;
|
||||||
|
trackMark(undo_state, changed, data[col][minFirst-1], MARK_WHITE);
|
||||||
|
}
|
||||||
|
if (minFirst + length < nrows) {
|
||||||
|
if (data[col][minFirst + length] == MARK_BLACK) return FALSE;
|
||||||
|
trackMark(undo_state, changed, data[col][minFirst + length], MARK_WHITE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return markVertical(undo_state, changed, col, maxFirst, minFirst+length-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool updateRows(UndoState *undo_state, bool &changed) {
|
||||||
|
for (int i = 0; i < nrows; ++i) {
|
||||||
|
QVector<Block> &line(rows[i]);
|
||||||
|
int lineLen = line.count();
|
||||||
|
|
||||||
|
if (0 == lineLen) {
|
||||||
|
if (!clearHorizontal(undo_state, changed, i, 0, ncols-1)) return FALSE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first block
|
||||||
|
{
|
||||||
|
int cell = line[0].minFirst;
|
||||||
|
// there must be "length" adjacent non white cells
|
||||||
|
for (int cell1 = cell, end = cell + line[0].length; cell <= line[0].maxFirst && cell1 < end; ++cell1) {
|
||||||
|
if (MARK_WHITE == data[cell1][i]) {
|
||||||
|
cell = cell1 + 1;
|
||||||
|
end = cell + line[0].length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell > line[0].minFirst) {
|
||||||
|
if (cell > line[0].maxFirst) return FALSE; // no solution impossible
|
||||||
|
trackInt(undo_state, changed, line[0].minFirst, cell);
|
||||||
|
}
|
||||||
|
// the first black can't be before the first block
|
||||||
|
while (cell < line[0].maxFirst && data[cell][i] != MARK_BLACK) ++cell;
|
||||||
|
if (cell < line[0].maxFirst) {
|
||||||
|
trackInt(undo_state, changed, line[0].maxFirst, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last block
|
||||||
|
{
|
||||||
|
int len = line.last().length;
|
||||||
|
int cell = line.last().maxFirst;
|
||||||
|
// there must be "length" adjacent non white cells
|
||||||
|
for (int cell1 = cell + len - 1; cell >= line.last().minFirst && cell1 >= cell; --cell1) {
|
||||||
|
if (MARK_WHITE == data[cell1][i]) {
|
||||||
|
cell = cell1 - len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell < line.last().maxFirst) {
|
||||||
|
if (cell < line.last().minFirst) return FALSE; // no solution impossible
|
||||||
|
trackInt(undo_state, changed, line.last().maxFirst, cell);
|
||||||
|
}
|
||||||
|
// the last black can't be after the last block
|
||||||
|
while (cell > line.last().minFirst && data[cell+len-1][i] != MARK_BLACK) --cell;
|
||||||
|
if (cell > line.last().minFirst) {
|
||||||
|
trackInt(undo_state, changed, line.last().minFirst, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check relative block offsets (min distance 1) */
|
||||||
|
for (int j = 1, k = lineLen - 1; j < lineLen; ++j, --k) {
|
||||||
|
{
|
||||||
|
int minFirst = qMax(line[j].minFirst, 1 + line[j-1].minFirst + line[j-1].length);
|
||||||
|
// the cell before first can't be black
|
||||||
|
while (minFirst <= line[j].maxFirst && MARK_BLACK == data[minFirst-1][i]) ++minFirst;
|
||||||
|
// there must be "length" adjacent non white cells
|
||||||
|
for (int cell = minFirst, end = minFirst + line[j].length; minFirst <= line[j].maxFirst && cell < end; ++cell) {
|
||||||
|
if (MARK_WHITE == data[cell][i]) {
|
||||||
|
minFirst = cell + 1;
|
||||||
|
end = minFirst + line[j].length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minFirst >= line[j-1].maxFirst + line[j-1].length) {
|
||||||
|
int cell = minFirst;
|
||||||
|
// next black cell can't be before this block
|
||||||
|
while (cell < line[j].maxFirst && data[cell][i] != MARK_BLACK) ++cell;
|
||||||
|
if (cell < line[j].maxFirst) {
|
||||||
|
trackInt(undo_state, changed, line[j].maxFirst, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minFirst > line[j].minFirst) {
|
||||||
|
if (minFirst > line[j].maxFirst) return FALSE; // no solution impossible
|
||||||
|
trackInt(undo_state, changed, line[j].minFirst, minFirst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int len = line[k-1].length;
|
||||||
|
int maxFirst = qMin(line[k-1].maxFirst, line[k].maxFirst - len - 1);
|
||||||
|
// the cell after last can't be black
|
||||||
|
while (maxFirst >= line[k-1].minFirst && MARK_BLACK == data[maxFirst + len][i]) --maxFirst;
|
||||||
|
// there must be "length" adjacent non white cells
|
||||||
|
for (int cell = maxFirst + len - 1; maxFirst >= line[k-1].minFirst && cell >= maxFirst; --cell) {
|
||||||
|
if (MARK_WHITE == data[cell][i]) {
|
||||||
|
maxFirst = cell - len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxFirst + len <= line[k].minFirst) {
|
||||||
|
int cell = maxFirst;
|
||||||
|
// next black cell before maxFirst+len can't be after this block
|
||||||
|
while (cell > line[k-1].minFirst && data[cell+len-1][i] != MARK_BLACK) --cell;
|
||||||
|
if (cell > line[k-1].minFirst) {
|
||||||
|
trackInt(undo_state, changed, line[k-1].minFirst, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxFirst < line[k-1].maxFirst) {
|
||||||
|
if (maxFirst < line[k-1].minFirst) return FALSE; // no solution impossible
|
||||||
|
trackInt(undo_state, changed, line[k-1].maxFirst, maxFirst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clearHorizontal(undo_state, changed, i, 0, line[0].minFirst-1)) return FALSE;
|
||||||
|
for (int j = 0; j < lineLen; ++j) {
|
||||||
|
if (j > 0 && !clearHorizontal(undo_state, changed, i, line[j-1].maxFirst + line[j-1].length, line[j].minFirst-1)) return FALSE;
|
||||||
|
if (!markHorizontalBlock(undo_state, changed, i, line[j].minFirst, line[j].maxFirst, line[j].length)) return FALSE;
|
||||||
|
}
|
||||||
|
if (!clearHorizontal(undo_state, changed, i, line.last().maxFirst + line.last().length, ncols-1)) return FALSE;
|
||||||
|
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool updateCols(UndoState *undo_state, bool &changed) {
|
||||||
|
for (int i = 0; i < ncols; ++i) {
|
||||||
|
QVector<Block> &line(cols[i]);
|
||||||
|
int lineLen = line.count();
|
||||||
|
|
||||||
|
if (0 == lineLen) {
|
||||||
|
if (!clearVertical(undo_state, changed, i, 0, nrows-1)) return FALSE;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// first block
|
||||||
|
{
|
||||||
|
int cell = line[0].minFirst;
|
||||||
|
// there must be "length" adjacent non white cells
|
||||||
|
for (int cell1 = cell, end = cell + line[0].length; cell <= line[0].maxFirst && cell1 < end; ++cell1) {
|
||||||
|
if (MARK_WHITE == data[i][cell1]) {
|
||||||
|
cell = cell1 + 1;
|
||||||
|
end = cell + line[0].length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell > line[0].minFirst) {
|
||||||
|
if (cell > line[0].maxFirst) return FALSE; // no solution impossible
|
||||||
|
trackInt(undo_state, changed, line[0].minFirst, cell);
|
||||||
|
}
|
||||||
|
// the first black can't be before the first block
|
||||||
|
while (cell < line[0].maxFirst && data[i][cell] != MARK_BLACK) ++cell;
|
||||||
|
if (cell < line[0].maxFirst) {
|
||||||
|
trackInt(undo_state, changed, line[0].maxFirst, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// last block
|
||||||
|
{
|
||||||
|
int len = line.last().length;
|
||||||
|
int cell = line.last().maxFirst;
|
||||||
|
// there must be "length" adjacent non white cells
|
||||||
|
for (int cell1 = cell + len - 1; cell >= line.last().minFirst && cell1 >= cell; --cell1) {
|
||||||
|
if (MARK_WHITE == data[i][cell1]) {
|
||||||
|
cell = cell1 - len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cell < line.last().maxFirst) {
|
||||||
|
if (cell < line.last().minFirst) return FALSE; // no solution impossible
|
||||||
|
trackInt(undo_state, changed, line.last().maxFirst, cell);
|
||||||
|
}
|
||||||
|
// the last black can't be after the last block
|
||||||
|
while (cell > line.last().minFirst && data[i][cell+len-1] != MARK_BLACK) --cell;
|
||||||
|
if (cell > line.last().minFirst) {
|
||||||
|
trackInt(undo_state, changed, line.last().minFirst, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* check relative block offsets (min distance 1) */
|
||||||
|
for (int j = 1, k = lineLen - 1; j < lineLen; ++j, --k) {
|
||||||
|
{
|
||||||
|
int minFirst = qMax(line[j].minFirst, 1 + line[j-1].minFirst + line[j-1].length);
|
||||||
|
// the cell before first can't be black
|
||||||
|
while (minFirst <= line[j].maxFirst && MARK_BLACK == data[i][minFirst-1]) ++minFirst;
|
||||||
|
// there must be "length" adjacent non white cells
|
||||||
|
for (int cell = minFirst, end = minFirst + line[j].length; minFirst <= line[j].maxFirst && cell < end; ++cell) {
|
||||||
|
if (MARK_WHITE == data[i][cell]) {
|
||||||
|
minFirst = cell + 1;
|
||||||
|
end = minFirst + line[j].length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minFirst >= line[j-1].maxFirst + line[j-1].length) {
|
||||||
|
int cell = minFirst;
|
||||||
|
// next black cell can't be before this block
|
||||||
|
while (cell < line[j].maxFirst && data[i][cell] != MARK_BLACK) ++cell;
|
||||||
|
if (cell < line[j].maxFirst) {
|
||||||
|
trackInt(undo_state, changed, line[j].maxFirst, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (minFirst > line[j].minFirst) {
|
||||||
|
if (minFirst > line[j].maxFirst) return FALSE; // no solution impossible
|
||||||
|
trackInt(undo_state, changed, line[j].minFirst, minFirst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
int len = line[k-1].length;
|
||||||
|
int maxFirst = qMin(line[k-1].maxFirst, line[k].maxFirst - len - 1);
|
||||||
|
// the cell after last can't be black
|
||||||
|
while (maxFirst >= line[k-1].minFirst && MARK_BLACK == data[i][maxFirst + len]) --maxFirst;
|
||||||
|
// there must be "length" adjacent non white cells
|
||||||
|
for (int cell = maxFirst + len - 1; maxFirst >= line[k-1].minFirst && cell >= maxFirst; --cell) {
|
||||||
|
if (MARK_WHITE == data[i][cell]) {
|
||||||
|
maxFirst = cell - len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxFirst + len <= line[k].minFirst) {
|
||||||
|
int cell = maxFirst;
|
||||||
|
// next black cell before maxFirst+len can't be after this block
|
||||||
|
while (cell > line[k-1].minFirst && data[i][cell+len-1] != MARK_BLACK) --cell;
|
||||||
|
if (cell > line[k-1].minFirst) {
|
||||||
|
trackInt(undo_state, changed, line[k-1].minFirst, cell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (maxFirst < line[k-1].maxFirst) {
|
||||||
|
if (maxFirst < line[k-1].minFirst) return FALSE; // no solution impossible
|
||||||
|
trackInt(undo_state, changed, line[k-1].maxFirst, maxFirst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clearVertical(undo_state, changed, i, 0, line[0].minFirst-1)) return FALSE;
|
||||||
|
for (int j = 0; j < lineLen; ++j) {
|
||||||
|
if (j > 0 && !clearVertical(undo_state, changed, i, line[j-1].maxFirst + line[j-1].length, line[j].minFirst-1)) return FALSE;
|
||||||
|
if (!markVerticalBlock(undo_state, changed, i, line[j].minFirst, line[j].maxFirst, line[j].length)) return FALSE;
|
||||||
|
}
|
||||||
|
if (!clearVertical(undo_state, changed, i, line.last().maxFirst + line.last().length, nrows-1)) return FALSE;
|
||||||
|
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogramSolution* solution() {
|
||||||
|
bool **data = new bool*[ncols];
|
||||||
|
for (int i = 0; i < ncols; ++i) {
|
||||||
|
data[i] = new bool[nrows];
|
||||||
|
for (int j = 0; j < nrows; ++j) {
|
||||||
|
data[i][j] = (this->data[i][j] == MARK_BLACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new CNonogramSolution(QSize(ncols, nrows), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void debugState() {
|
||||||
|
for (int j = 0; j < nrows; ++j) {
|
||||||
|
QDebug dbg = qDebug();
|
||||||
|
for (int i = 0; i < ncols; ++i) {
|
||||||
|
switch (data[i][j]) {
|
||||||
|
case MARK_UNKNOWN:
|
||||||
|
dbg << "?"; break;
|
||||||
|
case MARK_BLACK:
|
||||||
|
dbg << "M"; break;
|
||||||
|
case MARK_WHITE:
|
||||||
|
dbg << " "; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qDebug() << "Row blocks:";
|
||||||
|
for (int j = 0; j < nrows; ++j) {
|
||||||
|
QDebug dbg = qDebug() << j << ":";
|
||||||
|
foreach (Block block, rows[j]) {
|
||||||
|
dbg << "[" << block.minFirst << block.maxFirst << block.length << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
qDebug() << "Col blocks:";
|
||||||
|
for (int j = 0; j < ncols; ++j) {
|
||||||
|
QDebug dbg = qDebug() << j << ":";
|
||||||
|
foreach (Block block, cols[j]) {
|
||||||
|
dbg << "[" << block.minFirst << block.maxFirst << block.length << "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void solve(QList<CNonogramSolution*> &solutions, UndoState *undo_state = 0) {
|
||||||
|
bool changed = TRUE;
|
||||||
|
while (changed) {
|
||||||
|
changed = FALSE;
|
||||||
|
if (!updateRows(undo_state, changed)) return;
|
||||||
|
if (!updateCols(undo_state, changed)) return;
|
||||||
|
}
|
||||||
|
if (!undo_state) {
|
||||||
|
qDebug() << "State after first run:";
|
||||||
|
debugState();
|
||||||
|
}
|
||||||
|
for (int i = 0; i < ncols; ++i) {
|
||||||
|
for (int j = 0; j < nrows; ++j) {
|
||||||
|
if (data[i][j] == MARK_UNKNOWN) {
|
||||||
|
UndoState subundo;
|
||||||
|
|
||||||
|
trackMark(&subundo, changed, data[i][j], MARK_BLACK);
|
||||||
|
solve(solutions, &subundo);
|
||||||
|
undo(subundo);
|
||||||
|
|
||||||
|
trackMark(&subundo, changed, data[i][j], MARK_WHITE);
|
||||||
|
solve(solutions, &subundo);
|
||||||
|
undo(subundo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Found solution:";
|
||||||
|
debugState();
|
||||||
|
|
||||||
|
solutions << solution();
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
QList<CNonogramSolution*> solve(const CNonogramNumbers & numbers) {
|
||||||
|
QList<CNonogramSolution*> solutions;
|
||||||
|
State solveState(numbers);
|
||||||
|
solveState.solve(solutions);
|
||||||
|
|
||||||
|
foreach(CNonogramSolution* solution, solutions) {
|
||||||
|
Q_ASSERT(numbers.check(solution->data()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return solutions;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CNonogramSolver::CNonogramSolver(QObject * parent) : QObject(parent),
|
||||||
|
m_Nonogram(NULL)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
CNonogramSolver::~CNonogramSolver() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void CNonogramSolver::setNonogram(CNonogram * nonogram) {
|
||||||
|
m_Nonogram = nonogram;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CNonogramSolver::solve() {
|
||||||
|
if (!m_Nonogram) return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
QList<CNonogramSolution*> solutions = libqnono::solve(CNonogramNumbers(*m_Nonogram));
|
||||||
|
if (!solutions.empty()) {
|
||||||
|
bool **data = solutions.first()->data();
|
||||||
|
for (int i = 0; i < m_Nonogram->width(); ++i) {
|
||||||
|
for (int j = 0; j < m_Nonogram->height(); ++j) {
|
||||||
|
emit markRequested(i, j, data[i][j] ? CMT_MARKED : CMT_CROSSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (CNonogramSolution *s, solutions) delete s;
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
55
libqnono/cnonogramsolver.h
Normal file
55
libqnono/cnonogramsolver.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#ifndef LIBQCROSS_CNONOGRAMSOLVER_H
|
||||||
|
#define LIBQCROSS_CNONOGRAMSOLVER_H
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QSize>
|
||||||
|
#include <QList>
|
||||||
|
|
||||||
|
namespace libqnono {
|
||||||
|
class CNonogramNumbers;
|
||||||
|
|
||||||
|
class CNonogramSolution {
|
||||||
|
public:
|
||||||
|
CNonogramSolution(QSize size, bool ** data)
|
||||||
|
: m_size(size), m_data(data) {
|
||||||
|
}
|
||||||
|
|
||||||
|
~CNonogramSolution() {
|
||||||
|
int cols = m_size.width();
|
||||||
|
for (int col = 0; col < cols; ++col) delete [] m_data[col];
|
||||||
|
delete[] m_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QSize size() const { return m_size; }
|
||||||
|
bool** data() { return m_data; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
Q_DISABLE_COPY(CNonogramSolution)
|
||||||
|
|
||||||
|
QSize m_size;
|
||||||
|
bool ** m_data;
|
||||||
|
};
|
||||||
|
|
||||||
|
QList<CNonogramSolution*> solve(const CNonogramNumbers & numbers);
|
||||||
|
|
||||||
|
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
|
@ -1,199 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#include "nonogramimage.h"
|
|
||||||
#include "nonogrammarker.h"
|
|
||||||
|
|
||||||
#include <QDataStream>
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
static const quint64 NonogramImage_DataStream_MAGIC = Q_UINT64_C(0xe47028650d925b33);
|
|
||||||
|
|
||||||
NonogramImage::NonogramImage() : m_size(0,0), m_data(0), m_blackPixels(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramImage::NonogramImage(QSize size)
|
|
||||||
: m_size(size), m_blackPixels(0) {
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
m_data = new bool[n];
|
|
||||||
for (int i = 0; i < n; ++i) m_data[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramImage::NonogramImage(const NonogramImage& other)
|
|
||||||
: m_size(other.size()), m_data(0), m_blackPixels(other.m_blackPixels) {
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
m_data = new bool[n];
|
|
||||||
for (int i = 0; i < n; ++i) m_data[i] = other.m_data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramImage::NonogramImage(const QImage & image)
|
|
||||||
: m_size(0, 0), m_data(0), m_blackPixels(0) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramImage& NonogramImage::operator=(const NonogramImage& other) {
|
|
||||||
delete [] m_data; m_data = 0;
|
|
||||||
|
|
||||||
m_size = other.m_size;
|
|
||||||
m_blackPixels = other.m_blackPixels;
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
m_data = new bool[n];
|
|
||||||
for (int i = 0; i < n; ++i) m_data[i] = other.m_data[i];
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NonogramImage::operator==(const NonogramImage& other) const {
|
|
||||||
if (m_size != other.m_size || m_blackPixels != other.m_blackPixels) return false;
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
for (int i = 0; i < n; ++i) if (m_data[i] != other.m_data[i]) return false;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramImage::fill(bool value) {
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
for (int i = 0; i < n; ++i) m_data[i] = value;
|
|
||||||
m_blackPixels = value ? n : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramImage::resize(QSize size) {
|
|
||||||
int n = size.width() * size.height();
|
|
||||||
bool *data = new bool[n];
|
|
||||||
int oldwidth = m_size.width(), newwidth = size.width(), w = qMin(oldwidth, newwidth), h = qMin(size.height(), m_size.height());
|
|
||||||
int y, x;
|
|
||||||
m_blackPixels = 0;
|
|
||||||
for (y = 0; y < h; ++y) {
|
|
||||||
for (x = 0; x < w; ++x) {
|
|
||||||
data[y*newwidth+x] = m_data[y*oldwidth+x];
|
|
||||||
if (data[y*newwidth+x]) ++m_blackPixels;
|
|
||||||
}
|
|
||||||
for (; x < newwidth; ++x) {
|
|
||||||
data[y*newwidth+x] = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int i = y*newwidth; i < n; ++i) {
|
|
||||||
data[i] = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
delete [] m_data;
|
|
||||||
m_data = data;
|
|
||||||
m_size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NonogramImage::readFromStream(QDataStream & stream) {
|
|
||||||
quint64 magic;
|
|
||||||
stream >> magic;
|
|
||||||
if (NonogramImage_DataStream_MAGIC != magic || QDataStream::Ok != stream.status()) {
|
|
||||||
if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize size;
|
|
||||||
stream >> size;
|
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
|
||||||
|
|
||||||
int n = size.width() * size.height();
|
|
||||||
|
|
||||||
int bitcount = (n+7)/8;
|
|
||||||
char *bits = new char[bitcount];
|
|
||||||
if (bitcount != stream.readRawData(bits, bitcount) || QDataStream::Ok != stream.status()) {
|
|
||||||
delete[] bits;
|
|
||||||
if (QDataStream::ReadCorruptData != stream.status()) stream.setStatus(QDataStream::ReadPastEnd);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int blackPixels = 0;
|
|
||||||
delete[] m_data;
|
|
||||||
m_data = new bool[n];
|
|
||||||
for (int i = 0, k = 0; k < bitcount; ++k) {
|
|
||||||
unsigned char byte = bits[k];
|
|
||||||
for (int l = 0 ; l < 8 && i < n; ++l, ++i, byte >>= 1) {
|
|
||||||
m_data[i] = (byte & 0x1);
|
|
||||||
if (m_data[i]) ++blackPixels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
delete[] bits;
|
|
||||||
|
|
||||||
m_size = size;
|
|
||||||
m_blackPixels = blackPixels;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramImage::writeToStream(QDataStream & stream) const {
|
|
||||||
stream << NonogramImage_DataStream_MAGIC << m_size;
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
int bitcount = (n+7)/8;
|
|
||||||
char *bits = new char[bitcount];
|
|
||||||
for (int i = 0, k = 0; k < bitcount; ++k) {
|
|
||||||
unsigned char byte = 0, mask = 1;
|
|
||||||
for (int l = 0 ; l < 8 && i < n; ++l, ++i, mask <<= 1) {
|
|
||||||
if (m_data[i]) byte |= mask;
|
|
||||||
}
|
|
||||||
bits[k] = byte;
|
|
||||||
}
|
|
||||||
stream.writeRawData(bits, bitcount);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramImage::load(const QImage & image) {
|
|
||||||
delete [] m_data; m_data = 0;
|
|
||||||
m_blackPixels = 0;
|
|
||||||
|
|
||||||
m_size = image.size();
|
|
||||||
int rows = m_size.height(), cols = m_size.width();
|
|
||||||
m_data = new bool[rows*cols];
|
|
||||||
for (int i = 0, y = 0; y < rows; ++y) {
|
|
||||||
for (int x = 0; x < cols; ++x, ++i) {
|
|
||||||
m_data[i] = (0 == (image.pixel(x, y) & 0x00FFFFFF));
|
|
||||||
if (m_data[i]) ++m_blackPixels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramImage::load(const NonogramMarker & marker) {
|
|
||||||
delete [] m_data; m_data = 0;
|
|
||||||
m_blackPixels = 0;
|
|
||||||
|
|
||||||
m_size = marker.size();
|
|
||||||
int n = m_size.height() * m_size.width();
|
|
||||||
m_data = new bool[n];
|
|
||||||
for (int i = 0; i < n; ++i) {
|
|
||||||
m_data[i] = (NonogramMarker::MARKED == marker.m_data[i]);
|
|
||||||
if (m_data[i]) ++m_blackPixels;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream & operator<<(QDataStream & stream, const NonogramImage & image) {
|
|
||||||
image.writeToStream(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream & operator>>(QDataStream & stream, NonogramImage & image) {
|
|
||||||
image.readFromStream(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,66 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#ifndef LIBQNONO_NONOGRAMIMAGE_H
|
|
||||||
#define LIBQNONO_NONOGRAMIMAGE_H
|
|
||||||
|
|
||||||
#include <QSize>
|
|
||||||
#include <QImage>
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
class NonogramMarker;
|
|
||||||
|
|
||||||
class NonogramImage {
|
|
||||||
public:
|
|
||||||
NonogramImage();
|
|
||||||
explicit NonogramImage(QSize size);
|
|
||||||
NonogramImage(const NonogramImage & other);
|
|
||||||
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]; }
|
|
||||||
void setPixel(int x, int y, bool value) { m_data[y*m_size.width()+x] = value; }
|
|
||||||
void fill(bool value);
|
|
||||||
|
|
||||||
int blackPixels() const { return m_blackPixels; }
|
|
||||||
|
|
||||||
QSize size() const { return m_size; }
|
|
||||||
int width() const { return m_size.width(); }
|
|
||||||
int height() const { return m_size.height(); }
|
|
||||||
void resize(QSize size);
|
|
||||||
|
|
||||||
bool readFromStream(QDataStream & stream);
|
|
||||||
void writeToStream(QDataStream & stream) const;
|
|
||||||
|
|
||||||
void load(const QImage & image);
|
|
||||||
void load(const NonogramMarker & marker);
|
|
||||||
|
|
||||||
private:
|
|
||||||
QSize m_size;
|
|
||||||
bool *m_data;
|
|
||||||
int m_blackPixels;
|
|
||||||
};
|
|
||||||
|
|
||||||
QDataStream & operator<<(QDataStream & stream, const NonogramImage & image);
|
|
||||||
QDataStream & operator>>(QDataStream & stream, NonogramImage & image);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // LIBQNONO_NONOGRAMIMAGE_H
|
|
@ -1,130 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#include "nonogrammarker.h"
|
|
||||||
|
|
||||||
#include <QDataStream>
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
static const quint64 NonogramMarker_DataStream_MAGIC = Q_UINT64_C(0xe40d3ea829a828a7);
|
|
||||||
|
|
||||||
NonogramMarker::NonogramMarker() : m_size(0,0), m_data(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramMarker::NonogramMarker(QSize size)
|
|
||||||
: 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramMarker::NonogramMarker(const NonogramMarker& other)
|
|
||||||
: m_size(other.size()), m_data(0) {
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
m_data = new Mark[n];
|
|
||||||
for (int i = 0; i < n; ++i) m_data[i] = other.m_data[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramMarker::~NonogramMarker() {
|
|
||||||
delete [] m_data; m_data = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramMarker& NonogramMarker::operator=(const NonogramMarker& other) {
|
|
||||||
delete [] m_data; m_data = 0;
|
|
||||||
|
|
||||||
m_size = other.m_size;
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
m_data = new Mark[n];
|
|
||||||
for (int i = 0; i < n; ++i) m_data[i] = other.m_data[i];
|
|
||||||
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NonogramMarker::operator==(const NonogramMarker& other) const {
|
|
||||||
if (m_size != other.m_size) return false;
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
for (int i = 0; i < n; ++i) if (m_data[i] != other.m_data[i]) return false;
|
|
||||||
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;
|
|
||||||
if (NonogramMarker_DataStream_MAGIC != magic || QDataStream::Ok != stream.status()) {
|
|
||||||
if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QSize size;
|
|
||||||
stream >> size;
|
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
|
||||||
|
|
||||||
int n = size.width() * size.height();
|
|
||||||
Mark *data = new Mark[n];
|
|
||||||
for (int i = 0; i < n; ++i) {
|
|
||||||
if (stream.atEnd()) {
|
|
||||||
stream.setStatus(QDataStream::ReadPastEnd);
|
|
||||||
delete[] data;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
quint8 val;
|
|
||||||
stream >> val;
|
|
||||||
data[i] = static_cast<Mark>(val);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
|
||||||
|
|
||||||
m_size = size;
|
|
||||||
delete[] m_data;
|
|
||||||
m_data = data;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramMarker::writeToStream(QDataStream & stream) const {
|
|
||||||
stream << NonogramMarker_DataStream_MAGIC << m_size;
|
|
||||||
int n = m_size.width() * m_size.height();
|
|
||||||
for (int i = 0; i < n; ++i) {
|
|
||||||
stream << static_cast<quint8>(m_data[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream & operator<<(QDataStream & stream, const NonogramMarker & marker) {
|
|
||||||
marker.writeToStream(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream & operator>>(QDataStream & stream, NonogramMarker & marker) {
|
|
||||||
marker.readFromStream(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,63 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#ifndef LIBQNONO_NONOGRAMMARKER_H
|
|
||||||
#define LIBQNONO_NONOGRAMMARKER_H
|
|
||||||
|
|
||||||
#include <QSize>
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
class NonogramImage;
|
|
||||||
|
|
||||||
class NonogramMarker {
|
|
||||||
public:
|
|
||||||
enum Mark { NONE = 0, MARKED = 1, CROSSED = 2 };
|
|
||||||
|
|
||||||
NonogramMarker();
|
|
||||||
explicit NonogramMarker(QSize size);
|
|
||||||
NonogramMarker(const NonogramMarker& other);
|
|
||||||
~NonogramMarker();
|
|
||||||
NonogramMarker& operator=(const NonogramMarker& other);
|
|
||||||
bool operator==(const NonogramMarker& other) const;
|
|
||||||
|
|
||||||
Mark pixel(int x, int y) const { return m_data[y*m_size.width()+x]; }
|
|
||||||
Mark & pixel(int x, int y) { return m_data[y*m_size.width()+x]; }
|
|
||||||
void setPixel(int x, int y, Mark value) { m_data[y*m_size.width()+x] = value; }
|
|
||||||
|
|
||||||
QSize size() const { return m_size; }
|
|
||||||
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;
|
|
||||||
|
|
||||||
private:
|
|
||||||
friend class NonogramImage;
|
|
||||||
|
|
||||||
QSize m_size;
|
|
||||||
Mark *m_data;
|
|
||||||
};
|
|
||||||
|
|
||||||
QDataStream & operator<<(QDataStream & stream, const NonogramMarker & marker);
|
|
||||||
QDataStream & operator>>(QDataStream & stream, NonogramMarker & marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // LIBQNONO_NONOGRAMMARKER_H
|
|
@ -1,165 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#include "nonogramnumbers.h"
|
|
||||||
#include "nonogramimage.h"
|
|
||||||
|
|
||||||
#include <QDataStream>
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
static const quint64 NonogramNumbers_MAGIC = Q_UINT64_C(0x0c395a301353aad2);
|
|
||||||
|
|
||||||
NonogramNumbers::NonogramNumbers() {
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramNumbers::NonogramNumbers(const vv_num & rows, const vv_num & columns)
|
|
||||||
: m_rows(rows), m_columns(columns) {
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramNumbers::NonogramNumbers(const NonogramImage & image) {
|
|
||||||
calcFromImage(image);
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramNumbers::NonogramNumbers(const QImage & image) {
|
|
||||||
NonogramImage img(image);
|
|
||||||
calcFromImage(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramNumbers::calcFromImage(const NonogramImage & image) {
|
|
||||||
int rows = image.height(), cols = image.width();
|
|
||||||
m_rows.clear(); m_rows.resize(rows);
|
|
||||||
m_columns.clear(); m_columns.resize(cols);
|
|
||||||
for (int row = 0; row < rows; ++row) {
|
|
||||||
quint16 *val = 0;
|
|
||||||
for (int col = 0; col < cols; ++col) {
|
|
||||||
bool b = image.pixel(col, row);
|
|
||||||
if (!val && b) {
|
|
||||||
m_rows[row].append(1);
|
|
||||||
val = &m_rows[row].last();
|
|
||||||
} else if (b) {
|
|
||||||
++(*val);
|
|
||||||
} else {
|
|
||||||
val = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_rows[row].isEmpty()) m_rows[row].append(0);
|
|
||||||
}
|
|
||||||
for (int col = 0; col < cols; ++col) {
|
|
||||||
quint16 *val = 0;
|
|
||||||
for (int row = 0; row < rows; ++row) {
|
|
||||||
bool b = image.pixel(col, row);
|
|
||||||
if (!val && b) {
|
|
||||||
m_columns[col].append(1);
|
|
||||||
val = &m_columns[col].last();
|
|
||||||
} else if (b) {
|
|
||||||
++(*val);
|
|
||||||
} else {
|
|
||||||
val = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_columns[col].isEmpty()) m_columns[col].append(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramNumbers::updateFromImage(const NonogramImage & image, int x, int y) {
|
|
||||||
int rows = image.height(), cols = image.width();
|
|
||||||
m_rows[y].clear();
|
|
||||||
m_columns[x].clear();
|
|
||||||
{
|
|
||||||
int row = y;
|
|
||||||
|
|
||||||
quint16 *val = 0;
|
|
||||||
for (int col = 0; col < cols; ++col) {
|
|
||||||
bool b = image.pixel(col, row);
|
|
||||||
if (!val && b) {
|
|
||||||
m_rows[row].append(1);
|
|
||||||
val = &m_rows[row].last();
|
|
||||||
} else if (b) {
|
|
||||||
++(*val);
|
|
||||||
} else {
|
|
||||||
val = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_rows[row].isEmpty()) m_rows[row].append(0);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
int col = x;
|
|
||||||
|
|
||||||
quint16 *val = 0;
|
|
||||||
for (int row = 0; row < rows; ++row) {
|
|
||||||
bool b = image.pixel(col, row);
|
|
||||||
if (!val && b) {
|
|
||||||
m_columns[col].append(1);
|
|
||||||
val = &m_columns[col].last();
|
|
||||||
} else if (b) {
|
|
||||||
++(*val);
|
|
||||||
} else {
|
|
||||||
val = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (m_columns[col].isEmpty()) m_columns[col].append(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NonogramNumbers::readFromStream(QDataStream & stream) {
|
|
||||||
quint64 magic;
|
|
||||||
stream >> magic;
|
|
||||||
if (NonogramNumbers_MAGIC != magic || QDataStream::Ok != stream.status()) {
|
|
||||||
if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
vv_num rows, columns;
|
|
||||||
stream >> rows >> columns;
|
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
|
||||||
|
|
||||||
m_rows = rows; m_columns = columns;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramNumbers::writeToStream(QDataStream & stream) {
|
|
||||||
stream << NonogramNumbers_MAGIC << m_rows << m_columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NonogramNumbers::operator==(const NonogramNumbers &other) const {
|
|
||||||
return m_rows == other.m_rows && m_columns == other.m_columns;
|
|
||||||
}
|
|
||||||
|
|
||||||
int NonogramNumbers::maximumNumberCount() const {
|
|
||||||
int r = 1;
|
|
||||||
foreach (const v_num &line, m_rows) r = qMax<int>(r, line.count());
|
|
||||||
foreach (const v_num &line, m_columns) r = qMax<int>(r, line.count());
|
|
||||||
return r;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NonogramNumbers::check(const NonogramImage & image) const {
|
|
||||||
if (size() != image.size()) return FALSE;
|
|
||||||
NonogramNumbers tmp(image);
|
|
||||||
return *this == tmp;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream & operator<<(QDataStream & stream, NonogramNumbers & numbers) {
|
|
||||||
numbers.writeToStream(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream & operator>>(QDataStream & stream, NonogramNumbers & numbers) {
|
|
||||||
numbers.readFromStream(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,65 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#ifndef LIBQNONO_NONOGRAMNUMBERS_H
|
|
||||||
#define LIBQNONO_NONOGRAMNUMBERS_H
|
|
||||||
|
|
||||||
#include <QVector>
|
|
||||||
#include <QSize>
|
|
||||||
#include <QImage>
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
class NonogramImage;
|
|
||||||
|
|
||||||
class NonogramNumbers {
|
|
||||||
public:
|
|
||||||
typedef QVector<quint16> v_num;
|
|
||||||
typedef QVector<v_num> vv_num;
|
|
||||||
|
|
||||||
NonogramNumbers();
|
|
||||||
NonogramNumbers(const vv_num & rows, const vv_num & columns);
|
|
||||||
explicit NonogramNumbers(const NonogramImage & image);
|
|
||||||
explicit NonogramNumbers(const QImage & image);
|
|
||||||
|
|
||||||
void calcFromImage(const NonogramImage & image);
|
|
||||||
|
|
||||||
// one pixel changed, only update the row and column for it
|
|
||||||
void updateFromImage(const NonogramImage & image, int x, int y);
|
|
||||||
|
|
||||||
bool readFromStream(QDataStream & stream);
|
|
||||||
void writeToStream(QDataStream & stream);
|
|
||||||
|
|
||||||
bool operator==(const NonogramNumbers &other) const;
|
|
||||||
|
|
||||||
const vv_num & rows() const { return m_rows; }
|
|
||||||
const vv_num & columns() const { return m_columns; }
|
|
||||||
|
|
||||||
int maximumNumberCount() const;
|
|
||||||
|
|
||||||
QSize size() const { return QSize(m_columns.count(), m_rows.count()); }
|
|
||||||
int width() const { return m_columns.count(); }
|
|
||||||
int height() const { return m_rows.count(); }
|
|
||||||
|
|
||||||
bool check(const NonogramImage & image) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
vv_num m_rows, m_columns;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // LIBQNONO_NONOGRAMNUMBERS_H
|
|
@ -1,91 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#include "nonogramproblem.h"
|
|
||||||
#include "nonogramsolver.h"
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
static const quint64 NonogramProblem_MAGIC = Q_UINT64_C(0xfdbbd8d0936ea9a9);
|
|
||||||
|
|
||||||
NonogramProblem::NonogramProblem() {
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramProblem::NonogramProblem(const NonogramImage & solution)
|
|
||||||
: m_solution(solution), m_numbers(solution), m_name(), m_timeout(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
NonogramProblem::NonogramProblem(QString name, quint16 timeout, const NonogramImage & solution)
|
|
||||||
: m_solution(solution), m_numbers(solution), m_name(name), m_timeout(timeout) {
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NonogramProblem::hasUniqueSolution() const {
|
|
||||||
return 1 == solve(m_numbers).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramProblem::setPixel(int x, int y, bool value) {
|
|
||||||
m_solution.setPixel(x, y, value);
|
|
||||||
m_numbers.updateFromImage(m_solution, x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramProblem::fill(bool value) {
|
|
||||||
m_solution.fill(value);
|
|
||||||
m_numbers.calcFromImage(m_solution);
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramProblem::loadFromImage(const NonogramImage & solution) {
|
|
||||||
m_solution = solution;
|
|
||||||
m_numbers.calcFromImage(m_solution);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool NonogramProblem::readFromStream(QDataStream & stream, QString packageIdentifier, int packageIndex) {
|
|
||||||
quint64 magic;
|
|
||||||
stream >> magic;
|
|
||||||
if (NonogramProblem_MAGIC != magic || QDataStream::Ok != stream.status()) {
|
|
||||||
if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString name;
|
|
||||||
quint16 timeout;
|
|
||||||
NonogramImage solution;
|
|
||||||
stream >> name >> timeout >> solution;
|
|
||||||
if (QDataStream::Ok != stream.status()) return false;
|
|
||||||
|
|
||||||
m_solution = solution;
|
|
||||||
m_name = name;
|
|
||||||
m_timeout = timeout;
|
|
||||||
m_numbers.calcFromImage(m_solution);
|
|
||||||
m_PackageIdentifier = packageIdentifier;
|
|
||||||
m_PackageIndex = packageIndex;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void NonogramProblem::writeToStream(QDataStream & stream) const {
|
|
||||||
stream << NonogramProblem_MAGIC << m_name << m_timeout << m_solution;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream & operator<<(QDataStream & stream, const NonogramProblem & problem) {
|
|
||||||
problem.writeToStream(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
|
|
||||||
QDataStream & operator>>(QDataStream & stream, NonogramProblem & problem) {
|
|
||||||
problem.readFromStream(stream);
|
|
||||||
return stream;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,75 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#ifndef LIBQNONO_NONOGRAMPROBLEM_H
|
|
||||||
#define LIBQNONO_NONOGRAMPROBLEM_H
|
|
||||||
|
|
||||||
#include "nonogramimage.h"
|
|
||||||
#include "nonogramnumbers.h"
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
class NonogramMarker;
|
|
||||||
|
|
||||||
class NonogramProblem {
|
|
||||||
public:
|
|
||||||
NonogramProblem();
|
|
||||||
NonogramProblem(const NonogramImage & solution);
|
|
||||||
NonogramProblem(QString name, quint16 timeout, const NonogramImage & solution);
|
|
||||||
|
|
||||||
QString name() const { return m_name; }
|
|
||||||
quint16 timeout() const { return m_timeout; }
|
|
||||||
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(); }
|
|
||||||
|
|
||||||
bool hasUniqueSolution() const;
|
|
||||||
|
|
||||||
/* edit problem: */
|
|
||||||
void setName(QString value) { m_name = value; }
|
|
||||||
void setTimeout(quint16 value) { m_timeout = value; }
|
|
||||||
void setPixel(int x, int y, bool value);
|
|
||||||
|
|
||||||
void fill(bool value);
|
|
||||||
void loadFromImage(const NonogramImage & solution);
|
|
||||||
|
|
||||||
bool readFromStream(QDataStream & stream, QString packageIdentifier = QString(), int packageIndex = -1);
|
|
||||||
void writeToStream(QDataStream & stream) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
NonogramImage m_solution;
|
|
||||||
NonogramNumbers m_numbers;
|
|
||||||
|
|
||||||
QString m_name;
|
|
||||||
quint16 m_timeout;
|
|
||||||
|
|
||||||
/* not saved directly */
|
|
||||||
QString m_PackageIdentifier;
|
|
||||||
int m_PackageIndex;
|
|
||||||
};
|
|
||||||
|
|
||||||
QDataStream & operator<<(QDataStream & stream, const NonogramProblem & problem);
|
|
||||||
QDataStream & operator>>(QDataStream & stream, NonogramProblem & problem);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // LIBQNONO_NONOGRAMPROBLEM_H
|
|
@ -1,383 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#include "nonogramsolver.h"
|
|
||||||
#include "nonogramimage.h"
|
|
||||||
#include "nonogrammarker.h"
|
|
||||||
#include "nonogramnumbers.h"
|
|
||||||
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
struct SolverState {
|
|
||||||
struct Block {
|
|
||||||
int minFirst, maxFirst, length;
|
|
||||||
};
|
|
||||||
typedef NonogramMarker::Mark Mark;
|
|
||||||
|
|
||||||
static const Mark MARK_UNKNOWN = NonogramMarker::NONE;
|
|
||||||
static const Mark MARK_BLACK = NonogramMarker::MARKED;
|
|
||||||
static const Mark MARK_WHITE = NonogramMarker::CROSSED;
|
|
||||||
|
|
||||||
class UndoState {
|
|
||||||
private:
|
|
||||||
struct UndoOp {
|
|
||||||
union {
|
|
||||||
struct {
|
|
||||||
int *ptr, old;
|
|
||||||
} data_int;
|
|
||||||
struct {
|
|
||||||
Mark *ptr, old;
|
|
||||||
} data_mark;
|
|
||||||
};
|
|
||||||
enum { UNDO_INT, UNDO_MARK } type;
|
|
||||||
};
|
|
||||||
|
|
||||||
QList<UndoOp> m_ops;
|
|
||||||
|
|
||||||
public:
|
|
||||||
void trackInt(bool &changed, int &ptr, int val) {
|
|
||||||
if (val == ptr) return;
|
|
||||||
changed = TRUE;
|
|
||||||
if (this) {
|
|
||||||
UndoOp op;
|
|
||||||
op.type = UndoOp::UNDO_INT;
|
|
||||||
op.data_int.ptr = &ptr;
|
|
||||||
op.data_int.old = ptr;
|
|
||||||
m_ops.push_front(op);
|
|
||||||
}
|
|
||||||
ptr = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void trackMark(bool &changed, Mark &ptr, Mark val) {
|
|
||||||
if (val == ptr) return;
|
|
||||||
changed = TRUE;
|
|
||||||
if (this) {
|
|
||||||
UndoOp op;
|
|
||||||
op.type = UndoOp::UNDO_MARK;
|
|
||||||
op.data_mark.ptr = &ptr;
|
|
||||||
op.data_mark.old = ptr;
|
|
||||||
m_ops.push_front(op);
|
|
||||||
}
|
|
||||||
ptr = val;
|
|
||||||
}
|
|
||||||
|
|
||||||
void undo() {
|
|
||||||
foreach (const UndoOp &op, m_ops) {
|
|
||||||
switch (op.type) {
|
|
||||||
case UndoOp::UNDO_INT:
|
|
||||||
*op.data_int.ptr = op.data_int.old;
|
|
||||||
break;
|
|
||||||
case UndoOp::UNDO_MARK:
|
|
||||||
*op.data_mark.ptr = op.data_mark.old;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
m_ops.clear();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
NonogramMarker m_marks;
|
|
||||||
QVector< QVector<Block> > rows, cols;
|
|
||||||
|
|
||||||
inline Mark& pixel(int x, int y) {
|
|
||||||
return m_marks.pixel(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct View : public T {
|
|
||||||
View(SolverState & state) : T(state) { }
|
|
||||||
|
|
||||||
/* horizontal uses ViewRowColumn, vertical ViewColumnRow */
|
|
||||||
|
|
||||||
bool mark(UndoState *undo_state, bool & changed, int m, int from, int to) {
|
|
||||||
for (int i = from; i <= to; ++i) {
|
|
||||||
if (this->data(m, i) == MARK_WHITE) return FALSE;
|
|
||||||
undo_state->trackMark(changed, this->data(m, i), MARK_BLACK);
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
bool clear(UndoState *undo_state, bool & changed, int m, int from, int to) {
|
|
||||||
for (int i = from; i <= to; ++i) {
|
|
||||||
if (this->data(m, i) == MARK_BLACK) return FALSE;
|
|
||||||
undo_state->trackMark(changed, this->data(m, i), MARK_WHITE);
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
bool markBlock(UndoState *undo_state, bool & changed, int m, const Block &b) {
|
|
||||||
if (b.minFirst == b.maxFirst) {
|
|
||||||
if (b.minFirst > 0) {
|
|
||||||
if (this->data(m, b.minFirst-1) == MARK_BLACK) return FALSE;
|
|
||||||
undo_state->trackMark(changed, this->data(m, b.minFirst-1), MARK_WHITE);
|
|
||||||
}
|
|
||||||
if (b.minFirst + b.length < this->dimSecond()) {
|
|
||||||
if (this->data(m, b.minFirst + b.length) == MARK_BLACK) return FALSE;
|
|
||||||
undo_state->trackMark(changed, this->data(m, b.minFirst + b.length), MARK_WHITE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mark(undo_state, changed, m, b.maxFirst, b.minFirst+b.length-1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ViewRowColumn {
|
|
||||||
ViewRowColumn(SolverState & state) : state(state) { }
|
|
||||||
|
|
||||||
inline Mark& data(int row, int col) { return state.pixel(col, row); }
|
|
||||||
inline int dimFirst() const { return state.m_marks.height(); }
|
|
||||||
inline int dimSecond() const { return state.m_marks.width(); }
|
|
||||||
|
|
||||||
SolverState & state;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct ViewColumnRow {
|
|
||||||
ViewColumnRow(SolverState & state) : state(state) { }
|
|
||||||
|
|
||||||
inline Mark& data(int col, int row) { return state.pixel(col, row); }
|
|
||||||
inline int dimFirst() const { return state.m_marks.width(); }
|
|
||||||
inline int dimSecond() const { return state.m_marks.height(); }
|
|
||||||
|
|
||||||
SolverState & state;
|
|
||||||
};
|
|
||||||
|
|
||||||
SolverState(const NonogramNumbers & numbers)
|
|
||||||
: m_marks(numbers.size()) {
|
|
||||||
int nrows = m_marks.height(), ncols = m_marks.width();
|
|
||||||
rows.resize(nrows);
|
|
||||||
cols.resize(ncols);
|
|
||||||
for (int row = 0; row < nrows; ++row) {
|
|
||||||
foreach (quint16 len, numbers.rows()[row]) {
|
|
||||||
Block block = { 0, ncols - len, len };
|
|
||||||
rows[row] << block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (int col = 0; col < ncols; ++col) {
|
|
||||||
foreach (quint16 len, numbers.columns()[col]) {
|
|
||||||
Block block = { 0, nrows - len, len };
|
|
||||||
cols[col] << block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
bool update(UndoState *undo_state, QVector< QVector<Block> > & lines, View<T> & view, bool & changed) {
|
|
||||||
for (int i = 0; i < lines.count(); ++i) {
|
|
||||||
QVector<Block> &line(lines[i]);
|
|
||||||
int lineLen = line.count();
|
|
||||||
|
|
||||||
if (0 == lineLen || (1 == lineLen && 0 == line[0].length)) {
|
|
||||||
if (!view.clear(undo_state, changed, i, 0, view.dimSecond()-1)) return FALSE;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// first block
|
|
||||||
{
|
|
||||||
int cell = line[0].minFirst;
|
|
||||||
// there must be "length" adjacent non white cells
|
|
||||||
for (int cell1 = cell, end = cell + line[0].length; cell <= line[0].maxFirst && cell1 < end; ++cell1) {
|
|
||||||
if (MARK_WHITE == view.data(i, cell1)) {
|
|
||||||
cell = cell1 + 1;
|
|
||||||
end = cell + line[0].length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cell > line[0].minFirst) {
|
|
||||||
if (cell > line[0].maxFirst) return FALSE; // no solution impossible
|
|
||||||
undo_state->trackInt(changed, line[0].minFirst, cell);
|
|
||||||
}
|
|
||||||
// the first black can't be before the first block
|
|
||||||
while (cell < line[0].maxFirst && view.data(i, cell) != MARK_BLACK) ++cell;
|
|
||||||
if (cell < line[0].maxFirst) {
|
|
||||||
undo_state->trackInt(changed, line[0].maxFirst, cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// last block
|
|
||||||
{
|
|
||||||
int len = line.last().length;
|
|
||||||
int cell = line.last().maxFirst;
|
|
||||||
// there must be "length" adjacent non white cells
|
|
||||||
for (int cell1 = cell + len - 1; cell >= line.last().minFirst && cell1 >= cell; --cell1) {
|
|
||||||
if (MARK_WHITE == view.data(i, cell1)) {
|
|
||||||
cell = cell1 - len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cell < line.last().maxFirst) {
|
|
||||||
if (cell < line.last().minFirst) return FALSE; // no solution impossible
|
|
||||||
undo_state->trackInt(changed, line.last().maxFirst, cell);
|
|
||||||
}
|
|
||||||
// the last black can't be after the last block
|
|
||||||
while (cell > line.last().minFirst && view.data(i, cell+len-1) != MARK_BLACK) --cell;
|
|
||||||
if (cell > line.last().minFirst) {
|
|
||||||
undo_state->trackInt(changed, line.last().minFirst, cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* check relative block offsets (min distance 1) */
|
|
||||||
for (int j = 1, k = lineLen - 1; j < lineLen; ++j, --k) {
|
|
||||||
{
|
|
||||||
int minFirst = qMax(line[j].minFirst, 1 + line[j-1].minFirst + line[j-1].length);
|
|
||||||
// the cell before first can't be black
|
|
||||||
while (minFirst <= line[j].maxFirst && MARK_BLACK == view.data(i, minFirst-1)) ++minFirst;
|
|
||||||
// there must be "length" adjacent non white cells
|
|
||||||
for (int cell = minFirst, end = minFirst + line[j].length; minFirst <= line[j].maxFirst && cell < end; ++cell) {
|
|
||||||
if (MARK_WHITE == view.data(i, cell)) {
|
|
||||||
minFirst = cell + 1;
|
|
||||||
end = minFirst + line[j].length;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minFirst >= line[j-1].maxFirst + line[j-1].length) {
|
|
||||||
int cell = minFirst;
|
|
||||||
// next black cell can't be before this block
|
|
||||||
while (cell < line[j].maxFirst && view.data(i, cell) != MARK_BLACK) ++cell;
|
|
||||||
if (cell < line[j].maxFirst) {
|
|
||||||
undo_state->trackInt(changed, line[j].maxFirst, cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (minFirst > line[j].minFirst) {
|
|
||||||
if (minFirst > line[j].maxFirst) return FALSE; // no solution impossible
|
|
||||||
undo_state->trackInt(changed, line[j].minFirst, minFirst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
int len = line[k-1].length;
|
|
||||||
int maxFirst = qMin(line[k-1].maxFirst, line[k].maxFirst - len - 1);
|
|
||||||
// the cell after last can't be black
|
|
||||||
while (maxFirst >= line[k-1].minFirst && MARK_BLACK == view.data(i, maxFirst + len)) --maxFirst;
|
|
||||||
// there must be "length" adjacent non white cells
|
|
||||||
for (int cell = maxFirst + len - 1; maxFirst >= line[k-1].minFirst && cell >= maxFirst; --cell) {
|
|
||||||
if (MARK_WHITE == view.data(i, cell)) {
|
|
||||||
maxFirst = cell - len;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxFirst + len <= line[k].minFirst) {
|
|
||||||
int cell = maxFirst;
|
|
||||||
// next black cell before maxFirst+len can't be after this block
|
|
||||||
while (cell > line[k-1].minFirst && view.data(i, cell+len-1) != MARK_BLACK) --cell;
|
|
||||||
if (cell > line[k-1].minFirst) {
|
|
||||||
undo_state->trackInt(changed, line[k-1].minFirst, cell);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (maxFirst < line[k-1].maxFirst) {
|
|
||||||
if (maxFirst < line[k-1].minFirst) return FALSE; // no solution impossible
|
|
||||||
undo_state->trackInt(changed, line[k-1].maxFirst, maxFirst);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!view.clear(undo_state, changed, i, 0, line[0].minFirst-1)) return FALSE;
|
|
||||||
for (int j = 0; j < lineLen; ++j) {
|
|
||||||
if (j > 0 && !view.clear(undo_state, changed, i, line[j-1].maxFirst + line[j-1].length, line[j].minFirst-1)) return FALSE;
|
|
||||||
if (!view.markBlock(undo_state, changed, i, line[j])) return FALSE;
|
|
||||||
}
|
|
||||||
if (!view.clear(undo_state, changed, i, line.last().maxFirst + line.last().length, view.dimSecond()-1)) return FALSE;
|
|
||||||
|
|
||||||
}
|
|
||||||
return TRUE;
|
|
||||||
}
|
|
||||||
|
|
||||||
void debugState() {
|
|
||||||
#ifndef QT_NO_DEBUG_OUTPUT
|
|
||||||
for (int j = 0; j < m_marks.height(); ++j) {
|
|
||||||
QDebug dbg = qDebug();
|
|
||||||
for (int i = 0; i < m_marks.width(); ++i) {
|
|
||||||
switch (pixel(i, j)) {
|
|
||||||
case MARK_UNKNOWN:
|
|
||||||
dbg << "?"; break;
|
|
||||||
case MARK_BLACK:
|
|
||||||
dbg << "M"; break;
|
|
||||||
case MARK_WHITE:
|
|
||||||
dbg << " "; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
qDebug() << "Row blocks:";
|
|
||||||
for (int j = 0; j < rows.count(); ++j) {
|
|
||||||
QDebug dbg = qDebug() << j << ":";
|
|
||||||
foreach (Block block, rows[j]) {
|
|
||||||
dbg << "[" << block.minFirst << block.maxFirst << block.length << "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
qDebug() << "Col blocks:";
|
|
||||||
for (int j = 0; j < cols.count(); ++j) {
|
|
||||||
QDebug dbg = qDebug() << j << ":";
|
|
||||||
foreach (Block block, cols[j]) {
|
|
||||||
dbg << "[" << block.minFirst << block.maxFirst << block.length << "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void solve(QList<NonogramImage> &solutions, UndoState *undo_state = 0) {
|
|
||||||
bool changed = TRUE;
|
|
||||||
View<ViewRowColumn> rowColumn(*this);
|
|
||||||
View<ViewColumnRow> columnRow(*this);
|
|
||||||
while (changed) {
|
|
||||||
changed = FALSE;
|
|
||||||
if (!update(undo_state, rows, rowColumn, changed)) return;
|
|
||||||
if (!update(undo_state, cols, columnRow, changed)) return;
|
|
||||||
}
|
|
||||||
if (!undo_state) {
|
|
||||||
qDebug() << "State after first run:";
|
|
||||||
debugState();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < m_marks.width(); ++i) {
|
|
||||||
for (int j = 0; j < m_marks.height(); ++j) {
|
|
||||||
if (pixel(i, j) == MARK_UNKNOWN) {
|
|
||||||
UndoState subundo;
|
|
||||||
|
|
||||||
subundo.trackMark(changed, pixel(i, j), MARK_BLACK);
|
|
||||||
solve(solutions, &subundo);
|
|
||||||
subundo.undo();
|
|
||||||
|
|
||||||
subundo.trackMark(changed, pixel(i, j), MARK_WHITE);
|
|
||||||
solve(solutions, &subundo);
|
|
||||||
subundo.undo();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
qDebug() << "Found solution:";
|
|
||||||
debugState();
|
|
||||||
|
|
||||||
solutions.append(NonogramImage());;
|
|
||||||
solutions.last().load(m_marks);
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
QList<NonogramImage> solve(const NonogramNumbers & numbers) {
|
|
||||||
QList<NonogramImage> solutions;
|
|
||||||
SolverState solveState(numbers);
|
|
||||||
solveState.solve(solutions);
|
|
||||||
|
|
||||||
foreach(const NonogramImage& solution, solutions) {
|
|
||||||
Q_ASSERT(numbers.check(solution));
|
|
||||||
}
|
|
||||||
|
|
||||||
return solutions;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,31 +0,0 @@
|
|||||||
/***************************************************************************
|
|
||||||
* Copyright (C) 2012 Stefan Bühler <stbuehler@web.de> *
|
|
||||||
* *
|
|
||||||
* 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., *
|
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
|
||||||
***************************************************************************/
|
|
||||||
#ifndef NONOGRAMSOLVER_H
|
|
||||||
#define NONOGRAMSOLVER_H
|
|
||||||
|
|
||||||
#include <QList>
|
|
||||||
|
|
||||||
namespace libqnono {
|
|
||||||
class NonogramNumbers;
|
|
||||||
class NonogramImage;
|
|
||||||
|
|
||||||
QList<NonogramImage> solve(const NonogramNumbers & numbers);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif // NONOGRAMSOLVER_H
|
|
@ -20,6 +20,7 @@
|
|||||||
#include "ccrossfieldwidget.h"
|
#include "ccrossfieldwidget.h"
|
||||||
|
|
||||||
#include <libqnono/cnonogram.h>
|
#include <libqnono/cnonogram.h>
|
||||||
|
#include <libqnono/cnonogramsolver.h>
|
||||||
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <QMouseEvent>
|
#include <QMouseEvent>
|
||||||
@ -43,13 +44,19 @@ namespace qcross {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//public
|
//public
|
||||||
CCrossFieldWidget::CCrossFieldWidget(QWidget * parent) : QWidget(parent),
|
CCrossFieldWidget::CCrossFieldWidget(CNonogram * picture, QWidget * parent) : QWidget(parent),
|
||||||
m_MessageTimeoutId(-1),
|
m_Picture(picture),
|
||||||
m_Nonogram(new CNonogram(this)),
|
m_OverlayData(NULL),
|
||||||
m_MouseMark(NonogramMarker::NONE),
|
m_MouseMark(CMT_NONE),
|
||||||
m_MouseDown(false),
|
m_ErrorAware(false),
|
||||||
m_NumbersMarkable(true),
|
m_NumbersMarkable(true),
|
||||||
m_Clock(new QLCDNumber(8, this)),
|
m_Time(0),
|
||||||
|
m_Paused(true),
|
||||||
|
m_Solved(false),
|
||||||
|
m_TimerId(-1),
|
||||||
|
m_MessageTimeoutId(-1),
|
||||||
|
m_Clock(NULL),
|
||||||
|
m_ErrorCount(0),
|
||||||
m_LastErrorMark(-1, -1)
|
m_LastErrorMark(-1, -1)
|
||||||
{
|
{
|
||||||
// m_Notifier = new QFrame(this);
|
// m_Notifier = new QFrame(this);
|
||||||
@ -73,27 +80,51 @@ namespace qcross {
|
|||||||
|
|
||||||
// m_Notifier->hide();
|
// m_Notifier->hide();
|
||||||
|
|
||||||
m_Clock->setVisible(false);
|
m_Clock = new QLCDNumber(8, this);
|
||||||
|
m_Clock->setVisible(m_Picture);
|
||||||
|
|
||||||
|
if (m_Picture) {
|
||||||
|
initialize();
|
||||||
|
m_RemainingPixels = m_Picture->blackPixels();
|
||||||
|
updateTimeDisplay();
|
||||||
|
}
|
||||||
|
|
||||||
setEnabled(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() {
|
CCrossFieldWidget::~CCrossFieldWidget() {
|
||||||
qDebug("m_MessageTimeoutId = %i", m_MessageTimeoutId);
|
cleanup();
|
||||||
if (m_MessageTimeoutId != -1) {
|
|
||||||
killTimer(m_MessageTimeoutId);
|
// delete m_Clock;
|
||||||
m_MessageTimeoutId = -1;
|
}
|
||||||
|
|
||||||
|
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->blackPixels();
|
||||||
}
|
}
|
||||||
|
m_Clock->setVisible(m_Picture);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::showMessage(const QString message, int timeout, MessageType type) {
|
void CCrossFieldWidget::showMessage(const QString message, int timeout, MessageType type) {
|
||||||
@ -109,54 +140,127 @@ namespace qcross {
|
|||||||
nextMessage();
|
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);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 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
|
//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) {
|
void CCrossFieldWidget::setNumbersMarkable(bool value) {
|
||||||
m_NumbersMarkable = value;
|
m_NumbersMarkable = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
//protected slots:
|
void CCrossFieldWidget::start() {
|
||||||
void CCrossFieldWidget::loaded() {
|
reset();
|
||||||
m_LastErrorMark = QPoint(-1, -1);
|
resume();
|
||||||
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::won(int score) {
|
void CCrossFieldWidget::pause() {
|
||||||
showMessage(tr("Congratulations! You've solved the puzzle."));
|
if (m_Solved)
|
||||||
}
|
return;
|
||||||
|
|
||||||
void CCrossFieldWidget::timeup() {
|
killTimer(m_TimerId);
|
||||||
showMessage(tr("Too bad! Time's up."), 0, CCrossFieldWidget::Critical);
|
m_TimerId = -1;
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (m_Message.type != Invalid) {
|
||||||
m_Messages.enqueue(m_Message);
|
m_Messages.enqueue(m_Message);
|
||||||
|
|
||||||
@ -165,18 +269,19 @@ namespace qcross {
|
|||||||
m_MessageTimeoutId = -1;
|
m_MessageTimeoutId = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
m_Paused = true;
|
||||||
setEnabled(false);
|
setEnabled(false);
|
||||||
showMessage(tr("Game paused."));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::resumed(int time) {
|
void CCrossFieldWidget::resume() {
|
||||||
|
if (m_Solved)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_Paused = false;
|
||||||
setEnabled(true);
|
setEnabled(true);
|
||||||
showMessage(tr("Game resumed."), 1000);
|
|
||||||
update();
|
update();
|
||||||
}
|
m_TimerId = startTimer(1000);
|
||||||
|
|
||||||
void CCrossFieldWidget::tick(int time) {
|
|
||||||
updateTimeDisplay();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//protected
|
//protected
|
||||||
@ -221,34 +326,78 @@ namespace qcross {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::initialize() {
|
void CCrossFieldWidget::initialize() {
|
||||||
int minimumW = max(m_Nonogram->numbers().maximumNumberCount()+1, 5);
|
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->maximumNumberCount()+1, 5);
|
||||||
int minimumH = minimumW;
|
int minimumH = minimumW;
|
||||||
|
|
||||||
minimumW += m_Nonogram->width();
|
minimumW += m_Picture->width();
|
||||||
minimumH += m_Nonogram->height();
|
minimumH += m_Picture->height();
|
||||||
|
|
||||||
int minimumBoxSize = max(fontMetrics().width(QString::number(m_Nonogram->height())), fontMetrics().width(QString::number(m_Nonogram->width())));
|
int minimumBoxSize = max(fontMetrics().width(QString::number(m_Picture->height())), fontMetrics().width(QString::number(m_Picture->width())));
|
||||||
minimumBoxSize = max(minimumBoxSize, fontInfo().pixelSize() * 1.5);
|
minimumBoxSize = max(minimumBoxSize, fontInfo().pixelSize() * 1.5);
|
||||||
|
|
||||||
minimumW *= minimumBoxSize;
|
minimumW *= minimumBoxSize;
|
||||||
minimumH *= minimumBoxSize;
|
minimumH *= minimumBoxSize;
|
||||||
|
|
||||||
setMinimumSize(minimumW, minimumH);
|
setMinimumSize(minimumW, minimumH);
|
||||||
updateMetrics();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::timerEvent(QTimerEvent * event) {
|
void CCrossFieldWidget::cleanup() {
|
||||||
if (event->timerId() == m_MessageTimeoutId) {
|
qDebug("m_TimerId = %i m_MessageTimeoutId = %i", m_TimerId, m_MessageTimeoutId);
|
||||||
nextMessage();
|
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::updateTimeDisplay() {
|
void CCrossFieldWidget::reset() {
|
||||||
m_Clock->setVisible(m_Nonogram->valid());
|
m_Messages.clear();
|
||||||
|
nextMessage();
|
||||||
|
|
||||||
int secs = m_Nonogram->time();
|
for (int i = 0; i < m_Picture->width(); i++) {
|
||||||
quint8 seconds = secs % 60;
|
for (int j = 0; j < m_Picture->height(); j++)
|
||||||
quint8 minutes = secs / 60;
|
m_OverlayData[i][j] = CMT_UNMARKED;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Solved = false;
|
||||||
|
m_ErrorCount = 0;
|
||||||
|
m_LastErrorMark.setX(-1);
|
||||||
|
m_LastErrorMark.setY(-1);
|
||||||
|
m_RemainingPixels = m_Picture->blackPixels();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CCrossFieldWidget::checkNoError(int x, int y) {
|
||||||
|
return (m_OverlayData[x][y] == CMT_MARKED) || m_Picture->pixel(x, y);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCrossFieldWidget::timerEvent(QTimerEvent * event) {
|
||||||
|
if (event->timerId() == m_TimerId)
|
||||||
|
setTime(m_Time + (m_ErrorAware ? -1 : +1));
|
||||||
|
else if (event->timerId() == m_MessageTimeoutId)
|
||||||
|
nextMessage();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CCrossFieldWidget::updateTimeDisplay() {
|
||||||
|
quint8 seconds = m_Time % 60;
|
||||||
|
quint8 minutes = m_Time / 60;
|
||||||
quint8 hours = minutes / 60;
|
quint8 hours = minutes / 60;
|
||||||
minutes %= 60;
|
minutes %= 60;
|
||||||
|
|
||||||
@ -270,13 +419,64 @@ namespace qcross {
|
|||||||
m_Clock->display(time);
|
m_Clock->display(time);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::paintEvent(QPaintEvent *) {
|
void CCrossFieldWidget::execMark(int x, int y, MarkerType & marker) {
|
||||||
if (!m_Nonogram) {
|
switch (marker) {
|
||||||
return;
|
case CMT_MARKED:
|
||||||
|
if (m_Picture->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->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 *) {
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
|
|
||||||
|
if (!m_Picture) {
|
||||||
|
/* painter.drawText(originX, originY, m_BoxSize, m_BoxSize,
|
||||||
|
Qt::AlignVCenter | Qt::AlignCenter, QString::number(m_Picture->rowNumbers(i)[m_Picture->rowNumbers(i).size() - j]));*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
int originX = m_OffsetX + m_HeaderWidth;
|
int originX = m_OffsetX + m_HeaderWidth;
|
||||||
int originY = m_OffsetY + m_HeaderHeight;
|
int originY = m_OffsetY + m_HeaderHeight;
|
||||||
|
|
||||||
@ -334,23 +534,23 @@ namespace qcross {
|
|||||||
errorColor.setAlpha(128);
|
errorColor.setAlpha(128);
|
||||||
|
|
||||||
painter.setBrush(markerColor);
|
painter.setBrush(markerColor);
|
||||||
for (int i = 0; i < m_Nonogram->width(); i++) {
|
for (int i = 0; i < m_Picture->width(); i++) {
|
||||||
originX = m_OffsetX + m_HeaderWidth + i * m_BoxSize;
|
originX = m_OffsetX + m_HeaderWidth + i * m_BoxSize;
|
||||||
markerRect.moveLeft(originX + m_MarkerOffset);
|
markerRect.moveLeft(originX + m_MarkerOffset);
|
||||||
|
|
||||||
for (int j = 0; j < m_Nonogram->height(); j++) {
|
for (int j = 0; j < m_Picture->height(); j++) {
|
||||||
originY = m_OffsetY + m_HeaderHeight + j * m_BoxSize;
|
originY = m_OffsetY + m_HeaderHeight + j * m_BoxSize;
|
||||||
markerRect.moveTop(originY + m_MarkerOffset);
|
markerRect.moveTop(originY + m_MarkerOffset);
|
||||||
|
|
||||||
switch (m_Nonogram->marker().pixel(i, j)) {
|
switch (m_OverlayData[i][j]) {
|
||||||
case NonogramMarker::MARKED:
|
case CMT_MARKED:
|
||||||
painter.fillRect(markerRect, painter.brush());
|
painter.fillRect(markerRect, painter.brush());
|
||||||
break;
|
break;
|
||||||
case NonogramMarker::CROSSED:
|
case CMT_CROSSED:
|
||||||
if (m_Nonogram->isFinished())
|
if (m_Solved)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
if (i == m_LastErrorMark.x() && j == m_LastErrorMark.y()) {
|
if (m_ErrorAware && m_ErrorCount && i == m_LastErrorMark.x() && j == m_LastErrorMark.y()) {
|
||||||
painter.setBrush(errorColor);
|
painter.setBrush(errorColor);
|
||||||
painter.fillRect(markerRect, painter.brush());
|
painter.fillRect(markerRect, painter.brush());
|
||||||
painter.setBrush(markerColor);
|
painter.setBrush(markerColor);
|
||||||
@ -378,7 +578,7 @@ namespace qcross {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// draw numbers area
|
// draw numbers area
|
||||||
if (m_Nonogram->isPaused())
|
if (m_Paused)
|
||||||
painter.setPen(palette().color(QPalette::Shadow));
|
painter.setPen(palette().color(QPalette::Shadow));
|
||||||
|
|
||||||
QFont font = painter.font();
|
QFont font = painter.font();
|
||||||
@ -386,18 +586,16 @@ namespace qcross {
|
|||||||
|
|
||||||
painter.setFont(font);
|
painter.setFont(font);
|
||||||
|
|
||||||
for (int i = 0; i < m_Nonogram->width(); i++) {
|
for (int i = 0; i < m_Picture->width(); i++) {
|
||||||
originX = m_OffsetX + m_HeaderWidth + i * m_BoxSize;
|
originX = m_OffsetX + m_HeaderWidth + i * m_BoxSize;
|
||||||
painter.fillRect(originX, m_OffsetY, m_BoxSize, m_HeaderHeight, palette().color((i % 2) ? QPalette::AlternateBase : QPalette::Base));
|
painter.fillRect(originX, m_OffsetY, m_BoxSize, m_HeaderHeight, palette().color((i % 2) ? QPalette::AlternateBase : QPalette::Base));
|
||||||
if (!m_Nonogram->isPaused()) {
|
if (!m_Paused) {
|
||||||
painter.setPen(palette().color(QPalette::WindowText));
|
painter.setPen(palette().color(QPalette::WindowText));
|
||||||
int j = m_Nonogram->numbers().columns()[i].count();;
|
for (int j = 1; j <= m_Picture->columnNumbers(i).size(); j++) {
|
||||||
foreach (quint16 block, m_Nonogram->numbers().columns()[i]) {
|
|
||||||
originY = m_OffsetY + m_HeaderHeight - j * m_BoxSize;
|
originY = m_OffsetY + m_HeaderHeight - j * m_BoxSize;
|
||||||
|
|
||||||
painter.drawText(originX, originY, m_BoxSize, m_BoxSize,
|
painter.drawText(originX, originY, m_BoxSize, m_BoxSize,
|
||||||
Qt::AlignVCenter | Qt::AlignCenter, QString::number(block));
|
Qt::AlignVCenter | Qt::AlignCenter, QString::number(m_Picture->columnNumbers(i)[m_Picture->columnNumbers(i).size() - j]));
|
||||||
--j;
|
|
||||||
}
|
}
|
||||||
painter.setPen(palette().color(QPalette::Shadow));
|
painter.setPen(palette().color(QPalette::Shadow));
|
||||||
}
|
}
|
||||||
@ -406,17 +604,15 @@ namespace qcross {
|
|||||||
}
|
}
|
||||||
// painter.drawLine(m_OffsetX + m_RasterWidth, m_OffsetY, m_OffsetX + m_RasterWidth, m_OffsetY + m_RasterHeight);
|
// painter.drawLine(m_OffsetX + m_RasterWidth, m_OffsetY, m_OffsetX + m_RasterWidth, m_OffsetY + m_RasterHeight);
|
||||||
|
|
||||||
for (int i = 0; i < m_Nonogram->height(); i++) {
|
for (int i = 0; i < m_Picture->height(); i++) {
|
||||||
originY = m_OffsetY + m_HeaderHeight + i * m_BoxSize;
|
originY = m_OffsetY + m_HeaderHeight + i * m_BoxSize;
|
||||||
painter.fillRect(m_OffsetX, originY, m_HeaderWidth, m_BoxSize, palette().color((i % 2) ? QPalette::AlternateBase : QPalette::Base));
|
painter.fillRect(m_OffsetX, originY, m_HeaderWidth, m_BoxSize, palette().color((i % 2) ? QPalette::AlternateBase : QPalette::Base));
|
||||||
if (!m_Nonogram->isPaused()) {
|
if (!m_Paused) {
|
||||||
painter.setPen(palette().color(QPalette::WindowText));
|
painter.setPen(palette().color(QPalette::WindowText));
|
||||||
int j = m_Nonogram->numbers().rows()[i].count();
|
for (int j = 1; j <= m_Picture->rowNumbers(i).size(); j++) {
|
||||||
foreach (quint16 block, m_Nonogram->numbers().rows()[i]) {
|
|
||||||
originX = m_OffsetX + m_HeaderWidth - j * m_BoxSize;
|
originX = m_OffsetX + m_HeaderWidth - j * m_BoxSize;
|
||||||
painter.drawText(originX, originY, m_BoxSize, m_BoxSize,
|
painter.drawText(originX, originY, m_BoxSize, m_BoxSize,
|
||||||
Qt::AlignVCenter | Qt::AlignCenter, QString::number(block));
|
Qt::AlignVCenter | Qt::AlignCenter, QString::number(m_Picture->rowNumbers(i)[m_Picture->rowNumbers(i).size() - j]));
|
||||||
--j;
|
|
||||||
}
|
}
|
||||||
painter.setPen(palette().color(QPalette::Shadow));
|
painter.setPen(palette().color(QPalette::Shadow));
|
||||||
}
|
}
|
||||||
@ -459,16 +655,19 @@ namespace qcross {
|
|||||||
}*/
|
}*/
|
||||||
|
|
||||||
void CCrossFieldWidget::resizeEvent(QResizeEvent * /*event*/) {
|
void CCrossFieldWidget::resizeEvent(QResizeEvent * /*event*/) {
|
||||||
|
if (!m_Picture)
|
||||||
|
return;
|
||||||
|
|
||||||
updateMetrics();
|
updateMetrics();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::updateMetrics() {
|
void CCrossFieldWidget::updateMetrics() {
|
||||||
m_HeaderWidth = max(m_Nonogram->numbers().maximumNumberCount()+1, 5);
|
m_HeaderWidth = max(m_Picture->maximumNumberCount()+1, 5);
|
||||||
/* m_HeaderWidth = max(m_Picture->width() / 2 + 1, m_Picture->height() / 2 + 1);
|
/* m_HeaderWidth = max(m_Picture->width() / 2 + 1, m_Picture->height() / 2 + 1);
|
||||||
m_HeaderWidth = m_Picture->width() / 2 + 1;
|
m_HeaderWidth = m_Picture->width() / 2 + 1;
|
||||||
m_HeaderHeight = m_Picture->height() / 2 + 1;*/
|
m_HeaderHeight = m_Picture->height() / 2 + 1;*/
|
||||||
|
|
||||||
int fieldsize = min((m_Nonogram->width() + m_HeaderWidth), (m_Nonogram->height() + m_HeaderWidth));
|
int fieldsize = min((m_Picture->width() + m_HeaderWidth), (m_Picture->height() + m_HeaderWidth));
|
||||||
int widgetsize = min(width(), height());
|
int widgetsize = min(width(), height());
|
||||||
|
|
||||||
m_BoxSize = ((double)widgetsize / fieldsize);
|
m_BoxSize = ((double)widgetsize / fieldsize);
|
||||||
@ -480,8 +679,8 @@ namespace qcross {
|
|||||||
m_HeaderWidth *= m_BoxSize;
|
m_HeaderWidth *= m_BoxSize;
|
||||||
m_HeaderHeight = m_HeaderWidth;
|
m_HeaderHeight = m_HeaderWidth;
|
||||||
|
|
||||||
m_RasterWidth = m_BoxSize * m_Nonogram->width() + m_HeaderWidth;
|
m_RasterWidth = m_BoxSize * m_Picture->width() + m_HeaderWidth;
|
||||||
m_RasterHeight = m_BoxSize * m_Nonogram->height() + m_HeaderHeight;
|
m_RasterHeight = m_BoxSize * m_Picture->height() + m_HeaderHeight;
|
||||||
|
|
||||||
m_OffsetX = (width() - m_RasterWidth) / 2;
|
m_OffsetX = (width() - m_RasterWidth) / 2;
|
||||||
m_OffsetY = (height() - m_RasterHeight) / 2;
|
m_OffsetY = (height() - m_RasterHeight) / 2;
|
||||||
@ -492,10 +691,12 @@ namespace qcross {
|
|||||||
m_Clock->setGeometry(m_OffsetX, m_OffsetY, m_HeaderWidth, clockHeight);
|
m_Clock->setGeometry(m_OffsetX, m_OffsetY, m_HeaderWidth, clockHeight);
|
||||||
// if (m_Notifier && m_Notifier->isVisible())
|
// if (m_Notifier && m_Notifier->isVisible())
|
||||||
// m_Notifier->setGeometry(m_OffsetX, m_OffsetY + clockHeight, m_HeaderWidth, m_Notifier->sizeHint().height());
|
// m_Notifier->setGeometry(m_OffsetX, m_OffsetY + clockHeight, m_HeaderWidth, m_Notifier->sizeHint().height());
|
||||||
update();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::mousePressEvent(QMouseEvent * event) {
|
void CCrossFieldWidget::mousePressEvent(QMouseEvent * event) {
|
||||||
|
if (m_Paused)
|
||||||
|
return;
|
||||||
|
|
||||||
int pressedX = event->x() - m_OffsetX;
|
int pressedX = event->x() - m_OffsetX;
|
||||||
int pressedY = event->y() - m_OffsetY;
|
int pressedY = event->y() - m_OffsetY;
|
||||||
|
|
||||||
@ -505,23 +706,24 @@ namespace qcross {
|
|||||||
pressedX = (pressedX - m_HeaderWidth) / m_BoxSize;
|
pressedX = (pressedX - m_HeaderWidth) / m_BoxSize;
|
||||||
pressedY = (pressedY - m_HeaderHeight) / m_BoxSize;
|
pressedY = (pressedY - m_HeaderHeight) / m_BoxSize;
|
||||||
|
|
||||||
m_MouseLastX = pressedX; m_MouseLastY = pressedY;
|
|
||||||
|
|
||||||
if (event->button() == Qt::RightButton)
|
if (event->button() == Qt::RightButton)
|
||||||
m_MouseMark = NonogramMarker::CROSSED;
|
m_MouseMark = CMT_CROSSED;
|
||||||
else
|
else
|
||||||
m_MouseMark = NonogramMarker::MARKED;
|
m_MouseMark = CMT_MARKED;
|
||||||
|
|
||||||
m_MouseDown = true;
|
if (m_OverlayData[pressedX][pressedY] == m_MouseMark)
|
||||||
|
m_MouseMark = CMT_UNMARKED;
|
||||||
|
|
||||||
if (m_Nonogram->marker().pixel(pressedX, pressedY) == m_MouseMark)
|
execMark(pressedX, pressedY, m_MouseMark);
|
||||||
m_MouseMark = NonogramMarker::NONE;
|
update();
|
||||||
|
|
||||||
m_Nonogram->setMark(pressedX, pressedY, m_MouseMark);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::mouseMoveEvent(QMouseEvent * event) {
|
void CCrossFieldWidget::mouseMoveEvent(QMouseEvent * event) {
|
||||||
if (!m_MouseDown)
|
if (m_Paused)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (m_MouseMark == CMT_NONE)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
int pressedX = event->x() - m_OffsetX;
|
int pressedX = event->x() - m_OffsetX;
|
||||||
@ -533,13 +735,13 @@ namespace qcross {
|
|||||||
pressedX = (pressedX - m_HeaderWidth) / m_BoxSize;
|
pressedX = (pressedX - m_HeaderWidth) / m_BoxSize;
|
||||||
pressedY = (pressedY - m_HeaderHeight) / m_BoxSize;
|
pressedY = (pressedY - m_HeaderHeight) / m_BoxSize;
|
||||||
|
|
||||||
if (pressedX == m_MouseLastX && pressedY == m_MouseLastY) return;
|
if (m_OverlayData[pressedX][pressedY] != m_MouseMark) {
|
||||||
m_MouseLastX = pressedX; m_MouseLastY = pressedY;
|
execMark(pressedX, pressedY, m_MouseMark);
|
||||||
|
update();
|
||||||
m_Nonogram->setMark(pressedX, pressedY, m_MouseMark);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CCrossFieldWidget::mouseReleaseEvent(QMouseEvent *) {
|
void CCrossFieldWidget::mouseReleaseEvent(QMouseEvent *) {
|
||||||
m_MouseDown = false;
|
m_MouseMark = CMT_NONE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,8 +23,6 @@
|
|||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
#include <QQueue>
|
#include <QQueue>
|
||||||
|
|
||||||
#include <libqnono/nonogrammarker.h>
|
|
||||||
|
|
||||||
class QLCDNumber;
|
class QLCDNumber;
|
||||||
class QFrame;
|
class QFrame;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
@ -35,36 +33,47 @@ namespace libqnono {
|
|||||||
}
|
}
|
||||||
|
|
||||||
namespace qcross {
|
namespace qcross {
|
||||||
|
enum MarkerType {CMT_UNMARKED = 0, CMT_MARKED = 1, CMT_CROSSED = 2, CMT_NONE = 3};
|
||||||
|
|
||||||
class CCrossFieldWidget : public QWidget {
|
class CCrossFieldWidget : public QWidget {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
public:
|
public:
|
||||||
enum MessageType {Information = 1, Warning = 2, Critical = 3, Invalid = 0};
|
enum MessageType {Information = 1, Warning = 2, Critical = 3, Invalid = 0};
|
||||||
|
|
||||||
CCrossFieldWidget(QWidget * parent = 0);
|
bool isPaused() const { return m_Paused; }
|
||||||
|
bool isSolved() const { return m_Solved; }
|
||||||
|
bool isErrorAware() const { return m_ErrorAware; }
|
||||||
|
|
||||||
|
int time() const { return m_Time; }
|
||||||
|
qint32 errorCount() const { return m_ErrorCount; }
|
||||||
|
|
||||||
|
CCrossFieldWidget(libqnono::CNonogram * picture, QWidget * parent = 0);
|
||||||
~CCrossFieldWidget();
|
~CCrossFieldWidget();
|
||||||
|
|
||||||
libqnono::CNonogram * nonogram() { return m_Nonogram; }
|
QPoint lastErrorMark() const { return m_LastErrorMark; }
|
||||||
|
|
||||||
|
void setPicture(libqnono::CNonogram * picture);
|
||||||
|
|
||||||
void showMessage(const QString message = QString(), int timeout = 0, MessageType type = Information);
|
void showMessage(const QString message = QString(), int timeout = 0, MessageType type = Information);
|
||||||
|
|
||||||
|
void applyState(QDataStream & stream);
|
||||||
|
void dumpState(QDataStream & stream);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
void mark(int x, int y, int type);
|
||||||
|
void setTime(int value);
|
||||||
|
void setErrorAware(bool value);
|
||||||
void setNumbersMarkable(bool value);
|
void setNumbersMarkable(bool value);
|
||||||
|
void start();
|
||||||
|
void pause();
|
||||||
|
void resume();
|
||||||
|
|
||||||
protected slots:
|
signals:
|
||||||
/* signals from nonogram */
|
void markError();
|
||||||
void loaded();
|
void solved();
|
||||||
|
void timeUp();
|
||||||
void won(int score);
|
void timeChanged(int value);
|
||||||
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:
|
protected:
|
||||||
struct Message {
|
struct Message {
|
||||||
MessageType type;
|
MessageType type;
|
||||||
@ -75,10 +84,10 @@ namespace qcross {
|
|||||||
};
|
};
|
||||||
|
|
||||||
QQueue<Message> m_Messages;
|
QQueue<Message> m_Messages;
|
||||||
int m_MessageTimeoutId;
|
|
||||||
Message m_Message;
|
|
||||||
|
|
||||||
libqnono::CNonogram * m_Nonogram;
|
libqnono::CNonogram * m_Picture;
|
||||||
|
MarkerType ** m_OverlayData;
|
||||||
|
quint32 m_RemainingPixels;
|
||||||
|
|
||||||
int m_BoxSize;
|
int m_BoxSize;
|
||||||
qreal m_MarkerSize;
|
qreal m_MarkerSize;
|
||||||
@ -90,11 +99,15 @@ namespace qcross {
|
|||||||
int m_RasterWidth;
|
int m_RasterWidth;
|
||||||
int m_RasterHeight;
|
int m_RasterHeight;
|
||||||
|
|
||||||
libqnono::NonogramMarker::Mark m_MouseMark;
|
MarkerType m_MouseMark;
|
||||||
bool m_MouseDown;
|
|
||||||
int m_MouseLastX, m_MouseLastY;
|
|
||||||
|
|
||||||
|
bool m_ErrorAware;
|
||||||
bool m_NumbersMarkable;
|
bool m_NumbersMarkable;
|
||||||
|
int m_Time;
|
||||||
|
bool m_Paused;
|
||||||
|
bool m_Solved;
|
||||||
|
int m_TimerId;
|
||||||
|
int m_MessageTimeoutId;
|
||||||
|
|
||||||
QLCDNumber * m_Clock;
|
QLCDNumber * m_Clock;
|
||||||
|
|
||||||
@ -102,14 +115,22 @@ namespace qcross {
|
|||||||
// QLabel * m_NotifierIcon;
|
// QLabel * m_NotifierIcon;
|
||||||
// QLabel * m_NotifierText;
|
// QLabel * m_NotifierText;
|
||||||
|
|
||||||
|
qint32 m_ErrorCount;
|
||||||
QPoint m_LastErrorMark;
|
QPoint m_LastErrorMark;
|
||||||
|
|
||||||
|
Message m_Message;
|
||||||
void nextMessage();
|
void nextMessage();
|
||||||
|
|
||||||
void initialize(); /** update metrics for new problem */
|
inline void initialize();
|
||||||
|
inline void cleanup();
|
||||||
|
inline void reset();
|
||||||
|
|
||||||
|
inline bool checkNoError(int x, int y);
|
||||||
|
|
||||||
void timerEvent(QTimerEvent * event);
|
void timerEvent(QTimerEvent * event);
|
||||||
void updateTimeDisplay();
|
inline void updateTimeDisplay();
|
||||||
|
|
||||||
|
inline void execMark(int x, int y, MarkerType & marker);
|
||||||
|
|
||||||
void paintEvent(QPaintEvent * event);
|
void paintEvent(QPaintEvent * event);
|
||||||
|
|
||||||
@ -117,7 +138,7 @@ namespace qcross {
|
|||||||
*/
|
*/
|
||||||
void resizeEvent(QResizeEvent * event);
|
void resizeEvent(QResizeEvent * event);
|
||||||
|
|
||||||
void updateMetrics(); /** update metrics after resize */
|
void updateMetrics();
|
||||||
|
|
||||||
void mousePressEvent(QMouseEvent * event);
|
void mousePressEvent(QMouseEvent * event);
|
||||||
void mouseMoveEvent(QMouseEvent * event);
|
void mouseMoveEvent(QMouseEvent * event);
|
||||||
|
@ -29,6 +29,7 @@
|
|||||||
#include <QInputDialog>
|
#include <QInputDialog>
|
||||||
|
|
||||||
#include <libqnono/cnonogram.h>
|
#include <libqnono/cnonogram.h>
|
||||||
|
#include <libqnono/cnonogramsolver.h>
|
||||||
#include <libqnono/ccrosspackage.h>
|
#include <libqnono/ccrosspackage.h>
|
||||||
|
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
@ -41,24 +42,30 @@ namespace qcross {
|
|||||||
using namespace libqnono;
|
using namespace libqnono;
|
||||||
//public
|
//public
|
||||||
CGameWindow::CGameWindow(QWidget * parent) : QMainWindow(parent) {
|
CGameWindow::CGameWindow(QWidget * parent) : QMainWindow(parent) {
|
||||||
m_Field = new CCrossFieldWidget(this);
|
m_Highscore = NULL;
|
||||||
m_Nonogram = m_Field->nonogram();
|
m_PictureIndex = -1;
|
||||||
|
m_Picture = new CNonogram(QSize(10, 10));
|
||||||
|
m_Picture->updateNumbers();
|
||||||
|
|
||||||
|
m_Field = new CCrossFieldWidget(m_Picture, this);
|
||||||
|
m_Field->setTime(42 * 60 + 23);
|
||||||
|
|
||||||
setCentralWidget(m_Field);
|
setCentralWidget(m_Field);
|
||||||
m_Field->showMessage(tr("Welcome to QCross!"));
|
m_Field->showMessage(tr("Welcome to QCross!"));
|
||||||
|
|
||||||
createActions();
|
createActions();
|
||||||
|
// statusBar()->show();
|
||||||
|
m_Solver = new CNonogramSolver(this);
|
||||||
|
|
||||||
connect(m_Nonogram, SIGNAL(won(int)), this, SLOT(wonGame(int)));
|
connect(m_Solver, SIGNAL(markRequested(int, int, int)), m_Field, SLOT(mark(int, int, int)));
|
||||||
connect(m_Nonogram, SIGNAL(timeup()), this, SLOT(lostGame()));
|
connect(m_Field, SIGNAL(solved()), this, SLOT(wonGame()));
|
||||||
|
connect(m_Field, SIGNAL(timeUp()), this, SLOT(lostGame()));
|
||||||
connect(m_Nonogram, SIGNAL(loaded()), SLOT(updateStates()));
|
connect(m_Field, SIGNAL(markError()), this, SLOT(handleErrorMark()));
|
||||||
connect(m_Nonogram, SIGNAL(restarted()), SLOT(updateStates()));
|
|
||||||
connect(m_Nonogram, SIGNAL(paused(int)), SLOT(updateStates()));
|
|
||||||
connect(m_Nonogram, SIGNAL(resumed(int)), SLOT(updateStates()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CGameWindow::~CGameWindow() {
|
CGameWindow::~CGameWindow() {
|
||||||
|
m_Field->setPicture(NULL);
|
||||||
|
delete m_Picture;
|
||||||
}
|
}
|
||||||
|
|
||||||
//protected
|
//protected
|
||||||
@ -74,17 +81,18 @@ namespace qcross {
|
|||||||
m_SaveGameAction->setEnabled(false);
|
m_SaveGameAction->setEnabled(false);
|
||||||
currentMenu->addAction(tr("&Load..."), this, SLOT(loadGame()), Qt::CTRL + Qt::Key_N);
|
currentMenu->addAction(tr("&Load..."), this, SLOT(loadGame()), Qt::CTRL + Qt::Key_N);
|
||||||
currentMenu->addSeparator();
|
currentMenu->addSeparator();
|
||||||
m_RestartGameAction = currentMenu->addAction(tr("&Restart"), m_Nonogram, SLOT(restart()), Qt::CTRL + Qt::Key_R);
|
m_RestartGameAction = currentMenu->addAction(tr("&Restart"), this, SLOT(restartGame()), Qt::CTRL + Qt::Key_R);
|
||||||
m_RestartGameAction->setEnabled(false);
|
m_RestartGameAction->setEnabled(false);
|
||||||
m_PauseGameAction = currentMenu->addAction(tr("&Pause"));
|
m_PauseGameAction = currentMenu->addAction(tr("&Pause"));
|
||||||
m_PauseGameAction->setEnabled(false);
|
m_PauseGameAction->setEnabled(false);
|
||||||
m_PauseGameAction->setCheckable(true);
|
m_PauseGameAction->setCheckable(true);
|
||||||
|
m_PauseGameAction->setChecked(true);
|
||||||
m_PauseGameAction->setShortcut(Qt::CTRL + Qt::Key_P);
|
m_PauseGameAction->setShortcut(Qt::CTRL + Qt::Key_P);
|
||||||
connect(m_PauseGameAction, SIGNAL(toggled(bool)), this, SLOT(pauseGame(bool)));
|
connect(m_PauseGameAction, SIGNAL(toggled(bool)), this, SLOT(pauseGame(bool)));
|
||||||
currentMenu->addSeparator();
|
currentMenu->addSeparator();
|
||||||
currentMenu->addAction(tr("&Quit"), this, SLOT(close()), Qt::CTRL + Qt::Key_Q);
|
currentMenu->addAction(tr("&Quit"), this, SLOT(close()), Qt::CTRL + Qt::Key_Q);
|
||||||
currentMenu->addSeparator();
|
currentMenu->addSeparator();
|
||||||
currentMenu->addAction(tr("debug: solve"), m_Nonogram, SLOT(solve()));
|
currentMenu->addAction(tr("debug: solve"), this, SLOT(startSolver()));
|
||||||
|
|
||||||
currentToolBar = addToolBar(currentMenu->title());
|
currentToolBar = addToolBar(currentMenu->title());
|
||||||
currentToolBar->addActions(currentMenu->actions());
|
currentToolBar->addActions(currentMenu->actions());
|
||||||
@ -95,95 +103,234 @@ namespace qcross {
|
|||||||
currentMenu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));
|
currentMenu->addAction(tr("About &Qt"), qApp, SLOT(aboutQt()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char magicSaveGameHeader[] = {'C', 'R', 'S', 'V'};
|
||||||
|
|
||||||
|
bool CGameWindow::readSaveGame(QDataStream & stream) {
|
||||||
|
QString stringBuffer;
|
||||||
|
qint32 intBuffer;
|
||||||
|
QSize sizeBuffer;
|
||||||
|
|
||||||
|
// picture index
|
||||||
|
stream >> intBuffer; qDebug("picture index = %i", m_PictureIndex);
|
||||||
|
if (stream.atEnd()) {
|
||||||
|
qCritical("invalid savegame");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_PictureIndex = intBuffer;
|
||||||
|
|
||||||
|
// package name
|
||||||
|
if (m_PictureIndex > -1) {
|
||||||
|
stream >> stringBuffer; qDebug("package name = %s", qPrintable(stringBuffer));
|
||||||
|
if (stream.atEnd()) {
|
||||||
|
qCritical("invalid savegame");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_PackageName = stringBuffer;
|
||||||
|
|
||||||
|
stream >> stringBuffer; qDebug("highscoreFileName = %s", qPrintable(stringBuffer));
|
||||||
|
if (stream.atEnd()) {
|
||||||
|
qCritical("invalid savegame");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Highscore)
|
||||||
|
delete m_Highscore;
|
||||||
|
|
||||||
|
m_Highscore = new CHighscore(0);
|
||||||
|
m_Highscore->setFileName(QCROSS_STRING_DATAPATH + QDir::separator() + stringBuffer);
|
||||||
|
m_Highscore->open();
|
||||||
|
}
|
||||||
|
|
||||||
|
// picture
|
||||||
|
if (m_Picture)
|
||||||
|
delete m_Picture;
|
||||||
|
|
||||||
|
m_Picture = new CNonogram();
|
||||||
|
|
||||||
|
if (!m_Picture->readFromStream(stream)) {
|
||||||
|
qCritical("invalid savegame");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO handle save game porting properly
|
||||||
|
// TODO check for matching nonogram
|
||||||
|
|
||||||
|
m_Field->setPicture(m_Picture);
|
||||||
|
m_Field->applyState(stream);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CGameWindow::writeSaveGame(QDataStream & stream) {
|
||||||
|
// picture index
|
||||||
|
stream << qint32(m_PictureIndex);
|
||||||
|
if (m_PictureIndex > -1) {
|
||||||
|
stream << m_PackageName;
|
||||||
|
stream << m_Highscore->fileName().section(QDir::separator(), -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Picture->writeToStream(stream);
|
||||||
|
m_Field->dumpState(stream);
|
||||||
|
}
|
||||||
|
|
||||||
void CGameWindow::newGame() {
|
void CGameWindow::newGame() {
|
||||||
bool notPaused = !m_Nonogram->isPaused();
|
bool notPaused = !m_Field->isPaused();
|
||||||
if (notPaused)
|
if (notPaused)
|
||||||
m_Nonogram->pause();
|
pauseGame(true);
|
||||||
|
|
||||||
CNewGameDialog dialog;
|
CNewGameDialog dialog;
|
||||||
if (dialog.exec() == QDialog::Accepted) {
|
if (dialog.exec() == QDialog::Accepted) {
|
||||||
m_Nonogram->start(dialog.getProblem());
|
if (m_Highscore)
|
||||||
return;
|
delete m_Highscore;
|
||||||
|
|
||||||
|
m_Highscore = dialog.takeHighscore();
|
||||||
|
m_PackageName = dialog.selectedPackageName();
|
||||||
|
m_PictureIndex = m_Highscore ? dialog.nonogramIndex() : -1;
|
||||||
|
|
||||||
|
CNonogram * newPicture = dialog.takeNonogram();
|
||||||
|
newPicture->updateNumbers();
|
||||||
|
|
||||||
|
m_PauseGameAction->setEnabled(true);
|
||||||
|
m_RestartGameAction->setEnabled(true);
|
||||||
|
|
||||||
|
m_Field->setPicture(newPicture);
|
||||||
|
|
||||||
|
if (m_Picture)
|
||||||
|
delete m_Picture;
|
||||||
|
|
||||||
|
m_Picture = newPicture;
|
||||||
|
|
||||||
|
m_Field->resume();
|
||||||
|
m_Field->showMessage(tr("Game started!"), 1000);
|
||||||
}
|
}
|
||||||
|
else
|
||||||
if (notPaused)
|
pauseGame(false);
|
||||||
m_Nonogram->resume();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void CGameWindow::saveGame() {
|
void CGameWindow::saveGame() {
|
||||||
bool notPaused = !m_Nonogram->isPaused();
|
if (!m_Picture)
|
||||||
if (notPaused)
|
return;
|
||||||
m_Nonogram->pause();
|
|
||||||
|
if (!m_Field->isPaused())
|
||||||
|
m_PauseGameAction->setChecked(true);
|
||||||
|
|
||||||
QString fileName = QFileDialog::getSaveFileName(this, tr("Save current game"),
|
QString fileName = QFileDialog::getSaveFileName(this, tr("Save current game"),
|
||||||
QDir::homePath(), tr("QCross Saved games (*.csg)"));
|
QDir::homePath(), tr("QCross Saved games (*.csg)"));
|
||||||
|
|
||||||
m_Nonogram->saveGame(fileName);
|
qDebug("saving game state file: %s", fileName.toAscii().data());
|
||||||
|
QFile file(fileName);
|
||||||
|
if (!file.open(QIODevice::WriteOnly))
|
||||||
|
return;
|
||||||
|
|
||||||
if (notPaused)
|
QDataStream out(&file);
|
||||||
m_Nonogram->resume();
|
out.setVersion(QDataStream::Qt_4_0);
|
||||||
|
|
||||||
|
if (out.writeRawData(magicSaveGameHeader, 4) == -1)
|
||||||
|
qDebug("could not write magic save game header");
|
||||||
|
else
|
||||||
|
writeSaveGame(out);
|
||||||
|
|
||||||
|
m_PauseGameAction->setChecked(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameWindow::loadGame() {
|
void CGameWindow::loadGame() {
|
||||||
bool notPaused = !m_Nonogram->isPaused();
|
if (!m_Field->isPaused())
|
||||||
if (notPaused)
|
m_PauseGameAction->setChecked(true);
|
||||||
m_Nonogram->pause();
|
|
||||||
|
|
||||||
QString fileName = QFileDialog::getOpenFileName(this, tr("Open saved game"),
|
QString fileName = QFileDialog::getOpenFileName(this, tr("Open saved game"),
|
||||||
QDir::homePath(), tr("QCross Saved games (*.csg)"));
|
QDir::homePath(), tr("QCross Saved games (*.csg)"));
|
||||||
|
|
||||||
if (m_Nonogram->loadGame(fileName)) {
|
qDebug("opening game state file: %s", fileName.toAscii().data());
|
||||||
|
QFile file(fileName);
|
||||||
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
QDataStream in(&file);
|
||||||
|
in.setVersion(QDataStream::Qt_4_0);
|
||||||
|
|
||||||
|
char magicHeader[4];
|
||||||
|
in.readRawData(magicHeader, 4);
|
||||||
|
|
||||||
|
qDebug("checking magic savegame header");
|
||||||
|
for (int i = 0; i < 4; i++) {
|
||||||
|
if (magicHeader[i] != magicSaveGameHeader[i])
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notPaused)
|
if (readSaveGame(in)) {
|
||||||
m_Nonogram->resume();
|
m_PauseGameAction->setEnabled(true);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_Field->setPicture(NULL);
|
||||||
|
m_PictureIndex = -1;
|
||||||
|
|
||||||
|
if (m_Picture) {
|
||||||
|
delete m_Picture;
|
||||||
|
m_Picture = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_Highscore) {
|
||||||
|
delete m_Highscore;
|
||||||
|
m_Highscore = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_PauseGameAction->setChecked(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CGameWindow::restartGame() {
|
||||||
|
m_Field->setTime(m_Picture->timeout());
|
||||||
|
|
||||||
|
m_PauseGameAction->setChecked(false);
|
||||||
|
m_Field->start();
|
||||||
|
|
||||||
|
m_Field->showMessage(tr("Game restarted."), 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameWindow::pauseGame(bool value) {
|
void CGameWindow::pauseGame(bool value) {
|
||||||
if (value == m_Nonogram->isPaused())
|
|
||||||
return;
|
|
||||||
|
|
||||||
if (value) {
|
if (value) {
|
||||||
m_Nonogram->pause();
|
m_Field->pause();
|
||||||
|
m_Field->showMessage(tr("Game paused."));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
m_Nonogram->resume();
|
m_Field->showMessage(tr("Game resumed."), 1000);
|
||||||
|
m_Field->resume();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameWindow::wonGame(int score) {
|
void CGameWindow::wonGame() {
|
||||||
m_PauseGameAction->setEnabled(false);
|
m_PauseGameAction->setEnabled(false);
|
||||||
if (score < 0) return;
|
m_Field->showMessage(tr("Congratulations! You've solved the puzzle."));
|
||||||
const NonogramProblem &problem(m_Nonogram->problem());
|
if (m_Highscore) {
|
||||||
if (!problem.packageIdentifier().isEmpty() && problem.packageIndex() >= 0) {
|
qDebug("attempting to save highscore");
|
||||||
CHighscore highscore;
|
(*m_Highscore)[m_PictureIndex] = m_Field->time();
|
||||||
highscore.setPackageIdentifier(problem.packageIdentifier());
|
m_Highscore->save();
|
||||||
if (highscore.open()) {
|
|
||||||
int oldScore = highscore[problem.packageIndex()];
|
|
||||||
if (0 == oldScore || (problem.timeout() > 0 ? score > oldScore : score < oldScore)) {
|
|
||||||
qDebug("attempting to save highscore");
|
|
||||||
highscore[problem.packageIndex()] = score;
|
|
||||||
highscore.save();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameWindow::lostGame() {
|
void CGameWindow::lostGame() {
|
||||||
m_PauseGameAction->setEnabled(false);
|
m_PauseGameAction->setEnabled(false);
|
||||||
|
m_Field->showMessage(tr("Too bad! Time's up."), 0, CCrossFieldWidget::Critical);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CGameWindow::handleErrorMark() {
|
||||||
|
int timeDiff = (m_Field->errorCount() == 1) ? 2 : ((m_Field->errorCount() == 2) ? 4 : 8);
|
||||||
|
|
||||||
|
m_Field->showMessage(tr("Sorry this was not correct: -%1min").arg(timeDiff), 1000, CCrossFieldWidget::Warning);
|
||||||
|
|
||||||
|
m_Field->setTime(m_Field->time() - timeDiff*60);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameWindow::about() {
|
void CGameWindow::about() {
|
||||||
QMessageBox::about(this, tr("About"), tr("This is a still unfancy gui for solving nonograms."));
|
QMessageBox::about(this, tr("About"), tr("This is a still unfancy gui for solving nonograms."));
|
||||||
}
|
}
|
||||||
|
|
||||||
void CGameWindow::updateStates() {
|
void CGameWindow::startSolver() {
|
||||||
bool valid = m_Nonogram->valid();
|
m_Solver->setNonogram(m_Picture);
|
||||||
m_SaveGameAction->setEnabled(valid);
|
|
||||||
m_PauseGameAction->setEnabled(valid);
|
restartGame();
|
||||||
m_RestartGameAction->setEnabled(valid);
|
|
||||||
m_PauseGameAction->setChecked(m_Nonogram->isPaused());
|
m_Solver->solve();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,8 +41,14 @@ namespace qcross {
|
|||||||
~CGameWindow();
|
~CGameWindow();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
libqnono::CNonogramSolver * m_Solver;
|
||||||
|
|
||||||
CCrossFieldWidget * m_Field;
|
CCrossFieldWidget * m_Field;
|
||||||
libqnono::CNonogram * m_Nonogram;
|
libqnono::CNonogram * m_Picture;
|
||||||
|
|
||||||
|
QString m_PackageName;
|
||||||
|
int m_PictureIndex;
|
||||||
|
CHighscore * m_Highscore;
|
||||||
|
|
||||||
QAction * m_SaveGameAction;
|
QAction * m_SaveGameAction;
|
||||||
QAction * m_RestartGameAction;
|
QAction * m_RestartGameAction;
|
||||||
@ -50,20 +56,26 @@ namespace qcross {
|
|||||||
|
|
||||||
void createActions();
|
void createActions();
|
||||||
|
|
||||||
|
bool readSaveGame(QDataStream & stream);
|
||||||
|
void writeSaveGame(QDataStream & stream);
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void newGame();
|
void newGame();
|
||||||
|
|
||||||
void saveGame();
|
void saveGame();
|
||||||
void loadGame();
|
void loadGame();
|
||||||
|
|
||||||
|
void restartGame();
|
||||||
void pauseGame(bool value);
|
void pauseGame(bool value);
|
||||||
|
|
||||||
void wonGame(int score);
|
void wonGame();
|
||||||
void lostGame();
|
void lostGame();
|
||||||
|
|
||||||
|
void handleErrorMark();
|
||||||
|
|
||||||
void about();
|
void about();
|
||||||
|
|
||||||
void updateStates();
|
void startSolver();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,33 +17,23 @@
|
|||||||
* Free Software Foundation, Inc., *
|
* Free Software Foundation, Inc., *
|
||||||
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
|
||||||
***************************************************************************/
|
***************************************************************************/
|
||||||
|
|
||||||
#include "chighscore.h"
|
|
||||||
#include "constants.h"
|
|
||||||
#include <libqnono/ccrosspackage.h>
|
|
||||||
|
|
||||||
#include <QDir>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QDataStream>
|
#include <QDataStream>
|
||||||
|
|
||||||
|
#include "chighscore.h"
|
||||||
|
|
||||||
namespace qcross {
|
namespace qcross {
|
||||||
CHighscore::CHighscore(int initialSize) : QList<quint32>() {
|
CHighscore::CHighscore(int initialSize) : QList<quint32>() {
|
||||||
for (int i = 0; i < initialSize; i++)
|
for (int i = 0; i < initialSize; i++)
|
||||||
append(0);
|
append(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CHighscore::setPackageIdentifier(QString PackageIdentifier) {
|
|
||||||
m_PackageIdentifier = libqnono::CCrossPackage::identifierFromPath(PackageIdentifier);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CHighscore::open() {
|
bool CHighscore::open() {
|
||||||
if (m_PackageIdentifier.isEmpty())
|
if (m_FileName.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QString fileName = (QCROSS_STRING_DATAPATH + QDir::separator() + m_PackageIdentifier + ".hsc");
|
qDebug("reading highscore file: %s", m_FileName.toAscii().data());
|
||||||
|
QFile file(m_FileName);
|
||||||
qDebug("reading highscore file: %s", fileName.toAscii().data());
|
|
||||||
QFile file(fileName);
|
|
||||||
if (!file.open(QIODevice::ReadOnly))
|
if (!file.open(QIODevice::ReadOnly))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
@ -65,13 +55,11 @@ namespace qcross {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool CHighscore::save() {
|
bool CHighscore::save() {
|
||||||
if (m_PackageIdentifier.isEmpty())
|
if (m_FileName.isEmpty())
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
QString fileName = (QCROSS_STRING_DATAPATH + QDir::separator() + m_PackageIdentifier + ".hsc");
|
qDebug("saving highcore file: %s", m_FileName.toAscii().data());
|
||||||
|
QFile file(m_FileName);
|
||||||
qDebug("saving highcore file: %s", fileName.toAscii().data());
|
|
||||||
QFile file(fileName);
|
|
||||||
if (!file.open(QIODevice::WriteOnly))
|
if (!file.open(QIODevice::WriteOnly))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
@ -31,13 +31,13 @@ namespace qcross {
|
|||||||
public:
|
public:
|
||||||
CHighscore(int size = 0);
|
CHighscore(int size = 0);
|
||||||
|
|
||||||
void setPackageIdentifier(QString PackageIdentifier);
|
void setFileName(QString value) { m_FileName = value; }
|
||||||
QString packageIdentifier() const { return m_PackageIdentifier; }
|
QString fileName() const { return m_FileName; }
|
||||||
|
|
||||||
bool open();
|
bool open();
|
||||||
bool save();
|
bool save();
|
||||||
protected:
|
protected:
|
||||||
QString m_PackageIdentifier;
|
QString m_FileName;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ namespace qcross {
|
|||||||
return tr("#%1").arg(index.row());
|
return tr("#%1").arg(index.row());
|
||||||
case COL_TIME:
|
case COL_TIME:
|
||||||
if ((*m_Highscore)[index.row()])
|
if ((*m_Highscore)[index.row()])
|
||||||
return formatedTime((*m_Highscore)[index.row()]) + (m_Package->pictures()[index.row()].timeout() ?
|
return formatedTime((*m_Highscore)[index.row()]) + (m_Package->pictures()[index.row()]->timeout() ?
|
||||||
" (time out)" : "");
|
" (time out)" : "");
|
||||||
else
|
else
|
||||||
return QVariant();
|
return QVariant();
|
||||||
|
@ -41,7 +41,7 @@ namespace qcross {
|
|||||||
CNewGameDialog::CNewGameDialog(QWidget * parent, Qt::WindowFlags f) :
|
CNewGameDialog::CNewGameDialog(QWidget * parent, Qt::WindowFlags f) :
|
||||||
QDialog(parent, f),
|
QDialog(parent, f),
|
||||||
m_Highscore(NULL),
|
m_Highscore(NULL),
|
||||||
m_Problem(NULL)
|
m_Nonogram(NULL)
|
||||||
{
|
{
|
||||||
ui.setupUi(this);
|
ui.setupUi(this);
|
||||||
ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
ui.buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
|
||||||
@ -79,8 +79,8 @@ namespace qcross {
|
|||||||
if (m_Highscore)
|
if (m_Highscore)
|
||||||
delete m_Highscore;
|
delete m_Highscore;
|
||||||
|
|
||||||
if (m_Problem)
|
if (m_Nonogram)
|
||||||
delete m_Problem;
|
delete m_Nonogram;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* libqnono::CNonogram * CNewGameDialog::selectedNonogramData() const {
|
/* libqnono::CNonogram * CNewGameDialog::selectedNonogramData() const {
|
||||||
@ -94,29 +94,45 @@ namespace qcross {
|
|||||||
}
|
}
|
||||||
|
|
||||||
libqnono::CCrossPackage * CNewGameDialog::selectedPackage() const {
|
libqnono::CCrossPackage * CNewGameDialog::selectedPackage() const {
|
||||||
if (m_Problem) return NULL;
|
if (m_Nonogram) return NULL;
|
||||||
QModelIndex selected = ui.packageList->selectionModel()->selectedIndexes()[0];
|
QModelIndex selected = ui.packageList->selectionModel()->selectedIndexes()[0];
|
||||||
return static_cast<CCrossPackage *>(selected.internalPointer());
|
return static_cast<CCrossPackage *>(selected.internalPointer());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString CNewGameDialog::selectedPackageName() const {
|
QString CNewGameDialog::selectedPackageName() const {
|
||||||
if (m_Problem) return QString();
|
if (m_Nonogram) return QString();
|
||||||
libqnono::CCrossPackage *p = selectedPackage();
|
libqnono::CCrossPackage *p = selectedPackage();
|
||||||
return p ? p->name() : QString();
|
return p ? p->name() : QString();
|
||||||
}
|
}
|
||||||
|
|
||||||
NonogramProblem CNewGameDialog::getProblem() {
|
libqnono::CNonogram * CNewGameDialog::takeNonogram() {
|
||||||
if (m_Problem) {
|
libqnono::CNonogram * result;
|
||||||
return *m_Problem;
|
|
||||||
|
if (m_Nonogram) {
|
||||||
|
result = m_Nonogram;
|
||||||
|
m_Nonogram = NULL;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
QModelIndexList selected = ui.packageList->selectionModel()->selectedIndexes();
|
QModelIndexList selected = ui.packageList->selectionModel()->selectedIndexes();
|
||||||
if (!selected.isEmpty()) {
|
if (selected.isEmpty())
|
||||||
return selectedPackage()->getPicture(nonogramIndex());
|
result = NULL;
|
||||||
|
else {
|
||||||
|
result = static_cast<CCrossPackage *>(selected[0].internalPointer())->takePicture(nonogramIndex());
|
||||||
|
m_PicModel->setPackage(NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return NonogramProblem();
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHighscore * CNewGameDialog::takeHighscore() {
|
||||||
|
if (m_Nonogram)
|
||||||
|
return NULL;
|
||||||
|
|
||||||
|
CHighscore * result = m_Highscore;
|
||||||
|
m_Highscore = NULL;
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
//protected:
|
//protected:
|
||||||
@ -126,16 +142,7 @@ namespace qcross {
|
|||||||
if (!fileName.isEmpty()) {
|
if (!fileName.isEmpty()) {
|
||||||
QDir().mkpath(QCROSS_STRING_DATAPATH);
|
QDir().mkpath(QCROSS_STRING_DATAPATH);
|
||||||
|
|
||||||
QString packageName = QDir::toNativeSeparators(fileName).section(QDir::separator(), -1);
|
QString newFileName = QCROSS_STRING_DATAPATH + QDir::separator() + QDir::toNativeSeparators(fileName).section(QDir::separator(), -1);
|
||||||
QString newFileName = QCROSS_STRING_DATAPATH + QDir::separator() + packageName;
|
|
||||||
if (QFile::exists(newFileName)) {
|
|
||||||
if (QMessageBox::Ok == QMessageBox::question(this, tr("Overwrite old package"), tr("Overwrite old package %1?").arg(packageName),
|
|
||||||
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Ok)) {
|
|
||||||
QFile::remove(newFileName);
|
|
||||||
} else {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (QFile::copy(fileName, newFileName))
|
if (QFile::copy(fileName, newFileName))
|
||||||
dynamic_cast<CCrossPackageListModel *>(ui.packageList->model())->update();
|
dynamic_cast<CCrossPackageListModel *>(ui.packageList->model())->update();
|
||||||
else
|
else
|
||||||
@ -147,13 +154,18 @@ namespace qcross {
|
|||||||
QString fileName = QFileDialog::getOpenFileName(this, tr("Select a picture to open"),
|
QString fileName = QFileDialog::getOpenFileName(this, tr("Select a picture to open"),
|
||||||
QString(), tr("Images (*.png *.xpm *.xbm *.jpg)"));
|
QString(), tr("Images (*.png *.xpm *.xbm *.jpg)"));
|
||||||
if (!fileName.isEmpty()) {
|
if (!fileName.isEmpty()) {
|
||||||
delete m_Problem; m_Problem = 0;
|
if (m_Nonogram)
|
||||||
|
delete m_Nonogram;
|
||||||
|
|
||||||
QImage image(fileName);
|
QImage image(fileName);
|
||||||
if (!image.isNull()) {
|
if (!image.isNull()) {
|
||||||
m_Problem = new NonogramProblem(NonogramImage(image));
|
m_Nonogram = new CNonogram();
|
||||||
|
m_Nonogram->loadFromImage(image);
|
||||||
|
m_Nonogram->updateNumbers();
|
||||||
accept();
|
accept();
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
m_Nonogram = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +180,7 @@ namespace qcross {
|
|||||||
if (!m_Highscore)
|
if (!m_Highscore)
|
||||||
m_Highscore = new CHighscore(0);
|
m_Highscore = new CHighscore(0);
|
||||||
|
|
||||||
m_Highscore->setPackageIdentifier(package->identifier());
|
m_Highscore->setFileName(getHighscoreFileName(package->fileName()));
|
||||||
if (!m_Highscore->open()) {
|
if (!m_Highscore->open()) {
|
||||||
qDebug("opening highscore file failed. will create a new one when nonogram is solved");
|
qDebug("opening highscore file failed. will create a new one when nonogram is solved");
|
||||||
m_Highscore->clear();
|
m_Highscore->clear();
|
||||||
@ -178,7 +190,6 @@ namespace qcross {
|
|||||||
m_PicModel->setHighscore(m_Highscore);
|
m_PicModel->setHighscore(m_Highscore);
|
||||||
}
|
}
|
||||||
void CNewGameDialog::handlePictureSelectionChanged(const QItemSelection & selected, const QItemSelection &) {
|
void CNewGameDialog::handlePictureSelectionChanged(const QItemSelection & selected, const QItemSelection &) {
|
||||||
delete m_Problem; m_Problem = 0;
|
|
||||||
ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(selected.isEmpty());
|
ui.buttonBox->button(QDialogButtonBox::Ok)->setDisabled(selected.isEmpty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,7 +26,6 @@
|
|||||||
namespace libqnono {
|
namespace libqnono {
|
||||||
class CNonogram;
|
class CNonogram;
|
||||||
class CCrossPackage;
|
class CCrossPackage;
|
||||||
class NonogramProblem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace qcross {
|
namespace qcross {
|
||||||
@ -47,14 +46,15 @@ namespace qcross {
|
|||||||
libqnono::CCrossPackage * selectedPackage() const;
|
libqnono::CCrossPackage * selectedPackage() const;
|
||||||
QString selectedPackageName() const;
|
QString selectedPackageName() const;
|
||||||
|
|
||||||
libqnono::NonogramProblem getProblem();
|
libqnono::CNonogram * takeNonogram();
|
||||||
|
CHighscore * takeHighscore();
|
||||||
protected slots:
|
protected slots:
|
||||||
void importPackage();
|
void importPackage();
|
||||||
void openPictureFile();
|
void openPictureFile();
|
||||||
private:
|
private:
|
||||||
Ui::picselect ui;
|
Ui::picselect ui;
|
||||||
CHighscore * m_Highscore;
|
CHighscore * m_Highscore;
|
||||||
libqnono::NonogramProblem * m_Problem; /* if != NULL package wasn't used */
|
libqnono::CNonogram * m_Nonogram; /* if != NULL package wasn't used */
|
||||||
CMaskedCrossPackageModel * m_PicModel;
|
CMaskedCrossPackageModel * m_PicModel;
|
||||||
CMaskedCrossPackageProxyModel * m_PicProxyModel;
|
CMaskedCrossPackageProxyModel * m_PicProxyModel;
|
||||||
private slots:
|
private slots:
|
||||||
|
@ -27,15 +27,32 @@ QString formatedTime(quint32 seconds, bool showSeconds) {
|
|||||||
QString result;
|
QString result;
|
||||||
|
|
||||||
if (hours) {
|
if (hours) {
|
||||||
result = QString("%1:").arg(hours, 2, 10, QLatin1Char('0'));
|
if (hours < 10)
|
||||||
|
result += '0';
|
||||||
|
result += QString::number(hours);
|
||||||
|
result += ':';
|
||||||
}
|
}
|
||||||
|
|
||||||
minutes %= 60;
|
minutes %= 60;
|
||||||
|
|
||||||
result += QString("%1").arg(minutes, 2, 10, QLatin1Char('0'));
|
if (minutes < 10)
|
||||||
|
result += '0';
|
||||||
|
result += QString::number(minutes);
|
||||||
|
|
||||||
if (showSeconds) {
|
if (showSeconds) {
|
||||||
result += QString(":%1").arg(strippedSeconds, 2, 10, QLatin1Char('0'));
|
result += ':';
|
||||||
|
if (strippedSeconds < 10)
|
||||||
|
result += '0';
|
||||||
|
result += QString::number(strippedSeconds);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString getHighscoreFileName(QString packageFileName) {
|
||||||
|
if (packageFileName.isEmpty())
|
||||||
|
return QString();
|
||||||
|
|
||||||
|
int tagPosition = packageFileName.lastIndexOf('.');
|
||||||
|
|
||||||
|
return ((tagPosition == -1) ? packageFileName : packageFileName.left(tagPosition)) + ".hsc";
|
||||||
|
}
|
||||||
|
@ -20,3 +20,4 @@
|
|||||||
#include <QString>
|
#include <QString>
|
||||||
|
|
||||||
QString formatedTime(quint32 seconds, bool showSeconds = false);
|
QString formatedTime(quint32 seconds, bool showSeconds = false);
|
||||||
|
QString getHighscoreFileName(QString packageFileName);
|
||||||
|
@ -169,15 +169,11 @@ namespace qcrossedit {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CMainWindow::editCreateFromPicture() {
|
void CMainWindow::editCreateFromPicture() {
|
||||||
QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Select a image file to import"),
|
QString fileName = QFileDialog::getOpenFileName(this, tr("Select a image file to import"),
|
||||||
QString(), tr("Images (*.png *.xpm *.xbm *.jpg)"));
|
QString(), tr("Images (*.png *.xpm *.xbm *.jpg)"));
|
||||||
CCrossPackageModel *model = qobject_cast<CCrossPackageModel *>(m_PicListView->model());
|
if (!fileName.isEmpty()) {
|
||||||
foreach (QString fileName, fileNames) {
|
if (qobject_cast<CCrossPackageModel *>(m_PicListView->model())->appendImage(fileName, QImage(fileName)))
|
||||||
if (!fileName.isEmpty()) {
|
m_Unsaved = true;
|
||||||
QString title = QDir::toNativeSeparators(fileName).section(QDir::separator(), -1);
|
|
||||||
if (model->appendImage(title, QImage(fileName)))
|
|
||||||
m_Unsaved = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user