diff --git a/libqnono/cnonogram.cpp b/libqnono/cnonogram.cpp index 9c9650e..2bbade4 100644 --- a/libqnono/cnonogram.cpp +++ b/libqnono/cnonogram.cpp @@ -21,6 +21,127 @@ #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 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), diff --git a/libqnono/cnonogram.h b/libqnono/cnonogram.h index 77f1227..1b34e30 100644 --- a/libqnono/cnonogram.h +++ b/libqnono/cnonogram.h @@ -27,6 +27,41 @@ 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; diff --git a/libqnono/cnonogramsolver.cpp b/libqnono/cnonogramsolver.cpp index 53f75dc..25c94e7 100644 --- a/libqnono/cnonogramsolver.cpp +++ b/libqnono/cnonogramsolver.cpp @@ -5,523 +5,519 @@ #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), - - m_RowsOverlay(NULL), - m_ColumnsOverlay(NULL), - - m_OverlayData(NULL) + m_Nonogram(NULL) { } CNonogramSolver::~CNonogramSolver() { - if (m_Nonogram) - cleanup(); } void CNonogramSolver::setNonogram(CNonogram * nonogram) { - if (m_Nonogram) - cleanup(); - m_Nonogram = nonogram; } - void CNonogramSolver::printOverlays() { - qDebug("row overlays:"); - QString debugoutput; - for (int i = 0; i < m_Nonogram->height(); i++) { - if (i) debugoutput += '\n'; - for (int e = 0; e < m_RowsOverlay[i].numbersSize; e++) { - debugoutput += ' ' + QString::number(m_RowsOverlay[i].numbers[e].borderLeft) - + '-' + QString::number(m_RowsOverlay[i].numbers[e].borderRight - 1); - - if (m_RowsOverlay[i].numbers[e].finished) - debugoutput += '*'; - - debugoutput += ' '; - } - } - qDebug(qPrintable(debugoutput)); - - debugoutput.clear(); - - qDebug("column overlays:"); - for (int i = 0; i < m_Nonogram->width(); i++) { - if (i) debugoutput += '\n'; - for (int e = 0; e < m_ColumnsOverlay[i].numbersSize; e++) { - debugoutput += ' ' + QString::number(m_ColumnsOverlay[i].numbers[e].borderLeft) - + '-' + QString::number(m_ColumnsOverlay[i].numbers[e].borderRight - 1); - - if (m_ColumnsOverlay[i].numbers[e].finished) - debugoutput += '*'; - - debugoutput += ' '; - } - } - qDebug(qPrintable(debugoutput)); - } - bool CNonogramSolver::solve() { - if (!m_Nonogram) - return false; + if (!m_Nonogram) return false; - cleanup(); - prepare(); - - qDebug("clearing trivial lines ..."); - - // nach trivial lösbaren Reihen suchen und diese füllen bzw. abkreuzen - for (int i = 0; i < m_Nonogram->height(); i++) { - if (m_RowsOverlay[i].numbersSize == 1) { - if (m_RowsOverlay[i].numbers[0].entry == 0) { - fillRow(i, CMT_CROSSED); - m_RowsOverlay[i].numbers[0].finished = true; - } - else if (m_RowsOverlay[i].numbers[0].entry == m_Nonogram->height()) { - fillRow(i, CMT_MARKED); - m_RowsOverlay[i].numbers[0].finished = true; - } - } - } - - for (int i = 0; i < m_Nonogram->width(); i++) { - if (m_ColumnsOverlay[i].numbersSize == 1) { - if (m_ColumnsOverlay[i].numbers[0].entry == 0) { - fillColumn(i, CMT_CROSSED); - m_ColumnsOverlay[i].numbers[0].finished = true; - } - else if (m_ColumnsOverlay[i].numbers[0].entry == m_Nonogram->width()) { - fillColumn(i, CMT_MARKED); - m_ColumnsOverlay[i].numbers[0].finished = true; - } - } - } - - printOverlays(); - - qDebug("processing non-trivial lines..."); - - bool changed; - int iter = 0; - const int iter_max = 10; - do { - changed = false; - - for (int l = 0; l < m_Nonogram->height(); l++) - if (solveLine_ng(l, true)) - changed = true; - - for (int i = 0; i < m_Nonogram->width(); i++) - if (solveLine_ng(i, false)) - changed = true; - - iter++; - } while (changed && iter < iter_max); - -// qDebug("needed %i iterations", iter); - - printOverlays(); - - return false; - } - - bool CNonogramSolver::solveLine_ng(int index, bool isRow) { - LineOverlay * overlay = isRow ? &(m_RowsOverlay[index]) : &(m_ColumnsOverlay[index]); - int rightMax = isRow ? m_Nonogram->width() : m_Nonogram->height(); - - NumberOverlay * number; - - bool result = false; - - for (int i = 0; i < overlay->numbersSize; i++) { - number = &(overlay->numbers[i]); - - if (number->finished) - continue; - - /* - * |-start- null - * - * null -marked-> markierung - * null -crossed-> kreuze - * null -unmarked-> keine - * - * kreuze -marked-> markierung - * kreuze -crossed-> kreuze - * kreuze -unmarked-> keine - * - * keine -marked-> markierung - * keine -crossed-> kreuzstop - * keine -unmarked-> keine - * - * markierung -marked-> markierung - * markierung -crossed-> kreuzstop - * markierung -unmarked-> keine - * - * kreuzstop -stop-| - * fertig -stop-| - * - * null : keine aktion - * fertig : rechten rand stzen, als fertig markieren und stop - * kreuze : linken rand anpassen - * keine : keine aktion - * - * markierung : rechten rand anpassen - * kreuzstop : rechten rand setzen und stop - * - */ - - { - enum{ST_NULL = 0, ST_NONE = 1, ST_CROSSES = 2, ST_MARK = 3, ST_FINISHED = 4, ST_CROSS_STOP = 5} state = ST_NULL; - int f = number->borderLeft; - MarkerType marker; - while (f < number->borderRight && state < ST_FINISHED) { - marker = static_cast(m_OverlayData[isRow ? f : index][isRow ? index : f]); - - switch (state) { - case ST_NULL: - case ST_CROSSES: - switch (marker) { - case CMT_MARKED: state = ST_MARK; break; - case CMT_CROSSED: state = ST_CROSSES; break; - case CMT_UNMARKED: state = ST_NONE; break; - default: break; - } - break; - case ST_NONE: - case ST_MARK: - switch (marker) { - case CMT_MARKED: state = ST_MARK; break; - case CMT_CROSSED: state = ST_CROSS_STOP; break; - case CMT_UNMARKED: state = ST_NONE; break; - default: break; - } - break; - case ST_FINISHED: - case ST_CROSS_STOP: - default: - break; + { + 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); } - - switch (state) { - case ST_NONE: - case ST_NULL: - default: - break; - case ST_CROSSES: - number->borderLeft = f + 1; - break; - case ST_MARK: - if ((f < overlay->borderLeftDef(i + i, rightMax)) && - (overlay->borderRightDef(i - 1, 0) < f) && - (f + number->entry < number->borderRight)) - number->borderRight = f + number->entry; - break; - case ST_FINISHED: - number->finished = true; - number->borderRight = f + number->entry; - if (i + 1 < overlay->numbersSize && overlay->numbers[i+1].borderLeft < number->borderRight + 1) - overlay->numbers[i+1].borderLeft = number->borderRight + 1; - break; - case ST_CROSS_STOP: - number->borderRight = f; - if (i + 1 < overlay->numbersSize && overlay->numbers[i+1].borderLeft < number->borderRight + 1) - overlay->numbers[i+1].borderLeft = number->borderRight + 1; - break; - } - - f++; - } - } - - /* - sicher markierbare Steine - r - l < 2m - 1 - 0 < l + m - r + m - 1 - 0 < (l + m - 1) - (r - m) - */ - for (int k = number->borderRight - number->entry; k < number->borderLeft + number->entry; k++) - mark(isRow ? k : index, isRow ? index : k, CMT_MARKED); - - if (!number->finished && number->borderRight - number->borderLeft == number->entry) - number->finished = true; - - for (int g = overlay->borderRightDef(i - 1, 0); g < number->borderLeft; g++) - mark(isRow ? g : index, isRow ? index : g, CMT_CROSSED); - - for (int g = number->borderRight; g < overlay->borderLeftDef(i + 1, rightMax); g++) - mark(isRow ? g : index, isRow ? index : g, CMT_CROSSED); - - - result = result || overlay->dirty; - } - - return result; - } - - void CNonogramSolver::cleanup() { - if (m_OverlayData) { - for (int i = 0; i < m_Nonogram->width(); i++) - delete[] m_OverlayData[i]; - delete[] m_OverlayData; - } - - if (m_RowsOverlay) - delete[] m_RowsOverlay; - - if (m_ColumnsOverlay) - delete[] m_ColumnsOverlay; - } - - inline void CNonogramSolver::mark(int x, int y, int marker) { - if ((x < 0) || (y < 0) || (x > m_Nonogram->width() - 1) || (y > m_Nonogram->height() - 1) || m_OverlayData[x][y] == marker) - return; - - emit markRequested(x, y, marker); - m_OverlayData[x][y] = marker; - - m_RowsOverlay[y].dirty = true; - m_ColumnsOverlay[x].dirty = true; - } - - inline void CNonogramSolver::fillRow(int index, int marker) { - for (int i = 0; i < m_Nonogram->width(); i++) - mark(i, index, marker); - } - - inline void CNonogramSolver::fillColumn(int index, int marker) { - for (int i = 0; i < m_Nonogram->height(); i++) - mark(index, i, marker); - } - - void CNonogramSolver::prepare() { - m_OverlayData = new int *[m_Nonogram->width()]; - - for (int i = 0; i < m_Nonogram->width(); i++) { - m_OverlayData[i] = new int[m_Nonogram->height()]; - for (int j = 0; j < m_Nonogram->height(); j++) - m_OverlayData[i][j] = static_cast(CMT_UNMARKED); - } - - int leftSum, rightSum, numSize; - - m_RowsOverlay = new LineOverlay [m_Nonogram->height()]; - - for (int i = 0; i < m_Nonogram->height(); i++) { - m_RowsOverlay[i].numbersSize = m_Nonogram->rowNumbers(i).size(); - m_RowsOverlay[i].numbers = new NumberOverlay [m_RowsOverlay[i].numbersSize]; - - numSize = m_Nonogram->rowNumbers(i).size(); - - leftSum = 0; - for (int j = 0; j < numSize; j++) { - m_RowsOverlay[i].numbers[j].entry = m_Nonogram->rowNumbers(i).at(j); - m_RowsOverlay[i].numbers[j].finished = false; - m_RowsOverlay[i].numbers[j].borderRight = m_Nonogram->width(); - - m_RowsOverlay[i].numbers[j].borderLeft = leftSum + j; - leftSum += m_RowsOverlay[i].numbers[j].entry; - } - - rightSum = 0; - for (int j = numSize - 1; j > -1; j--) { - m_RowsOverlay[i].numbers[j].borderRight -= rightSum; - rightSum += m_RowsOverlay[i].numbers[j].entry + 1; - } - } - - m_ColumnsOverlay = new LineOverlay [m_Nonogram->width()]; - - for (int i = 0; i < m_Nonogram->width(); i++) { - m_ColumnsOverlay[i].numbersSize = m_Nonogram->columnNumbers(i).size(); - m_ColumnsOverlay[i].numbers = new NumberOverlay [m_ColumnsOverlay[i].numbersSize]; - - numSize = m_Nonogram->columnNumbers(i).size(); - - leftSum = 0; - for (int j = 0; j < numSize; j++) { - m_ColumnsOverlay[i].numbers[j].borderRight = m_Nonogram->height(); - m_ColumnsOverlay[i].numbers[j].finished = false; - m_ColumnsOverlay[i].numbers[j].entry = m_Nonogram->columnNumbers(i).at(j); - - m_ColumnsOverlay[i].numbers[j].borderLeft = leftSum + j; - leftSum += m_ColumnsOverlay[i].numbers[j].entry; - } - - rightSum = 0; - for (int j = numSize - 1; j > -1; j--) { - m_ColumnsOverlay[i].numbers[j].borderRight -= rightSum; - rightSum += m_ColumnsOverlay[i].numbers[j].entry + 1; - } - } - } - - inline bool CNonogramSolver::solveLine(int index, bool isRow) { - LineOverlay * overlay = isRow ? &(m_RowsOverlay[index]) : &(m_ColumnsOverlay[index]); - - bool result = false; - - QString dbgOut; - QTextStream dbg(&dbgOut); - - dbg << "overlay "; - if (isRow) - dbg << "row "; - else - dbg << "col "; - - dbg << index << ": "; - - int length = 0; - int offset = 0; - for (int i = 0; i < overlay->numbersSize; i++) { - if (overlay->numbers[i].finished) { - dbg << "(fin)"; - continue; - } - - dbg << '(' << overlay->numbers[i].borderLeft << ", " << overlay->numbers[i].borderRight << "; "; - - length = safeLength(&(overlay->numbers[i])); - offset = overlay->numbers[i].entry - length + overlay->numbers[i].borderLeft; - - dbg << length << ", " << offset << ") "; - - if (length > 0) { - if (isRow) { - for (int j = 0; j < length; j++) - mark(offset + j, index, CMT_MARKED); - } - else { - for (int j = 0; j < length; j++) - mark(index, offset + j, CMT_MARKED); } - if (length == overlay->numbers[i].entry) { - if (isRow) { - mark(overlay->numbers[i].borderLeft - 1, index, CMT_CROSSED); - mark(overlay->numbers[i].borderRight, index, CMT_CROSSED); - } - else { - mark(index, overlay->numbers[i].borderLeft - 1, CMT_CROSSED); - mark(index, overlay->numbers[i].borderRight, CMT_CROSSED); - } - overlay->numbers[i].finished = true; - } - - result = true; + foreach (CNonogramSolution *s, solutions) delete s; + return TRUE; } } - qDebug() << dbgOut; - - if (fillGaps(index, isRow)) - return true; - - return result; - } - - inline bool CNonogramSolver::fillGaps(int index, bool isRow) { - LineOverlay * overlay = isRow ? &(m_RowsOverlay[index]) : &(m_ColumnsOverlay[index]); - - bool result = false; - - if (isRow) { - for (int i = 0; i < overlay->numbersSize + 1; i++) { - for (int j = overlay->borderRightDef(i - 1, 0); j < overlay->borderLeftDef(i, m_Nonogram->width()); j++) { - if (m_OverlayData[j][index] == 0) { - mark(j, index, CMT_CROSSED); - result = true; - } - } - } - } - else { - for (int i = 0; i < overlay->numbersSize + 1; i++) { - for (int j = overlay->borderRightDef(i - 1, 0); j < overlay->borderLeftDef(i, m_Nonogram->height()); j++) { - if (m_OverlayData[index][j] == 0) { - mark(index, j, CMT_CROSSED); - result = true; - } - } - } - } - - return result; - } - - inline void gtSet(int & target, int value) { - if (target > value) - target = value; - } - - inline void ltSet(int & target, int value) { - if (target < value) - target = value; - } - - inline void CNonogramSolver::prepareBorders(LineOverlay * overlay) { - for (int i = 1; i < overlay->numbersSize; i++) - ltSet(overlay->numbers[i].borderLeft, overlay->numbers[i - 1].borderLeft + overlay->numbers[i - 1].entry + 1); - - for (int i = overlay->numbersSize - 1; i > 0; i--) - gtSet(overlay->numbers[i - 1].borderRight, overlay->numbers[i].borderRight - overlay->numbers[i].entry - 1); - } - - inline void CNonogramSolver::updateBorders(int index, bool isRow) { - LineOverlay * overlay = isRow ? &(m_RowsOverlay[index]) : &(m_ColumnsOverlay[index]); - - if (!overlay->dirty) - return; - - int marker = 0; - - for (int i = 0; i < overlay->numbersSize; i++) { - if (overlay->numbers[i].finished) - continue; - - int j = overlay->numbers[i].borderLeft; -// int markedCount = 0; - while (j < overlay->numbers[i].borderRight) { - marker = (isRow ? m_OverlayData[j][index] : m_OverlayData[index][j]); - switch (marker) { - case CMT_CROSSED: - if (overlay->numbers[i].borderLeft == j) - overlay->numbers[i].borderLeft = j+1; - else { - if (j - overlay->numbers[i].borderLeft + 1 < j + overlay->numbers[i].entry) - overlay->numbers[i].borderLeft = j+1; - else - overlay->numbers[i].borderRight = j; - if (i < overlay->numbersSize-1) - ltSet(overlay->numbers[i + 1].borderLeft, j); - } - break; - case CMT_MARKED: - gtSet(overlay->numbers[i].borderRight, j + overlay->numbers[i].entry); -/* if (markedCount != -1) - markedCount++;*/ - break; - default: -/* if (markedCount > 0) { - markedCount = -1; - ltSet(overlay->numbers[i].borderLeft, j - overlay->numbers[i].entry + 1); - }*/ - break; - } - j++; - } - } - - overlay->dirty = false; - } - - void CNonogramSolver::updateBorders() { - for (int i = 0; i < m_Nonogram->height(); i++) - updateBorders(i, true); - - for (int i = 0; i < m_Nonogram->width(); i++) - updateBorders(i, false); + return FALSE; } } diff --git a/libqnono/cnonogramsolver.h b/libqnono/cnonogramsolver.h index fadd658..de0443f 100644 --- a/libqnono/cnonogramsolver.h +++ b/libqnono/cnonogramsolver.h @@ -2,8 +2,36 @@ #define LIBQCROSS_CNONOGRAMSOLVER_H #include +#include +#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 { @@ -20,59 +48,7 @@ namespace libqnono { protected: enum MarkerType {CMT_UNMARKED = 0, CMT_MARKED = 1, CMT_CROSSED = 2, CMT_NONE = 3}; - struct NumberOverlay { - int borderLeft; - int borderRight; - - quint16 entry; - bool finished; - }; - - struct LineOverlay { - NumberOverlay * numbers; - int numbersSize; - - bool dirty; - - inline int borderLeftDef(int index, int def) { - return (index > -1 && index < numbersSize) ? numbers[index].borderLeft : def; - } - - inline int borderRightDef(int index, int def) { - return (index > -1 && index < numbersSize) ? numbers[index].borderRight : def; - } - - LineOverlay() : numbers(NULL), dirty(false) {} - ~LineOverlay() { if (numbers) delete[] numbers; } - }; - CNonogram * m_Nonogram; - - LineOverlay * m_RowsOverlay; - LineOverlay * m_ColumnsOverlay; - - int ** m_OverlayData; - - void cleanup(); - void prepare(); - - inline void mark(int x, int y, int marker); - inline void fillRow(int index, int marker); - inline void fillColumn(int index, int marker); - - void printOverlays(); - bool solveLine_ng(int index, bool isRow); - - inline bool solveLine(int index, bool isRow); - inline bool fillGaps(int index, bool isRow); - inline void prepareBorders(LineOverlay * overlay); - inline void updateBorders(int index, bool isRow); - - void updateBorders(); - - inline int safeLength(NumberOverlay * overlay) { - return (overlay->entry * 2) + overlay->borderLeft - overlay->borderRight; - } }; }