summary refs log tree commit diff stats
path: root/Source
diff options
context:
space:
mode:
Diffstat (limited to 'Source')
-rw-r--r--Source/Puzzle.cpp412
-rw-r--r--Source/Puzzle.h76
-rw-r--r--Source/PuzzlerSerializer.cpp400
-rw-r--r--Source/PuzzlerSerializer.h45
-rw-r--r--Source/Source.vcxproj2
5 files changed, 478 insertions, 457 deletions
diff --git a/Source/Puzzle.cpp b/Source/Puzzle.cpp index cc552d3..72af129 100644 --- a/Source/Puzzle.cpp +++ b/Source/Puzzle.cpp
@@ -1,400 +1,40 @@
1#include "Puzzle.h" 1#include "Puzzle.h"
2#include "Memory.h" 2#include "Memory.h"
3#include <cassert>
3 4
4#pragma warning (disable:26451)
5#pragma warning (disable:26812)
6 5
7PuzzleSerializer::PuzzleSerializer(const std::shared_ptr<Memory>& memory) : _memory(memory) {} 6inline Cell Puzzle::GetCell(int x, int y) const {
8 7 x = Mod(x);
9Puzzle PuzzleSerializer::ReadPuzzle(int id) { 8 if (!SafeCell(x, y)) return Cell::Undefined();
10 int width = 2 * _memory->ReadPanelData<int>(id, GRID_SIZE_X, 1)[0] - 1; 9 return grid[x][y];
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} 10}
126 11
127void PuzzleSerializer::WritePuzzle(const Puzzle& p, int id) { 12inline Cell::Color Puzzle::GetLine(int x, int y) const {
128 _memory->WritePanelData<int>(id, GRID_SIZE_X, {(p.width + 1)/2}); 13 return grid[x][y].color;
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} 14}
136 15
137void PuzzleSerializer::WriteIntersections(const Puzzle& p, int id) { 16inline void Puzzle::NewGrid(int newWidth, int newHeight) {
138 std::vector<float> intersectionLocations; 17 if (newWidth == 0) {
139 std::vector<int> intersectionFlags; 18 assert(false);
140 std::vector<int> connections_a; 19 newWidth = width;
141 std::vector<int> connections_b; 20 newHeight = height;
142 21 } else {
143 float min = 0.1f; 22 // @Cleanup! This should be in the ctor...
144 float max = 0.9f; 23 width = 2*newWidth + 1;
145 float width_interval = (max - min) / (p.width/2); 24 height = 2*newHeight + 1;
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 } 25 }
239 26 grid.clear();
240 // Dots 27 grid.resize(width);
241 for (int x=0; x<p.width; x++) { 28 for (int x=0; x<width; x++) grid[x].resize(height);
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} 29}
355 30
356int PuzzleSerializer::xy_to_loc(const Puzzle& p, int x, int y) const { 31inline int Puzzle::Mod(int x) const {
357 int height2 = (p.height - 1) / 2; 32 if (!pillar) return x;
358 int width2 = (p.width + 1) / 2; 33 return (x + width * height * 2) % width;
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} 34}
393 35
394int PuzzleSerializer::FindConnection(int i, const std::vector<int>& connections_a, const std::vector<int>& connections_b) const { 36inline bool Puzzle::SafeCell(int x, int y) const {
395 for (int j=0; j<connections_a.size(); j++) { 37 if (x < 0 || x >= width) return false;
396 if (connections_a[j] == i) return connections_b[j]; 38 if (y < 0 || y >= height) return false;
397 if (connections_b[j] == i) return connections_a[j]; 39 return true;
398 }
399 return -1;
400} 40}
diff --git a/Source/Puzzle.h b/Source/Puzzle.h index 94cb4b0..3a8e73b 100644 --- a/Source/Puzzle.h +++ b/Source/Puzzle.h
@@ -62,7 +62,6 @@ struct Cell {
62struct Negation {}; 62struct Negation {};
63struct Pos {int x; int y;}; 63struct Pos {int x; int y;};
64 64
65#include <cassert> // TODO: Move this + impl to cpp
66class Puzzle { 65class Puzzle {
67public: 66public:
68 int16_t height; 67 int16_t height;
@@ -77,28 +76,9 @@ public:
77 std::vector<Negation> negations; 76 std::vector<Negation> negations;
78 std::vector<Pos> invalidElements; 77 std::vector<Pos> invalidElements;
79 78
80 inline Cell GetCell(int x, int y) const { 79 inline Cell GetCell(int x, int y) const;
81 x = Mod(x); 80 inline Cell::Color GetLine(int x, int y) const;
82 if (!SafeCell(x, y)) return Cell::Undefined(); 81 inline void NewGrid(int newWidth, int newHeight);
83 return grid[x][y];
84 }
85 inline Cell::Color GetLine(int x, int y) const {
86 return grid[x][y].color;
87 }
88 inline void NewGrid(int newWidth, int newHeight) {
89 if (newWidth == 0) {
90 assert(false);
91 newWidth = width;
92 newHeight = height;
93 } else {
94 // @Cleanup! This should be in the ctor...
95 width = 2*newWidth + 1;
96 height = 2*newHeight + 1;
97 }
98 grid.clear();
99 grid.resize(width);
100 for (int x=0; x<width; x++) grid[x].resize(height);
101 }
102 82
103 // @TODO: 83 // @TODO:
104 Pos GetSymmetricalPos(int x, int y); 84 Pos GetSymmetricalPos(int x, int y);
@@ -107,52 +87,6 @@ public:
107 std::vector<std::vector<Cell>> grid; 87 std::vector<std::vector<Cell>> grid;
108 88
109private: 89private:
110 inline int Mod(int x) const { 90 inline int Mod(int x) const;
111 if (!pillar) return x; 91 inline bool SafeCell(int x, int y) const;
112 return (x + width * height * 2) % width;
113 }
114
115 inline bool SafeCell(int x, int y) const {
116 if (x < 0 || x >= width) return false;
117 if (y < 0 || y >= height) return false;
118 return true;
119 }
120};
121
122class PuzzleSerializer {
123public:
124 PuzzleSerializer(const std::shared_ptr<Memory>& memory);
125 Puzzle ReadPuzzle(int id);
126 void WritePuzzle(const Puzzle& p, int id);
127
128private:
129 // @Bug: Blue and orange are swapped?
130 enum Flags {
131 IS_ENDPOINT = 0x1,
132 IS_STARTPOINT = 0x2,
133 IS_FULL_GAP = 0x8,
134 HAS_DOT = 0x20,
135 DOT_IS_BLUE = 0x100,
136 DOT_IS_ORANGE = 0x200,
137 DOT_IS_INVISIBLE = 0x1000,
138 HAS_ONE_CONN = 0x100000,
139 HAS_VERTI_CONN = 0x200000,
140 HAS_HORIZ_CONN = 0x400000,
141 };
142
143 void ReadIntersections(Puzzle& p, int id);
144 void ReadDecorations(Puzzle& p, int id);
145 void WriteIntersections(const Puzzle& p, int id);
146 void WriteDecorations(const Puzzle& p, int id);
147
148 std::tuple<int, int> loc_to_xy(const Puzzle& p, int location) const;
149 int xy_to_loc(const Puzzle& p, int x, int y) const;
150 // Decoration location
151 std::tuple<int, int> dloc_to_xy(const Puzzle& p, int location) const;
152 int xy_to_dloc(const Puzzle& p, int x, int y) const;
153 Cell::Dot FlagsToDot(int flags) const;
154 // Iterate connection lists for another location which is connected to us; return that other location.
155 int FindConnection(int i, const std::vector<int>& connections_a, const std::vector<int>& connections_b) const;
156
157 std::shared_ptr<Memory> _memory;
158}; 92};
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}
diff --git a/Source/PuzzlerSerializer.h b/Source/PuzzlerSerializer.h new file mode 100644 index 0000000..00c89f5 --- /dev/null +++ b/Source/PuzzlerSerializer.h
@@ -0,0 +1,45 @@
1#pragma once
2#include <memory>
3
4#include "Puzzle.h"
5
6
7class Memory;
8
9class PuzzleSerializer {
10public:
11 PuzzleSerializer(const std::shared_ptr<Memory>& memory);
12 Puzzle ReadPuzzle(int id);
13 void WritePuzzle(const Puzzle& p, int id);
14
15private:
16 // @Bug: Blue and orange are swapped?
17 enum Flags {
18 IS_ENDPOINT = 0x1,
19 IS_STARTPOINT = 0x2,
20 IS_FULL_GAP = 0x8,
21 HAS_DOT = 0x20,
22 DOT_IS_BLUE = 0x100,
23 DOT_IS_ORANGE = 0x200,
24 DOT_IS_INVISIBLE = 0x1000,
25 HAS_ONE_CONN = 0x100000,
26 HAS_VERTI_CONN = 0x200000,
27 HAS_HORIZ_CONN = 0x400000,
28 };
29
30 void ReadIntersections(Puzzle& p, int id);
31 void ReadDecorations(Puzzle& p, int id);
32 void WriteIntersections(const Puzzle& p, int id);
33 void WriteDecorations(const Puzzle& p, int id);
34
35 std::tuple<int, int> loc_to_xy(const Puzzle& p, int location) const;
36 int xy_to_loc(const Puzzle& p, int x, int y) const;
37 // Decoration location
38 std::tuple<int, int> dloc_to_xy(const Puzzle& p, int location) const;
39 int xy_to_dloc(const Puzzle& p, int x, int y) const;
40 Cell::Dot FlagsToDot(int flags) const;
41 // Iterate connection lists for another location which is connected to us; return that other location.
42 int FindConnection(int i, const std::vector<int>& connections_a, const std::vector<int>& connections_b) const;
43
44 std::shared_ptr<Memory> _memory;
45};
diff --git a/Source/Source.vcxproj b/Source/Source.vcxproj index aebeaeb..08e44a1 100644 --- a/Source/Source.vcxproj +++ b/Source/Source.vcxproj
@@ -161,6 +161,7 @@
161 <ClInclude Include="Memory.h" /> 161 <ClInclude Include="Memory.h" />
162 <ClInclude Include="Puzzle.h" /> 162 <ClInclude Include="Puzzle.h" />
163 <ClInclude Include="Panels.h" /> 163 <ClInclude Include="Panels.h" />
164 <ClInclude Include="PuzzlerSerializer.h" />
164 <ClInclude Include="Random.h" /> 165 <ClInclude Include="Random.h" />
165 <ClInclude Include="Randomizer.h" /> 166 <ClInclude Include="Randomizer.h" />
166 <ClInclude Include="Randomizer2.h" /> 167 <ClInclude Include="Randomizer2.h" />
@@ -171,6 +172,7 @@
171 <ClCompile Include="ChallengeRandomizer.cpp" /> 172 <ClCompile Include="ChallengeRandomizer.cpp" />
172 <ClCompile Include="Memory.cpp" /> 173 <ClCompile Include="Memory.cpp" />
173 <ClCompile Include="Puzzle.cpp" /> 174 <ClCompile Include="Puzzle.cpp" />
175 <ClCompile Include="PuzzlerSerializer.cpp" />
174 <ClCompile Include="Random.cpp" /> 176 <ClCompile Include="Random.cpp" />
175 <ClCompile Include="Randomizer.cpp" /> 177 <ClCompile Include="Randomizer.cpp" />
176 <ClCompile Include="Randomizer2.cpp" /> 178 <ClCompile Include="Randomizer2.cpp" />