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"