qcross/libqnono/cnonogramsolver.cpp

524 lines
16 KiB
C++
Raw Normal View History

2010-06-16 12:53:56 +00:00
#include "cnonogramsolver.h"
#include "cnonogram.h"
#include <QString>
#include <QDebug>
namespace libqnono {
2012-04-11 13:01:54 +00:00
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<UndoOp> 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;
2010-06-16 12:53:56 +00:00
}
2012-04-11 13:01:54 +00:00
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;
}
2012-04-11 13:01:54 +00:00
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();
}
2012-04-11 13:01:54 +00:00
struct State {
int nrows, ncols;
QVector< QVector<Block> > 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;
}
}
}
2012-04-11 13:01:54 +00:00
~State() {
for (int col = 0; col < ncols; ++col) delete [] data[col];
delete[] data;
}
2012-04-11 13:01:54 +00:00
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);
}
2012-04-11 13:01:54 +00:00
return TRUE;
}
2012-04-11 13:01:54 +00:00
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);
}
2012-04-11 13:01:54 +00:00
if (minFirst + length < ncols) {
if (data[minFirst + length][row] == MARK_BLACK) return FALSE;
trackMark(undo_state, changed, data[minFirst + length][row], MARK_WHITE);
}
}
2012-04-11 13:01:54 +00:00
return markHorizontal(undo_state, changed, row, maxFirst, minFirst+length-1);
}
2010-06-16 12:53:56 +00:00
2012-04-11 13:01:54 +00:00
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);
}
2012-04-11 13:01:54 +00:00
if (minFirst + length < nrows) {
if (data[col][minFirst + length] == MARK_BLACK) return FALSE;
trackMark(undo_state, changed, data[col][minFirst + length], MARK_WHITE);
}
}
2012-04-11 13:01:54 +00:00
return markVertical(undo_state, changed, col, maxFirst, minFirst+length-1);
2010-06-16 12:53:56 +00:00
}
2012-04-11 13:01:54 +00:00
bool updateRows(UndoState *undo_state, bool &changed) {
for (int i = 0; i < nrows; ++i) {
QVector<Block> &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;
}
2012-04-11 13:01:54 +00:00
}
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;
}
}
2012-04-11 13:01:54 +00:00
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);
}
}
2012-04-11 13:01:54 +00:00
{
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);
}
}
}
2012-04-11 13:01:54 +00:00
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;
}
2012-04-11 13:01:54 +00:00
return TRUE;
}
2012-04-11 13:01:54 +00:00
bool updateCols(UndoState *undo_state, bool &changed) {
for (int i = 0; i < ncols; ++i) {
QVector<Block> &line(cols[i]);
int lineLen = line.count();
if (0 == lineLen) {
if (!clearVertical(undo_state, changed, i, 0, nrows-1)) return FALSE;
continue;
}
2012-04-11 13:01:54 +00:00
// 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);
}
}
2012-04-11 13:01:54 +00:00
// 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;
}
}
2012-04-11 13:01:54 +00:00
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);
}
}
2012-04-11 13:01:54 +00:00
/* 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;
}
2012-04-11 13:01:54 +00:00
return TRUE;
}
2012-04-11 13:01:54 +00:00
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);
}
2012-04-11 13:01:54 +00:00
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;
}
}
}
2012-04-11 13:01:54 +00:00
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 << "]";
}
}
}
2012-04-11 13:01:54 +00:00
void solve(QList<CNonogramSolution*> &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;
}
}
}
2012-04-11 13:01:54 +00:00
qDebug() << "Found solution:";
debugState();
solutions << solution();
}
};
QList<CNonogramSolution*> solve(const CNonogramNumbers & numbers) {
QList<CNonogramSolution*> solutions;
State solveState(numbers);
solveState.solve(solutions);
foreach(CNonogramSolution* solution, solutions) {
Q_ASSERT(numbers.check(solution->data()));
}
2012-04-11 13:01:54 +00:00
return solutions;
}
2012-04-11 13:01:54 +00:00
CNonogramSolver::CNonogramSolver(QObject * parent) : QObject(parent),
m_Nonogram(NULL)
{
}
2012-04-11 13:01:54 +00:00
CNonogramSolver::~CNonogramSolver() {
}
2012-04-11 13:01:54 +00:00
void CNonogramSolver::setNonogram(CNonogram * nonogram) {
m_Nonogram = nonogram;
}
2012-04-11 13:01:54 +00:00
bool CNonogramSolver::solve() {
if (!m_Nonogram) return false;
{
QList<CNonogramSolution*> 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);
}
}
2012-04-11 13:01:54 +00:00
foreach (CNonogramSolution *s, solutions) delete s;
return TRUE;
}
}
2012-04-11 13:01:54 +00:00
return FALSE;
2010-06-16 12:53:56 +00:00
}
}