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 }