File indexing completed on 2024-04-28 04:05:21

0001 /*
0002     SPDX-FileCopyrightText: 2015 Jakob Gruber <jakob.gruber@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include <QRandomGenerator>
0008 #include <config.h>
0009 #include "picmi.h"
0010 
0011 #include <assert.h>
0012 
0013 void IOHandler::set(int x, int y, Board::State state) {
0014     switch (state) {
0015     case Board::Cross: setCross(x, y); break;
0016     case Board::Box: setBox(x, y); break;
0017     default: assert(0);
0018     }
0019 }
0020 
0021 void IOHandler::setCross(int x, int y) {
0022     switch (m_state->get(x, y)) {
0023     case Board::Cross: m_state->set(x, y, Board::Nothing); break;
0024     case Board::Box: break;
0025     case Board::Nothing: m_state->set(x, y, Board::Cross); break;
0026     default: assert(0);
0027     }
0028 }
0029 
0030 class IOHandlerNoHints : public IOHandler
0031 {
0032 public:
0033     IOHandlerNoHints(BoardMap *map, BoardState *state, ElapsedTime *timer) : IOHandler(map, state, timer) { }
0034 
0035 protected:
0036     void setBox(int x, int y) override;
0037 };
0038 
0039 
0040 void IOHandlerNoHints::setBox(int x, int y) {
0041     switch (m_state->get(x, y)) {
0042     case Board::Cross: break;
0043     case Board::Box: m_state->set(x, y, Board::Nothing); break;
0044     case Board::Nothing: m_state->set(x, y, Board::Box); break;
0045     default: assert(0);
0046     }
0047 }
0048 
0049 class IOHandlerHints : public IOHandler
0050 {
0051 public:
0052     IOHandlerHints(BoardMap *map, BoardState *state, ElapsedTime *timer) : IOHandler(map, state, timer) { }
0053 
0054 protected:
0055     void setBox(int x, int y) override;
0056 };
0057 
0058 
0059 void IOHandlerHints::setBox(int x, int y) {
0060     switch (m_state->get(x, y)) {
0061     case Board::Cross: break;
0062     case Board::Box: m_state->set(x, y, Board::Nothing); break;
0063     case Board::Nothing:
0064         if (m_map->get(x, y) == Board::Box) {
0065             m_state->set(x, y, Board::Box);
0066         } else {
0067             m_state->set(x, y, Board::Cross);
0068             m_timer->addPenaltyTime();
0069         }
0070         break;
0071     default: assert(0);
0072     }
0073 }
0074 
0075 Picmi::Picmi()
0076 {
0077     int width, height;
0078     double density;
0079     bool prevent_mistakes;
0080 
0081     switch (Settings::instance()->level()) {
0082     case KGameDifficultyLevel::Easy: width = height = 10; density = 0.55; prevent_mistakes = false; break;
0083     case KGameDifficultyLevel::Medium: width = 15; height = 10; density = 0.55; prevent_mistakes = false; break;
0084     case KGameDifficultyLevel::Hard: width = height = 15; density = 0.55; prevent_mistakes = false; break;
0085     case KGameDifficultyLevel::Custom:
0086     default:
0087         width = Settings::instance()->width();
0088         height = Settings::instance()->height();
0089         density = Settings::instance()->boxDensity();
0090         prevent_mistakes = Settings::instance()->preventMistakes();
0091         break;
0092     }
0093 
0094     m_map = QSharedPointer<BoardMap>(new BoardMap(width, height, density));
0095     m_state = QSharedPointer<BoardState>(new BoardState(width, height));
0096     m_streaks = QSharedPointer<Streaks>(new Streaks(m_map, m_state));
0097 
0098     if (prevent_mistakes) {
0099         m_io_handler = QSharedPointer<IOHandler>(new IOHandlerHints(m_map.data(), m_state.data(), &m_timer));
0100     } else {
0101         m_io_handler = QSharedPointer<IOHandler>(new IOHandlerNoHints(m_map.data(), m_state.data(), &m_timer));
0102     }
0103 
0104     m_timer.start();
0105 
0106     setupSlots();
0107 }
0108 
0109 Picmi::Picmi(QSharedPointer<BoardMap> board) {
0110     m_map = board;
0111     m_state = QSharedPointer<BoardState>(new BoardState(board->width(), board->height()));
0112     m_streaks = QSharedPointer<Streaks>(new Streaks(m_map, m_state));
0113     m_io_handler = QSharedPointer<IOHandler>(new IOHandlerNoHints(m_map.data(), m_state.data(), &m_timer));
0114     m_timer.start();
0115 
0116     setupSlots();
0117 }
0118 
0119 void Picmi::setupSlots()
0120 {
0121     connect(m_state.data(), &BoardState::undoStackSizeChanged, this, &Picmi::undoStackSizeChanged);
0122     connect(m_state.data(), &BoardState::saveStackSizeChanged, this, &Picmi::saveStackSizeChanged);
0123 }
0124 
0125 bool Picmi::won() const {
0126     /* detect a win by comparing streaks.
0127        the reason we don't use the raw map and state is because we can't guarantee
0128        that our generated puzzles have exactly one solution, but we can work around
0129        this by ending the game once all streaks are marked solved. */
0130 
0131     for (int x = 0; x < width(); x++) {
0132         QList<Streaks::Streak> streak = getColStreak(x);
0133         for (int i = 0; i < (int)streak.size(); i++) {
0134             if (!streak[i].solved) {
0135                 return false;
0136             }
0137         }
0138     }
0139 
0140     for (int y = 0; y < height(); y++) {
0141         QList<Streaks::Streak> streak = getRowStreak(y);
0142         for (int i = 0; i < (int)streak.size(); i++) {
0143             if (!streak[i].solved) {
0144                 return false;
0145             }
0146         }
0147     }
0148 
0149     return true;
0150 }
0151 
0152 QPoint Picmi::undo() {
0153     QPoint coord = m_state->undo();
0154     m_streaks->update();
0155     Q_EMIT stateChanged();
0156     return coord;
0157 }
0158 
0159 QPoint Picmi::hint()
0160 {
0161     QList<QPoint> incorrect_cells;
0162     for (int x = 0; x != width(); ++x) {
0163         for (int y = 0; y != height(); ++y) {
0164             const Board::State m = m_map->get(x, y);
0165             const Board::State s = m_state->get(x, y);
0166 
0167             if ((m == Board::Box && s != Board::Box) ||
0168                 (m == Board::Nothing && s != Board::Cross)) {
0169                 incorrect_cells.push_back(QPoint(x, y));
0170             }
0171         }
0172     }
0173 
0174     if (incorrect_cells.size() == 0) {
0175         return QPoint(0, 0);
0176     }
0177 
0178     const int idx = QRandomGenerator::global()->bounded(incorrect_cells.size());
0179     const QPoint cell(incorrect_cells.at(idx));
0180     Board::State state = m_map->get(cell.x(), cell.y());
0181     if (state == Board::Nothing) {
0182         state = Board::Cross;
0183     }
0184 
0185     /* Clear the state in order to ensure the subsequent setState succeeds. */
0186     m_state->set(cell.x(), cell.y(), Board::Nothing);
0187     setState(cell.x(), cell.y(), state);
0188     m_timer.addPenaltyTime();
0189     return cell;
0190 }
0191 
0192 void Picmi::solve() {
0193     m_state->solve(m_map.data());
0194     m_streaks->update();
0195     endGame();
0196     Q_EMIT gameCompleted();
0197 }
0198 
0199 KGameHighScoreDialog::FieldInfo Picmi::endGame() {
0200     m_timer.stop();
0201 
0202     KGameHighScoreDialog::FieldInfo score;
0203     score[KGameHighScoreDialog::Score].setNum(m_timer.elapsedSecs());
0204     score[KGameHighScoreDialog::Time] = Time(m_timer.elapsedSecs()).toString();
0205     score[KGameHighScoreDialog::Date] = m_timer.startDate().toString(QStringLiteral("dd MMM yyyy hh:mm"));
0206 
0207     return score;
0208 }
0209 
0210 int Picmi::height() const {
0211     return m_map->height();
0212 }
0213 
0214 int Picmi::width() const {
0215     return m_map->width();
0216 }
0217 
0218 bool Picmi::outOfBounds(int x, int y) const {
0219     return m_map->outOfBounds(x, y);
0220 }
0221 
0222 void Picmi::setPaused(bool paused) {
0223     m_timer.pause(paused);
0224 }
0225 
0226 int Picmi::elapsedSecs() const {
0227     return m_timer.elapsedSecs();
0228 }
0229 
0230 void Picmi::setState(int x, int y, Board::State state) {
0231     m_io_handler->set(x, y, state);
0232     m_streaks->update(x, y);
0233     Q_EMIT stateChanged();
0234     if (m_state->boxCount() == m_map->boxCount() && won()) {
0235         m_state->replace(Board::Nothing, Board::Cross);
0236         Q_EMIT gameCompleted();
0237         Q_EMIT gameWon();
0238     }
0239 }
0240 
0241 QList<Streaks::Streak> Picmi::getRowStreak(int y) const {
0242     return m_streaks->getRowStreak(y);
0243 }
0244 
0245 QList<Streaks::Streak> Picmi::getColStreak(int x) const {
0246     return m_streaks->getColStreak(x);
0247 }
0248 
0249 #include "moc_picmi.cpp"