/*************************************************************************** * 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 "nonogrammarker.h" #include "nonogramnumbers.h" #include namespace libqnono { struct SolverState { struct Block { int minFirst, maxFirst, length; }; typedef NonogramMarker::Mark Mark; static const Mark MARK_UNKNOWN = NonogramMarker::NONE; static const Mark MARK_BLACK = NonogramMarker::MARKED; static const Mark MARK_WHITE = NonogramMarker::CROSSED; class UndoState { private: struct UndoOp { union { struct { int *ptr, old; } data_int; struct { Mark *ptr, old; } data_mark; }; enum { UNDO_INT, UNDO_MARK } type; }; QList m_ops; public: void trackInt(bool &changed, int &ptr, int val) { if (val == ptr) return; changed = TRUE; if (this) { UndoOp op; op.type = UndoOp::UNDO_INT; op.data_int.ptr = &ptr; op.data_int.old = ptr; m_ops.push_front(op); } ptr = val; } void trackMark(bool &changed, Mark &ptr, Mark val) { if (val == ptr) return; changed = TRUE; if (this) { UndoOp op; op.type = UndoOp::UNDO_MARK; op.data_mark.ptr = &ptr; op.data_mark.old = ptr; m_ops.push_front(op); } ptr = val; } void undo() { foreach (const UndoOp &op, m_ops) { switch (op.type) { case UndoOp::UNDO_INT: *op.data_int.ptr = op.data_int.old; break; case UndoOp::UNDO_MARK: *op.data_mark.ptr = op.data_mark.old; break; } } m_ops.clear(); } }; NonogramMarker m_marks; QVector< QVector > rows, cols; inline Mark& pixel(int x, int y) { return m_marks.pixel(x, y); } template struct View : public T { View(SolverState & state) : T(state) { } /* horizontal uses ViewRowColumn, vertical ViewColumnRow */ bool mark(UndoState *undo_state, bool & changed, int m, int from, int to) { for (int i = from; i <= to; ++i) { if (this->data(m, i) == MARK_WHITE) return FALSE; undo_state->trackMark(changed, this->data(m, i), MARK_BLACK); } return TRUE; } bool clear(UndoState *undo_state, bool & changed, int m, int from, int to) { for (int i = from; i <= to; ++i) { if (this->data(m, i) == MARK_BLACK) return FALSE; undo_state->trackMark(changed, this->data(m, i), MARK_WHITE); } return TRUE; } bool markBlock(UndoState *undo_state, bool & changed, int m, const Block &b) { if (b.minFirst == b.maxFirst) { if (b.minFirst > 0) { if (this->data(m, b.minFirst-1) == MARK_BLACK) return FALSE; undo_state->trackMark(changed, this->data(m, b.minFirst-1), MARK_WHITE); } if (b.minFirst + b.length < this->dimSecond()) { if (this->data(m, b.minFirst + b.length) == MARK_BLACK) return FALSE; undo_state->trackMark(changed, this->data(m, b.minFirst + b.length), MARK_WHITE); } } return mark(undo_state, changed, m, b.maxFirst, b.minFirst+b.length-1); } }; struct ViewRowColumn { ViewRowColumn(SolverState & state) : state(state) { } inline Mark& data(int row, int col) { return state.pixel(col, row); } inline int dimFirst() const { return state.m_marks.height(); } inline int dimSecond() const { return state.m_marks.width(); } SolverState & state; }; struct ViewColumnRow { ViewColumnRow(SolverState & state) : state(state) { } inline Mark& data(int col, int row) { return state.pixel(col, row); } inline int dimFirst() const { return state.m_marks.width(); } inline int dimSecond() const { return state.m_marks.height(); } SolverState & state; }; SolverState(const NonogramNumbers & numbers) : m_marks(numbers.size()) { int nrows = m_marks.height(), ncols = m_marks.width(); rows.resize(nrows); cols.resize(ncols); for (int row = 0; row < nrows; ++row) { foreach (quint16 len, numbers.rows()[row]) { Block block = { 0, ncols - len, len }; rows[row] << block; } } for (int col = 0; col < ncols; ++col) { foreach (quint16 len, numbers.columns()[col]) { Block block = { 0, nrows - len, len }; cols[col] << block; } } } template bool update(UndoState *undo_state, QVector< QVector > & lines, View & view, bool & changed) { for (int i = 0; i < lines.count(); ++i) { QVector &line(lines[i]); int lineLen = line.count(); if (0 == lineLen || (1 == lineLen && 0 == line[0].length)) { if (!view.clear(undo_state, changed, i, 0, view.dimSecond()-1)) return FALSE; continue; } // first block { int cell = line[0].minFirst; // there must be "length" adjacent non white cells for (int cell1 = cell, end = cell + line[0].length; cell <= line[0].maxFirst && cell1 < end; ++cell1) { if (MARK_WHITE == view.data(i, cell1)) { cell = cell1 + 1; end = cell + line[0].length; } } if (cell > line[0].minFirst) { if (cell > line[0].maxFirst) return FALSE; // no solution impossible undo_state->trackInt(changed, line[0].minFirst, cell); } // the first black can't be before the first block while (cell < line[0].maxFirst && view.data(i, cell) != MARK_BLACK) ++cell; if (cell < line[0].maxFirst) { undo_state->trackInt(changed, line[0].maxFirst, cell); } } // last block { int len = line.last().length; int cell = line.last().maxFirst; // there must be "length" adjacent non white cells for (int cell1 = cell + len - 1; cell >= line.last().minFirst && cell1 >= cell; --cell1) { if (MARK_WHITE == view.data(i, cell1)) { cell = cell1 - len; } } if (cell < line.last().maxFirst) { if (cell < line.last().minFirst) return FALSE; // no solution impossible undo_state->trackInt(changed, line.last().maxFirst, cell); } // the last black can't be after the last block while (cell > line.last().minFirst && view.data(i, cell+len-1) != MARK_BLACK) --cell; if (cell > line.last().minFirst) { undo_state->trackInt(changed, line.last().minFirst, cell); } } /* check relative block offsets (min distance 1) */ for (int j = 1, k = lineLen - 1; j < lineLen; ++j, --k) { { int minFirst = qMax(line[j].minFirst, 1 + line[j-1].minFirst + line[j-1].length); // the cell before first can't be black while (minFirst <= line[j].maxFirst && MARK_BLACK == view.data(i, minFirst-1)) ++minFirst; // there must be "length" adjacent non white cells for (int cell = minFirst, end = minFirst + line[j].length; minFirst <= line[j].maxFirst && cell < end; ++cell) { if (MARK_WHITE == view.data(i, cell)) { minFirst = cell + 1; end = minFirst + line[j].length; } } if (minFirst >= line[j-1].maxFirst + line[j-1].length) { int cell = minFirst; // next black cell can't be before this block while (cell < line[j].maxFirst && view.data(i, cell) != MARK_BLACK) ++cell; if (cell < line[j].maxFirst) { undo_state->trackInt(changed, line[j].maxFirst, cell); } } if (minFirst > line[j].minFirst) { if (minFirst > line[j].maxFirst) return FALSE; // no solution impossible undo_state->trackInt(changed, line[j].minFirst, minFirst); } } { int len = line[k-1].length; int maxFirst = qMin(line[k-1].maxFirst, line[k].maxFirst - len - 1); // the cell after last can't be black while (maxFirst >= line[k-1].minFirst && MARK_BLACK == view.data(i, maxFirst + len)) --maxFirst; // there must be "length" adjacent non white cells for (int cell = maxFirst + len - 1; maxFirst >= line[k-1].minFirst && cell >= maxFirst; --cell) { if (MARK_WHITE == view.data(i, cell)) { maxFirst = cell - len; } } if (maxFirst + len <= line[k].minFirst) { int cell = maxFirst; // next black cell before maxFirst+len can't be after this block while (cell > line[k-1].minFirst && view.data(i, cell+len-1) != MARK_BLACK) --cell; if (cell > line[k-1].minFirst) { undo_state->trackInt(changed, line[k-1].minFirst, cell); } } if (maxFirst < line[k-1].maxFirst) { if (maxFirst < line[k-1].minFirst) return FALSE; // no solution impossible undo_state->trackInt(changed, line[k-1].maxFirst, maxFirst); } } } if (!view.clear(undo_state, changed, i, 0, line[0].minFirst-1)) return FALSE; for (int j = 0; j < lineLen; ++j) { if (j > 0 && !view.clear(undo_state, changed, i, line[j-1].maxFirst + line[j-1].length, line[j].minFirst-1)) return FALSE; if (!view.markBlock(undo_state, changed, i, line[j])) return FALSE; } if (!view.clear(undo_state, changed, i, line.last().maxFirst + line.last().length, view.dimSecond()-1)) return FALSE; } return TRUE; } void debugState() { #ifndef QT_NO_DEBUG_OUTPUT for (int j = 0; j < m_marks.height(); ++j) { QDebug dbg = qDebug(); for (int i = 0; i < m_marks.width(); ++i) { switch (pixel(i, j)) { case MARK_UNKNOWN: dbg << "?"; break; case MARK_BLACK: dbg << "M"; break; case MARK_WHITE: dbg << " "; break; } } } qDebug() << "Row blocks:"; for (int j = 0; j < rows.count(); ++j) { QDebug dbg = qDebug() << j << ":"; foreach (Block block, rows[j]) { dbg << "[" << block.minFirst << block.maxFirst << block.length << "]"; } } qDebug() << "Col blocks:"; for (int j = 0; j < cols.count(); ++j) { QDebug dbg = qDebug() << j << ":"; foreach (Block block, cols[j]) { dbg << "[" << block.minFirst << block.maxFirst << block.length << "]"; } } #endif } void solve(QList &solutions, UndoState *undo_state = 0) { bool changed = TRUE; View rowColumn(*this); View columnRow(*this); while (changed) { changed = FALSE; if (!update(undo_state, rows, rowColumn, changed)) return; if (!update(undo_state, cols, columnRow, changed)) return; } if (!undo_state) { qDebug() << "State after first run:"; debugState(); } for (int i = 0; i < m_marks.width(); ++i) { for (int j = 0; j < m_marks.height(); ++j) { if (pixel(i, j) == MARK_UNKNOWN) { UndoState subundo; subundo.trackMark(changed, pixel(i, j), MARK_BLACK); solve(solutions, &subundo); subundo.undo(); subundo.trackMark(changed, pixel(i, j), MARK_WHITE); solve(solutions, &subundo); subundo.undo(); return; } } } qDebug() << "Found solution:"; debugState(); solutions.append(NonogramImage());; solutions.last().load(m_marks); } }; QList solve(const NonogramNumbers & numbers) { QList solutions; SolverState solveState(numbers); solveState.solve(solutions); foreach(const NonogramImage& solution, solutions) { Q_ASSERT(numbers.check(solution)); } return solutions; } }