From bff40e55c9c55fbc8439bb225d1937b2d805e629 Mon Sep 17 00:00:00 2001 From: jbzdarkid Date: Mon, 18 Nov 2019 09:16:16 -0800 Subject: Cleanup & progress on tutorial --- App/Main.cpp | 4 +- Source/PuzzleSerializer.cpp | 461 +++++++++++++++++++++++++++++++++++++++++++ Source/PuzzleSerializer.h | 62 ++++++ Source/PuzzlerSerializer.cpp | 461 ------------------------------------------- Source/PuzzlerSerializer.h | 62 ------ Source/Randomizer2.cpp | 197 +++++------------- Source/Randomizer2.h | 4 + Source/Randomizer2Core.cpp | 29 +-- Source/Randomizer2Core.h | 7 +- Source/Source.vcxproj | 4 +- 10 files changed, 605 insertions(+), 686 deletions(-) create mode 100644 Source/PuzzleSerializer.cpp create mode 100644 Source/PuzzleSerializer.h delete mode 100644 Source/PuzzlerSerializer.cpp delete mode 100644 Source/PuzzlerSerializer.h diff --git a/App/Main.cpp b/App/Main.cpp index a815746..f780069 100644 --- a/App/Main.cpp +++ b/App/Main.cpp @@ -24,7 +24,7 @@ #include "Puzzle.h" #include "Solver.h" #include "Randomizer2.h" -#include "PuzzlerSerializer.h" +#include "PuzzleSerializer.h" #include #define TMP1 0x501 @@ -167,7 +167,7 @@ LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) break; case TMP4: SetRandomSeed(); - g_randomizer2->RandomizeKeep(); + g_randomizer2->Randomize(); } } return DefWindowProc(hwnd, message, wParam, lParam); 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 @@ +#include "PuzzleSerializer.h" +#include "Memory.h" + +#pragma warning (disable:26451) +#pragma warning (disable:26812) + +PuzzleSerializer::PuzzleSerializer(const std::shared_ptr& memory) : _memory(memory) {} + +Puzzle PuzzleSerializer::ReadPuzzle(int id) { + int width = 2 * _memory->ReadEntityData(id, GRID_SIZE_X, 1)[0] - 1; + int height = 2 * _memory->ReadEntityData(id, GRID_SIZE_Y, 1)[0] - 1; + if (width < 0 || height < 0) return Puzzle(); // @Error: Grid size should be always positive? Looks like the starting panels break this rule, though. + + int numIntersections = _memory->ReadEntityData(id, NUM_DOTS, 1)[0]; + _intersectionFlags = _memory->ReadArray(id, DOT_FLAGS, numIntersections); + int numConnections = _memory->ReadEntityData(id, NUM_CONNECTIONS, 1)[0]; + _connectionsA = _memory->ReadArray(id, DOT_CONNECTION_A, numConnections); + _connectionsB = _memory->ReadArray(id, DOT_CONNECTION_B, numConnections); + _intersectionLocations = _memory->ReadArray(id, DOT_POSITIONS, numIntersections*2); + + Puzzle p; + p.NewGrid(width, height); + ReadIntersections(p); + ReadExtras(p); + ReadDecorations(p, id); + ReadSequence(p, id); + return p; +} + +void PuzzleSerializer::WritePuzzle(const Puzzle& p, int id) { + _intersectionFlags.clear(); + _connectionsA.clear(); + _connectionsB.clear(); + _intersectionLocations.clear(); + + MIN = 0.1f; + MAX = 0.9f; + WIDTH_INTERVAL = (MAX - MIN) / (p.width/2); + HEIGHT_INTERVAL = (MAX - MIN) / (p.height/2); + HORIZ_GAP_SIZE = WIDTH_INTERVAL / 2; + VERTI_GAP_SIZE = HEIGHT_INTERVAL / 2; + + WriteIntersections(p); + WriteDots(p); + WriteGaps(p); + WriteEndpoints(p); + WriteDecorations(p, id); + WriteSequence(p, id); + + _memory->WriteEntityData(id, GRID_SIZE_X, {(p.width + 1)/2}); + _memory->WriteEntityData(id, GRID_SIZE_Y, {(p.height + 1)/2}); + _memory->WriteEntityData(id, NUM_DOTS, {static_cast(_intersectionFlags.size())}); + _memory->WriteArray(id, DOT_POSITIONS, _intersectionLocations); + _memory->WriteArray(id, DOT_FLAGS, _intersectionFlags); + _memory->WriteEntityData(id, NUM_CONNECTIONS, {static_cast(_connectionsA.size())}); + _memory->WriteArray(id, DOT_CONNECTION_A, _connectionsA); + _memory->WriteArray(id, DOT_CONNECTION_B, _connectionsB); + _memory->WriteEntityData(id, NEEDS_REDRAW, {1}); +} + +void PuzzleSerializer::ReadIntersections(Puzzle& p) { + // @Cleanup: Change defaults? + for (int x=0; x x2) x--; + else if (y1 < y2) y--; + else if (y1 > y2) y++; + p.grid[x][y].gap = Cell::Gap::NONE; + } +} + +void PuzzleSerializer::ReadExtras(Puzzle& p) { + // This iterates bottom-top, left-right + int i = 0; + for (; i < _intersectionFlags.size(); i++) { + int flags = _intersectionFlags[i]; + auto [x, y] = loc_to_xy(p, i); + if (y < 0) break; // This is the expected exit point + if (flags & Flags::IS_STARTPOINT) { + p.grid[x][y].start = true; + } + p.grid[x][y].dot = FlagsToDot(flags); + if (flags & Flags::HAS_NO_CONN) { + p.grid[x][y].gap = Cell::Gap::FULL; + } + } + + // Iterate the remaining intersections (endpoints, dots, gaps) + for (; i < _intersectionFlags.size(); i++) { + int location = FindConnection(i); + if (location == -1) continue; // @Error: Unable to find connection point + // (x1, y1) location of this intersection + // (x2, y2) location of the connected intersection + float x1 = _intersectionLocations[2*i]; + float y1 = _intersectionLocations[2*i+1]; + float x2 = _intersectionLocations[2*location]; + float y2 = _intersectionLocations[2*location+1]; + auto [x, y] = loc_to_xy(p, location); + + if (_intersectionFlags[i] & Flags::IS_ENDPOINT) { + // Our x coordinate is less than the target's + if (x1 < x2) p.grid[x][y].end = Cell::Dir::LEFT; + else if (x1 > x2) p.grid[x][y].end = Cell::Dir::RIGHT; + // Note that Y coordinates are reversed: 0.0 (bottom) 1.0 (top) + else if (y1 < y2) p.grid[x][y].end = Cell::Dir::DOWN; + else if (y1 > y2) p.grid[x][y].end = Cell::Dir::UP; + } else if (_intersectionFlags[i] & Flags::HAS_DOT) { + if (x1 < x2) x--; + else if (x1 > x2) x++; + else if (y1 < y2) y++; + else if (y1 > y2) y--; + p.grid[x][y].dot = FlagsToDot(_intersectionFlags[i]); + } else if (_intersectionFlags[i] & Flags::HAS_ONE_CONN) { + if (x1 < x2) x--; + else if (x1 > x2) x++; + else if (y1 < y2) y++; + else if (y1 > y2) y--; + p.grid[x][y].gap = Cell::Gap::BREAK; + } + } +} + +void PuzzleSerializer::ReadDecorations(Puzzle& p, int id) { + int numDecorations = _memory->ReadEntityData(id, NUM_DECORATIONS, 1)[0]; + std::vector decorations = _memory->ReadArray(id, DECORATIONS, numDecorations); + if (numDecorations > 0) p.hasDecorations = true; + + for (int i=0; i(); + p.grid[x][y].decoration = d; + d->type = static_cast(decorations[i] & 0xFF00); + switch(d->type) { + case Type::Poly: + case Type::RPoly: + case Type::Ylop: + d->polyshape = decorations[i] & 0xFFFF0000; + break; + case Type::Triangle: + d->count = decorations[i] & 0x000F0000; + break; + } + d->color = static_cast(decorations[i] & 0xF); + } +} + +void PuzzleSerializer::ReadSequence(Puzzle& p, int id) { + int sequenceLength = _memory->ReadEntityData(id, SEQUENCE_LEN, 1)[0]; + std::vector sequence = _memory->ReadArray(id, SEQUENCE, sequenceLength); + + for (int location : sequence) { + auto [x, y] = loc_to_xy(p, location); + p.sequence.emplace_back(Pos{x, y}); + } +} + +void PuzzleSerializer::WriteIntersections(const Puzzle& p) { + // @Cleanup: If I write directly to locations, then I can simplify this gross loop iterator. + // int numIntersections = (p.width / 2 + 1) * (p.height / 2 + 1); + // Grided intersections + for (int y=p.height-1; y>=0; y-=2) { + for (int x=0; x 0 && p.grid[x][y-1].gap != Cell::Gap::FULL) { + _connectionsA.push_back(xy_to_loc(p, x, y-2)); + _connectionsB.push_back(xy_to_loc(p, x, y)); + flags |= Flags::HAS_VERTI_CONN; + numConnections++; + } + // Bottom connection + if (y < p.height - 1 && p.grid[x][y+1].gap != Cell::Gap::FULL) { + flags |= Flags::HAS_VERTI_CONN; + numConnections++; + } + // Left connection + if (x > 0 && p.grid[x-1][y].gap != Cell::Gap::FULL) { + _connectionsA.push_back(xy_to_loc(p, x-2, y)); + _connectionsB.push_back(xy_to_loc(p, x, y)); + flags |= Flags::HAS_HORIZ_CONN; + numConnections++; + } + // Right connection + if (x < p.width - 1 && p.grid[x+1][y].gap != Cell::Gap::FULL) { + flags |= Flags::HAS_HORIZ_CONN; + numConnections++; + } + if (numConnections == 0) flags |= HAS_NO_CONN; + if (numConnections == 1) flags |= HAS_ONE_CONN; + + _intersectionFlags.push_back(flags); + } + } +} + +void PuzzleSerializer::WriteEndpoints(const Puzzle& p) { + for (int x=0; x(_intersectionFlags.size())); + + auto [xPos, yPos] = xy_to_pos(p, x, y); + switch (p.grid[x][y].end) { + case Cell::Dir::LEFT: + xPos -= .05f; + break; + case Cell::Dir::RIGHT: + xPos += .05f; + break; + case Cell::Dir::UP: + yPos += .05f; // Y position goes from 0 (bottom) to 1 (top), so this is reversed. + break; + case Cell::Dir::DOWN: + yPos -= .05f; + break; + } + _endpointLocations.emplace_back(x, y, static_cast(_intersectionFlags.size())); + _intersectionLocations.push_back(xPos); + _intersectionLocations.push_back(yPos); + _intersectionFlags.push_back(Flags::IS_ENDPOINT); + } + } +} + +void PuzzleSerializer::WriteDots(const Puzzle& p) { + for (int x=0; x A for connections. To remove, add the horiz/verti check, see gaps. + int other_connection = _connectionsB[connectionLocation]; + _connectionsB[connectionLocation] = static_cast(_intersectionFlags.size()); + _connectionsA.push_back(other_connection); + _connectionsB.push_back(static_cast(_intersectionFlags.size())); + + // Add this dot to the end + auto [xPos, yPos] = xy_to_pos(p, x, y); + _intersectionLocations.push_back(xPos); + _intersectionLocations.push_back(yPos); + + int flags = Flags::HAS_DOT; + switch (p.grid[x][y].dot) { + case Cell::Dot::BLACK: + break; + case Cell::Dot::BLUE: + flags |= DOT_IS_BLUE; + break; + case Cell::Dot::YELLOW: + flags |= DOT_IS_ORANGE; + break; + case Cell::Dot::INVISIBLE: + flags |= DOT_IS_INVISIBLE; + break; + } + _intersectionFlags.push_back(flags); + } + } +} + +void PuzzleSerializer::WriteGaps(const Puzzle& p) { + for (int x=0; x(_intersectionFlags.size()); + _intersectionLocations.push_back(xPos); + _intersectionLocations.push_back(yPos + VERTI_GAP_SIZE / 2); + _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_VERTI_CONN); + + _connectionsA.push_back(xy_to_loc(p, x, y+1)); + _connectionsB.push_back(static_cast(_intersectionFlags.size())); + _intersectionLocations.push_back(xPos); + _intersectionLocations.push_back(yPos - VERTI_GAP_SIZE / 2); + _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_VERTI_CONN); + } else if (y%2 == 0) { // Horizontal gap + _connectionsA[connectionLocation] = xy_to_loc(p, x-1, y); + _connectionsB[connectionLocation] = static_cast(_intersectionFlags.size()); + _intersectionLocations.push_back(xPos - HORIZ_GAP_SIZE / 2); + _intersectionLocations.push_back(yPos); + _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_HORIZ_CONN); + + _connectionsA.push_back(xy_to_loc(p, x+1, y)); + _connectionsB.push_back(static_cast(_intersectionFlags.size())); + _intersectionLocations.push_back(xPos + HORIZ_GAP_SIZE / 2); + _intersectionLocations.push_back(yPos); + _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_HORIZ_CONN); + } + } + } +} + +void PuzzleSerializer::WriteDecorations(const Puzzle& p, int id) { + if (!p.hasDecorations) return; + + std::vector decorations; + for (int y=p.height-2; y>0; y-=2) { + for (int x=1; xcolor | d->type | d->count | d->polyshape); + } else { + decorations.push_back(0); + } + } + } + + _memory->WriteEntityData(id, NUM_DECORATIONS, {static_cast(decorations.size())}); + _memory->WriteArray(id, DECORATIONS, decorations); +} + +void PuzzleSerializer::WriteSequence(const Puzzle& p, int id) { + if (p.sequence.size() == 0) return; + + std::vector sequence; + for (Pos pos : p.sequence) { + // Only include intersections, the game does not treat segments as real objects + if (pos.x%2 == 0 && pos.y%2 == 0) { + sequence.emplace_back(xy_to_loc(p, pos.x, pos.y)); + } + } + + Pos endpoint = p.sequence[p.sequence.size() - 1]; + for (auto [x, y, location] : _endpointLocations) { + if (x == endpoint.x && y == endpoint.y) { + sequence.emplace_back(location); + break; + } + } + + _memory->WriteEntityData(id, SEQUENCE_LEN, {static_cast(sequence.size())}); + _memory->WriteNewArray(id, SEQUENCE, sequence); +} + +std::tuple PuzzleSerializer::loc_to_xy(const Puzzle& p, int location) const { + int height2 = (p.height - 1) / 2; + int width2 = (p.width + 1) / 2; + + int x = 2 * (location % width2); + int y = 2 * (height2 - location / width2); + return {x, y}; +} + +int PuzzleSerializer::xy_to_loc(const Puzzle& p, int x, int y) const { + int height2 = (p.height - 1) / 2; + int width2 = (p.width + 1) / 2; + + int rowsFromBottom = height2 - y/2; + return rowsFromBottom * width2 + x/2; +} + +std::tuple PuzzleSerializer::dloc_to_xy(const Puzzle& p, int location) const { + int height2 = (p.height - 3) / 2; + int width2 = (p.width - 1) / 2; + + int x = 2 * (location % width2) + 1; + int y = 2 * (height2 - location / width2) + 1; + return {x, y}; +} + +int PuzzleSerializer::xy_to_dloc(const Puzzle& p, int x, int y) const { + int height2 = (p.height - 3) / 2; + int width2 = (p.width - 1) / 2; + + int rowsFromBottom = height2 - (y - 1)/2; + return rowsFromBottom * width2 + (x - 1)/2; +} + +std::tuple PuzzleSerializer::xy_to_pos(const Puzzle& p, int x, int y) const { + return { + MIN + (x/2.0f) * WIDTH_INTERVAL, + MAX - (y/2.0f) * HEIGHT_INTERVAL + }; +} + +Cell::Dot PuzzleSerializer::FlagsToDot(int flags) const { + if (!(flags & Flags::HAS_DOT)) return Cell::Dot::NONE; + if (flags & Flags::DOT_IS_BLUE) return Cell::Dot::BLUE; + else if (flags & Flags::DOT_IS_ORANGE) return Cell::Dot::YELLOW; + else if (flags & Flags::DOT_IS_INVISIBLE) return Cell::Dot::INVISIBLE; + else return Cell::Dot::BLACK; +} + +int PuzzleSerializer::FindConnection(int location) const { + for (int j=0; j<_connectionsA.size(); j++) { + if (_connectionsA[j] == location) return _connectionsB[j]; + if (_connectionsB[j] == location) return _connectionsA[j]; + } + return -1; +} diff --git a/Source/PuzzleSerializer.h b/Source/PuzzleSerializer.h new file mode 100644 index 0000000..d9b9edd --- /dev/null +++ b/Source/PuzzleSerializer.h @@ -0,0 +1,62 @@ +#pragma once +#include +#include + +#include "Puzzle.h" + +class Memory; + +class PuzzleSerializer { +public: + PuzzleSerializer(const std::shared_ptr& memory); + Puzzle ReadPuzzle(int id); + void WritePuzzle(const Puzzle& p, int id); + +private: + // @Bug: Blue and orange are swapped? + enum Flags { + IS_ENDPOINT = 0x1, + IS_STARTPOINT = 0x2, + HAS_NO_CONN = 0x8, + HAS_DOT = 0x20, + DOT_IS_BLUE = 0x100, + DOT_IS_ORANGE = 0x200, + DOT_IS_INVISIBLE = 0x1000, + HAS_ONE_CONN = 0x100000, + HAS_VERTI_CONN = 0x200000, + HAS_HORIZ_CONN = 0x400000, + }; + + void ReadIntersections(Puzzle& p); + void ReadExtras(Puzzle& p); + void ReadDecorations(Puzzle& p, int id); + void ReadSequence(Puzzle& p, int id); + + void WriteIntersections(const Puzzle& p); + void WriteDots(const Puzzle& p); + void WriteGaps(const Puzzle& p); + void WriteEndpoints(const Puzzle& p); + void WriteDecorations(const Puzzle& p, int id); + void WriteSequence(const Puzzle& p, int id); + + std::tuple loc_to_xy(const Puzzle& p, int location) const; + int xy_to_loc(const Puzzle& p, int x, int y) const; + // Decoration location + std::tuple dloc_to_xy(const Puzzle& p, int location) const; + int xy_to_dloc(const Puzzle& p, int x, int y) const; + // Grid coordinates + std::tuple xy_to_pos(const Puzzle& p, int x, int y) const; + Cell::Dot FlagsToDot(int flags) const; + // Iterate connection lists for another location which is connected to us; return that other location. + int FindConnection(int location) const; + + std::shared_ptr _memory; + + std::vector _intersectionLocations; + std::vector _intersectionFlags; + std::vector _connectionsA; + std::vector _connectionsB; + std::vector> _endpointLocations; + + float MIN, MAX, WIDTH_INTERVAL, HEIGHT_INTERVAL, HORIZ_GAP_SIZE, VERTI_GAP_SIZE; +}; diff --git a/Source/PuzzlerSerializer.cpp b/Source/PuzzlerSerializer.cpp deleted file mode 100644 index 2ba0ce7..0000000 --- a/Source/PuzzlerSerializer.cpp +++ /dev/null @@ -1,461 +0,0 @@ -#include "PuzzlerSerializer.h" -#include "Memory.h" - -#pragma warning (disable:26451) -#pragma warning (disable:26812) - -PuzzleSerializer::PuzzleSerializer(const std::shared_ptr& memory) : _memory(memory) {} - -Puzzle PuzzleSerializer::ReadPuzzle(int id) { - int width = 2 * _memory->ReadEntityData(id, GRID_SIZE_X, 1)[0] - 1; - int height = 2 * _memory->ReadEntityData(id, GRID_SIZE_Y, 1)[0] - 1; - if (width < 0 || height < 0) return Puzzle(); // @Error: Grid size should be always positive? Looks like the starting panels break this rule, though. - - int numIntersections = _memory->ReadEntityData(id, NUM_DOTS, 1)[0]; - _intersectionFlags = _memory->ReadArray(id, DOT_FLAGS, numIntersections); - int numConnections = _memory->ReadEntityData(id, NUM_CONNECTIONS, 1)[0]; - _connectionsA = _memory->ReadArray(id, DOT_CONNECTION_A, numConnections); - _connectionsB = _memory->ReadArray(id, DOT_CONNECTION_B, numConnections); - _intersectionLocations = _memory->ReadArray(id, DOT_POSITIONS, numIntersections*2); - - Puzzle p; - p.NewGrid(width, height); - ReadIntersections(p); - ReadExtras(p); - ReadDecorations(p, id); - ReadSequence(p, id); - return p; -} - -void PuzzleSerializer::WritePuzzle(const Puzzle& p, int id) { - _intersectionFlags.clear(); - _connectionsA.clear(); - _connectionsB.clear(); - _intersectionLocations.clear(); - - MIN = 0.1f; - MAX = 0.9f; - WIDTH_INTERVAL = (MAX - MIN) / (p.width/2); - HEIGHT_INTERVAL = (MAX - MIN) / (p.height/2); - HORIZ_GAP_SIZE = WIDTH_INTERVAL / 2; - VERTI_GAP_SIZE = HEIGHT_INTERVAL / 2; - - WriteIntersections(p); - WriteDots(p); - WriteGaps(p); - WriteEndpoints(p); - WriteDecorations(p, id); - WriteSequence(p, id); - - _memory->WriteEntityData(id, GRID_SIZE_X, {(p.width + 1)/2}); - _memory->WriteEntityData(id, GRID_SIZE_Y, {(p.height + 1)/2}); - _memory->WriteEntityData(id, NUM_DOTS, {static_cast(_intersectionFlags.size())}); - _memory->WriteArray(id, DOT_POSITIONS, _intersectionLocations); - _memory->WriteArray(id, DOT_FLAGS, _intersectionFlags); - _memory->WriteEntityData(id, NUM_CONNECTIONS, {static_cast(_connectionsA.size())}); - _memory->WriteArray(id, DOT_CONNECTION_A, _connectionsA); - _memory->WriteArray(id, DOT_CONNECTION_B, _connectionsB); - _memory->WriteEntityData(id, NEEDS_REDRAW, {1}); -} - -void PuzzleSerializer::ReadIntersections(Puzzle& p) { - // @Cleanup: Change defaults? - for (int x=0; x x2) x--; - else if (y1 < y2) y--; - else if (y1 > y2) y++; - p.grid[x][y].gap = Cell::Gap::NONE; - } -} - -void PuzzleSerializer::ReadExtras(Puzzle& p) { - // This iterates bottom-top, left-right - int i = 0; - for (; i < _intersectionFlags.size(); i++) { - int flags = _intersectionFlags[i]; - auto [x, y] = loc_to_xy(p, i); - if (y < 0) break; // This is the expected exit point - if (flags & Flags::IS_STARTPOINT) { - p.grid[x][y].start = true; - } - p.grid[x][y].dot = FlagsToDot(flags); - if (flags & Flags::HAS_NO_CONN) { - p.grid[x][y].gap = Cell::Gap::FULL; - } - } - - // Iterate the remaining intersections (endpoints, dots, gaps) - for (; i < _intersectionFlags.size(); i++) { - int location = FindConnection(i); - if (location == -1) continue; // @Error: Unable to find connection point - // (x1, y1) location of this intersection - // (x2, y2) location of the connected intersection - float x1 = _intersectionLocations[2*i]; - float y1 = _intersectionLocations[2*i+1]; - float x2 = _intersectionLocations[2*location]; - float y2 = _intersectionLocations[2*location+1]; - auto [x, y] = loc_to_xy(p, location); - - if (_intersectionFlags[i] & Flags::IS_ENDPOINT) { - // Our x coordinate is less than the target's - if (x1 < x2) p.grid[x][y].end = Cell::Dir::LEFT; - else if (x1 > x2) p.grid[x][y].end = Cell::Dir::RIGHT; - // Note that Y coordinates are reversed: 0.0 (bottom) 1.0 (top) - else if (y1 < y2) p.grid[x][y].end = Cell::Dir::DOWN; - else if (y1 > y2) p.grid[x][y].end = Cell::Dir::UP; - } else if (_intersectionFlags[i] & Flags::HAS_DOT) { - if (x1 < x2) x--; - else if (x1 > x2) x++; - else if (y1 < y2) y++; - else if (y1 > y2) y--; - p.grid[x][y].dot = FlagsToDot(_intersectionFlags[i]); - } else if (_intersectionFlags[i] & Flags::HAS_ONE_CONN) { - if (x1 < x2) x--; - else if (x1 > x2) x++; - else if (y1 < y2) y++; - else if (y1 > y2) y--; - p.grid[x][y].gap = Cell::Gap::BREAK; - } - } -} - -void PuzzleSerializer::ReadDecorations(Puzzle& p, int id) { - int numDecorations = _memory->ReadEntityData(id, NUM_DECORATIONS, 1)[0]; - std::vector decorations = _memory->ReadArray(id, DECORATIONS, numDecorations); - if (numDecorations > 0) p.hasDecorations = true; - - for (int i=0; i(); - p.grid[x][y].decoration = d; - d->type = static_cast(decorations[i] & 0xFF00); - switch(d->type) { - case Type::Poly: - case Type::RPoly: - case Type::Ylop: - d->polyshape = decorations[i] & 0xFFFF0000; - break; - case Type::Triangle: - d->count = decorations[i] & 0x000F0000; - break; - } - d->color = static_cast(decorations[i] & 0xF); - } -} - -void PuzzleSerializer::ReadSequence(Puzzle& p, int id) { - int sequenceLength = _memory->ReadEntityData(id, SEQUENCE_LEN, 1)[0]; - std::vector sequence = _memory->ReadArray(id, SEQUENCE, sequenceLength); - - for (int location : sequence) { - auto [x, y] = loc_to_xy(p, location); - p.sequence.emplace_back(Pos{x, y}); - } -} - -void PuzzleSerializer::WriteIntersections(const Puzzle& p) { - // @Cleanup: If I write directly to locations, then I can simplify this gross loop iterator. - // int numIntersections = (p.width / 2 + 1) * (p.height / 2 + 1); - // Grided intersections - for (int y=p.height-1; y>=0; y-=2) { - for (int x=0; x 0 && p.grid[x][y-1].gap != Cell::Gap::FULL) { - _connectionsA.push_back(xy_to_loc(p, x, y-2)); - _connectionsB.push_back(xy_to_loc(p, x, y)); - flags |= Flags::HAS_VERTI_CONN; - numConnections++; - } - // Bottom connection - if (y < p.height - 1 && p.grid[x][y+1].gap != Cell::Gap::FULL) { - flags |= Flags::HAS_VERTI_CONN; - numConnections++; - } - // Left connection - if (x > 0 && p.grid[x-1][y].gap != Cell::Gap::FULL) { - _connectionsA.push_back(xy_to_loc(p, x-2, y)); - _connectionsB.push_back(xy_to_loc(p, x, y)); - flags |= Flags::HAS_HORIZ_CONN; - numConnections++; - } - // Right connection - if (x < p.width - 1 && p.grid[x+1][y].gap != Cell::Gap::FULL) { - flags |= Flags::HAS_HORIZ_CONN; - numConnections++; - } - if (numConnections == 0) flags |= HAS_NO_CONN; - if (numConnections == 1) flags |= HAS_ONE_CONN; - - _intersectionFlags.push_back(flags); - } - } -} - -void PuzzleSerializer::WriteEndpoints(const Puzzle& p) { - for (int x=0; x(_intersectionFlags.size())); - - auto [xPos, yPos] = xy_to_pos(p, x, y); - switch (p.grid[x][y].end) { - case Cell::Dir::LEFT: - xPos -= .05f; - break; - case Cell::Dir::RIGHT: - xPos += .05f; - break; - case Cell::Dir::UP: - yPos += .05f; // Y position goes from 0 (bottom) to 1 (top), so this is reversed. - break; - case Cell::Dir::DOWN: - yPos -= .05f; - break; - } - _endpointLocations.emplace_back(x, y, static_cast(_intersectionFlags.size())); - _intersectionLocations.push_back(xPos); - _intersectionLocations.push_back(yPos); - _intersectionFlags.push_back(Flags::IS_ENDPOINT); - } - } -} - -void PuzzleSerializer::WriteDots(const Puzzle& p) { - for (int x=0; x A for connections. To remove, add the horiz/verti check, see gaps. - int other_connection = _connectionsB[connectionLocation]; - _connectionsB[connectionLocation] = static_cast(_intersectionFlags.size()); - _connectionsA.push_back(other_connection); - _connectionsB.push_back(static_cast(_intersectionFlags.size())); - - // Add this dot to the end - auto [xPos, yPos] = xy_to_pos(p, x, y); - _intersectionLocations.push_back(xPos); - _intersectionLocations.push_back(yPos); - - int flags = Flags::HAS_DOT; - switch (p.grid[x][y].dot) { - case Cell::Dot::BLACK: - break; - case Cell::Dot::BLUE: - flags |= DOT_IS_BLUE; - break; - case Cell::Dot::YELLOW: - flags |= DOT_IS_ORANGE; - break; - case Cell::Dot::INVISIBLE: - flags |= DOT_IS_INVISIBLE; - break; - } - _intersectionFlags.push_back(flags); - } - } -} - -void PuzzleSerializer::WriteGaps(const Puzzle& p) { - for (int x=0; x(_intersectionFlags.size()); - _intersectionLocations.push_back(xPos); - _intersectionLocations.push_back(yPos + VERTI_GAP_SIZE / 2); - _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_VERTI_CONN); - - _connectionsA.push_back(xy_to_loc(p, x, y+1)); - _connectionsB.push_back(static_cast(_intersectionFlags.size())); - _intersectionLocations.push_back(xPos); - _intersectionLocations.push_back(yPos - VERTI_GAP_SIZE / 2); - _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_VERTI_CONN); - } else if (y%2 == 0) { // Horizontal gap - _connectionsA[connectionLocation] = xy_to_loc(p, x-1, y); - _connectionsB[connectionLocation] = static_cast(_intersectionFlags.size()); - _intersectionLocations.push_back(xPos - HORIZ_GAP_SIZE / 2); - _intersectionLocations.push_back(yPos); - _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_HORIZ_CONN); - - _connectionsA.push_back(xy_to_loc(p, x+1, y)); - _connectionsB.push_back(static_cast(_intersectionFlags.size())); - _intersectionLocations.push_back(xPos + HORIZ_GAP_SIZE / 2); - _intersectionLocations.push_back(yPos); - _intersectionFlags.push_back(Flags::HAS_ONE_CONN | Flags::HAS_HORIZ_CONN); - } - } - } -} - -void PuzzleSerializer::WriteDecorations(const Puzzle& p, int id) { - if (!p.hasDecorations) return; - - std::vector decorations; - for (int y=p.height-2; y>0; y-=2) { - for (int x=1; xcolor | d->type | d->count | d->polyshape); - } else { - decorations.push_back(0); - } - } - } - - _memory->WriteEntityData(id, NUM_DECORATIONS, {static_cast(decorations.size())}); - _memory->WriteArray(id, DECORATIONS, decorations); -} - -void PuzzleSerializer::WriteSequence(const Puzzle& p, int id) { - if (p.sequence.size() == 0) return; - - std::vector sequence; - for (Pos pos : p.sequence) { - // Only include intersections, the game does not treat segments as real objects - if (pos.x%2 == 0 && pos.y%2 == 0) { - sequence.emplace_back(xy_to_loc(p, pos.x, pos.y)); - } - } - - Pos endpoint = p.sequence[p.sequence.size() - 1]; - for (auto [x, y, location] : _endpointLocations) { - if (x == endpoint.x && y == endpoint.y) { - sequence.emplace_back(location); - break; - } - } - - _memory->WriteEntityData(id, SEQUENCE_LEN, {static_cast(sequence.size())}); - _memory->WriteNewArray(id, SEQUENCE, sequence); -} - -std::tuple PuzzleSerializer::loc_to_xy(const Puzzle& p, int location) const { - int height2 = (p.height - 1) / 2; - int width2 = (p.width + 1) / 2; - - int x = 2 * (location % width2); - int y = 2 * (height2 - location / width2); - return {x, y}; -} - -int PuzzleSerializer::xy_to_loc(const Puzzle& p, int x, int y) const { - int height2 = (p.height - 1) / 2; - int width2 = (p.width + 1) / 2; - - int rowsFromBottom = height2 - y/2; - return rowsFromBottom * width2 + x/2; -} - -std::tuple PuzzleSerializer::dloc_to_xy(const Puzzle& p, int location) const { - int height2 = (p.height - 3) / 2; - int width2 = (p.width - 1) / 2; - - int x = 2 * (location % width2) + 1; - int y = 2 * (height2 - location / width2) + 1; - return {x, y}; -} - -int PuzzleSerializer::xy_to_dloc(const Puzzle& p, int x, int y) const { - int height2 = (p.height - 3) / 2; - int width2 = (p.width - 1) / 2; - - int rowsFromBottom = height2 - (y - 1)/2; - return rowsFromBottom * width2 + (x - 1)/2; -} - -std::tuple PuzzleSerializer::xy_to_pos(const Puzzle& p, int x, int y) const { - return { - MIN + (x/2.0f) * WIDTH_INTERVAL, - MAX - (y/2.0f) * HEIGHT_INTERVAL - }; -} - -Cell::Dot PuzzleSerializer::FlagsToDot(int flags) const { - if (!(flags & Flags::HAS_DOT)) return Cell::Dot::NONE; - if (flags & Flags::DOT_IS_BLUE) return Cell::Dot::BLUE; - else if (flags & Flags::DOT_IS_ORANGE) return Cell::Dot::YELLOW; - else if (flags & Flags::DOT_IS_INVISIBLE) return Cell::Dot::INVISIBLE; - else return Cell::Dot::BLACK; -} - -int PuzzleSerializer::FindConnection(int location) const { - for (int j=0; j<_connectionsA.size(); j++) { - if (_connectionsA[j] == location) return _connectionsB[j]; - if (_connectionsB[j] == location) return _connectionsA[j]; - } - return -1; -} diff --git a/Source/PuzzlerSerializer.h b/Source/PuzzlerSerializer.h deleted file mode 100644 index d9b9edd..0000000 --- a/Source/PuzzlerSerializer.h +++ /dev/null @@ -1,62 +0,0 @@ -#pragma once -#include -#include - -#include "Puzzle.h" - -class Memory; - -class PuzzleSerializer { -public: - PuzzleSerializer(const std::shared_ptr& memory); - Puzzle ReadPuzzle(int id); - void WritePuzzle(const Puzzle& p, int id); - -private: - // @Bug: Blue and orange are swapped? - enum Flags { - IS_ENDPOINT = 0x1, - IS_STARTPOINT = 0x2, - HAS_NO_CONN = 0x8, - HAS_DOT = 0x20, - DOT_IS_BLUE = 0x100, - DOT_IS_ORANGE = 0x200, - DOT_IS_INVISIBLE = 0x1000, - HAS_ONE_CONN = 0x100000, - HAS_VERTI_CONN = 0x200000, - HAS_HORIZ_CONN = 0x400000, - }; - - void ReadIntersections(Puzzle& p); - void ReadExtras(Puzzle& p); - void ReadDecorations(Puzzle& p, int id); - void ReadSequence(Puzzle& p, int id); - - void WriteIntersections(const Puzzle& p); - void WriteDots(const Puzzle& p); - void WriteGaps(const Puzzle& p); - void WriteEndpoints(const Puzzle& p); - void WriteDecorations(const Puzzle& p, int id); - void WriteSequence(const Puzzle& p, int id); - - std::tuple loc_to_xy(const Puzzle& p, int location) const; - int xy_to_loc(const Puzzle& p, int x, int y) const; - // Decoration location - std::tuple dloc_to_xy(const Puzzle& p, int location) const; - int xy_to_dloc(const Puzzle& p, int x, int y) const; - // Grid coordinates - std::tuple xy_to_pos(const Puzzle& p, int x, int y) const; - Cell::Dot FlagsToDot(int flags) const; - // Iterate connection lists for another location which is connected to us; return that other location. - int FindConnection(int location) const; - - std::shared_ptr _memory; - - std::vector _intersectionLocations; - std::vector _intersectionFlags; - std::vector _connectionsA; - std::vector _connectionsB; - std::vector> _endpointLocations; - - float MIN, MAX, WIDTH_INTERVAL, HEIGHT_INTERVAL, HORIZ_GAP_SIZE, VERTI_GAP_SIZE; -}; diff --git a/Source/Randomizer2.cpp b/Source/Randomizer2.cpp index d9c00c0..7a50c7b 100644 --- a/Source/Randomizer2.cpp +++ b/Source/Randomizer2.cpp @@ -1,128 +1,39 @@ +#include "Memory.h" #include "Randomizer2.h" +#include "Randomizer2Core.h" #include "Puzzle.h" #include "Random.h" #include "Solver.h" -#include "Memory.h" -#include "Randomizer2Core.h" -#include "PuzzlerSerializer.h" + #include #include -void FloodFillInternal(const Puzzle& p, std::vector>& reached, int x, int y) { - if (x%2 == 1 && y%2 == 1) return; - auto cell = p.GetCell(x, y); - if (cell.undefined) return; - if (cell.gap != Cell::Gap::NONE) return; - if (reached[x][y]) return; +#pragma warning (disable: 26451) - reached[x][y] = true; - FloodFillInternal(p, reached, x-1, y); - FloodFillInternal(p, reached, x+1, y); - FloodFillInternal(p, reached, x, y-1); - FloodFillInternal(p, reached, x, y+1); -} +Randomizer2::Randomizer2(const std::shared_ptr& memory) : _memory(memory), _serializer(PuzzleSerializer(_memory)) {} -// Returns true: All nodes reachable / false: Some node disconnected -bool FloodFill(const Puzzle& p) { - std::vector> reached; - reached.resize(p.width); - for (int x=0; x& memory) : _memory(memory) {} - -void Randomizer2::Randomize() { - // 4x4 - // 14 gaps - // start (x=0, y=8) - // end (x=8, y=0) Up - // 1 solution - Puzzle p; - while (true) { +void Randomizer2::RandomizeTutorial() { + { // Far center + Puzzle p; p.NewGrid(4, 4); - - std::vector corners; - std::vector cells; - std::vector edges; - for (int x=0; x(edges.size() - 1)); - Pos pos = edges[edge]; - edges.erase(edges.begin() + edge); - - if (FloodFill(p)) { - p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; - break; - } else { - p.grid[pos.x][pos.y].gap = Cell::Gap::NONE; - } - } - } - p.grid[0][8].start = true; p.grid[8][0].end = Cell::Dir::UP; - auto solutions = Solver::Solve(p); - if (solutions.size() > 0) break; + for (Pos pos : Randomizer2Core::CutEdges(p, 14)) { + p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; + } + _serializer.WritePuzzle(p, 0x293); } - PuzzleSerializer(_memory).WritePuzzle(p, 0x293); - // 7x7 - // 35 gaps - // start (x=8, y=8) - // end (x=4, y=0) Up - // 2 solutions, 37 & 39 - while (true) { + { + Puzzle p; p.NewGrid(7, 7); - std::vector corners; - std::vector cells; - std::vector edges; - for (int x=0; x(edges.size() - 1)); - Pos pos = edges[edge]; - edges.erase(edges.begin() + edge); - - if (FloodFill(p)) { - p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; - break; - } else { - p.grid[pos.x][pos.y].gap = Cell::Gap::NONE; - } - } - } - switch (Random::RandInt(1, 4)) { case 1: p.grid[Random::RandInt(0, p.width-1)][0].end = Cell::Dir::UP; @@ -148,17 +59,17 @@ void Randomizer2::Randomize() { p.grid[Random::RandInt(0, 2)*2 + 4][Random::RandInt(0, 2)*2 + 4].start = true; break; } + + for (Pos pos : Randomizer2Core::CutEdges(p, 35)) { + p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; + } - auto solutions = Solver::Solve(p); - if (solutions.size() > 0) break; + _serializer.WritePuzzle(p, 0x295); } - PuzzleSerializer(_memory).WritePuzzle(p, 0x295); - } void Randomizer2::RandomizeKeep() { - // *** Hedges 1 *** - { + { // Hedges 1 Puzzle p; p.NewGrid(4, 4); @@ -177,8 +88,7 @@ void Randomizer2::RandomizeKeep() { p.grid[4][8].start = true; p.grid[6][0].end = Cell::Dir::UP; - std::vector cutEdges = Randomizer2Core::CutEdgesToBeUnique(p); - assert(cutEdges.size() == 5); + std::vector cutEdges = Randomizer2Core::CutEdges2(p, 5); Puzzle copy = p; std::vector gates = {0x00344, 0x00488, 0x00489, 0x00495, 0x00496}; for (int i=0; i cutEdges = Randomizer2Core::CutEdgesToBeUnique(p); - assert(cutEdges.size() == 7); + std::vector cutEdges = Randomizer2Core::CutEdges2(p, 7); for (Pos pos : cutEdges) { p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; } - auto solutions = Solver::Solve(p); - assert(solutions.size() == 1); + auto solution = GetUniqueSolution(p); Puzzle q; q.NewGrid(4, 4); q.grid[0][8].start = true; q.grid[8][0].end = Cell::Dir::RIGHT; - q.sequence = solutions[0].sequence; + q.sequence = solution.sequence; for (Pos pos : cutEdges) { q.grid[pos.x][pos.y].gap = Cell::Gap::FULL; } - // Cut to 4 of 9 additional edges (total: 11) - Randomizer2Core::CutEdgesNotOutsideNotBreakingSequence(q, 4); - PuzzleSerializer(_memory).WritePuzzle(q, 0x19DC); + // Cut to 6 of 9 additional edges + for (Pos pos : Randomizer2Core::CutEdges2(q, 6)) { + q.grid[pos.x][pos.y].gap = Cell::Gap::FULL; + } + _serializer.WritePuzzle(q, 0x19DC); } - // *** Hedges 3 ** - { + { // Hedges 3 [WIP] Puzzle p; p.NewGrid(4, 4); @@ -251,17 +158,14 @@ void Randomizer2::RandomizeKeep() { std::vector pebbleMarkers = {0x034a9, 0x034b1, 0x034be, 0x034c4}; - - std::vector cutEdges = Randomizer2Core::CutEdgesToBeUnique(p); - assert(cutEdges.size() == 7); + std::vector cutEdges = Randomizer2Core::CutEdges2(p, 7); for (Pos pos : cutEdges) { p.grid[pos.x][pos.y].gap = Cell::Gap::BREAK; } - PuzzleSerializer(_memory).WritePuzzle(p, 0x19E7); + // _serializer.WritePuzzle(p, 0x19E7); } - // *** Hedges 4 *** - { + { // Hedges 4 Puzzle p; p.NewGrid(4, 4); @@ -283,28 +187,33 @@ void Randomizer2::RandomizeKeep() { p.grid[0][8].start = true; p.grid[4][0].end = Cell::Dir::UP; - std::vector cutEdges = Randomizer2Core::CutEdgesToBeUnique(p); - assert(cutEdges.size() == 2); + std::vector cutEdges = Randomizer2Core::CutEdges2(p, 2); for (Pos pos : cutEdges) { p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; } - auto solutions = Solver::Solve(p); - assert(solutions.size() == 1); + auto solution = GetUniqueSolution(p); Puzzle q; q.NewGrid(4, 4); q.grid[0][8].start = true; q.grid[4][0].end = Cell::Dir::UP; - q.sequence = solutions[0].sequence; + q.sequence = solution.sequence; for (Pos pos : cutEdges) { q.grid[pos.x][pos.y].gap = Cell::Gap::FULL; } - // 9 cuts, -2 from existing cuts - Randomizer2Core::CutEdgesNotOutsideNotBreakingSequence(q, 7); - PuzzleSerializer(_memory).WritePuzzle(q, 0x1A0F); + for (Pos pos : Randomizer2Core::CutEdges2(q, 7)) { + q.grid[pos.x][pos.y].gap = Cell::Gap::FULL; + } + _serializer.WritePuzzle(q, 0x1A0F); } } +Puzzle Randomizer2::GetUniqueSolution(Puzzle& p) { + auto solutions = Solver::Solve(p); + assert(solutions.size() == 1); + return solutions[0]; +} + void Randomizer2::SetGate(int panel, int X, int Y) { float x, y, z, w; if (X%2 == 0 && Y%2 == 1) { // Horizontal diff --git a/Source/Randomizer2.h b/Source/Randomizer2.h index 359e357..6e79694 100644 --- a/Source/Randomizer2.h +++ b/Source/Randomizer2.h @@ -1,16 +1,20 @@ #pragma once #include +#include "PuzzleSerializer.h" class Memory; class Randomizer2 { public: Randomizer2(const std::shared_ptr& memory); void Randomize(); + void RandomizeTutorial(); void RandomizeKeep(); private: + Puzzle GetUniqueSolution(Puzzle& p); void SetGate(int panel, int X, int Y); void SetPos(int panel, float x, float y, float z); std::shared_ptr _memory; + PuzzleSerializer _serializer; }; diff --git a/Source/Randomizer2Core.cpp b/Source/Randomizer2Core.cpp index 2659076..8bd5765 100644 --- a/Source/Randomizer2Core.cpp +++ b/Source/Randomizer2Core.cpp @@ -6,40 +6,46 @@ #include #include -std::vector Randomizer2Core::CutEdgesToBeUnique(const Puzzle& p) { - auto [colorGrid, numColors] = CreateColorGrid(p); +// @Cutnpaste +std::vector Randomizer2Core::CutEdges(const Puzzle& p, size_t numEdges) { std::vector edges; for (int x=0; x Randomizer2Core::CutEdges2(const Puzzle& p, size_t numEdges) { std::vector edges; + // Note the iterator bounds; we skip the outer edges. for (int x=1; x cutEdges = CutEdgesInternal(p, colorGrid, edges, numEdges); - for (Pos pos : cutEdges) { - p.grid[pos.x][pos.y].gap = Cell::Gap::FULL; - } + return CutEdgesInternal(p, edges, numEdges); } -std::vector Randomizer2Core::CutEdgesInternal(const Puzzle& p, std::vector>& colorGrid, std::vector& edges, size_t numEdges) { +std::vector Randomizer2Core::CutEdgesInternal(const Puzzle& p, std::vector& edges, size_t numEdges) { + auto [colorGrid, numColors] = CreateColorGrid(p); + assert(numEdges <= numColors); + std::vector cutEdges; for (int i=0; i Randomizer2Core::CutEdgesInternal(const Puzzle& p, std::vector< } } } + assert(cutEdges.size() == numEdges); return cutEdges; } diff --git a/Source/Randomizer2Core.h b/Source/Randomizer2Core.h index 1b97fba..63ba6a8 100644 --- a/Source/Randomizer2Core.h +++ b/Source/Randomizer2Core.h @@ -7,12 +7,11 @@ class Puzzle; class Randomizer2Core { public: // CAUTION: Does not actually cut edges, just returns a list of suggested cuts. - // Cuts a number of edges equal to the number of colors in the color grid. - static std::vector CutEdgesToBeUnique(const Puzzle& p); - static void CutEdgesNotOutsideNotBreakingSequence(Puzzle& p, size_t numEdges); + static std::vector CutEdges(const Puzzle& p, size_t numEdges); + static std::vector CutEdges2(const Puzzle& p, size_t numEdges); private: - static std::vector CutEdgesInternal(const Puzzle& p, std::vector>& colorGrid, std::vector& edges, size_t numEdges); + static std::vector CutEdgesInternal(const Puzzle& p, std::vector& edges, size_t numEdges); static void DebugColorGrid(const std::vector>& colorGrid); static void FloodFill(const Puzzle& p, std::vector>& colorGrid, int color, int x, int y); static void FloodFillOutside(const Puzzle& p, std::vector>& colorGrid, int x, int y); diff --git a/Source/Source.vcxproj b/Source/Source.vcxproj index 05f9ffa..33e3697 100644 --- a/Source/Source.vcxproj +++ b/Source/Source.vcxproj @@ -161,7 +161,7 @@ - + @@ -173,7 +173,7 @@ - + -- cgit 1.4.1