/*************************************************************************** * 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 #include #include 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 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; } }