#include "cnonogramsolver.h" #include "cnonogram.h" #include #include namespace libqnono { CNonogramSolver::CNonogramSolver(QObject * parent) : QObject(parent), m_Nonogram(NULL), m_RowsOverlay(NULL), m_ColumnsOverlay(NULL), m_OverlayData(NULL) { } CNonogramSolver::~CNonogramSolver() { if (m_Nonogram) cleanup(); } void CNonogramSolver::setNonogram(CNonogram * nonogram) { if (m_Nonogram) cleanup(); m_Nonogram = nonogram; } 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 += ' '; } } qDebug(qPrintable(debugoutput)); debugoutput.clear(); 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 += ' '; } } 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; } else if (m_RowsOverlay[i].numbers[0].entry == m_Nonogram->height()) { fillRow(i, CMT_MARKED); m_RowsOverlay[i].numbers[0].finished = true; } } } 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; } else if (m_ColumnsOverlay[i].numbers[0].entry == m_Nonogram->width()) { fillColumn(i, CMT_MARKED); m_ColumnsOverlay[i].numbers[0].finished = true; } } } 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(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; } 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; } 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; } f++; } } /* 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(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); 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; } } } 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); } else { for (int j = 0; j < length; j++) mark(index, offset + j, CMT_MARKED); } 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); } else { mark(index, overlay->numbers[i].borderLeft - 1, CMT_CROSSED); mark(index, overlay->numbers[i].borderRight, CMT_CROSSED); } overlay->numbers[i].finished = true; } result = 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; 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; } } } } 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; } } } } return result; } inline void gtSet(int & target, int value) { if (target > value) target = value; } inline void ltSet(int & target, int value) { if (target < value) target = value; } 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); } 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); } 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++; } } 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); } }