#include "cnonogramsolver.h" #include "cnonogram.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() { } void CNonogramSolver::setNonogram(CNonogram * nonogram) { m_Nonogram = nonogram; } 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); } } foreach (CNonogramSolution *s, solutions) delete s; return TRUE; } } return FALSE; } }