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