From e9c726b27e17a74377c82a6e20badab182fedc6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stefan=20B=C3=BChler?= Date: Thu, 19 Apr 2012 17:30:54 +0200 Subject: [PATCH] add new abstractions, include magic numbers in serialize --- debug.cpk | Bin 547 -> 617 bytes libqnono/CMakeLists.txt | 5 + libqnono/ccrosspackage.cpp | 28 +- libqnono/ccrosspackage.h | 6 +- libqnono/ccrosspackagemodel.cpp | 29 +- libqnono/cnonogram.cpp | 340 ++----------------- libqnono/cnonogram.h | 107 ++---- libqnono/cnonogramsolver.cpp | 508 +--------------------------- libqnono/cnonogramsolver.h | 26 -- libqnono/nonogramimage.cpp | 208 ++++++++++++ libqnono/nonogramimage.h | 70 ++++ libqnono/nonogrammarker.cpp | 116 +++++++ libqnono/nonogrammarker.h | 54 +++ libqnono/nonogramnumbers.cpp | 165 +++++++++ libqnono/nonogramnumbers.h | 65 ++++ libqnono/nonogramproblem.cpp | 89 +++++ libqnono/nonogramproblem.h | 68 ++++ libqnono/nonogramsolver.cpp | 506 +++++++++++++++++++++++++++ libqnono/nonogramsolver.h | 31 ++ qcross/ccrossfieldwidget.cpp | 28 +- qcross/cgamewindow.cpp | 1 - qcross/cmaskedcrosspackagemodel.cpp | 2 +- qcross/cnewgamedialog.cpp | 17 +- qcrossedit/cmainwindow.cpp | 12 +- 24 files changed, 1491 insertions(+), 990 deletions(-) create mode 100644 libqnono/nonogramimage.cpp create mode 100644 libqnono/nonogramimage.h create mode 100644 libqnono/nonogrammarker.cpp create mode 100644 libqnono/nonogrammarker.h create mode 100644 libqnono/nonogramnumbers.cpp create mode 100644 libqnono/nonogramnumbers.h create mode 100644 libqnono/nonogramproblem.cpp create mode 100644 libqnono/nonogramproblem.h create mode 100644 libqnono/nonogramsolver.cpp create mode 100644 libqnono/nonogramsolver.h diff --git a/debug.cpk b/debug.cpk index dbf14c2ca95d3890aadc80cff571dc6e475c2869..87cc498802a3fa87533f328ce70fad8775fc7f3d 100644 GIT binary patch literal 617 zcmZQzV31-6WN>B(V(@1O2EqUaMWME+byZgq4 z$$2YR0@*?gDGaF$NkBE}40;R|KvpiFz>@-vRNhI^#z1*iFcx57K++?Eq{j%WE-s)h zE(RtZ34??K4UEieJh-$Q|=iAr+`}6ZNKa%Zy3 zKmRZO^S|f6^~ZXSKlaW4-#_?o`%(V%mico@90O~nB z!P+L7-X~l4<3Y3RGYRCFglWYdq~MSNIvN5Dco=$^8HyQ>@HKpAK2XCTAkT1!?|}ik bK@T&>VFnh4hGymizZsj94;*Cp0Mi2iJ+jAR literal 547 zcmZutOG*Pl5Pid-fe^+)M1ln42_)!Bh$JH7!iB#J1V4xiAsa!VaoW8{5IllMNMXQL zz$4g_MK*#X*(eNM)=V<75xe_U(N*=T`xSr%>|+xL*u|mjJ#5HrN9;DX02Fb8Qyk$E zXL2hD0<>|CV=UuJ;ujK`A0Y^tlkD{PL)6TeIcL@3Otj~{YY5vY*Sz)6?`rQ>d(Z$?lU6k`9%=tg z7e4jO8dWN~T*^tgpLM4FJKrfu3(|irWtDSI+5ZBXAW!?4MoA`DLkpA=U7jmqBAhkD PHWT`$Ar0t-9;iV-y(5|j diff --git a/libqnono/CMakeLists.txt b/libqnono/CMakeLists.txt index 68aa1e0..1cfac0e 100644 --- a/libqnono/CMakeLists.txt +++ b/libqnono/CMakeLists.txt @@ -18,6 +18,11 @@ set(SOURCES_CPP ccrosspackage.cpp ccrosspackagemodel.cpp ccrosspackagelistmodel.cpp + nonogramimage.cpp + nonogrammarker.cpp + nonogramnumbers.cpp + nonogramproblem.cpp + nonogramsolver.cpp ) qt4_wrap_cpp(SOURCES_MOC_CPP ${SOURCES_MOC_H}) diff --git a/libqnono/ccrosspackage.cpp b/libqnono/ccrosspackage.cpp index 4723182..3f37f06 100644 --- a/libqnono/ccrosspackage.cpp +++ b/libqnono/ccrosspackage.cpp @@ -18,7 +18,7 @@ * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * ***************************************************************************/ #include "ccrosspackage.h" -#include "cnonogram.h" +#include "nonogramproblem.h" #include #include @@ -28,10 +28,6 @@ namespace libqnono { CCrossPackage::~CCrossPackage() { close(); - - foreach (CNonogram * i, m_PictureList) { - delete i; - } } bool CCrossPackage::open() { @@ -108,8 +104,6 @@ namespace libqnono { QDataStream in(m_File); in.setVersion(QDataStream::Qt_4_0); - CNonogram * newPicture = NULL; - qint32 pictureCount = 0; if (in.atEnd()) { @@ -121,14 +115,13 @@ namespace libqnono { int i; for (i = 0; i < pictureCount && !in.atEnd(); i++) { - newPicture = new CNonogram(); - if (!newPicture->readFromStream(in)) { + m_PictureList.append(NonogramProblem()); + if (!m_PictureList.last().readFromStream(in)) { + m_PictureList.pop_back(); qCritical("invalid package file - invalid picture"); - delete newPicture; close(); return false; } - m_PictureList << newPicture; } if (i < pictureCount) @@ -139,11 +132,9 @@ namespace libqnono { return true; } - CNonogram * CCrossPackage::takePicture(int ndx) { + const NonogramProblem & CCrossPackage::getPicture(int ndx) { loadPictures(); - CNonogram * result = m_PictureList.takeAt(ndx); - unloadPictures(); - return result; + return m_PictureList[ndx]; } bool CCrossPackage::loadPictures() { @@ -158,9 +149,6 @@ namespace libqnono { void CCrossPackage::unloadPictures() { if (!m_PictureList.empty()) { - foreach (CNonogram * i, m_PictureList) { - delete i; - } m_PictureList.clear(); m_headersOnly = true; } @@ -179,8 +167,8 @@ namespace libqnono { out << m_Name; out << (qint32)(m_PictureList.size()); - foreach (CNonogram * i, m_PictureList) - i->writeToStream(out); + foreach (const NonogramProblem & i, m_PictureList) + i.writeToStream(out); file.close(); return true; diff --git a/libqnono/ccrosspackage.h b/libqnono/ccrosspackage.h index d137f77..b3f6c07 100644 --- a/libqnono/ccrosspackage.h +++ b/libqnono/ccrosspackage.h @@ -26,9 +26,9 @@ class QFile; namespace libqnono { - class CNonogram; + class NonogramProblem; - typedef QList QMonoPictureList; + typedef QList QMonoPictureList; class CCrossPackage { public: @@ -42,7 +42,7 @@ namespace libqnono { QString name() const { return m_Name; } QMonoPictureList & pictures() { Q_ASSERT(!m_headersOnly); return m_PictureList; } - CNonogram * takePicture(int ndx); + const NonogramProblem & getPicture(int ndx); bool loadPictures(); void unloadPictures(); diff --git a/libqnono/ccrosspackagemodel.cpp b/libqnono/ccrosspackagemodel.cpp index fa12d47..638913c 100644 --- a/libqnono/ccrosspackagemodel.cpp +++ b/libqnono/ccrosspackagemodel.cpp @@ -81,7 +81,7 @@ namespace libqnono { QModelIndex CCrossPackageModel::index(int row, int column, const QModelIndex & /*parent*/) const { return (m_Package) && (column >= 0) && (row >= 0) && (row < m_Package->pictures().size()) ? - createIndex(row, column, static_cast(m_Package->pictures()[row])) : + createIndex(row, column, static_cast(&m_Package->pictures()[row])) : QModelIndex(); } @@ -160,12 +160,12 @@ namespace libqnono { if (name.isEmpty()) return false; - foreach (CNonogram * i, m_Package->pictures()) { - if (i->name() == name) + foreach (const NonogramProblem & i, m_Package->pictures()) { + if (i.name() == name) return false; } - m_Package->pictures()[index.row()]->setName(name); + m_Package->pictures()[index.row()].setName(name); emit dataChanged(index, index); return true; } @@ -178,9 +178,8 @@ namespace libqnono { beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1); - CNonogram * newNonogram = new CNonogram(size); - newNonogram->fill(fillValue); - newNonogram->setName(name); + NonogramProblem newNonogram(name, 60*60, NonogramImage(size)); + newNonogram.fill(fillValue); m_Package->pictures() << newNonogram; @@ -192,12 +191,9 @@ namespace libqnono { if (!m_Package) return false; - CNonogram * newNonogram = new CNonogram(); - newNonogram->loadFromImage(image); - newNonogram->setName(name); - newNonogram->setTimeout(60*60); // set an hour as default timeout + NonogramProblem newNonogram(name, 60*60, NonogramImage(image)); - if (newNonogram->isValid()) { + if (!newNonogram.size().isEmpty()) { beginInsertRows(QModelIndex(), m_Package->pictures().size(), 1); m_Package->pictures() << newNonogram; @@ -206,7 +202,6 @@ namespace libqnono { return true; } else { - delete newNonogram; return false; } } @@ -217,12 +212,8 @@ namespace libqnono { beginInsertRows(parent, row, count); - CNonogram * newNonogram; - for (int i = row; i < row+count; ++i) { - newNonogram = new CNonogram(); - newNonogram->fill(false); - m_Package->pictures() << newNonogram; + m_Package->pictures().push_back(NonogramProblem()); } endInsertRows(); @@ -236,7 +227,7 @@ namespace libqnono { beginRemoveRows(parent, row, row+count-1); for (int i = row; i < row+count; ++i) - delete m_Package->pictures().takeAt(i); + m_Package->pictures().removeAt(i); endRemoveRows(); return true; diff --git a/libqnono/cnonogram.cpp b/libqnono/cnonogram.cpp index 2bbade4..77ccd0d 100644 --- a/libqnono/cnonogram.cpp +++ b/libqnono/cnonogram.cpp @@ -19,342 +19,48 @@ ***************************************************************************/ #include "cnonogram.h" #include +#include namespace libqnono { - static void boolsFromImage(bool ** & data, const QImage &image) { - int cols = image.width(), rows = image.height(); - data = new bool*[cols]; - 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 const quint64 CNonogram_MAGIC = Q_UINT64_C(0x35a8bca32006c5a9); + + CNonogram::CNonogram() { } - static void freeBools(const QSize & size, bool ** data) { - int cols = size.width(); - 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); + CNonogram::CNonogram(const NonogramProblem & problem) + : m_problem(problem), m_marker(problem.size()) { } bool CNonogram::readFromStream(QDataStream & stream) { - unsigned char data; - QString stringBuffer; - qint32 intBuffer; - QSize sizeBuffer; - - stream >> stringBuffer; qDebug("reading image: %s", qPrintable(stringBuffer)); - if (stream.atEnd()) { - qCritical("invalid nonogram"); - cleanup(); + quint64 magic; + stream >> magic; + if (CNonogram_MAGIC != magic || QDataStream::Ok != stream.status()) { + if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData); return false; } - stream >> intBuffer; qDebug("width %i", intBuffer); - if (stream.atEnd()) { - qCritical("invalid nonogram"); - cleanup(); + NonogramProblem problem; + NonogramMarker marker; + + if (!problem.readFromStream(stream)) return false; + if (!marker.readFromStream(stream)) return false; + + if (problem.size() != marker.size()) { + stream.setStatus(QDataStream::ReadCorruptData); return false; } - sizeBuffer.setWidth(intBuffer); - - 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++; - } - } - } + m_problem = problem; + m_marker = marker; return true; } - void CNonogram::writeToStream(QDataStream & stream) { - stream << m_Name; - 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); - } - } + void CNonogram::writeToStream(QDataStream & stream) const { + stream << CNonogram_MAGIC << m_problem << m_marker; } - 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) { + QDataStream & operator<<(QDataStream& stream, const libqnono::CNonogram& nonogram) { nonogram.writeToStream(stream); return stream; } diff --git a/libqnono/cnonogram.h b/libqnono/cnonogram.h index 1b34e30..8c7a0ea 100644 --- a/libqnono/cnonogram.h +++ b/libqnono/cnonogram.h @@ -24,106 +24,43 @@ #include #include +#include "nonogramproblem.h" +#include "nonogrammarker.h" + class QImage; namespace libqnono { - class CNonogram; - class CNonogramNumbers; - - class CNonogramNumbers { - public: - typedef QVector v_num; - typedef QVector 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: typedef QVector NumbersVector; CNonogram(); - CNonogram(QSize size); - CNonogram(QImage & image); - CNonogram(QDataStream & stream); - CNonogram(const CNonogram &other); - ~CNonogram(); - CNonogram& operator=(const CNonogram &other); + CNonogram(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; } + + 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); - void writeToStream(QDataStream & stream); - - 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; } + void writeToStream(QDataStream& stream) const; protected: - QSize m_Size; - - 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(); + NonogramProblem m_problem; + NonogramMarker m_marker; }; - QDataStream & operator<<(QDataStream & stream, CNonogram & nonogram); + QDataStream & operator<<(QDataStream & stream, const CNonogram & nonogram); QDataStream & operator>>(QDataStream & stream, CNonogram & nonogram); } diff --git a/libqnono/cnonogramsolver.cpp b/libqnono/cnonogramsolver.cpp index 25c94e7..eb2b99c 100644 --- a/libqnono/cnonogramsolver.cpp +++ b/libqnono/cnonogramsolver.cpp @@ -1,496 +1,14 @@ #include "cnonogramsolver.h" #include "cnonogram.h" +#include "nonogramsolver.h" + #include #include 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 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 > 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 &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 &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 &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 solve(const CNonogramNumbers & numbers) { - QList 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(QObject * parent) + : QObject(parent), m_Nonogram(0) { } CNonogramSolver::~CNonogramSolver() { @@ -503,19 +21,15 @@ namespace libqnono { bool CNonogramSolver::solve() { if (!m_Nonogram) return false; - { - QList 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); - } + QList solutions = libqnono::solve(m_Nonogram->numbers()); + if (!solutions.empty()) { + NonogramImage &sol(solutions.first()); + for (int i = 0; i < m_Nonogram->width(); ++i) { + for (int j = 0; j < m_Nonogram->height(); ++j) { + emit markRequested(i, j, sol.pixel(i, j) ? CMT_MARKED : CMT_CROSSED); } - - foreach (CNonogramSolution *s, solutions) delete s; - return TRUE; } + return TRUE; } return FALSE; diff --git a/libqnono/cnonogramsolver.h b/libqnono/cnonogramsolver.h index de0443f..3d34b41 100644 --- a/libqnono/cnonogramsolver.h +++ b/libqnono/cnonogramsolver.h @@ -6,32 +6,6 @@ #include 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 solve(const CNonogramNumbers & numbers); - class CNonogram; class CNonogramSolver : public QObject { diff --git a/libqnono/nonogramimage.cpp b/libqnono/nonogramimage.cpp new file mode 100644 index 0000000..39a36d0 --- /dev/null +++ b/libqnono/nonogramimage.cpp @@ -0,0 +1,208 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 + +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; + } +} diff --git a/libqnono/nonogramimage.h b/libqnono/nonogramimage.h new file mode 100644 index 0000000..4dd9852 --- /dev/null +++ b/libqnono/nonogramimage.h @@ -0,0 +1,70 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 +#include + +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 diff --git a/libqnono/nonogrammarker.cpp b/libqnono/nonogrammarker.cpp new file mode 100644 index 0000000..2d1c621 --- /dev/null +++ b/libqnono/nonogrammarker.cpp @@ -0,0 +1,116 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 + +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(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(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; + } +} \ No newline at end of file diff --git a/libqnono/nonogrammarker.h b/libqnono/nonogrammarker.h new file mode 100644 index 0000000..87af2ab --- /dev/null +++ b/libqnono/nonogrammarker.h @@ -0,0 +1,54 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 + +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 diff --git a/libqnono/nonogramnumbers.cpp b/libqnono/nonogramnumbers.cpp new file mode 100644 index 0000000..8f1c4ef --- /dev/null +++ b/libqnono/nonogramnumbers.cpp @@ -0,0 +1,165 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 + +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(r, line.count()); + foreach (const v_num &line, m_columns) r = qMax(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; + } +} diff --git a/libqnono/nonogramnumbers.h b/libqnono/nonogramnumbers.h new file mode 100644 index 0000000..3de5c44 --- /dev/null +++ b/libqnono/nonogramnumbers.h @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 +#include +#include + +namespace libqnono { + class NonogramImage; + + class NonogramNumbers { + public: + typedef QVector v_num; + typedef QVector 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 diff --git a/libqnono/nonogramproblem.cpp b/libqnono/nonogramproblem.cpp new file mode 100644 index 0000000..aea0fbf --- /dev/null +++ b/libqnono/nonogramproblem.cpp @@ -0,0 +1,89 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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; + } +} diff --git a/libqnono/nonogramproblem.h b/libqnono/nonogramproblem.h new file mode 100644 index 0000000..b4a0c25 --- /dev/null +++ b/libqnono/nonogramproblem.h @@ -0,0 +1,68 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 diff --git a/libqnono/nonogramsolver.cpp b/libqnono/nonogramsolver.cpp new file mode 100644 index 0000000..998a4ae --- /dev/null +++ b/libqnono/nonogramsolver.cpp @@ -0,0 +1,506 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 + +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 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 > 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 &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 &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 &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 solve(const NonogramNumbers & numbers) { + QList solutions; + SolverState solveState(numbers); + solveState.solve(solutions); + + foreach(const NonogramImage& solution, solutions) { + Q_ASSERT(numbers.check(solution)); + } + + return solutions; + } +} diff --git a/libqnono/nonogramsolver.h b/libqnono/nonogramsolver.h new file mode 100644 index 0000000..3a7c298 --- /dev/null +++ b/libqnono/nonogramsolver.h @@ -0,0 +1,31 @@ +/*************************************************************************** + * Copyright (C) 2012 Stefan Bühler * + * * + * 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 + +namespace libqnono { + class NonogramNumbers; + class NonogramImage; + + QList solve(const NonogramNumbers & numbers); +} + +#endif // NONOGRAMSOLVER_H diff --git a/qcross/ccrossfieldwidget.cpp b/qcross/ccrossfieldwidget.cpp index 3b72020..1a9a6b7 100644 --- a/qcross/ccrossfieldwidget.cpp +++ b/qcross/ccrossfieldwidget.cpp @@ -85,7 +85,7 @@ namespace qcross { if (m_Picture) { initialize(); - m_RemainingPixels = m_Picture->blackPixels(); + m_RemainingPixels = m_Picture->solution().blackPixels(); updateTimeDisplay(); } @@ -122,7 +122,7 @@ namespace qcross { updateMetrics(); - m_RemainingPixels = m_Picture->blackPixels(); + m_RemainingPixels = m_Picture->solution().blackPixels(); } m_Clock->setVisible(m_Picture); } @@ -340,7 +340,7 @@ namespace qcross { 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; minimumW += m_Picture->width(); @@ -388,11 +388,11 @@ namespace qcross { m_ErrorCount = 0; m_LastErrorMark.setX(-1); m_LastErrorMark.setY(-1); - m_RemainingPixels = m_Picture->blackPixels(); + m_RemainingPixels = m_Picture->solution().blackPixels(); } 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) { @@ -429,7 +429,7 @@ namespace qcross { void CCrossFieldWidget::execMark(int x, int y, MarkerType & marker) { switch (marker) { case CMT_MARKED: - if (m_Picture->pixel(x, y)) { + if (m_Picture->solution().pixel(x, y)) { m_RemainingPixels--; m_OverlayData[x][y] = marker; } @@ -454,7 +454,7 @@ namespace qcross { } if (m_OverlayData[x][y] == CMT_MARKED) { - if (m_Picture->pixel(x, y)) + if (m_Picture->solution().pixel(x, y)) m_RemainingPixels++; else if (!m_ErrorAware) 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)); if (!m_Paused) { 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; 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)); } @@ -614,10 +616,12 @@ namespace qcross { painter.fillRect(m_OffsetX, originY, m_HeaderWidth, m_BoxSize, palette().color((i % 2) ? QPalette::AlternateBase : QPalette::Base)); if (!m_Paused) { 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; 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)); } @@ -667,7 +671,7 @@ namespace qcross { } 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 = m_Picture->width() / 2 + 1; m_HeaderHeight = m_Picture->height() / 2 + 1;*/ diff --git a/qcross/cgamewindow.cpp b/qcross/cgamewindow.cpp index 58ef724..9ff7796 100644 --- a/qcross/cgamewindow.cpp +++ b/qcross/cgamewindow.cpp @@ -189,7 +189,6 @@ namespace qcross { m_PictureIndex = m_Highscore ? dialog.nonogramIndex() : -1; CNonogram * newPicture = dialog.takeNonogram(); - newPicture->updateNumbers(); m_SaveGameAction->setEnabled(true); m_PauseGameAction->setEnabled(true); diff --git a/qcross/cmaskedcrosspackagemodel.cpp b/qcross/cmaskedcrosspackagemodel.cpp index e3ce704..d712698 100644 --- a/qcross/cmaskedcrosspackagemodel.cpp +++ b/qcross/cmaskedcrosspackagemodel.cpp @@ -69,7 +69,7 @@ namespace qcross { return tr("#%1").arg(index.row()); case COL_TIME: 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)" : ""); else return QVariant(); diff --git a/qcross/cnewgamedialog.cpp b/qcross/cnewgamedialog.cpp index a9557b7..054fdec 100644 --- a/qcross/cnewgamedialog.cpp +++ b/qcross/cnewgamedialog.cpp @@ -117,7 +117,7 @@ namespace qcross { if (selected.isEmpty()) result = NULL; else { - result = static_cast(selected[0].internalPointer())->takePicture(nonogramIndex()); + result = new CNonogram(static_cast(selected[0].internalPointer())->getPicture(nonogramIndex())); m_PicModel->setPackage(NULL); } } @@ -142,7 +142,16 @@ namespace qcross { if (!fileName.isEmpty()) { 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)) dynamic_cast(ui.packageList->model())->update(); else @@ -159,9 +168,7 @@ namespace qcross { QImage image(fileName); if (!image.isNull()) { - m_Nonogram = new CNonogram(); - m_Nonogram->loadFromImage(image); - m_Nonogram->updateNumbers(); + m_Nonogram = new CNonogram(NonogramProblem(NonogramImage(image))); accept(); } else diff --git a/qcrossedit/cmainwindow.cpp b/qcrossedit/cmainwindow.cpp index 482392a..73d0fa5 100644 --- a/qcrossedit/cmainwindow.cpp +++ b/qcrossedit/cmainwindow.cpp @@ -169,11 +169,15 @@ namespace qcrossedit { } 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)")); - if (!fileName.isEmpty()) { - if (qobject_cast(m_PicListView->model())->appendImage(fileName, QImage(fileName))) - m_Unsaved = true; + CCrossPackageModel *model = qobject_cast(m_PicListView->model()); + foreach (QString fileName, fileNames) { + if (!fileName.isEmpty()) { + QString title = QDir::toNativeSeparators(fileName).section(QDir::separator(), -1); + if (model->appendImage(title, QImage(fileName))) + m_Unsaved = true; + } } }