363 lines
10 KiB
C++
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;
|
|
}
|
|
}
|