Browse Source

New solver

stefan-wip
Stefan Bühler 9 years ago
parent
commit
fe3b10e35a
  1. 121
      libqnono/cnonogram.cpp
  2. 35
      libqnono/cnonogram.h
  3. 902
      libqnono/cnonogramsolver.cpp
  4. 80
      libqnono/cnonogramsolver.h

121
libqnono/cnonogram.cpp

@ -21,6 +21,127 @@
#include <QImage>
namespace libqnono {
static void boolsFromImage(bool ** & data, const QImage &image) {
int cols = image.width(), rows = image.height();
data = new bool*[cols];
for (int col = 0; col < cols; ++col) data[col] = new bool[rows];
for (int row = 0; row < rows; ++row) {
for (int col = 0; col < cols; ++col) {
data[col][row] = 0 == (image.pixel(col, row) & 0x00FFFFFF);
}
}
}
static void freeBools(const QSize & size, bool ** data) {
int cols = size.width();
for (int col = 0; col < cols; ++col) delete [] data[col];
delete[] data;
}
static const quint64 CNonogramNumbers_MAGIC = Q_UINT64_C(0xd33efcacc0050164);
CNonogramNumbers::CNonogramNumbers(const vv_num & rows, const vv_num & columns)
: m_rows(rows), m_columns(columns) {
}
CNonogramNumbers::CNonogramNumbers(const CNonogram & source)
: m_rows(source.height()), m_columns(source.width()) {
// remove the "0" blocks (they are used for otherwise empty lines)
for (int i = 0; i < source.height(); ++i) {
m_rows[i] = source.rowNumbers(i);
if (1 == m_rows[i].count() && 0 == m_rows[i][0]) m_rows[i].clear();
}
for (int i = 0; i < source.width(); ++i) {
m_columns[i] = source.columnNumbers(i);
if (1 == m_columns[i].count() && 0 == m_columns[i][0]) m_columns[i].clear();
}
}
CNonogramNumbers::CNonogramNumbers(QSize size, bool **data) {
calcFromImage(size, data);
}
CNonogramNumbers::CNonogramNumbers(const QImage & image) {
bool ** data = 0;
boolsFromImage(data, image);
calcFromImage(image.size(), data);
freeBools(image.size(), data);
}
void CNonogramNumbers::calcFromImage(QSize size, bool **data) {
int rows = size.height(), cols = size.width();
m_rows.clear(); m_rows.resize(rows);
m_columns.clear(); m_columns.resize(cols);
for (int row = 0; row < rows; ++row) {
quint16 *val = 0;
for (int col = 0; col < cols; ++col) {
bool b = data[col][row];
if (!val && b) {
m_rows[row].append(1);
val = &m_rows[row].last();
} else if (b) {
++(*val);
} else {
val = 0;
}
}
}
for (int col = 0; col < cols; ++col) {
quint16 *val = 0;
for (int row = 0; row < rows; ++row) {
bool b = data[col][row];
if (!val && b) {
m_columns[col].append(1);
val = &m_columns[col].last();
} else if (b) {
++(*val);
} else {
val = 0;
}
}
}
}
bool CNonogramNumbers::readFromStream(QDataStream & stream) {
quint64 magic;
stream >> magic;
if (CNonogramNumbers_MAGIC != magic || QDataStream::Ok != stream.status()) {
if (QDataStream::ReadPastEnd != stream.status()) stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
stream >> m_rows >> m_columns;
return QDataStream::Ok == stream.status();
}
void CNonogramNumbers::writeToStream(QDataStream & stream) {
stream << CNonogramNumbers_MAGIC << m_rows << m_columns;
}
bool CNonogramNumbers::operator==(const CNonogramNumbers &other) const {
return m_rows == other.m_rows && m_columns == other.m_columns;
}
bool CNonogramNumbers::check(bool **data) const {
CNonogramNumbers tmp(size(), data);
return *this == tmp;
}
bool CNonogramNumbers::check(const QImage & image) const {
if (size() != image.size()) return FALSE;
CNonogramNumbers tmp(image);
return *this == tmp;
}
QDataStream & operator<<(QDataStream & stream, CNonogramNumbers & numbers) {
numbers.writeToStream(stream);
return stream;
}
QDataStream & operator>>(QDataStream & stream, CNonogramNumbers & numbers) {
numbers.readFromStream(stream);
return stream;
}
CNonogram::CNonogram()
: m_Size(0, 0),
m_Data(NULL),

35
libqnono/cnonogram.h

@ -27,6 +27,41 @@
class QImage;
namespace libqnono {
class CNonogram;
class CNonogramNumbers;
class CNonogramNumbers {
public:
typedef QVector<quint16> v_num;
typedef QVector<v_num> vv_num;
CNonogramNumbers(const vv_num & rows, const vv_num & columns);
CNonogramNumbers(const CNonogram & source);
CNonogramNumbers(QSize size, bool **data);
CNonogramNumbers(const QImage & image);
void calcFromImage(QSize size, bool **data);
bool readFromStream(QDataStream & stream);
void writeToStream(QDataStream & stream);
bool operator==(const CNonogramNumbers &other) const;
inline const vv_num & rows() const { return m_rows; }
inline const vv_num & columns() const { return m_columns; }
inline QSize size() const { return QSize(m_columns.count(), m_rows.count()); }
inline int width() const { return m_columns.count(); }
inline int height() const { return m_rows.count(); }
bool check(bool **data) const;
bool check(const QImage & image) const;
private:
vv_num m_rows, m_columns;
};
class CNonogram {
public:
typedef QVector<quint16> NumbersVector;

902
libqnono/cnonogramsolver.cpp

@ -5,523 +5,519 @@
#include <QDebug>
namespace libqnono {
CNonogramSolver::CNonogramSolver(QObject * parent) : QObject(parent),
m_Nonogram(NULL),
m_RowsOverlay(NULL),
m_ColumnsOverlay(NULL),
m_OverlayData(NULL)
{
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;
}
CNonogramSolver::~CNonogramSolver() {
if (m_Nonogram)
cleanup();
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;
}
void CNonogramSolver::setNonogram(CNonogram * nonogram) {
if (m_Nonogram)
cleanup();
m_Nonogram = nonogram;
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();
}
void CNonogramSolver::printOverlays() {
qDebug("row overlays:");
QString debugoutput;
for (int i = 0; i < m_Nonogram->height(); i++) {
if (i) debugoutput += '\n';
for (int e = 0; e < m_RowsOverlay[i].numbersSize; e++) {
debugoutput += ' ' + QString::number(m_RowsOverlay[i].numbers[e].borderLeft)
+ '-' + QString::number(m_RowsOverlay[i].numbers[e].borderRight - 1);
if (m_RowsOverlay[i].numbers[e].finished)
debugoutput += '*';
debugoutput += ' ';
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;
}
}
}
qDebug(qPrintable(debugoutput));
debugoutput.clear();
~State() {
for (int col = 0; col < ncols; ++col) delete [] data[col];
delete[] data;
}
qDebug("column overlays:");
for (int i = 0; i < m_Nonogram->width(); i++) {
if (i) debugoutput += '\n';
for (int e = 0; e < m_ColumnsOverlay[i].numbersSize; e++) {
debugoutput += ' ' + QString::number(m_ColumnsOverlay[i].numbers[e].borderLeft)
+ '-' + QString::number(m_ColumnsOverlay[i].numbers[e].borderRight - 1);
if (m_ColumnsOverlay[i].numbers[e].finished)
debugoutput += '*';
debugoutput += ' ';
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;
}
qDebug(qPrintable(debugoutput));
}
bool CNonogramSolver::solve() {
if (!m_Nonogram)
return false;
cleanup();
prepare();
qDebug("clearing trivial lines ...");
// nach trivial lösbaren Reihen suchen und diese füllen bzw. abkreuzen
for (int i = 0; i < m_Nonogram->height(); i++) {
if (m_RowsOverlay[i].numbersSize == 1) {
if (m_RowsOverlay[i].numbers[0].entry == 0) {
fillRow(i, CMT_CROSSED);
m_RowsOverlay[i].numbers[0].finished = 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);
}
else if (m_RowsOverlay[i].numbers[0].entry == m_Nonogram->height()) {
fillRow(i, CMT_MARKED);
m_RowsOverlay[i].numbers[0].finished = true;
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);
}
for (int i = 0; i < m_Nonogram->width(); i++) {
if (m_ColumnsOverlay[i].numbersSize == 1) {
if (m_ColumnsOverlay[i].numbers[0].entry == 0) {
fillColumn(i, CMT_CROSSED);
m_ColumnsOverlay[i].numbers[0].finished = true;
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);
}
else if (m_ColumnsOverlay[i].numbers[0].entry == m_Nonogram->width()) {
fillColumn(i, CMT_MARKED);
m_ColumnsOverlay[i].numbers[0].finished = true;
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);
}
printOverlays();
qDebug("processing non-trivial lines...");
bool changed;
int iter = 0;
const int iter_max = 10;
do {
changed = false;
for (int l = 0; l < m_Nonogram->height(); l++)
if (solveLine_ng(l, true))
changed = true;
for (int i = 0; i < m_Nonogram->width(); i++)
if (solveLine_ng(i, false))
changed = true;
iter++;
} while (changed && iter < iter_max);
// qDebug("needed %i iterations", iter);
printOverlays();
return false;
}
bool CNonogramSolver::solveLine_ng(int index, bool isRow) {
LineOverlay * overlay = isRow ? &(m_RowsOverlay[index]) : &(m_ColumnsOverlay[index]);
int rightMax = isRow ? m_Nonogram->width() : m_Nonogram->height();
NumberOverlay * number;
bool result = false;
for (int i = 0; i < overlay->numbersSize; i++) {
number = &(overlay->numbers[i]);
if (number->finished)
continue;
/*
* |-start- null
*
* null -marked-> markierung
* null -crossed-> kreuze
* null -unmarked-> keine
*
* kreuze -marked-> markierung
* kreuze -crossed-> kreuze
* kreuze -unmarked-> keine
*
* keine -marked-> markierung
* keine -crossed-> kreuzstop
* keine -unmarked-> keine
*
* markierung -marked-> markierung
* markierung -crossed-> kreuzstop
* markierung -unmarked-> keine
*
* kreuzstop -stop-|
* fertig -stop-|
*
* null : keine aktion
* fertig : rechten rand stzen, als fertig markieren und stop
* kreuze : linken rand anpassen
* keine : keine aktion
*
* markierung : rechten rand anpassen
* kreuzstop : rechten rand setzen und stop
*
*/
{
enum{ST_NULL = 0, ST_NONE = 1, ST_CROSSES = 2, ST_MARK = 3, ST_FINISHED = 4, ST_CROSS_STOP = 5} state = ST_NULL;
int f = number->borderLeft;
MarkerType marker;
while (f < number->borderRight && state < ST_FINISHED) {
marker = static_cast<MarkerType>(m_OverlayData[isRow ? f : index][isRow ? index : f]);
switch (state) {
case ST_NULL:
case ST_CROSSES:
switch (marker) {
case CMT_MARKED: state = ST_MARK; break;
case CMT_CROSSED: state = ST_CROSSES; break;
case CMT_UNMARKED: state = ST_NONE; break;
default: break;
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;
}
break;
case ST_NONE:
case ST_MARK:
switch (marker) {
case CMT_MARKED: state = ST_MARK; break;
case CMT_CROSSED: state = ST_CROSS_STOP; break;
case CMT_UNMARKED: state = ST_NONE; break;
default: break;
}
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;
}
break;
case ST_FINISHED:
case ST_CROSS_STOP:
default:
break;
}
switch (state) {
case ST_NONE:
case ST_NULL:
default:
break;
case ST_CROSSES:
number->borderLeft = f + 1;
break;
case ST_MARK:
if ((f < overlay->borderLeftDef(i + i, rightMax)) &&
(overlay->borderRightDef(i - 1, 0) < f) &&
(f + number->entry < number->borderRight))
number->borderRight = f + number->entry;
break;
case ST_FINISHED:
number->finished = true;
number->borderRight = f + number->entry;
if (i + 1 < overlay->numbersSize && overlay->numbers[i+1].borderLeft < number->borderRight + 1)
overlay->numbers[i+1].borderLeft = number->borderRight + 1;
break;
case ST_CROSS_STOP:
number->borderRight = f;
if (i + 1 < overlay->numbersSize && overlay->numbers[i+1].borderLeft < number->borderRight + 1)
overlay->numbers[i+1].borderLeft = number->borderRight + 1;
break;
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);
}
}
f++;
{
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);
}
}
}
}
/*
sicher markierbare Steine
r - l < 2m - 1
0 < l + m - r + m - 1
0 < (l + m - 1) - (r - m)
*/
for (int k = number->borderRight - number->entry; k < number->borderLeft + number->entry; k++)
mark(isRow ? k : index, isRow ? index : k, CMT_MARKED);
if (!number->finished && number->borderRight - number->borderLeft == number->entry)
number->finished = true;
for (int g = overlay->borderRightDef(i - 1, 0); g < number->borderLeft; g++)
mark(isRow ? g : index, isRow ? index : g, CMT_CROSSED);
for (int g = number->borderRight; g < overlay->borderLeftDef(i + 1, rightMax); g++)
mark(isRow ? g : index, isRow ? index : g, CMT_CROSSED);
result = result || overlay->dirty;
}
return result;
}
void CNonogramSolver::cleanup() {
if (m_OverlayData) {
for (int i = 0; i < m_Nonogram->width(); i++)
delete[] m_OverlayData[i];
delete[] m_OverlayData;
}
if (m_RowsOverlay)
delete[] m_RowsOverlay;
if (m_ColumnsOverlay)
delete[] m_ColumnsOverlay;
}
inline void CNonogramSolver::mark(int x, int y, int marker) {
if ((x < 0) || (y < 0) || (x > m_Nonogram->width() - 1) || (y > m_Nonogram->height() - 1) || m_OverlayData[x][y] == marker)
return;
emit markRequested(x, y, marker);
m_OverlayData[x][y] = marker;
m_RowsOverlay[y].dirty = true;
m_ColumnsOverlay[x].dirty = true;
}
inline void CNonogramSolver::fillRow(int index, int marker) {
for (int i = 0; i < m_Nonogram->width(); i++)
mark(i, index, marker);
}
inline void CNonogramSolver::fillColumn(int index, int marker) {
for (int i = 0; i < m_Nonogram->height(); i++)
mark(index, i, marker);
}
void CNonogramSolver::prepare() {
m_OverlayData = new int *[m_Nonogram->width()];
for (int i = 0; i < m_Nonogram->width(); i++) {
m_OverlayData[i] = new int[m_Nonogram->height()];
for (int j = 0; j < m_Nonogram->height(); j++)
m_OverlayData[i][j] = static_cast<int>(CMT_UNMARKED);
}
int leftSum, rightSum, numSize;
m_RowsOverlay = new LineOverlay [m_Nonogram->height()];
for (int i = 0; i < m_Nonogram->height(); i++) {
m_RowsOverlay[i].numbersSize = m_Nonogram->rowNumbers(i).size();
m_RowsOverlay[i].numbers = new NumberOverlay [m_RowsOverlay[i].numbersSize];
numSize = m_Nonogram->rowNumbers(i).size();
leftSum = 0;
for (int j = 0; j < numSize; j++) {
m_RowsOverlay[i].numbers[j].entry = m_Nonogram->rowNumbers(i).at(j);
m_RowsOverlay[i].numbers[j].finished = false;
m_RowsOverlay[i].numbers[j].borderRight = m_Nonogram->width();
m_RowsOverlay[i].numbers[j].borderLeft = leftSum + j;
leftSum += m_RowsOverlay[i].numbers[j].entry;
}
rightSum = 0;
for (int j = numSize - 1; j > -1; j--) {
m_RowsOverlay[i].numbers[j].borderRight -= rightSum;
rightSum += m_RowsOverlay[i].numbers[j].entry + 1;
}
}
m_ColumnsOverlay = new LineOverlay [m_Nonogram->width()];
for (int i = 0; i < m_Nonogram->width(); i++) {
m_ColumnsOverlay[i].numbersSize = m_Nonogram->columnNumbers(i).size();
m_ColumnsOverlay[i].numbers = new NumberOverlay [m_ColumnsOverlay[i].numbersSize];
numSize = m_Nonogram->columnNumbers(i).size();
leftSum = 0;
for (int j = 0; j < numSize; j++) {
m_ColumnsOverlay[i].numbers[j].borderRight = m_Nonogram->height();
m_ColumnsOverlay[i].numbers[j].finished = false;
m_ColumnsOverlay[i].numbers[j].entry = m_Nonogram->columnNumbers(i).at(j);
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;
m_ColumnsOverlay[i].numbers[j].borderLeft = leftSum + j;
leftSum += m_ColumnsOverlay[i].numbers[j].entry;
}
rightSum = 0;
for (int j = numSize - 1; j > -1; j--) {
m_ColumnsOverlay[i].numbers[j].borderRight -= rightSum;
rightSum += m_ColumnsOverlay[i].numbers[j].entry + 1;
}
return TRUE;
}
}
inline bool CNonogramSolver::solveLine(int index, bool isRow) {
LineOverlay * overlay = isRow ? &(m_RowsOverlay[index]) : &(m_ColumnsOverlay[index]);
bool result = false;
QString dbgOut;
QTextStream dbg(&dbgOut);
dbg << "overlay ";
if (isRow)
dbg << "row ";
else
dbg << "col ";
dbg << index << ": ";
int length = 0;
int offset = 0;
for (int i = 0; i < overlay->numbersSize; i++) {
if (overlay->numbers[i].finished) {
dbg << "(fin)";
continue;
}
dbg << '(' << overlay->numbers[i].borderLeft << ", " << overlay->numbers[i].borderRight << "; ";
length = safeLength(&(overlay->numbers[i]));
offset = overlay->numbers[i].entry - length + overlay->numbers[i].borderLeft;
dbg << length << ", " << offset << ") ";
if (length > 0) {
if (isRow) {
for (int j = 0; j < length; j++)
mark(offset + j, index, CMT_MARKED);
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;
}
else {
for (int j = 0; j < length; j++)
mark(index, offset + j, CMT_MARKED);
// 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);
}
}
if (length == overlay->numbers[i].entry) {
if (isRow) {
mark(overlay->numbers[i].borderLeft - 1, index, CMT_CROSSED);
mark(overlay->numbers[i].borderRight, index, CMT_CROSSED);
// 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;
}
}
else {
mark(index, overlay->numbers[i].borderLeft - 1, CMT_CROSSED);
mark(index, overlay->numbers[i].borderRight, CMT_CROSSED);
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);
}
overlay->numbers[i].finished = true;
}
result = true;
/* 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;
}
qDebug() << dbgOut;
if (fillGaps(index, isRow))
return true;
return result;
}
inline bool CNonogramSolver::fillGaps(int index, bool isRow) {
LineOverlay * overlay = isRow ? &(m_RowsOverlay[index]) : &(m_ColumnsOverlay[index]);
bool result = false;
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);
}
if (isRow) {
for (int i = 0; i < overlay->numbersSize + 1; i++) {
for (int j = overlay->borderRightDef(i - 1, 0); j < overlay->borderLeftDef(i, m_Nonogram->width()); j++) {
if (m_OverlayData[j][index] == 0) {
mark(j, index, CMT_CROSSED);
result = true;
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 << "]";
}
}
}
else {
for (int i = 0; i < overlay->numbersSize + 1; i++) {
for (int j = overlay->borderRightDef(i - 1, 0); j < overlay->borderLeftDef(i, m_Nonogram->height()); j++) {
if (m_OverlayData[index][j] == 0) {
mark(index, j, CMT_CROSSED);
result = true;
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;
}
}
}
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()));
}
return result;
return solutions;
}
inline void gtSet(int & target, int value) {
if (target > value)
target = value;
CNonogramSolver::CNonogramSolver(QObject * parent) : QObject(parent),
m_Nonogram(NULL)
{
}
inline void ltSet(int & target, int value) {
if (target < value)
target = value;
CNonogramSolver::~CNonogramSolver() {
}
inline void CNonogramSolver::prepareBorders(LineOverlay * overlay) {
for (int i = 1; i < overlay->numbersSize; i++)
ltSet(overlay->numbers[i].borderLeft, overlay->numbers[i - 1].borderLeft + overlay->numbers[i - 1].entry + 1);
for (int i = overlay->numbersSize - 1; i > 0; i--)
gtSet(overlay->numbers[i - 1].borderRight, overlay->numbers[i].borderRight - overlay->numbers[i].entry - 1);
void CNonogramSolver::setNonogram(CNonogram * nonogram) {
m_Nonogram = nonogram;
}
inline void CNonogramSolver::updateBorders(int index, bool isRow) {
LineOverlay * overlay = isRow ? &(m_RowsOverlay[index]) : &(m_ColumnsOverlay[index]);
if (!overlay->dirty)
return;
int marker = 0;
for (int i = 0; i < overlay->numbersSize; i++) {
if (overlay->numbers[i].finished)
continue;
int j = overlay->numbers[i].borderLeft;
// int markedCount = 0;
while (j < overlay->numbers[i].borderRight) {
marker = (isRow ? m_OverlayData[j][index] : m_OverlayData[index][j]);
switch (marker) {
case CMT_CROSSED:
if (overlay->numbers[i].borderLeft == j)
overlay->numbers[i].borderLeft = j+1;
else {
if (j - overlay->numbers[i].borderLeft + 1 < j + overlay->numbers[i].entry)
overlay->numbers[i].borderLeft = j+1;
else
overlay->numbers[i].borderRight = j;
if (i < overlay->numbersSize-1)
ltSet(overlay->numbers[i + 1].borderLeft, j);
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);
}
break;
case CMT_MARKED:
gtSet(overlay->numbers[i].borderRight, j + overlay->numbers[i].entry);
/* if (markedCount != -1)
markedCount++;*/
break;
default:
/* if (markedCount > 0) {
markedCount = -1;
ltSet(overlay->numbers[i].borderLeft, j - overlay->numbers[i].entry + 1);
}*/
break;
}
j++;
foreach (CNonogramSolution *s, solutions) delete s;
return TRUE;
}
}
overlay->dirty = false;
}
void CNonogramSolver::updateBorders() {
for (int i = 0; i < m_Nonogram->height(); i++)
updateBorders(i, true);
for (int i = 0; i < m_Nonogram->width(); i++)
updateBorders(i, false);
return FALSE;
}
}

80
libqnono/cnonogramsolver.h

@ -2,8 +2,36 @@
#define LIBQCROSS_CNONOGRAMSOLVER_H
#include <QObject>
#include <QSize>
#include <QList>
namespace libqnono {
class CNonogramNumbers;
class CNonogramSolution {
public:
CNonogramSolution(QSize size, bool ** data)
: m_size(size), m_data(data) {
}
~CNonogramSolution() {
int cols = m_size.width();
for (int col = 0; col < cols; ++col) delete [] m_data[col];
delete[] m_data;
}
QSize size() const { return m_size; }
bool** data() { return m_data; }
private:
Q_DISABLE_COPY(CNonogramSolution)
QSize m_size;
bool ** m_data;
};
QList<CNonogramSolution*> solve(const CNonogramNumbers & numbers);
class CNonogram;
class CNonogramSolver : public QObject {
@ -20,59 +48,7 @@ namespace libqnono {
protected:
enum MarkerType {CMT_UNMARKED = 0, CMT_MARKED = 1, CMT_CROSSED = 2, CMT_NONE = 3};
struct NumberOverlay {
int borderLeft;
int borderRight;
quint16 entry;
bool finished;
};
struct LineOverlay {
NumberOverlay * numbers;
int numbersSize;
bool dirty;
inline int borderLeftDef(int index, int def) {
return (index > -1 && index < numbersSize) ? numbers[index].borderLeft : def;
}
inline int borderRightDef(int index, int def) {
return (index > -1 && index < numbersSize) ? numbers[index].borderRight : def;
}
LineOverlay() : numbers(NULL), dirty(false) {}
~LineOverlay() { if (numbers) delete[] numbers; }
};
CNonogram * m_Nonogram;
LineOverlay * m_RowsOverlay;
LineOverlay * m_ColumnsOverlay;
int ** m_OverlayData;
void cleanup();
void prepare();
inline void mark(int x, int y, int marker);
inline void fillRow(int index, int marker);
inline void fillColumn(int index, int marker);
void printOverlays();
bool solveLine_ng(int index, bool isRow);
inline bool solveLine(int index, bool isRow);
inline bool fillGaps(int index, bool isRow);
inline void prepareBorders(LineOverlay * overlay);
inline void updateBorders(int index, bool isRow);
void updateBorders();
inline int safeLength(NumberOverlay * overlay) {
return (overlay->entry * 2) + overlay->borderLeft - overlay->borderRight;
}
};
}

Loading…
Cancel
Save