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

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 "streaks.h"
0008 
0009 #include <algorithm>
0010 
0011 /** 0 <= x < m_width; 0 <= y < m_height; returns a row as a sequence of states */
0012 static QList<Board::State> colToLine(const QSharedPointer<Board> &board,
0013                                        int x)
0014 {
0015     QList<Board::State> line;
0016     for (int y = 0; y < board->height(); y++) {
0017         line.push_back(board->get(x, y));
0018     }
0019     return line;
0020 }
0021 
0022 /** 0 <= x < m_width; 0 <= y < m_height; returns a row as a sequence of states */
0023 static QList<Board::State> rowToLine(const QSharedPointer<Board> &board,
0024                                        int y)
0025 {
0026     QList<Board::State> line;
0027     for (int x = 0; x < board->width(); x++) {
0028         line.push_back(board->get(x, y));
0029     }
0030     return line;
0031 }
0032 
0033 /**
0034  * Streaks are generated using a simple state machine. Either we're in a filler
0035  * section (Nothing for map streaks, Cross for state streaks), or we're in a Box
0036  * streak. A final possibility is that we're done processing.
0037  */
0038 
0039 enum {
0040     S_FILLER,
0041     S_STREAK,
0042     S_END
0043 };
0044 
0045 QList<Streaks::StreakPrivate>
0046 Streaks::lineToStreaks(const QList<Board::State> &line,
0047                        Board::State filler)
0048 {
0049     StreakPrivate s;
0050     QList<StreakPrivate> streaks;
0051     int state = S_FILLER;
0052 
0053     for (int i = 0; i < line.size(); i++) {
0054         const Board::State t = line[i];
0055 
0056         switch (state) {
0057         case S_FILLER:
0058             if (t == Board::Box) {
0059                 s.begin = i;
0060                 s.value = 0;
0061                 state = S_STREAK;
0062             } else if (t == filler) {
0063                 /* Nothing. */
0064             } else {
0065                 state = S_END;
0066             }
0067             break;
0068         case S_STREAK:
0069             if (t == Board::Box) {
0070                 /* Nothing. */
0071             } else {
0072                 s.end = i;
0073                 s.value = s.end - s.begin;
0074                 streaks.append(s);
0075                 state = (t == filler) ? S_FILLER : S_END;
0076             }
0077             break;
0078         case S_END:
0079         default:
0080             return streaks;
0081         }
0082     }
0083 
0084     if (state == S_STREAK) {
0085         s.end = line.size();
0086         s.value = s.end - s.begin;
0087         streaks.append(s);
0088     }
0089 
0090     return streaks;
0091 }
0092 
0093 QList<Streaks::Streak>
0094 Streaks::processStreak(const QList<StreakPrivate> &map,
0095                        const QList<Board::State> &l)
0096 {        
0097     QList<Streaks::Streak> streak;
0098     QList<StreakPrivate *> assocs(map.size(), NULL);
0099 
0100     /* Initial values for returned streaks. */
0101 
0102     for (int i = 0; i < map.size(); i++) {
0103         streak.push_back(map[i]);
0104     }
0105 
0106     /* Create the state streaks. */
0107 
0108     QList<StreakPrivate> streaks_regular = lineToStreaks(l, Board::Cross);
0109 
0110     QList<Board::State> line_reversed(l);
0111     std::reverse(line_reversed.begin(), line_reversed.end());
0112     QList<StreakPrivate> streaks_reversed = lineToStreaks(line_reversed, Board::Cross);
0113 
0114     /* Fix begin and end indices of reversed streaks. */
0115 
0116     for (int i = 0; i < streaks_reversed.size(); i++) {
0117         streaks_reversed[i].begin = l.size() - streaks_reversed[i].begin;
0118         streaks_reversed[i].end   = l.size() - streaks_reversed[i].end;
0119         std::swap(streaks_reversed[i].begin, streaks_reversed[i].end);
0120     }
0121 
0122     /* Preliminary checks
0123      * Do not match anything in any of these cases:
0124      * 1. The number of streaks in any direction exceeds the solution.
0125      * 2. A completely filled line does not match exactly the number of streaks.
0126      * The second case fixes https://bugs.kde.org/435211
0127      */
0128 
0129     if (streaks_regular.size() > map.size() || streaks_reversed.size() > map.size()
0130             || (!l.contains(Board::Nothing) && (streaks_regular.size() != map.size()))) {
0131         return streak;
0132     }
0133 
0134     /* The concept of this check is fairly simple, and consists of two passes:
0135      * 1. Compare and match the regular state streaks to map streaks.
0136      * 2. Compare and match the reverse state streaks to map streaks.
0137      * A streak is solved, iff it is matched with exactly one streak (reverse
0138      * or regular), or it is matched with two and their begin and end indices match.
0139      */
0140 
0141     for (int i = 0; i < streaks_regular.size(); i++) {
0142         streak[i].solved = (streak[i].value == streaks_regular[i].value);
0143         assocs[i] = &streaks_regular[i];
0144     }
0145 
0146     for (int i = 0; i < streaks_reversed.size(); i++) {
0147         const int ix = map.size() - i - 1;
0148 
0149         streak[ix].solved = (streak[ix].value == streaks_reversed[i].value);
0150 
0151         if (assocs[ix] != NULL) {
0152             const bool range_matches = (assocs[ix]->begin == streaks_reversed[i].begin &&
0153                                         assocs[ix]->end   == streaks_reversed[i].end);
0154             streak[ix].solved &= range_matches;
0155         }
0156     }
0157 
0158     return streak;
0159 }
0160 
0161 Streaks::Streaks(QSharedPointer<BoardMap> map, QSharedPointer<BoardState> state)
0162     : m_map(map), m_state(state)
0163 {
0164     for (int x = 0; x < m_map->width(); x++) {
0165         QList<Board::State> line = colToLine(m_map, x);
0166         m_map_col_streaks.push_back(lineToStreaks(line, Board::Nothing));
0167     }
0168 
0169     for (int y = 0; y < m_map->height(); y++) {
0170         QList<Board::State> line = rowToLine(m_map, y);
0171         m_map_row_streaks.push_back(lineToStreaks(line, Board::Nothing));
0172     }
0173 
0174     update();
0175 }
0176 
0177 void Streaks::update(int x, int y) {
0178     m_state_col_streaks[x] = processStreak(m_map_col_streaks[x], colToLine(m_state, x));
0179     m_state_row_streaks[y] = processStreak(m_map_row_streaks[y], rowToLine(m_state, y));
0180 }
0181 
0182 void Streaks::update() {
0183     m_state_col_streaks.clear();
0184     for (int x = 0; x < m_state->width(); x++) {
0185         m_state_col_streaks.push_back(processStreak(m_map_col_streaks[x], colToLine(m_state, x)));
0186     }
0187 
0188     m_state_row_streaks.clear();
0189     for (int y = 0; y < m_state->height(); y++) {
0190         m_state_row_streaks.push_back(processStreak(m_map_row_streaks[y], rowToLine(m_state, y)));
0191     }
0192 }
0193 
0194 QList<Streaks::Streak> Streaks::getRowStreak(int y) const {
0195     return m_state_row_streaks[y];
0196 }
0197 
0198 QList<Streaks::Streak> Streaks::getColStreak(int x) const {
0199     return m_state_col_streaks[x];
0200 }