add new abstractions, include magic numbers in serialize

This commit is contained in:
Stefan Bühler 2012-04-19 17:30:54 +02:00
parent 5cb6dd6880
commit e9c726b27e
24 changed files with 1491 additions and 990 deletions

BIN
debug.cpk

Binary file not shown.

View File

@ -18,6 +18,11 @@ set(SOURCES_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})

View File

@ -18,7 +18,7 @@
* 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 "cnonogram.h" #include "nonogramproblem.h"
#include <QDataStream> #include <QDataStream>
#include <QFile> #include <QFile>
@ -28,10 +28,6 @@ namespace libqnono {
CCrossPackage::~CCrossPackage() { CCrossPackage::~CCrossPackage() {
close(); close();
foreach (CNonogram * i, m_PictureList) {
delete i;
}
} }
bool CCrossPackage::open() { bool CCrossPackage::open() {
@ -108,8 +104,6 @@ namespace libqnono {
QDataStream in(m_File); QDataStream in(m_File);
in.setVersion(QDataStream::Qt_4_0); in.setVersion(QDataStream::Qt_4_0);
CNonogram * newPicture = NULL;
qint32 pictureCount = 0; qint32 pictureCount = 0;
if (in.atEnd()) { if (in.atEnd()) {
@ -121,14 +115,13 @@ namespace libqnono {
int i; int i;
for (i = 0; i < pictureCount && !in.atEnd(); i++) { for (i = 0; i < pictureCount && !in.atEnd(); i++) {
newPicture = new CNonogram(); m_PictureList.append(NonogramProblem());
if (!newPicture->readFromStream(in)) { if (!m_PictureList.last().readFromStream(in)) {
m_PictureList.pop_back();
qCritical("invalid package file - invalid picture"); qCritical("invalid package file - invalid picture");
delete newPicture;
close(); close();
return false; return false;
} }
m_PictureList << newPicture;
} }
if (i < pictureCount) if (i < pictureCount)
@ -139,11 +132,9 @@ namespace libqnono {
return true; return true;
} }
CNonogram * CCrossPackage::takePicture(int ndx) { const NonogramProblem & CCrossPackage::getPicture(int ndx) {
loadPictures(); loadPictures();
CNonogram * result = m_PictureList.takeAt(ndx); return m_PictureList[ndx];
unloadPictures();
return result;
} }
bool CCrossPackage::loadPictures() { bool CCrossPackage::loadPictures() {
@ -158,9 +149,6 @@ namespace libqnono {
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;
} }
@ -179,8 +167,8 @@ namespace libqnono {
out << m_Name; out << m_Name;
out << (qint32)(m_PictureList.size()); out << (qint32)(m_PictureList.size());
foreach (CNonogram * i, m_PictureList) foreach (const NonogramProblem & i, m_PictureList)
i->writeToStream(out); i.writeToStream(out);
file.close(); file.close();
return true; return true;

View File

@ -26,9 +26,9 @@
class QFile; class QFile;
namespace libqnono { namespace libqnono {
class CNonogram; class NonogramProblem;
typedef QList<CNonogram *> QMonoPictureList; typedef QList<NonogramProblem> QMonoPictureList;
class CCrossPackage { class CCrossPackage {
public: public:
@ -42,7 +42,7 @@ namespace libqnono {
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; }
CNonogram * takePicture(int ndx); const NonogramProblem & getPicture(int ndx);
bool loadPictures(); bool loadPictures();
void unloadPictures(); void unloadPictures();

View File

@ -81,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();
} }
@ -160,12 +160,12 @@ namespace libqnono {
if (name.isEmpty()) if (name.isEmpty())
return false; return false;
foreach (CNonogram * i, m_Package->pictures()) { foreach (const NonogramProblem & 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;
} }
@ -178,9 +178,8 @@ namespace libqnono {
beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1); beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1);
CNonogram * newNonogram = new CNonogram(size); NonogramProblem newNonogram(name, 60*60, NonogramImage(size));
newNonogram->fill(fillValue); newNonogram.fill(fillValue);
newNonogram->setName(name);
m_Package->pictures() << newNonogram; m_Package->pictures() << newNonogram;
@ -192,12 +191,9 @@ namespace libqnono {
if (!m_Package) if (!m_Package)
return false; return false;
CNonogram * newNonogram = new CNonogram(); NonogramProblem newNonogram(name, 60*60, NonogramImage(image));
newNonogram->loadFromImage(image);
newNonogram->setName(name);
newNonogram->setTimeout(60*60); // set an hour as default timeout
if (newNonogram->isValid()) { if (!newNonogram.size().isEmpty()) {
beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1); beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1);
m_Package->pictures() << newNonogram; m_Package->pictures() << newNonogram;
@ -206,7 +202,6 @@ namespace libqnono {
return true; return true;
} }
else { else {
delete newNonogram;
return false; return false;
} }
} }
@ -217,12 +212,8 @@ 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) {
newNonogram = new CNonogram(); m_Package->pictures().push_back(NonogramProblem());
newNonogram->fill(false);
m_Package->pictures() << newNonogram;
} }
endInsertRows(); endInsertRows();
@ -236,7 +227,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)
delete m_Package->pictures().takeAt(i); m_Package->pictures().removeAt(i);
endRemoveRows(); endRemoveRows();
return true; return true;

View File

@ -19,342 +19,48 @@
***************************************************************************/ ***************************************************************************/
#include "cnonogram.h" #include "cnonogram.h"
#include <QImage> #include <QImage>
#include <QDataStream>
namespace libqnono { namespace libqnono {
static void boolsFromImage(bool ** & data, const QImage &image) { static const quint64 CNonogram_MAGIC = Q_UINT64_C(0x35a8bca32006c5a9);
int cols = image.width(), rows = image.height();
data = new bool*[cols]; CNonogram::CNonogram() {
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) {
data[col][row] = 0 == (image.pixel(col, row) & 0x00FFFFFF);
}
}
} }
static void freeBools(const QSize & size, bool ** data) { CNonogram::CNonogram(const NonogramProblem & problem)
int cols = size.width(); : m_problem(problem), m_marker(problem.size()) {
for (int col = 0; col < cols; ++col) delete [] data[col];
delete[] data;
}
static const quint64 CNonogramNumbers_MAGIC = Q_UINT64_C(0xd33efcacc0050164);
CNonogramNumbers::CNonogramNumbers(const vv_num & rows, const vv_num & columns)
: m_rows(rows), m_columns(columns) {
}
CNonogramNumbers::CNonogramNumbers(const CNonogram & source)
: m_rows(source.height()), m_columns(source.width()) {
// 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;
}
}
}
for (int col = 0; col < cols; ++col) {
quint16 *val = 0;
for (int row = 0; row < rows; ++row) {
bool b = data[col][row];
if (!val && b) {
m_columns[col].append(1);
val = &m_columns[col].last();
} else if (b) {
++(*val);
} else {
val = 0;
}
}
}
}
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;
}
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;
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) {
unsigned char data; quint64 magic;
QString stringBuffer; stream >> magic;
qint32 intBuffer; if (CNonogram_MAGIC != magic || QDataStream::Ok != stream.status()) {
QSize sizeBuffer; if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData);
stream >> stringBuffer; qDebug("reading image: %s", qPrintable(stringBuffer));
if (stream.atEnd()) {
qCritical("invalid nonogram");
cleanup();
return false; return false;
} }
stream >> intBuffer; qDebug("width %i", intBuffer); NonogramProblem problem;
if (stream.atEnd()) { NonogramMarker marker;
qCritical("invalid nonogram");
cleanup(); if (!problem.readFromStream(stream)) return false;
if (!marker.readFromStream(stream)) return false;
if (problem.size() != marker.size()) {
stream.setStatus(QDataStream::ReadCorruptData);
return false; return false;
} }
sizeBuffer.setWidth(intBuffer); m_problem = problem;
m_marker = marker;
stream >> intBuffer; qDebug("height %i", intBuffer);
if (stream.atEnd()) {
qCritical("invalid nonogram");
cleanup();
return false;
}
sizeBuffer.setHeight(intBuffer);
m_Name = stringBuffer;
stream >> intBuffer; qDebug("timeout %i", intBuffer);
if (stream.atEnd()) {
qCritical("invalid nonogram");
cleanup();
return false;
}
m_Timeout = intBuffer;
resize(sizeBuffer);
for (int x = 0; x < sizeBuffer.width(); x++) {
int y = 0;
while (y < sizeBuffer.height()) {
if (stream.atEnd()) {
qCritical("invalid nonogram");
cleanup();
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++;
}
}
}
return true; return true;
} }
void CNonogram::writeToStream(QDataStream & stream) { void CNonogram::writeToStream(QDataStream & stream) const {
stream << m_Name; stream << CNonogram_MAGIC << m_problem << m_marker;
stream << qint32(m_Size.width());
stream << qint32(m_Size.height());
stream << qint32(m_Timeout);
unsigned char data;
for (int x = 0; x < m_Size.width(); x++) {
data = 0x00;
int y = 0;
while (y < m_Size.height()) {
int b;
for (b = 0; b < 8; b++) {
data = data << 1;
if (y < m_Size.height() && m_Data[x][y])
data |= 0x01;
y++;
} }
stream.writeRawData((char *)&data, 1); QDataStream & operator<<(QDataStream& stream, const libqnono::CNonogram& nonogram) {
}
}
}
void CNonogram::resize(QSize size) {
if (m_Data)
cleanup();
m_Size = size;
init();
}
void CNonogram::setPixel(int x, int y, bool value) {
m_Data[x][y] = value;
}
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;
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::fill(bool value) {
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::cleanup() {
delete[] m_RowNumbers;
delete[] m_ColumnNumbers;
for (int i = 0; i < m_Size.width(); i++)
delete[] m_Data[i];
delete[] m_Data;
m_Data = NULL;
}
void CNonogram::init() {
m_Data = new bool *[m_Size.width()];
for (int i = 0; i < m_Size.width(); i++)
m_Data[i] = new bool[m_Size.height()];
m_RowNumbers = new NumbersVector[m_Size.height()];
m_ColumnNumbers = new NumbersVector[m_Size.width()];
}
QDataStream & operator<<(QDataStream & stream, CNonogram & nonogram) {
nonogram.writeToStream(stream); nonogram.writeToStream(stream);
return stream; return stream;
} }

View File

@ -24,106 +24,43 @@
#include <QVector> #include <QVector>
#include <QString> #include <QString>
#include "nonogramproblem.h"
#include "nonogrammarker.h"
class QImage; class QImage;
namespace libqnono { namespace libqnono {
class CNonogram;
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 { class CNonogram {
public: public:
typedef QVector<quint16> NumbersVector; typedef QVector<quint16> NumbersVector;
CNonogram(); CNonogram();
CNonogram(QSize size); CNonogram(const NonogramProblem & problem);
CNonogram(QImage & image);
CNonogram(QDataStream & stream);
CNonogram(const CNonogram &other);
~CNonogram();
CNonogram& operator=(const CNonogram &other);
void loadFromImage(QImage & image); const NonogramProblem & problem() const { return m_problem; }
void resize(QSize size); 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; }
QSize size() const { return m_problem.size(); }
int width() const { return size().width(); }
int height() const { return size().height(); }
/* returns false if set MARKED on white solution pixel (it still sets the mark) */
bool setMark(int x, int y, NonogramMarker::Mark mark);
bool readFromStream(QDataStream & stream); bool readFromStream(QDataStream & stream);
void writeToStream(QDataStream & stream); void writeToStream(QDataStream& stream) const;
inline QString name() const { return m_Name; }
inline void setName(QString value) { m_Name = value; }
inline quint16 timeout() const { return m_Timeout; }
void setTimeout(quint16 value) { m_Timeout = value; }
inline QSize size() const { return m_Size; }
inline int width() const { return m_Size.width(); }
inline int height() const { return m_Size.height(); }
inline bool pixel(int x, int y) const { return m_Data[x][y]; }
void setPixel(int x, int y, bool value);
NumbersVector & rowNumbers(int index) const { return m_RowNumbers[index]; }
NumbersVector & columnNumbers(int index) const { return m_ColumnNumbers[index]; }
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:
QSize m_Size; NonogramProblem m_problem;
NonogramMarker m_marker;
bool ** m_Data;
QString m_Name;
quint16 m_Timeout;
NumbersVector * m_RowNumbers;
NumbersVector * m_ColumnNumbers;
quint32 m_BlackPixels;
int m_MaximumNumberCount;
void cleanup();
void init();
}; };
QDataStream & operator<<(QDataStream & stream, CNonogram & nonogram); QDataStream & operator<<(QDataStream & stream, const CNonogram & nonogram);
QDataStream & operator>>(QDataStream & stream, CNonogram & nonogram); QDataStream & operator>>(QDataStream & stream, CNonogram & nonogram);
} }

View File

@ -1,496 +1,14 @@
#include "cnonogramsolver.h" #include "cnonogramsolver.h"
#include "cnonogram.h" #include "cnonogram.h"
#include "nonogramsolver.h"
#include <QString> #include <QString>
#include <QDebug> #include <QDebug>
namespace libqnono { namespace libqnono {
CNonogramSolver::CNonogramSolver(QObject * parent)
struct Block { : QObject(parent), m_Nonogram(0) {
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() { CNonogramSolver::~CNonogramSolver() {
@ -503,20 +21,16 @@ namespace libqnono {
bool CNonogramSolver::solve() { bool CNonogramSolver::solve() {
if (!m_Nonogram) return false; if (!m_Nonogram) return false;
{ QList<NonogramImage> solutions = libqnono::solve(m_Nonogram->numbers());
QList<CNonogramSolution*> solutions = libqnono::solve(CNonogramNumbers(*m_Nonogram));
if (!solutions.empty()) { if (!solutions.empty()) {
bool **data = solutions.first()->data(); NonogramImage &sol(solutions.first());
for (int i = 0; i < m_Nonogram->width(); ++i) { for (int i = 0; i < m_Nonogram->width(); ++i) {
for (int j = 0; j < m_Nonogram->height(); ++j) { for (int j = 0; j < m_Nonogram->height(); ++j) {
emit markRequested(i, j, data[i][j] ? CMT_MARKED : CMT_CROSSED); emit markRequested(i, j, sol.pixel(i, j) ? CMT_MARKED : CMT_CROSSED);
} }
} }
foreach (CNonogramSolution *s, solutions) delete s;
return TRUE; return TRUE;
} }
}
return FALSE; return FALSE;
} }

View File

@ -6,32 +6,6 @@
#include <QList> #include <QList>
namespace libqnono { 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 CNonogram;
class CNonogramSolver : public QObject { class CNonogramSolver : public QObject {

208
libqnono/nonogramimage.cpp Normal file
View File

@ -0,0 +1,208 @@
/***************************************************************************
* 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 <QDataStream>
namespace libqnono {
static const quint64 NonogramImage_DataStream_MAGIC = Q_UINT64_C(0xe47028650d925b33);
class NonogramImageViewRowColumn : public NonogramImageView {
public:
NonogramImageViewRowColumn(NonogramImage & img) : m_img(img) { }
virtual bool pixel(int row, int col) const {
return m_img.pixel(col, row);
}
virtual bool & pixel(int row, int col) {
return m_img.pixel(col, row);
}
private:
NonogramImage &m_img;
};
class NonogramImageViewColumnRow : public NonogramImageView {
public:
NonogramImageViewColumnRow(NonogramImage & img) : m_img(img) { }
virtual bool pixel(int col, int row) const {
return m_img.pixel(col, row);
}
virtual bool & pixel(int col, int row) {
return m_img.pixel(col, row);
}
private:
NonogramImage &m_img;
};
NonogramImage::NonogramImage() : m_size(0,0), m_data(0), m_blackPixels(0) {
}
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(image.size()), m_data(0), m_blackPixels(0) {
int rows = image.height(), cols = image.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;
}
}
}
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;
}
NonogramImageView* NonogramImage::viewRowColumn() {
return new NonogramImageViewRowColumn(*this);
}
NonogramImageView* NonogramImage::viewColumnRow() {
return new NonogramImageViewColumnRow(*this);
}
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);
}
QDataStream & operator<<(QDataStream & stream, const NonogramImage & image) {
image.writeToStream(stream);
return stream;
}
QDataStream & operator>>(QDataStream & stream, NonogramImage & image) {
image.readFromStream(stream);
return stream;
}
}

70
libqnono/nonogramimage.h Normal file
View File

@ -0,0 +1,70 @@
/***************************************************************************
* 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 NonogramImageView {
public:
virtual bool pixel(int coord1, int coord2) const = 0;
virtual bool & pixel(int coord1, int coord2) = 0;
};
class NonogramImage {
public:
NonogramImage();
NonogramImage(QSize size);
NonogramImage(const NonogramImage & other);
NonogramImage(const QImage & image);
~NonogramImage();
NonogramImage& operator=(const NonogramImage & other);
bool operator==(const NonogramImage & other) const;
bool pixel(int x, int y) const { return m_data[y*m_size.width()+x]; }
bool & pixel(int x, int y) { return m_data[y*m_size.width()+x]; }
void setPixel(int x, int y, bool value) { m_data[y*m_size.width()+x] = value; }
void fill(bool value);
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);
NonogramImageView* viewRowColumn();
NonogramImageView* viewColumnRow();
bool readFromStream(QDataStream & stream);
void writeToStream(QDataStream & stream) const;
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

116
libqnono/nonogrammarker.cpp Normal file
View File

@ -0,0 +1,116 @@
/***************************************************************************
* 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;
}
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;
}
}

54
libqnono/nonogrammarker.h Normal file
View File

@ -0,0 +1,54 @@
/***************************************************************************
* 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 NonogramMarker {
public:
enum Mark { NONE = 0, MARKED = 1, CROSSED = 2 };
NonogramMarker();
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; }
bool readFromStream(QDataStream & stream);
void writeToStream(QDataStream & stream) const;
private:
QSize m_size;
Mark *m_data;
};
QDataStream & operator<<(QDataStream & stream, const NonogramMarker & marker);
QDataStream & operator>>(QDataStream & stream, NonogramMarker & marker);
}
#endif // LIBQNONO_NONOGRAMMARKER_H

View File

@ -0,0 +1,165 @@
/***************************************************************************
* 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;
}
}

View File

@ -0,0 +1,65 @@
/***************************************************************************
* 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);
NonogramNumbers(const NonogramImage & image);
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

View File

@ -0,0 +1,89 @@
/***************************************************************************
* 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) {
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);
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;
}
}

View File

@ -0,0 +1,68 @@
/***************************************************************************
* 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; }
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);
void writeToStream(QDataStream & stream) const;
private:
NonogramImage m_solution;
NonogramNumbers m_numbers;
QString m_name;
quint16 m_timeout;
};
QDataStream & operator<<(QDataStream & stream, const NonogramProblem & problem);
QDataStream & operator>>(QDataStream & stream, NonogramProblem & problem);
}
#endif // LIBQNONO_NONOGRAMPROBLEM_H

506
libqnono/nonogramsolver.cpp Normal file
View File

@ -0,0 +1,506 @@
/***************************************************************************
* 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 "nonogramnumbers.h"
#include <QDebug>
namespace libqnono {
struct SolverState {
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();
}
int nrows, ncols;
QVector< QVector<Block> > rows, cols;
Mark **data;
SolverState(const NonogramNumbers & 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;
}
}
}
~SolverState() {
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 || (1 == lineLen && 0 == line[0].length)) {
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 || (1 == lineLen && 0 == line[0].length)) {
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;
}
void storeSolution(NonogramImage & image) {
image.resize(QSize(ncols, nrows));
for (int i = 0; i < ncols; ++i) {
for (int j = 0; j < nrows; ++j) {
image.setPixel(i, j, data[i][j] == MARK_BLACK);
}
}
}
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<NonogramImage> &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.append(NonogramImage());;
storeSolution(solutions.last());
}
};
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;
}
}

31
libqnono/nonogramsolver.h Normal file
View File

@ -0,0 +1,31 @@
/***************************************************************************
* 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

View File

@ -85,7 +85,7 @@ namespace qcross {
if (m_Picture) { if (m_Picture) {
initialize(); initialize();
m_RemainingPixels = m_Picture->blackPixels(); m_RemainingPixels = m_Picture->solution().blackPixels();
updateTimeDisplay(); updateTimeDisplay();
} }
@ -122,7 +122,7 @@ namespace qcross {
updateMetrics(); updateMetrics();
m_RemainingPixels = m_Picture->blackPixels(); m_RemainingPixels = m_Picture->solution().blackPixels();
} }
m_Clock->setVisible(m_Picture); m_Clock->setVisible(m_Picture);
} }
@ -340,7 +340,7 @@ namespace qcross {
m_OverlayData[i][j] = CMT_UNMARKED; m_OverlayData[i][j] = CMT_UNMARKED;
} }
int minimumW = max(m_Picture->maximumNumberCount()+1, 5); int minimumW = max(m_Picture->numbers().maximumNumberCount()+1, 5);
int minimumH = minimumW; int minimumH = minimumW;
minimumW += m_Picture->width(); minimumW += m_Picture->width();
@ -388,11 +388,11 @@ namespace qcross {
m_ErrorCount = 0; m_ErrorCount = 0;
m_LastErrorMark.setX(-1); m_LastErrorMark.setX(-1);
m_LastErrorMark.setY(-1); m_LastErrorMark.setY(-1);
m_RemainingPixels = m_Picture->blackPixels(); m_RemainingPixels = m_Picture->solution().blackPixels();
} }
bool CCrossFieldWidget::checkNoError(int x, int y) { bool CCrossFieldWidget::checkNoError(int x, int y) {
return (m_OverlayData[x][y] == CMT_MARKED) || m_Picture->pixel(x, y); return (m_OverlayData[x][y] == CMT_MARKED) || m_Picture->solution().pixel(x, y);
} }
void CCrossFieldWidget::timerEvent(QTimerEvent * event) { void CCrossFieldWidget::timerEvent(QTimerEvent * event) {
@ -429,7 +429,7 @@ namespace qcross {
void CCrossFieldWidget::execMark(int x, int y, MarkerType & marker) { void CCrossFieldWidget::execMark(int x, int y, MarkerType & marker) {
switch (marker) { switch (marker) {
case CMT_MARKED: case CMT_MARKED:
if (m_Picture->pixel(x, y)) { if (m_Picture->solution().pixel(x, y)) {
m_RemainingPixels--; m_RemainingPixels--;
m_OverlayData[x][y] = marker; m_OverlayData[x][y] = marker;
} }
@ -454,7 +454,7 @@ namespace qcross {
} }
if (m_OverlayData[x][y] == CMT_MARKED) { if (m_OverlayData[x][y] == CMT_MARKED) {
if (m_Picture->pixel(x, y)) if (m_Picture->solution().pixel(x, y))
m_RemainingPixels++; m_RemainingPixels++;
else if (!m_ErrorAware) else if (!m_ErrorAware)
m_ErrorCount--; m_ErrorCount--;
@ -596,11 +596,13 @@ namespace qcross {
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_Paused) { if (!m_Paused) {
painter.setPen(palette().color(QPalette::WindowText)); painter.setPen(palette().color(QPalette::WindowText));
for (int j = 1; j <= m_Picture->columnNumbers(i).size(); j++) { int j = m_Picture->numbers().columns()[i].count();;
foreach (quint16 block, m_Picture->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(m_Picture->columnNumbers(i)[m_Picture->columnNumbers(i).size() - j])); Qt::AlignVCenter | Qt::AlignCenter, QString::number(block));
--j;
} }
painter.setPen(palette().color(QPalette::Shadow)); painter.setPen(palette().color(QPalette::Shadow));
} }
@ -614,10 +616,12 @@ namespace qcross {
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_Paused) { if (!m_Paused) {
painter.setPen(palette().color(QPalette::WindowText)); painter.setPen(palette().color(QPalette::WindowText));
for (int j = 1; j <= m_Picture->rowNumbers(i).size(); j++) { int j = m_Picture->numbers().rows()[i].count();
foreach (quint16 block, m_Picture->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(m_Picture->rowNumbers(i)[m_Picture->rowNumbers(i).size() - j])); Qt::AlignVCenter | Qt::AlignCenter, QString::number(block));
--j;
} }
painter.setPen(palette().color(QPalette::Shadow)); painter.setPen(palette().color(QPalette::Shadow));
} }
@ -667,7 +671,7 @@ namespace qcross {
} }
void CCrossFieldWidget::updateMetrics() { void CCrossFieldWidget::updateMetrics() {
m_HeaderWidth = max(m_Picture->maximumNumberCount()+1, 5); m_HeaderWidth = max(m_Picture->numbers().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;*/

View File

@ -189,7 +189,6 @@ namespace qcross {
m_PictureIndex = m_Highscore ? dialog.nonogramIndex() : -1; m_PictureIndex = m_Highscore ? dialog.nonogramIndex() : -1;
CNonogram * newPicture = dialog.takeNonogram(); CNonogram * newPicture = dialog.takeNonogram();
newPicture->updateNumbers();
m_SaveGameAction->setEnabled(true); m_SaveGameAction->setEnabled(true);
m_PauseGameAction->setEnabled(true); m_PauseGameAction->setEnabled(true);

View File

@ -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();

View File

@ -117,7 +117,7 @@ namespace qcross {
if (selected.isEmpty()) if (selected.isEmpty())
result = NULL; result = NULL;
else { else {
result = static_cast<CCrossPackage *>(selected[0].internalPointer())->takePicture(nonogramIndex()); result = new CNonogram(static_cast<CCrossPackage *>(selected[0].internalPointer())->getPicture(nonogramIndex()));
m_PicModel->setPackage(NULL); m_PicModel->setPackage(NULL);
} }
} }
@ -142,7 +142,16 @@ namespace qcross {
if (!fileName.isEmpty()) { if (!fileName.isEmpty()) {
QDir().mkpath(QCROSS_STRING_DATAPATH); QDir().mkpath(QCROSS_STRING_DATAPATH);
QString newFileName = QCROSS_STRING_DATAPATH + QDir::separator() + QDir::toNativeSeparators(fileName).section(QDir::separator(), -1); QString packageName = 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
@ -159,9 +168,7 @@ namespace qcross {
QImage image(fileName); QImage image(fileName);
if (!image.isNull()) { if (!image.isNull()) {
m_Nonogram = new CNonogram(); m_Nonogram = new CNonogram(NonogramProblem(NonogramImage(image)));
m_Nonogram->loadFromImage(image);
m_Nonogram->updateNumbers();
accept(); accept();
} }
else else

View File

@ -169,13 +169,17 @@ namespace qcrossedit {
} }
void CMainWindow::editCreateFromPicture() { void CMainWindow::editCreateFromPicture() {
QString fileName = QFileDialog::getOpenFileName(this, tr("Select a image file to import"), QStringList fileNames = QFileDialog::getOpenFileNames(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());
foreach (QString fileName, fileNames) {
if (!fileName.isEmpty()) { if (!fileName.isEmpty()) {
if (qobject_cast<CCrossPackageModel *>(m_PicListView->model())->appendImage(fileName, QImage(fileName))) QString title = QDir::toNativeSeparators(fileName).section(QDir::separator(), -1);
if (model->appendImage(title, QImage(fileName)))
m_Unsaved = true; m_Unsaved = true;
} }
} }
}
void CMainWindow::editRename() { void CMainWindow::editRename() {
} }