/*************************************************************************** * 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 **m_data; Mark& data(int row, int col) { return m_data[col][row]; } 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; trackMark(undo_state, 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; trackMark(undo_state, 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; trackMark(undo_state, 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; trackMark(undo_state, changed, this->data(m, b.minFirst + b.length), MARK_WHITE); } } return mark(undo_state, changed, m, b.maxFirst, b.minFirst+b.length-1); } SolverState *state; }; struct ViewRowColumn { ViewRowColumn(SolverState *state) : state(state) { } Mark& data(int row, int col) { return state->data(row, col); } int dimFirst() const { return state->nrows; } int dimSecond() const { return state->ncols; } SolverState *state; }; struct ViewColumnRow { ViewColumnRow(SolverState *state) : state(state) { } Mark& data(int col, int row) { return state->data(row, col); } int dimFirst() const { return state->ncols; } int dimSecond() const { return state->nrows; } SolverState *state; }; SolverState(const NonogramNumbers & numbers) : nrows(numbers.height()), ncols(numbers.width()) { m_data = new Mark*[ncols]; for (int col = 0; col < ncols; ++col) { m_data[col] = new Mark[nrows]; for (int row = 0; row < nrows; ++row) { m_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 [] m_data[col]; delete[] m_data; } 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 trackInt(undo_state, 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) { 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 == view->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 && view->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 == 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) { 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 == 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) { 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 (!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 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(j, i) == MARK_BLACK); } } } void debugState() { for (int j = 0; j < nrows; ++j) { QDebug dbg = qDebug(); for (int i = 0; i < ncols; ++i) { switch (data(j, i)) { 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; 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 < ncols; ++i) { for (int j = 0; j < nrows; ++j) { if (data(j, i) == MARK_UNKNOWN) { UndoState subundo; trackMark(&subundo, changed, data(j, i), MARK_BLACK); solve(solutions, &subundo); undo(subundo); trackMark(&subundo, changed, data(j, i), 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; } }