1 /***********************************************************************
2 *
3 * Copyright (C) 2009, 2013, 2014 Graeme Gott <graeme@gottcode.org>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 *
18 ***********************************************************************/
19
20 #include "word.h"
21
22 #include "board.h"
23 #include "cell.h"
24 #include "letter.h"
25 #include "wordlist.h"
26
27 #include <QGraphicsPathItem>
28 #include <QPainterPath>
29
30 #include <algorithm>
31
factorial(int num)32 static int factorial(int num) {
33 return (num > 1) ? (factorial(num - 1) * num) : 1;
34 }
35
countPermutations(const QString & word)36 static int countPermutations(const QString& word) {
37 Q_ASSERT(!word.isEmpty());
38
39 int n = factorial(word.length());
40 int d = 1;
41
42 QString sorted = word;
43 std::sort(sorted.begin(), sorted.end());
44 QChar c;
45 int count = 0;
46 for (const QChar& c2 : sorted) {
47 if (c2 != c) {
48 c = c2;
49 d *= factorial(count);
50 count = 1;
51 } else {
52 count++;
53 }
54 }
55 d *= factorial(count);
56
57 return n / d;
58 }
59
60 //-----------------------------------------------------------------------------
61
Word(const QString & word,const QPoint & position,Qt::Orientation orientation,std::mt19937 & random)62 Word::Word(const QString& word, const QPoint& position, Qt::Orientation orientation, std::mt19937& random)
63 : m_board(0), m_correct(false), m_orientation(orientation), m_random(random) {
64 if (word.isEmpty()) {
65 return;
66 }
67 m_solutions.append(word);
68
69 QPoint delta = (orientation == Qt::Horizontal) ? QPoint(1, 0) : QPoint(0, 1);
70 QPoint pos = position;
71 for (int i = 0; i < word.length(); ++i) {
72 m_positions.append(pos);
73 pos += delta;
74 }
75 }
76
77 //-----------------------------------------------------------------------------
78
click()79 void Word::click() {
80 m_board->click(toString());
81 }
82
83 //-----------------------------------------------------------------------------
84
check()85 void Word::check() {
86 QString word;
87 for (const QPoint& pos : m_positions) {
88 word += m_board->cell(pos.x(), pos.y())->letter()->character();
89 }
90 if (m_solutions.contains(word)) {
91 m_correct = true;
92 for (const QPoint& pos : m_positions) {
93 Cell* cell = m_board->cell(pos.x(), pos.y());
94 cell->setWord(this);
95 cell->letter()->setCorrect();
96 }
97 m_board->setCurrentWord(0);
98 m_board->check(m_solutions.at(0), word);
99 }
100 }
101
102 //-----------------------------------------------------------------------------
103
hint()104 QGraphicsItem* Word::hint() {
105 if (isCorrect()) {
106 return 0;
107 }
108
109 // Find position of first incorrect character
110 int count = m_positions.count();
111 int pos = -1;
112 QString solution;
113 for (const QString& word : m_solutions) {
114 for (int i = 0; i < count; ++i) {
115 const QPoint& point = m_positions.at(i);
116 Letter* letter = m_board->cell(point.x(), point.y())->letter();
117 if (letter->character() != word.at(i)) {
118 if (i > pos) {
119 pos = i;
120 solution = word;
121 }
122 break;
123 }
124 }
125 }
126
127 // Find letter from end of string which goes in that position
128 QChar c = solution.at(pos);
129 int pos2;
130 QPointF position;
131 for (pos2 = count - 1; pos2 > pos; --pos2) {
132 const QPoint& point = m_positions.at(pos2);
133 Letter* letter = m_board->cell(point.x(), point.y())->letter();
134 if (letter->isMovable() && letter->character() == c) {
135 position = letter->scenePos();
136 letter->setBrush(QColor("#ffbf00"));
137 break;
138 }
139 }
140
141 // Create hint graphicsitem
142 int edge = ((pos2 - pos - 1) * 34) + 16;
143 QList<QPointF> positions;
144 positions += QPointF(0, 8);
145 positions += QPointF(12, 0);
146 positions += QPointF(12, 5);
147 positions += QPointF(edge, 5);
148 positions += QPointF(edge, 11);
149 positions += QPointF(12, 11);
150 positions += QPointF(12, 16);
151 positions += QPointF(0, 8);
152 QPointF delta = QPointF(-edge, 8);
153 if (m_orientation == Qt::Vertical) {
154 delta = QPointF(8, -edge);
155 for (int i = 0; i < 8; ++i) {
156 QPointF& pos = positions[i];
157 qSwap(pos.rx(), pos.ry());
158 }
159 }
160 QPainterPath path;
161 path.moveTo(positions.at(0));
162 for (int i = 1; i < 8; ++i) {
163 path.lineTo(positions.at(i));
164 }
165
166 QGraphicsPathItem* item = m_board->addPath(path, Qt::NoPen, QColor("#ffbf00"));
167 item->setPos(position + delta);
168 item->setZValue(20);
169
170 return item;
171 }
172
173 //-----------------------------------------------------------------------------
174
moveBy(const QPoint & delta)175 void Word::moveBy(const QPoint& delta) {
176 int count = m_positions.count();
177 for (int i = 0; i < count; ++i) {
178 m_positions[i] += delta;
179 }
180 }
181
182 //-----------------------------------------------------------------------------
183
fromString(const QString & shuffled)184 void Word::fromString(const QString& shuffled) {
185 // Find movable letters
186 QString sorted1;
187 QList<Letter*> letters;
188 for (int i = 0; i < m_positions.count(); ++i) {
189 const QPoint& pos = m_positions.at(i);
190 Letter* letter = m_board->cell(pos.x(), pos.y())->letter();
191 QChar c = letter->character();
192 sorted1.append(c);
193 if (letter->isMovable()) {
194 letters.append(letter);
195 } else if (c != shuffled.at(i)) {
196 return;
197 }
198 }
199
200 // Check if shuffled has the right characters
201 QString sorted2 = shuffled;
202 std::sort(sorted1.begin(), sorted1.end());
203 std::sort(sorted2.begin(), sorted2.end());
204 if (sorted1 != sorted2) {
205 return;
206 }
207
208 // Move letters to match shuffled
209 for (int i = 0; i < m_positions.count(); ++i) {
210 const QPoint& pos = m_positions.at(i);
211 Cell* cell = m_board->cell(pos.x(), pos.y());
212 if (!cell->letter()->isMovable()) {
213 continue;
214 }
215
216 QChar c = shuffled.at(i);
217 for (int j = 0; j < letters.count(); ++j) {
218 if (letters.at(j)->character() == c) {
219 cell->setLetter(letters.takeAt(j));
220 break;
221 }
222 }
223 }
224
225 check();
226 }
227
228 //-----------------------------------------------------------------------------
229
toString() const230 QString Word::toString() const {
231 QString result;
232 for (const QPoint& pos : m_positions) {
233 result.append(m_board->cell(pos.x(), pos.y())->letter()->character());
234 }
235 return result;
236 }
237
238 //-----------------------------------------------------------------------------
239
setHighlight(bool highlight)240 void Word::setHighlight(bool highlight) {
241 for (const QPoint& pos : m_positions) {
242 m_board->cell(pos.x(), pos.y())->letter()->setHighlight(highlight);
243 }
244 }
245
246 //-----------------------------------------------------------------------------
247
shuffle(const WordList * words)248 void Word::shuffle(const WordList* words) {
249 // Create list of characters and filter the list of words
250 QString chars;
251 QString movable;
252 QString filter;
253 for (int i = 0; i < m_positions.count(); ++i) {
254 const QPoint& pos = m_positions.at(i);
255 Letter* letter = m_board->cell(pos.x(), pos.y())->letter();
256 QChar c = letter->character();
257 chars.append(c);
258 if (letter->isMovable()) {
259 movable.append(c);
260 filter.append('.');
261 } else {
262 filter.append(c);
263 }
264 }
265 std::sort(chars.begin(), chars.end());
266 std::sort(movable.begin(), movable.end());
267 QStringList filtered = words->filter(filter);
268
269 // Find valid solutions
270 for (const QString& valid : filtered) {
271 QString sorted = valid;
272 std::sort(sorted.begin(), sorted.end());
273 if (sorted == chars && !m_solutions.contains(valid)) {
274 m_solutions.append(valid);
275 }
276 }
277
278 // Find permutation
279 QString permuted;
280 if (m_solutions.count() < countPermutations(movable)) {
281 do {
282 std::shuffle(movable.begin(), movable.end(), m_random);
283 permuted = movable;
284 for (int i = 0, end = filter.length(); i < end; ++i) {
285 if (filter.at(i) == '.') {
286 continue;
287 }
288 permuted.insert(i, filter.at(i));
289 }
290 } while (m_solutions.contains(permuted));
291 } else {
292 permuted = m_solutions.at(0);
293 }
294 fromString(permuted);
295 }
296