/*************************************************************************** * 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; } }