summary refs log tree commit diff stats
path: root/Source/PuzzleSerializer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Source/PuzzleSerializer.cpp')
-rw-r--r--Source/PuzzleSerializer.cpp461
1 files changed, 461 insertions, 0 deletions
diff --git a/Source/PuzzleSerializer.cpp b/Source/PuzzleSerializer.cpp new file mode 100644 index 0000000..c1e93a5 --- /dev/null +++ b/Source/PuzzleSerializer.cpp
@@ -0,0 +1,461 @@
1#include "PuzzleSerializer.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->ReadEntityData<int>(id, GRID_SIZE_X, 1)[0] - 1;
11 int height = 2 * _memory->ReadEntityData<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 int numIntersections = _memory->ReadEntityData<int>(id, NUM_DOTS, 1)[0];
15 _intersectionFlags = _memory->ReadArray<int>(id, DOT_FLAGS, numIntersections);
16 int numConnections = _memory->ReadEntityData<int>(id, NUM_CONNECTIONS, 1)[0];
17 _connectionsA = _memory->ReadArray<int>(id, DOT_CONNECTION_A, numConnections);
18 _connectionsB = _memory->ReadArray<int>(id, DOT_CONNECTION_B, numConnections);
19 _intersectionLocations = _memory->ReadArray<float>(id, DOT_POSITIONS, numIntersections*2);
20
21 Puzzle p;
22 p.NewGrid(width, height);
23 ReadIntersections(p);
24 ReadExtras(p);
25 ReadDecorations(p, id);
26 ReadSequence(p, id);
27 return p;
28}
29
30void PuzzleSerializer::WritePuzzle(const Puzzle& p, int id) {
31 _intersectionFlags.clear();
32 _connectionsA.clear();
33 _connectionsB.clear();
34 _intersectionLocations.clear();
35
36 MIN = 0.1f;
37 MAX = 0.9f;
38 WIDTH_INTERVAL = (MAX - MIN) / (p.width/2);
39 HEIGHT_INTERVAL = (MAX - MIN) / (p.height/2);
40 HORIZ_GAP_SIZE = WIDTH_INTERVAL / 2;
41 VERTI_GAP_SIZE = HEIGHT_INTERVAL / 2;
42
43 WriteIntersections(p);
44 WriteDots(p);
45 WriteGaps(p);
46 WriteEndpoints(p);
47 WriteDecorations(p, id);
48 WriteSequence(p, id);
49
50 _memory->WriteEntityData<int>(id, GRID_SIZE_X, {(p.width + 1)/2});
51 _memory->WriteEntityData<int>(id, GRID_SIZE_Y, {(p.height + 1)/2});
52 _memory->WriteEntityData<int>(id, NUM_DOTS, {static_cast<int>(_intersectionFlags.size())});
53 _memory->WriteArray<float>(id, DOT_POSITIONS, _intersectionLocations);
54 _memory->WriteArray<int>(id, DOT_FLAGS, _intersectionFlags);
55 _memory->WriteEntityData<int>(id, NUM_CONNECTIONS, {static_cast<int>(_connectionsA.size())});
56 _memory->WriteArray<int>(id, DOT_CONNECTION_A, _connectionsA);
57 _memory->WriteArray<int>(id, DOT_CONNECTION_B, _connectionsB);
58 _memory->WriteEntityData<int>(id, NEEDS_REDRAW, {1});
59}
60
61void PuzzleSerializer::ReadIntersections(Puzzle& p) {
62 // @Cleanup: Change defaults?
63 for (int x=0; x<p.width; x++) {
64 for (int y=0; y<p.height; y++) {
65 if (x%2 == y%2) continue;
66 p.grid[x][y].gap = Cell::Gap::FULL;
67 }
68 }
69
70 for (int j=0; j<_intersectionFlags.size(); j++) {
71 if (_intersectionFlags[_connectionsA[j]] & Flags::IS_ENDPOINT) break;
72 if (_intersectionFlags[_connectionsB[j]] & Flags::IS_ENDPOINT) break;
73 float x1 = _intersectionLocations[2*_connectionsA[j]];
74 float y1 = _intersectionLocations[2*_connectionsA[j]+1];
75 float x2 = _intersectionLocations[2*_connectionsB[j]];
76 float y2 = _intersectionLocations[2*_connectionsB[j]+1];
77 auto [x, y] = loc_to_xy(p, _connectionsA[j]);
78
79 if (x1 < x2) x++;
80 else if (x1 > x2) x--;
81 else if (y1 < y2) y--;
82 else if (y1 > y2) y++;
83 p.grid[x][y].gap = Cell::Gap::NONE;
84 }
85}
86
87void PuzzleSerializer::ReadExtras(Puzzle& p) {
88 // This iterates bottom-top, left-right
89 int i = 0;
90 for (; i < _intersectionFlags.size(); i++) {
91 int flags = _intersectionFlags[i];
92 auto [x, y] = loc_to_xy(p, i);
93 if (y < 0) break; // This is the expected exit point
94 if (flags & Flags::IS_STARTPOINT) {
95 p.grid[x][y].start = true;
96 }
97 p.grid[x][y].dot = FlagsToDot(flags);
98 if (flags & Flags::HAS_NO_CONN) {
99 p.grid[x][y].gap = Cell::Gap::FULL;
100 }
101 }
102
103 // Iterate the remaining intersections (endpoints, dots, gaps)
104 for (; i < _intersectionFlags.size(); i++) {
105 int location = FindConnection(i);
106 if (location == -1) continue; // @Error: Unable to find connection point
107 // (x1, y1) location of this intersection
108 // (x2, y2) location of the connected intersection
109 float x1 = _intersectionLocations[2*i];
110 float y1 = _intersectionLocations[2*i+1];
111 float x2 = _intersectionLocations[2*location];
112 float y2 = _intersectionLocations[2*location+1];
113 auto [x, y] = loc_to_xy(p, location);
114
115 if (_intersectionFlags[i] & Flags::IS_ENDPOINT) {
116 // Our x coordinate is less than the target's
117 if (x1 < x2) p.grid[x][y].end = Cell::Dir::LEFT;
118 else if (x1 > x2) p.grid[x][y].end = Cell::Dir::RIGHT;
119 // Note that Y coordinates are reversed: 0.0 (bottom) 1.0 (top)
120 else if (y1 < y2) p.grid[x][y].end = Cell::Dir::DOWN;
121 else if (y1 > y2) p.grid[x][y].end = Cell::Dir::UP;
122 } else if (_intersectionFlags[i] & Flags::HAS_DOT) {
123 if (x1 < x2) x--;
124 else if (x1 > x2) x++;
125 else if (y1 < y2) y++;
126 else if (y1 > y2) y--;
127 p.grid[x][y].dot = FlagsToDot(_intersectionFlags[i]);
128 } else if (_intersectionFlags[i] & Flags::HAS_ONE_CONN) {
129 if (x1 < x2) x--;
130 else if (x1 > x2) x++;
131 else if (y1 < y2) y++;
132 else if (y1 > y2) y--;
133 p.grid[x][y].gap = Cell::Gap::BREAK;
134 }
135 }
136}
137
138void PuzzleSerializer::ReadDecorations(Puzzle& p, int id) {
139 int numDecorations = _memory->ReadEntityData<int>(id, NUM_DECORATIONS, 1)[0];
140 std::vector<int> decorations = _memory->ReadArray<int>(id, DECORATIONS, numDecorations);
141 if (numDecorations > 0) p.hasDecorations = true;
142
143 for (int i=0; i<numDecorations; i++) {
144 auto [x, y] = dloc_to_xy(p, i);
145 auto d = std::make_shared<Decoration>();
146 p.grid[x][y].decoration = d;
147 d->type = static_cast<Type>(decorations[i] & 0xFF00);
148 switch(d->type) {
149 case Type::Poly:
150 case Type::RPoly:
151 case Type::Ylop:
152 d->polyshape = decorations[i] & 0xFFFF0000;
153 break;
154 case Type::Triangle:
155 d->count = decorations[i] & 0x000F0000;
156 break;
157 }
158 d->color = static_cast<Color>(decorations[i] & 0xF);
159 }
160}
161
162void PuzzleSerializer::ReadSequence(Puzzle& p, int id) {
163 int sequenceLength = _memory->ReadEntityData<int>(id, SEQUENCE_LEN, 1)[0];
164 std::vector<int> sequence = _memory->ReadArray<int>(id, SEQUENCE, sequenceLength);
165
166 for (int location : sequence) {
167 auto [x, y] = loc_to_xy(p, location);
168 p.sequence.emplace_back(Pos{x, y});
169 }
170}
171
172void PuzzleSerializer::WriteIntersections(const Puzzle& p) {
173 // @Cleanup: If I write directly to locations, then I can simplify this gross loop iterator.
174 // int numIntersections = (p.width / 2 + 1) * (p.height / 2 + 1);
175 // Grided intersections
176 for (int y=p.height-1; y>=0; y-=2) {
177 for (int x=0; x<p.width; x+=2) {
178 auto [xPos, yPos] = xy_to_pos(p, x, y);
179 _intersectionLocations.push_back(xPos);
180 _intersectionLocations.push_back(yPos);
181 int flags = 0;
182 if (p.grid[x][y].start) {
183 flags |= Flags::IS_STARTPOINT;
184 }
185 switch (p.grid[x][y].dot) {
186 case Cell::Dot::BLACK:
187 flags |= Flags::HAS_DOT;
188 break;
189 case Cell::Dot::BLUE:
190 flags |= Flags::HAS_DOT | Flags::DOT_IS_BLUE;
191 break;
192 case Cell::Dot::YELLOW:
193 flags |= Flags::HAS_DOT | Flags::DOT_IS_ORANGE;
194 break;
195 case Cell::Dot::INVISIBLE:
196 flags |= Flags::HAS_DOT | Flags::DOT_IS_INVISIBLE;
197 break;
198 }
199
200 int numConnections = 0;
201 if (p.grid[x][y].end != Cell::Dir::NONE) numConnections++;
202 // Create connections for this intersection for top/left only.
203 // Top connection
204 if (y > 0 && p.grid[x][y-1].gap != Cell::Gap::FULL) {
205 _connectionsA.push_back(xy_to_loc(p, x, y-2));
206 _connectionsB.push_back(xy_to_loc(p, x, y));
207 flags |= Flags::HAS_VERTI_CONN;
208 numConnections++;
209 }
210 // Bottom connection
211 if (y < p.height - 1 && p.grid[x][y+1].gap != Cell::Gap::FULL) {
212 flags |= Flags::HAS_VERTI_CONN;
213 numConnections++;
214 }
215 // Left connection
216 if (x > 0 && p.grid[x-1][y].gap != Cell::Gap::FULL) {
217 _connectionsA.push_back(xy_to_loc(p, x-2, y));
218 _connectionsB.push_back(xy_to_loc(p, x, y));
219 flags |= Flags::HAS_HORIZ_CONN;
220 numConnections++;
221 }
222 // Right connection
223 if (x < p.width - 1 && p.grid[x+1][y].gap != Cell::Gap::FULL) {
224 flags |= Flags::HAS_HORIZ_CONN;
225 numConnections++;
226 }
227 if (numConnections == 0) flags |= HAS_NO_CONN;
228 if (numConnections == 1) flags |= HAS_ONE_CONN;
229
230 _intersectionFlags.push_back(flags);
231 }
232 }
233}
234
235void PuzzleSerializer::WriteEndpoints(const Puzzle& p) {
236 for (int x=0; x<p.width; x++) {
237 for (int y=0; y<p.height; y++) {
238 if (p.grid[x][y].end == Cell::Dir::NONE) continue;
239 _connectionsA.push_back(xy_to_loc(p, x, y));
240 _connectionsB.push_back(static_cast<int>(_intersectionFlags.size()));
241
242 auto [xPos, yPos] = xy_to_pos(p, x, y);
243 switch (p.grid[x][y].end) {
244 case Cell::Dir::LEFT:
245 xPos -= .05f;
246 break;
247 case Cell::Dir::RIGHT:
248 xPos += .05f;
249 break;
250 case Cell::Dir::UP:
251 yPos += .05f; // Y position goes from 0 (bottom) to 1 (top), so this is reversed.
252 break;
253 case Cell::Dir::DOWN:
254 yPos -= .05f;
255 break;
256 }
257 _endpointLocations.emplace_back(x, y, static_cast<int>(_intersectionFlags.size()));
258 _intersectionLocations.push_back(xPos);
259 _intersectionLocations.push_back(yPos);
260 _intersectionFlags.push_back(Flags::IS_ENDPOINT);
261 }
262 }
263}
264
265void PuzzleSerializer::WriteDots(const Puzzle& p) {
266 for (int x=0; x<p.width; x++) {
267 for (int y=0; y<p.height; y++) {
268 if (x%2 == y%2) continue; // Cells are invalid, intersections are already handled.
269 if (p.grid[x][y].dot == Cell::Dot::NONE) continue;
270
271 // We need to introduce a new segment which contains this dot. Break the existing segment, and add one.
272 int connectionLocation = -1;
273 for (int i=0; i<_connectionsA.size(); i++) {
274 auto [x1, y1] = loc_to_xy(p, _connectionsA[i]);
275 auto [x2, y2] = loc_to_xy(p, _connectionsB[i]);
276 if ((x1+1 == x && x2-1 == x && y1 == y && y2 == y) ||
277 (y1+1 == y && y2-1 == y && x1 == x && x2 == x)) {
278 connectionLocation = i;
279 break;
280 }
281 }
282 if (connectionLocation == -1) continue; // @Error
283
284 // @Assume: B > A for connections. To remove, add the horiz/verti check, see gaps.
285 int other_connection = _connectionsB[connectionLocation];
286 _connectionsB[connectionLocation] = static_cast<int>(_intersectionFlags.size());
287 _connectionsA.push_back(other_connection);
288 _connectionsB.push_back(static_cast<int>(_intersectionFlags.size()));
289
290 // Add this dot to the end
291 auto [xPos, yPos] = xy_to_pos(p, x, y);
292 _intersectionLocations.push_back(xPos);
293 _intersectionLocations.push_back(yPos);
294
295 int flags = Flags::HAS_DOT;
296 switch (p.grid[x][y].dot) {
297 case Cell::Dot::BLACK:
298 break;
299 case Cell::Dot::BLUE:
300 flags |= DOT_IS_BLUE;
301 break;
302 case Cell::Dot::YELLOW:
303 flags |= DOT_IS_ORANGE;
304 break;
305 case Cell::Dot::INVISIBLE:
306 flags |= DOT_IS_INVISIBLE;
307 break;
308 }
309 _intersectionFlags.push_back(flags);
310 }
311 }
312}
313
314void PuzzleSerializer::WriteGaps(const Puzzle& p) {
315 for (int x=0; x<p.width; x++) {
316 for (int y=0; y<p.height; y++) {
317 if (x%2 == y%2) continue; // Cells are invalid, intersections are already handled.
318 if (p.grid[x][y].gap != Cell::Gap::BREAK) continue;
319
320 // We need to introduce a new segment which contains this dot. Break the existing segment, and add one.
321 int connectionLocation = -1;
322 for (int i=0; i<_connectionsA.size(); i++) {
323 auto [x1, y1] = loc_to_xy(p, _connectionsA[i]);
324 auto [x2, y2] = loc_to_xy(p, _connectionsB[i]);
325 if ((x1+1 == x && x2-1 == x && y1 == y && y2 == y) ||
326 (y1+1 == y && y2-1 == y && x1 == x && x2 == x)) {
327 connectionLocation = i;
328 break;
329 }
330 }
331 if (connectionLocation == -1) continue; // @Error
332
333 auto [xPos, yPos] = xy_to_pos(p, x, y);
334 // Reminder: Y goes from 0.0 (bottom) to 1.0 (top)
335 if (x%2 == 0) { // Vertical gap
336 _connectionsA[connectionLocation] = xy_to_loc(p, x, y-1);
337 _connectionsB[connectionLocation] = static_cast<int>(_intersectionFlags.size());
338 _intersectionLocations.push_back(xPos);
339 _intersectionLocations.push_back(yPos + VERTI_GAP_SIZE / 2);
340 _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_VERTI_CONN);
341
342 _connectionsA.push_back(xy_to_loc(p, x, y+1));
343 _connectionsB.push_back(static_cast<int>(_intersectionFlags.size()));
344 _intersectionLocations.push_back(xPos);
345 _intersectionLocations.push_back(yPos - VERTI_GAP_SIZE / 2);
346 _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_VERTI_CONN);
347 } else if (y%2 == 0) { // Horizontal gap
348 _connectionsA[connectionLocation] = xy_to_loc(p, x-1, y);
349 _connectionsB[connectionLocation] = static_cast<int>(_intersectionFlags.size());
350 _intersectionLocations.push_back(xPos - HORIZ_GAP_SIZE / 2);
351 _intersectionLocations.push_back(yPos);
352 _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_HORIZ_CONN);
353
354 _connectionsA.push_back(xy_to_loc(p, x+1, y));
355 _connectionsB.push_back(static_cast<int>(_intersectionFlags.size()));
356 _intersectionLocations.push_back(xPos + HORIZ_GAP_SIZE / 2);
357 _intersectionLocations.push_back(yPos);
358 _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_HORIZ_CONN);
359 }
360 }
361 }
362}
363
364void PuzzleSerializer::WriteDecorations(const Puzzle& p, int id) {
365 if (!p.hasDecorations) return;
366
367 std::vector<int> decorations;
368 for (int y=p.height-2; y>0; y-=2) {
369 for (int x=1; x<p.width-1; x+=2) {
370 auto d = p.grid[x][y].decoration;
371 if (d) {
372 decorations.push_back(d->color | d->type | d->count | d->polyshape);
373 } else {
374 decorations.push_back(0);
375 }
376 }
377 }
378
379 _memory->WriteEntityData<int>(id, NUM_DECORATIONS, {static_cast<int>(decorations.size())});
380 _memory->WriteArray<int>(id, DECORATIONS, decorations);
381}
382
383void PuzzleSerializer::WriteSequence(const Puzzle& p, int id) {
384 if (p.sequence.size() == 0) return;
385
386 std::vector<int> sequence;
387 for (Pos pos : p.sequence) {
388 // Only include intersections, the game does not treat segments as real objects
389 if (pos.x%2 == 0 && pos.y%2 == 0) {
390 sequence.emplace_back(xy_to_loc(p, pos.x, pos.y));
391 }
392 }
393
394 Pos endpoint = p.sequence[p.sequence.size() - 1];
395 for (auto [x, y, location] : _endpointLocations) {
396 if (x == endpoint.x && y == endpoint.y) {
397 sequence.emplace_back(location);
398 break;
399 }
400 }
401
402 _memory->WriteEntityData<int>(id, SEQUENCE_LEN, {static_cast<int>(sequence.size())});
403 _memory->WriteNewArray<int>(id, SEQUENCE, sequence);
404}
405
406std::tuple<int, int> PuzzleSerializer::loc_to_xy(const Puzzle& p, int location) const {
407 int height2 = (p.height - 1) / 2;
408 int width2 = (p.width + 1) / 2;
409
410 int x = 2 * (location % width2);
411 int y = 2 * (height2 - location / width2);
412 return {x, y};
413}
414
415int PuzzleSerializer::xy_to_loc(const Puzzle& p, int x, int y) const {
416 int height2 = (p.height - 1) / 2;
417 int width2 = (p.width + 1) / 2;
418
419 int rowsFromBottom = height2 - y/2;
420 return rowsFromBottom * width2 + x/2;
421}
422
423std::tuple<int, int> PuzzleSerializer::dloc_to_xy(const Puzzle& p, int location) const {
424 int height2 = (p.height - 3) / 2;
425 int width2 = (p.width - 1) / 2;
426
427 int x = 2 * (location % width2) + 1;
428 int y = 2 * (height2 - location / width2) + 1;
429 return {x, y};
430}
431
432int PuzzleSerializer::xy_to_dloc(const Puzzle& p, int x, int y) const {
433 int height2 = (p.height - 3) / 2;
434 int width2 = (p.width - 1) / 2;
435
436 int rowsFromBottom = height2 - (y - 1)/2;
437 return rowsFromBottom * width2 + (x - 1)/2;
438}
439
440std::tuple<float, float> PuzzleSerializer::xy_to_pos(const Puzzle& p, int x, int y) const {
441 return {
442 MIN + (x/2.0f) * WIDTH_INTERVAL,
443 MAX - (y/2.0f) * HEIGHT_INTERVAL
444 };
445}
446
447Cell::Dot PuzzleSerializer::FlagsToDot(int flags) const {
448 if (!(flags & Flags::HAS_DOT)) return Cell::Dot::NONE;
449 if (flags & Flags::DOT_IS_BLUE) return Cell::Dot::BLUE;
450 else if (flags & Flags::DOT_IS_ORANGE) return Cell::Dot::YELLOW;
451 else if (flags & Flags::DOT_IS_INVISIBLE) return Cell::Dot::INVISIBLE;
452 else return Cell::Dot::BLACK;
453}
454
455int PuzzleSerializer::FindConnection(int location) const {
456 for (int j=0; j<_connectionsA.size(); j++) {
457 if (_connectionsA[j] == location) return _connectionsB[j];
458 if (_connectionsB[j] == location) return _connectionsA[j];
459 }
460 return -1;
461}