about summary refs log tree commit diff stats
path: root/Source/Puzzle.cpp
diff options
context:
space:
mode:
authorjbzdarkid <jbzdarkid@gmail.com>2019-11-09 13:39:10 -0800
committerjbzdarkid <jbzdarkid@gmail.com>2019-11-09 13:39:10 -0800
commit36be1ed32ac9a554f0b11fcc13b5699e717b81f2 (patch)
tree383618d781bc5b4701b31555f90b8a629fe6d205 /Source/Puzzle.cpp
parent413e1f0aaae961660781675158e38520126c11b6 (diff)
downloadwitness-tutorializer-36be1ed32ac9a554f0b11fcc13b5699e717b81f2.tar.gz
witness-tutorializer-36be1ed32ac9a554f0b11fcc13b5699e717b81f2.tar.bz2
witness-tutorializer-36be1ed32ac9a554f0b11fcc13b5699e717b81f2.zip
Functioning solver/validator (at least for mazes)
Diffstat (limited to 'Source/Puzzle.cpp')
-rw-r--r--Source/Puzzle.cpp402
1 files changed, 402 insertions, 0 deletions
diff --git a/Source/Puzzle.cpp b/Source/Puzzle.cpp new file mode 100644 index 0000000..ee4c2e8 --- /dev/null +++ b/Source/Puzzle.cpp
@@ -0,0 +1,402 @@
1#include "Puzzle.h"
2#include "Memory.h"
3
4#pragma warning (disable:26451)
5#pragma warning (disable:26812)
6
7PuzzleSerializer::PuzzleSerializer(const std::shared_ptr<Memory>& memory) : _memory(memory) {}
8
9Puzzle PuzzleSerializer::ReadPuzzle(int id) {
10 Puzzle p;
11 p.width = 2 * _memory->ReadPanelData<int>(id, GRID_SIZE_X, 1)[0] - 1;
12 p.height = 2 * _memory->ReadPanelData<int>(id, GRID_SIZE_Y, 1)[0] - 1;
13 if (p.width < 0 || p.height < 0) return p; // @Error: Grid size should be always positive? Looks like the starting panels break this rule, though.
14 p.grid.resize(p.width);
15 for (auto& row : p.grid) row.resize(p.height);
16
17 ReadIntersections(p, id);
18 ReadDecorations(p, id);
19
20 return p;
21}
22
23void PuzzleSerializer::ReadIntersections(Puzzle& p, int id) {
24 int numIntersections = _memory->ReadPanelData<int>(id, NUM_DOTS, 1)[0];
25 std::vector<int> intersectionFlags = _memory->ReadArray<int>(id, DOT_FLAGS, numIntersections);
26 int numConnections = _memory->ReadPanelData<int>(id, NUM_CONNECTIONS, 1)[0];
27 std::vector<int> connections_a = _memory->ReadArray<int>(id, DOT_CONNECTION_A, numConnections);
28 std::vector<int> connections_b = _memory->ReadArray<int>(id, DOT_CONNECTION_B, numConnections);
29 std::vector<float> intersectionLocations = _memory->ReadArray<float>(id, DOT_POSITIONS, numIntersections*2);
30
31 // @Cleanup: Change defaults?
32 for (int x=0; x<p.width; x++) {
33 for (int y=0; y<p.height; y++) {
34 if (x%2 == y%2) continue;
35 p.grid[x][y].gap = Cell::Gap::FULL;
36 }
37 }
38
39 for (int j=0; j<numIntersections; j++) {
40 if (intersectionFlags[connections_a[j]] & Flags::IS_ENDPOINT) break;
41 if (intersectionFlags[connections_b[j]] & Flags::IS_ENDPOINT) break;
42 float x1 = intersectionLocations[2*connections_a[j]];
43 float y1 = intersectionLocations[2*connections_a[j]+1];
44 float x2 = intersectionLocations[2*connections_b[j]];
45 float y2 = intersectionLocations[2*connections_b[j]+1];
46 auto [x, y] = loc_to_xy(p, connections_a[j]);
47
48 if (x1 < x2) x++;
49 else if (x1 > x2) x--;
50 else if (y1 < y2) y--;
51 else if (y1 > y2) y++;
52 p.grid[x][y].gap = Cell::Gap::NONE;
53 }
54
55 // This iterates bottom-top, left-right
56 int i = 0;
57 for (;; i++) {
58 int flags = intersectionFlags[i];
59 auto [x, y] = loc_to_xy(p, i);
60 if (y < 0) break;
61 if (flags & Flags::IS_STARTPOINT) {
62 p.grid[x][y].start = true;
63 }
64 p.grid[x][y].dot = FlagsToDot(flags);
65 if (flags & Flags::IS_FULL_GAP) {
66 p.grid[x][y].gap = Cell::Gap::FULL;
67 }
68 }
69
70 // Iterate the remaining intersections (endpoints, dots, gaps)
71 for (; i < numIntersections; i++) {
72 int location = FindConnection(i, connections_a, connections_b);
73 if (location == -1) continue; // @Error: Unable to find connection point
74 // (x1, y1) location of this intersection
75 // (x2, y2) location of the connected intersection
76 float x1 = intersectionLocations[2*i];
77 float y1 = intersectionLocations[2*i+1];
78 float x2 = intersectionLocations[2*location];
79 float y2 = intersectionLocations[2*location+1];
80 auto [x, y] = loc_to_xy(p, location);
81
82 if (intersectionFlags[i] & Flags::IS_ENDPOINT) {
83 // Our x coordinate is less than the target's
84 if (x1 < x2) p.grid[x][y].end = Cell::Dir::LEFT;
85 else if (x1 > x2) p.grid[x][y].end = Cell::Dir::RIGHT;
86 // Note that Y coordinates are reversed: 0.0 (bottom) 1.0 (top)
87 else if (y1 < y2) p.grid[x][y].end = Cell::Dir::DOWN;
88 else if (y1 > y2) p.grid[x][y].end = Cell::Dir::UP;
89 } else if (intersectionFlags[i] & Flags::HAS_DOT) {
90 if (x1 < x2) x--;
91 else if (x1 > x2) x++;
92 else if (y1 < y2) y++;
93 else if (y1 > y2) y--;
94 p.grid[x][y].dot = FlagsToDot(intersectionFlags[i]);
95 } else if (intersectionFlags[i] & Flags::HAS_ONE_CONN) {
96 if (x1 < x2) x--;
97 else if (x1 > x2) x++;
98 else if (y1 < y2) y++;
99 else if (y1 > y2) y--;
100 p.grid[x][y].gap = Cell::Gap::BREAK;
101 }
102 }
103}
104
105void PuzzleSerializer::ReadDecorations(Puzzle& p, int id) {
106 int numDecorations = _memory->ReadPanelData<int>(id, NUM_DECORATIONS, 1)[0];
107 std::vector<int> decorations = _memory->ReadArray<int>(id, DECORATIONS, numDecorations);
108 if (numDecorations > 0) p.hasDecorations = true;
109
110 for (int i=0; i<numDecorations; i++) {
111 auto [x, y] = dloc_to_xy(p, i);
112 auto d = std::make_shared<Decoration>();
113 p.grid[x][y].decoration = d;
114 d->type = static_cast<Type>(decorations[i] & 0xFF00);
115 switch(d->type) {
116 case Type::Poly:
117 case Type::RPoly:
118 case Type::Ylop:
119 d->polyshape = decorations[i] & 0xFFFF0000;
120 break;
121 case Type::Triangle:
122 d->count = decorations[i] & 0x000F0000;
123 break;
124 }
125 d->color = static_cast<Color>(decorations[i] & 0xF);
126 }
127}
128
129void PuzzleSerializer::WritePuzzle(const Puzzle& p, int id) {
130 _memory->WritePanelData<int>(id, GRID_SIZE_X, {(p.width + 1)/2});
131 _memory->WritePanelData<int>(id, GRID_SIZE_Y, {(p.height + 1)/2});
132
133 WriteIntersections(p, id);
134 if (p.hasDecorations) WriteDecorations(p, id);
135
136 _memory->WritePanelData<int>(id, NEEDS_REDRAW, {1});
137}
138
139void PuzzleSerializer::WriteIntersections(const Puzzle& p, int id) {
140 std::vector<float> intersectionLocations;
141 std::vector<int> intersectionFlags;
142 std::vector<int> connections_a;
143 std::vector<int> connections_b;
144
145 float min = 0.1f;
146 float max = 0.9f;
147 float width_interval = (max - min) / (p.width/2);
148 float height_interval = (max - min) / (p.height/2);
149 float horiz_gap_size = width_interval / 2;
150 float verti_gap_size = height_interval / 2;
151
152 // @Cleanup: If I write directly to locations, then I can simplify this gross loop iterator.
153 // int numIntersections = (p.width / 2 + 1) * (p.height / 2 + 1);
154 // Grided intersections
155 for (int y=p.height-1; y>=0; y-=2) {
156 for (int x=0; x<p.width; x+=2) {
157 intersectionLocations.push_back(min + (x/2) * width_interval);
158 intersectionLocations.push_back(max - (y/2) * height_interval);
159 int flags = 0;
160 if (p.grid[x][y].start) {
161 flags |= Flags::IS_STARTPOINT;
162 }
163 if (p.grid[x][y].gap == Cell::Gap::FULL) {
164 flags |= Flags::IS_FULL_GAP;
165 }
166 switch (p.grid[x][y].dot) {
167 case Cell::Dot::BLACK:
168 flags |= Flags::HAS_DOT;
169 break;
170 case Cell::Dot::BLUE:
171 flags |= Flags::HAS_DOT | Flags::DOT_IS_BLUE;
172 break;
173 case Cell::Dot::YELLOW:
174 flags |= Flags::HAS_DOT | Flags::DOT_IS_ORANGE;
175 break;
176 case Cell::Dot::INVISIBLE:
177 flags |= Flags::HAS_DOT | Flags::DOT_IS_INVISIBLE;
178 break;
179 }
180
181 int numConnections = 0;
182 if (p.grid[x][y].end != Cell::Dir::NONE) numConnections++;
183 // Create connections for this intersection for bottom/left only.
184 // Bottom connection
185 if (y > 0 && p.grid[x][y-1].gap == Cell::Gap::NONE) {
186 connections_a.push_back(xy_to_loc(p, x, y-2));
187 connections_b.push_back(xy_to_loc(p, x, y));
188 flags |= Flags::HAS_VERTI_CONN;
189 numConnections++;
190 }
191 // Top connection
192 if (y < p.height - 1 && p.grid[x][y+1].gap == Cell::Gap::NONE) {
193 flags |= Flags::HAS_VERTI_CONN;
194 numConnections++;
195 }
196 // Left connection
197 if (x > 0 && p.grid[x-1][y].gap == Cell::Gap::NONE) {
198 connections_a.push_back(xy_to_loc(p, x-2, y));
199 connections_b.push_back(xy_to_loc(p, x, y));
200 flags |= Flags::HAS_HORIZ_CONN;
201 numConnections++;
202 }
203 // Right connection
204 if (x < p.width - 1 && p.grid[x+1][y].gap == Cell::Gap::NONE) {
205 flags |= Flags::HAS_HORIZ_CONN;
206 numConnections++;
207 }
208 if (numConnections == 1) flags |= HAS_ONE_CONN;
209 intersectionFlags.push_back(flags);
210 }
211 }
212
213 // Endpoints
214 for (int x=0; x<p.width; x++) {
215 for (int y=0; y<p.height; y++) {
216 if (p.grid[x][y].end == Cell::Dir::NONE) continue;
217 connections_a.push_back(xy_to_loc(p, x, y)); // Target to connect to
218 connections_b.push_back(static_cast<int>(intersectionFlags.size())); // This endpoint
219
220 float xPos = min + (x/2) * width_interval;
221 float yPos = max - (y/2) * height_interval;
222 switch (p.grid[x][y].end) {
223 case Cell::Dir::LEFT:
224 xPos -= .05f;
225 break;
226 case Cell::Dir::RIGHT:
227 xPos += .05f;
228 break;
229 case Cell::Dir::UP:
230 yPos += .05f; // Y position goes from 0 (bottom) to 1 (top), so this is reversed.
231 break;
232 case Cell::Dir::DOWN:
233 yPos -= .05f;
234 break;
235 }
236 intersectionLocations.push_back(xPos);
237 intersectionLocations.push_back(yPos);
238 intersectionFlags.push_back(Flags::IS_ENDPOINT);
239 }
240 }
241
242 // Dots
243 for (int x=0; x<p.width; x++) {
244 for (int y=0; y<p.height; y++) {
245 if (x%2 == y%2) continue; // Cells are invalid, intersections are already handled.
246 if (p.grid[x][y].dot == Cell::Dot::NONE) continue;
247
248 // We need to introduce a new segment --
249 // Locate the segment we're breaking
250 for (int i=0; i<connections_a.size(); i++) {
251 auto [x1, y1] = loc_to_xy(p, connections_a[i]);
252 auto [x2, y2] = loc_to_xy(p, connections_b[i]);
253 if ((x1+1 == x && x2-1 == x && y1 == y && y2 == y) ||
254 (y1+1 == y && y2-1 == y && x1 == x && x2 == x)) {
255 int other_connection = connections_b[i];
256 connections_b[i] = static_cast<int>(intersectionFlags.size()); // This endpoint
257
258 connections_a.push_back(other_connection);
259 connections_b.push_back(static_cast<int>(intersectionFlags.size())); // This endpoint
260 break;
261 }
262 }
263 // Add this dot to the end
264 float xPos = min + (x/2.0f) * width_interval;
265 float yPos = max - (y/2.0f) * height_interval;
266 intersectionLocations.push_back(xPos);
267 intersectionLocations.push_back(yPos);
268
269 int flags = Flags::HAS_DOT;
270 switch (p.grid[x][y].dot) {
271 case Cell::Dot::BLACK:
272 break;
273 case Cell::Dot::BLUE:
274 flags |= DOT_IS_BLUE;
275 break;
276 case Cell::Dot::YELLOW:
277 flags |= DOT_IS_ORANGE;
278 break;
279 case Cell::Dot::INVISIBLE:
280 flags |= DOT_IS_INVISIBLE;
281 break;
282 }
283 intersectionFlags.push_back(flags);
284 }
285 }
286
287 // Gaps
288 for (int x=0; x<p.width; x++) {
289 for (int y=0; y<p.height; y++) {
290 if (x%2 == y%2) continue; // Cells are invalid, intersections are already handled.
291 if (p.grid[x][y].gap != Cell::Gap::BREAK) continue;
292
293 float xPos = min + (x/2.0f) * width_interval;
294 float yPos = max - (y/2.0f) * height_interval;
295 // Reminder: Y goes from 0.0 (bottom) to 1.0 (top)
296 if (x%2 == 0) { // Vertical gap
297 connections_a.push_back(xy_to_loc(p, x, y-1));
298 connections_b.push_back(static_cast<int>(intersectionFlags.size())); // This endpoint
299 intersectionLocations.push_back(xPos);
300 intersectionLocations.push_back(yPos + verti_gap_size / 2);
301 intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_VERTI_CONN);
302
303 connections_a.push_back(xy_to_loc(p, x, y+1));
304 connections_b.push_back(static_cast<int>(intersectionFlags.size())); // This endpoint
305 intersectionLocations.push_back(xPos);
306 intersectionLocations.push_back(yPos - verti_gap_size / 2);
307 intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_VERTI_CONN);
308 } else if (y%2 == 0) { // Horizontal gap
309 connections_a.push_back(xy_to_loc(p, x-1, y));
310 connections_b.push_back(static_cast<int>(intersectionFlags.size())); // This endpoint
311 intersectionLocations.push_back(xPos - horiz_gap_size / 2);
312 intersectionLocations.push_back(yPos);
313 intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_HORIZ_CONN);
314
315 connections_a.push_back(xy_to_loc(p, x+1, y));
316 connections_b.push_back(static_cast<int>(intersectionFlags.size())); // This endpoint
317 intersectionLocations.push_back(xPos + horiz_gap_size / 2);
318 intersectionLocations.push_back(yPos);
319 intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_HORIZ_CONN);
320 }
321 }
322 }
323
324 _memory->WritePanelData<int>(id, NUM_DOTS, {static_cast<int>(intersectionFlags.size())});
325 _memory->WriteArray<float>(id, DOT_POSITIONS, intersectionLocations);
326 _memory->WriteArray<int>(id, DOT_FLAGS, intersectionFlags);
327 _memory->WritePanelData<int>(id, NUM_CONNECTIONS, {static_cast<int>(connections_a.size())});
328 _memory->WriteArray<int>(id, DOT_CONNECTION_A, connections_a);
329 _memory->WriteArray<int>(id, DOT_CONNECTION_B, connections_b);
330}
331
332void PuzzleSerializer::WriteDecorations(const Puzzle& p, int id) {
333 std::vector<int> decorations;
334 for (int y=p.height-2; y>0; y-=2) {
335 for (int x=1; x<p.width-1; x+=2) {
336 auto d = p.grid[x][y].decoration;
337 if (d) {
338 decorations.push_back(d->color | d->type | d->count | d->polyshape);
339 } else {
340 decorations.push_back(0);
341 }
342 }
343 }
344
345 _memory->WritePanelData<int>(id, NUM_DECORATIONS, {static_cast<int>(decorations.size())});
346 _memory->WriteArray<int>(id, DECORATIONS, decorations);
347}
348
349std::tuple<int, int> PuzzleSerializer::loc_to_xy(const Puzzle& p, int location) const {
350 int height2 = (p.height - 1) / 2;
351 int width2 = (p.width + 1) / 2;
352
353 int x = 2 * (location % width2);
354 int y = 2 * (height2 - location / width2);
355 return {x, y};
356}
357
358int PuzzleSerializer::xy_to_loc(const Puzzle& p, int x, int y) const {
359 int height2 = (p.height - 1) / 2;
360 int width2 = (p.width + 1) / 2;
361
362 int rowsFromBottom = height2 - y/2;
363 return rowsFromBottom * width2 + x/2;
364}
365
366std::tuple<int, int> PuzzleSerializer::dloc_to_xy(const Puzzle& p, int location) const {
367 int height2 = (p.height - 3) / 2;
368 int width2 = (p.width - 1) / 2;
369
370 int x = 2 * (location % width2) + 1;
371 int y = 2 * (height2 - location / width2) + 1;
372 return {x, y};
373}
374
375int PuzzleSerializer::xy_to_dloc(const Puzzle& p, int x, int y) const {
376 int height2 = (p.height - 3) / 2;
377 int width2 = (p.width - 1) / 2;
378
379 int rowsFromBottom = height2 - (y - 1)/2;
380 return rowsFromBottom * width2 + (x - 1)/2;
381}
382
383Cell::Dot PuzzleSerializer::FlagsToDot(int flags) const {
384 if (!(flags & Flags::HAS_DOT)) return Cell::Dot::NONE;
385 if (flags & Flags::DOT_IS_BLUE) {
386 return Cell::Dot::BLUE;
387 } else if (flags & Flags::DOT_IS_ORANGE) {
388 return Cell::Dot::YELLOW;
389 } else if (flags & Flags::DOT_IS_INVISIBLE) {
390 return Cell::Dot::INVISIBLE;
391 } else {
392 return Cell::Dot::BLACK;
393 }
394}
395
396int PuzzleSerializer::FindConnection(int i, const std::vector<int>& connections_a, const std::vector<int>& connections_b) const {
397 for (int j=0; j<connections_a.size(); j++) {
398 if (connections_a[j] == i) return connections_b[j];
399 if (connections_b[j] == i) return connections_a[j];
400 }
401 return -1;
402}