qcross/libqnono/cnonogram.cpp

363 lines
10 KiB
C++

/***************************************************************************
* Copyright (C) 2008 by Oliver Groß *
* z.o.gross@gmx.de *
* *
* 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 "cnonogram.h"
#include "nonogramsolver.h"
#include <QImage>
#include <QFile>
#include <QDataStream>
namespace libqnono {
static const char CNonogram_HEADER[] = {'C', 'R', 'S', 'V'}; /* readable magic */
static const quint64 CNonogram_MAGIC = Q_UINT64_C(0x35a8bca32006c5a9); /* binary magic */
CNonogram::CNonogram(QObject *parent) : QObject(parent), m_markedPixels(0), m_time(0), m_errorCount(0), m_preventErrors(true), m_finished(true), m_paused(true) {
}
void CNonogram::start(const NonogramProblem & problem) {
m_problem = problem;
m_marker.reset(problem.size());
m_markedPixels = 0;
m_time = problem.timeout();
m_errorCount = 0;
m_preventErrors = problem.timeout() > 0;
m_finished = true;
m_paused = true;
if (!valid()) {
emit loaded();
emit tick(0);
emit timeup();
return;
}
internRestart(true);
emit loaded();
}
void CNonogram::solve() {
if (!valid() || m_finished || m_paused) return;
QList<NonogramImage> solutions = libqnono::solve(m_problem.numbers());
if (!solutions.empty()) {
NonogramImage &sol(solutions.first());
m_errorCount = 0;
//m_time = -1;
m_markedPixels = sol.blackPixels();
for (int i = 0; i < width(); ++i) {
for (int j = 0; j < height(); ++j) {
NonogramMarker::Mark mark = sol.pixel(i, j) ? NonogramMarker::MARKED : NonogramMarker::CROSSED;
if (mark != m_marker.pixel(i, j)) {
m_marker.setPixel(i, j, mark);
emit changedMark(i, j);
}
}
}
m_finished = true;
m_timer.stop();
emit won(m_time);
}
}
bool CNonogram::saveGame(QString fileName) {
qDebug("saving game state file: %s", fileName.toAscii().data());
QFile file(fileName);
if (!file.open(QIODevice::WriteOnly)) {
qDebug("couldn't open file for writing");
return false;
}
QDataStream out(&file);
out.setVersion(QDataStream::Qt_4_0);
writeToStream(out);
return true;
}
bool CNonogram::loadGame(QString fileName) {
qDebug("opening game state file: %s", fileName.toAscii().data());
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) {
qDebug("couldn't open file for writing: %s", file.errorString().toAscii().data());
return false;
}
QDataStream in(&file);
in.setVersion(QDataStream::Qt_4_0);
if (!readFromStream(in)) {
switch (in.status()) {
case QDataStream::ReadPastEnd:
qDebug("Unexpected end of file");
break;
case QDataStream::ReadCorruptData:
default:
qDebug("Corrupted save game");
break;
}
return false;
}
return true;
}
bool CNonogram::readFromStream(QDataStream & stream) {
char magicHeader[sizeof(CNonogram_HEADER)];
stream.readRawData(magicHeader, sizeof(CNonogram_HEADER));
if (QDataStream::Ok != stream.status()) return false;
if (0 != memcmp(magicHeader, CNonogram_HEADER, sizeof(CNonogram_HEADER))) {
qDebug("Invalid text magic");
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
quint64 magic;
stream >> magic;
if (QDataStream::Ok != stream.status()) return false;
if (CNonogram_MAGIC != magic) {
qDebug("Invalid binary magic");
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
QString packageIdentifier;
int packageIndex;
stream >> packageIdentifier >> packageIndex;
if (QDataStream::Ok != stream.status()) return false;
NonogramProblem problem;
NonogramMarker marker;
if (!problem.readFromStream(stream, packageIdentifier, packageIndex)) return false;
if (!marker.readFromStream(stream)) return false;
if (problem.size() != marker.size()) {
qDebug("Problem size doesn't match maker size");
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
int time, errorCount;
bool preventErrors;
stream >> time >> errorCount >> preventErrors;
if (QDataStream::Ok != stream.status()) return false;
if (preventErrors && (0 == problem.timeout())) {
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
NonogramImage markerImage(marker);
const NonogramImage &solution(problem.solution());
int markedPixels = markerImage.blackPixels();
bool finished;
if (preventErrors) {
/* check no wrong mark is set */
for (int x = 0; x < problem.width(); ++x) {
for (int y = 0; y < problem.height(); ++y) {
if (markerImage.pixel(x, y) && !solution.pixel(x, y)) {
qDebug("Wrong marked pixel and preventErrors is on: %i, %i", x, y);
stream.setStatus(QDataStream::ReadCorruptData);
return false;
}
}
}
finished = (markedPixels == solution.blackPixels());
} else {
finished = (markedPixels == solution.blackPixels() && problem.numbers().check(markerImage));
}
m_problem = problem;
m_marker = marker;
m_markedPixels = markedPixels;
m_time = time;
m_errorCount = errorCount;
m_preventErrors = preventErrors;
m_finished = finished;
m_paused = false;
if (time == 0) {
m_finished = true;
emit loaded();
emit tick(m_time);
emit timeup();
} else if (finished) {
emit loaded();
emit tick(m_time);
emit won(m_time);
} else {
m_timer.start(1000, this);
emit tick(m_time);
emit loaded();
}
return true;
}
void CNonogram::writeToStream(QDataStream & stream) const {
stream.writeRawData(CNonogram_HEADER, sizeof(CNonogram_HEADER));
stream << CNonogram_MAGIC;
/* problem meta data; not stored by m_problem itself */
stream << m_problem.packageIdentifier() << m_problem.packageIndex();
stream << m_problem << m_marker;
stream << m_time << m_errorCount << m_preventErrors;
}
// public slots
void CNonogram::setMark(int x, int y, NonogramMarker::Mark mark) {
if (x < 0 || x >= width() || y < 0 || y >= height()) return;
if (m_finished || m_paused) return;
if (m_preventErrors) {
// if pixel already is marked it must be correct - no reason to undo it
if (m_marker.pixel(x, y) == NonogramMarker::MARKED) return;
if (m_marker.pixel(x, y) == mark) return; /* nothing to change */
if (mark == NonogramMarker::MARKED && !m_problem.solution().pixel(x, y)) {
int penalty;
m_marker.setPixel(x, y, NonogramMarker::CROSSED);
++m_errorCount;
switch (m_errorCount) {
case 1: penalty = 2*60; break;
case 2: penalty = 4*60; break;
default: penalty = 8*60; break;
}
if (penalty >= m_time) {
// lost
m_finished = true;
m_timer.stop();
m_time = 0;
emit wrongMark(x, y, penalty);
emit changedMark(x, y);
emit tick(m_time);
emit timeup();
} else {
m_time -= penalty;
emit wrongMark(x, y, penalty);
emit changedMark(x, y);
emit tick(m_time);
}
} else {
// unmarking is prevented above
// if (m_marker.pixel(x, y) == NonogramMarker::MARKED) --m_markedPixels;
m_marker.setPixel(x, y, mark);
if (mark == NonogramMarker::MARKED) ++m_markedPixels;
if (m_markedPixels == m_problem.solution().blackPixels()) {
m_finished = true;
m_timer.stop();
emit changedMark(x, y);
emit won(m_time);
return;
}
emit changedMark(x, y);
}
} else {
if (m_marker.pixel(x, y) == mark) return; /* nothing to change */
if (m_marker.pixel(x, y) == NonogramMarker::MARKED) --m_markedPixels;
m_marker.setPixel(x, y, mark);
if (mark == NonogramMarker::MARKED) ++m_markedPixels;
// blackPixels is an invariant on all solutions for a set of numbers
if (m_markedPixels == m_problem.solution().blackPixels() && m_problem.numbers().check(NonogramImage(m_marker))) {
m_finished = true;
m_timer.stop();
emit changedMark(x, y);
emit won(m_time);
return;
}
emit changedMark(x, y);
}
}
void CNonogram::restart() {
internRestart(false);
}
void CNonogram::pause() {
if (m_finished || m_paused) return;
m_paused = true;
m_timer.stop();
emit paused(m_time);
}
void CNonogram::resume() {
if (m_finished || !m_paused) return;
m_paused = false;
m_timer.start(1000, this);
emit resumed(m_time);
}
// protected
void CNonogram::timerEvent(QTimerEvent * event) {
if (m_finished) {
m_timer.stop();
return;
}
m_time = m_time + (timeout() ? -1 : 1);
if (m_time <= 0) m_time = 0;
if (0 == m_time) {
// lost
m_finished = true;
m_timer.stop();
emit tick(m_time);
emit timeup();
} else {
emit tick(m_time);
}
}
void CNonogram::internRestart(bool newGame) {
if (!valid()) {
emit tick(0);
emit timeup();
return;
}
m_finished = false;
m_errorCount = 0;
m_time = timeout();
m_marker.reset();
m_markedPixels = 0;
m_paused = false;
m_timer.start(1000, this);
emit tick(m_time);
if (!newGame) emit restarted();
}
QDataStream & operator<<(QDataStream& stream, const libqnono::CNonogram& nonogram) {
nonogram.writeToStream(stream);
return stream;
}
QDataStream & operator>>(QDataStream & stream, CNonogram & nonogram) {
nonogram.readFromStream(stream);
return stream;
}
}