From 4f25fa9ae42a0d43c20e954e8e50c66a6b057c92 Mon Sep 17 00:00:00 2001 From: Star Rauchenberger Date: Fri, 27 Oct 2023 17:20:23 -0400 Subject: We can output a code for witnesspuzzles --- ext/wittle_generator/Base64.h | 127 +++++++++++++++++++++++++++++++++ ext/wittle_generator/CMakeLists.txt | 2 +- ext/wittle_generator/Generate.cpp | 57 +++++++++++---- ext/wittle_generator/Generate.h | 2 +- ext/wittle_generator/Panel.cpp | 110 +++++++++++++++++++++++++++++ ext/wittle_generator/Panel.h | 10 +-- ext/wittle_generator/Serializer.cpp | 135 ++++++++++++++++++++++++++++++++++++ ext/wittle_generator/Serializer.h | 76 ++++++++++++++++++++ ext/wittle_generator/Test.cpp | 7 +- 9 files changed, 506 insertions(+), 20 deletions(-) create mode 100644 ext/wittle_generator/Base64.h create mode 100644 ext/wittle_generator/Serializer.cpp create mode 100644 ext/wittle_generator/Serializer.h (limited to 'ext/wittle_generator') diff --git a/ext/wittle_generator/Base64.h b/ext/wittle_generator/Base64.h new file mode 100644 index 0000000..2f30c1a --- /dev/null +++ b/ext/wittle_generator/Base64.h @@ -0,0 +1,127 @@ +#ifndef _MACARON_BASE64_H_ +#define _MACARON_BASE64_H_ + +/** + * The MIT License (MIT) + * Copyright (c) 2016 tomykaira + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#include + +namespace macaron { + +class Base64 { + public: + static std::string Encode(const std::string data) { + static constexpr char sEncodingTable[] = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', + 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', + 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/'}; + + size_t in_len = data.size(); + size_t out_len = 4 * ((in_len + 2) / 3); + std::string ret(out_len, '\0'); + size_t i; + char* p = const_cast(ret.c_str()); + + for (i = 0; i < in_len - 2; i += 3) { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | + ((int)(data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2) | + ((int)(data[i + 2] & 0xC0) >> 6)]; + *p++ = sEncodingTable[data[i + 2] & 0x3F]; + } + if (i < in_len) { + *p++ = sEncodingTable[(data[i] >> 2) & 0x3F]; + if (i == (in_len - 1)) { + *p++ = sEncodingTable[((data[i] & 0x3) << 4)]; + *p++ = '='; + } else { + *p++ = sEncodingTable[((data[i] & 0x3) << 4) | + ((int)(data[i + 1] & 0xF0) >> 4)]; + *p++ = sEncodingTable[((data[i + 1] & 0xF) << 2)]; + } + *p++ = '='; + } + + return ret; + } + + static std::string Decode(const std::string& input, std::string& out) { + static constexpr unsigned char kDecodingTable[] = { + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 62, 64, 64, 64, 63, 52, 53, 54, 55, 56, 57, + 58, 59, 60, 61, 64, 64, 64, 64, 64, 64, 64, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 64, 64, 64, 64, 64, 64, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, + 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, + 64, 64, 64, 64}; + + size_t in_len = input.size(); + if (in_len % 4 != 0) return "Input data size is not a multiple of 4"; + + size_t out_len = in_len / 4 * 3; + if (input[in_len - 1] == '=') out_len--; + if (input[in_len - 2] == '=') out_len--; + + out.resize(out_len); + + for (size_t i = 0, j = 0; i < in_len;) { + uint32_t a = input[i] == '=' + ? 0 & i++ + : kDecodingTable[static_cast(input[i++])]; + uint32_t b = input[i] == '=' + ? 0 & i++ + : kDecodingTable[static_cast(input[i++])]; + uint32_t c = input[i] == '=' + ? 0 & i++ + : kDecodingTable[static_cast(input[i++])]; + uint32_t d = input[i] == '=' + ? 0 & i++ + : kDecodingTable[static_cast(input[i++])]; + + uint32_t triple = + (a << 3 * 6) + (b << 2 * 6) + (c << 1 * 6) + (d << 0 * 6); + + if (j < out_len) out[j++] = (triple >> 2 * 8) & 0xFF; + if (j < out_len) out[j++] = (triple >> 1 * 8) & 0xFF; + if (j < out_len) out[j++] = (triple >> 0 * 8) & 0xFF; + } + + return ""; + } +}; + +} // namespace macaron + +#endif /* _MACARON_BASE64_H_ */ diff --git a/ext/wittle_generator/CMakeLists.txt b/ext/wittle_generator/CMakeLists.txt index 236cc83..561e78a 100644 --- a/ext/wittle_generator/CMakeLists.txt +++ b/ext/wittle_generator/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required (VERSION 3.1) project (wittle_generator) -add_executable(wittle_generator Generate.cpp Panel.cpp Random.cpp Test.cpp) +add_executable(wittle_generator Generate.cpp Panel.cpp Random.cpp Serializer.cpp Test.cpp) set_property(TARGET wittle_generator PROPERTY CXX_STANDARD 17) set_property(TARGET wittle_generator PROPERTY CXX_STANDARD_REQUIRED ON) diff --git a/ext/wittle_generator/Generate.cpp b/ext/wittle_generator/Generate.cpp index 22c211f..5776386 100644 --- a/ext/wittle_generator/Generate.cpp +++ b/ext/wittle_generator/Generate.cpp @@ -55,6 +55,33 @@ void Generate::initPanel() { init_treehouse_layout(); } + if (_custom_grid.size() > + 0) { // If we want to start with a certain default grid when generating + if (_custom_grid.size() < _panel->width()) { + _custom_grid.resize(_panel->width()); + } + if (_custom_grid[_custom_grid.size() - 1].size() < _panel->height()) { + for (auto& row : _custom_grid) { + row.resize(_panel->height()); + } + } + for (int x = 0; x < _panel->width(); x++) { + for (int y = 0; y < _panel->height(); y++) { + set(x, y, _custom_grid[x][y]); + } + } + } + // Sync up start/exit points between panel and generator. If both are + // different, the generator's start/exit point list will be used + for (Point e : _starts) { + _panel->SetGridSymbol(e.first, e.second, Decoration::Start, + Decoration::Color::None); + } + for (Point e : _exits) { + _panel->SetGridSymbol(e.first, e.second, Decoration::Exit, + Decoration::Color::None); + } + // Fill gridpos with every available grid block _gridpos.clear(); for (int x = 1; x < _panel->width(); x += 2) { @@ -503,6 +530,9 @@ bool Generate::generateInternal(int width, int height, PuzzleSymbols symbols) { std::cout << row << std::endl; } + erase_path(); + std::cout << _panel->Write() << std::endl; + return true; } @@ -944,12 +974,14 @@ bool Generate::place_start(int amount) { } if (adjacent && Random::rand() % 10 > 0) continue; _starts.insert(pos); - set(pos.first, pos.second, Decoration::Start | Decoration::Color::None); + _panel->SetGridSymbol(pos.first, pos.second, Decoration::Start, + Decoration::Color::None); amount--; if (_panel->symmetry) { Point sp = get_sym_point(pos); _starts.insert(sp); - set(sp.first, sp.second, Decoration::Start | Decoration::Color::None); + _panel->SetGridSymbol(sp.first, sp.second, Decoration::Start, + Decoration::Color::None); } } return true; @@ -994,12 +1026,14 @@ bool Generate::place_exit(int amount) { } if (adjacent) continue; _exits.insert(pos); - set(pos.first, pos.second, Decoration::Exit | Decoration::Color::None); + _panel->SetGridSymbol(pos.first, pos.second, Decoration::Exit, + Decoration::Color::None); amount--; if (_panel->symmetry) { Point sp = get_sym_point(pos); _exits.insert(sp); - set(sp.first, sp.second, Decoration::Exit | Decoration::Color::None); + _panel->SetGridSymbol(sp.first, sp.second, Decoration::Exit, + Decoration::Color::None); } } return true; @@ -1123,8 +1157,7 @@ bool Generate::can_place_dot(Point pos, bool intersectionOnly) { } // Place the given amount of dots at random points on the path -bool Generate::place_dots(int amount, Decoration::Color color, - bool intersectionOnly) { +bool Generate::place_dots(int amount, int color, bool intersectionOnly) { if (_parity != -1) { // For full dot puzzles, don't put dots on the starts // and exits unless there are multiple for (int x = 0; x < _panel->width(); x += 2) { @@ -1141,19 +1174,17 @@ bool Generate::place_dots(int amount, Decoration::Color color, setFlagOnce(Config::DisableDotIntersection); } - /*if (color == Decoration::Color::Blue || color == Decoration::Color::Cyan) + if (color == Decoration::Color::Blue || color == Decoration::Color::Cyan) color = IntersectionFlags::DOT_IS_BLUE; else if (color == Decoration::Color::Yellow || color == Decoration::Color::Orange) color = IntersectionFlags::DOT_IS_ORANGE; else - color = 0;*/ + color = 0; - std::set open = - (color == 0 ? _path - : (color == Decoration::Color::Blue || color == Decoration::Color::Cyan) - ? _path1 - : _path2); + std::set open = (color == 0 ? _path + : (color == IntersectionFlags::DOT_IS_BLUE) ? _path1 + : _path2); for (Point p : _starts) open.erase(p); for (Point p : _exits) open.erase(p); for (Point p : blockPos) open.erase(p); diff --git a/ext/wittle_generator/Generate.h b/ext/wittle_generator/Generate.h index c28a47d..e37c2fd 100644 --- a/ext/wittle_generator/Generate.h +++ b/ext/wittle_generator/Generate.h @@ -175,7 +175,7 @@ class Generate { bool can_place_gap(Point pos); bool place_gaps(int amount); bool can_place_dot(Point pos, bool intersectionOnly); - bool place_dots(int amount, Decoration::Color color, bool intersectionOnly); + bool place_dots(int amount, int color, bool intersectionOnly); bool can_place_stone(const std::set& region, int color); bool place_stones(int color, int amount); Shape generate_shape(std::set& region, std::set& bufferRegion, diff --git a/ext/wittle_generator/Panel.cpp b/ext/wittle_generator/Panel.cpp index a005467..54c7283 100644 --- a/ext/wittle_generator/Panel.cpp +++ b/ext/wittle_generator/Panel.cpp @@ -3,6 +3,8 @@ #include #include +#include "Serializer.h" + template int find(const std::vector& data, T search, size_t startIndex = 0) { for (size_t i = startIndex; i < data.size(); i++) { @@ -114,3 +116,111 @@ void Panel::Resize(int width, int height) { for (auto& row : _grid) row.resize(height); _resized = true; } + +std::string Panel::Write() { + Serializer serializer; + serializer.writeInt(SERIALIZER_VERSION); + serializer.writeByte(_width); + serializer.writeByte(_height); + serializer.writeString("Generated"); + + int genericFlags = 0; + if (symmetry != Symmetry::None) { + genericFlags |= Serializer::Symmetrical; + if (symmetry == Symmetry::Horizontal) { + genericFlags |= Serializer::SymmetryX; + } + if (symmetry == Symmetry::Vertical) { + genericFlags |= Serializer::SymmetryY; + } + } + serializer.writeByte(genericFlags); + + for (int x = 0; x < _width; x++) { + for (int y = 0; y < _height; y++) { + int val = _grid[x][y]; + + if (x % 2 == 1 && y % 2 == 1) { + // This is a grid cell. + int symbol = val & 0xF00; + if (symbol == Decoration::Triangle) { + serializer.writeByte(Serializer::Triangle); + serializer.writeColor(val & 0xF); + serializer.writeByte((val & 0xF0000) >> 16); + } else if (symbol == Decoration::Star) { + serializer.writeByte(Serializer::Star); + serializer.writeColor(val & 0xF); + } else if (symbol == Decoration::Stone) { + serializer.writeByte(Serializer::Square); + serializer.writeColor(val & 0xF); + } else if (symbol == Decoration::Eraser) { + serializer.writeByte(Serializer::Nega); + serializer.writeColor(val & 0xF); + } else if (symbol == Decoration::Poly) { + serializer.writeByte(Serializer::Poly); + serializer.writeColor(val & 0xF); + // TODO: write polyshape + } else if (symbol == Decoration::Negative) { + serializer.writeByte(Serializer::Ylop); + serializer.writeColor(val & 0xF); + // TODO: write polyshape + } else { + serializer.writeByte(Serializer::Nonce); + } + } else { + serializer.writeByte(Serializer::Line); + // line, dot, gap + serializer.writeByte(Serializer::LineNone); + if (val & Decoration::Dot) { + if (val & IntersectionFlags::DOT_IS_BLUE) { + serializer.writeByte(Serializer::DotBlue); + } else if (val & IntersectionFlags::DOT_IS_ORANGE) { + serializer.writeByte(Serializer::DotYellow); + } else if (val & IntersectionFlags::DOT_IS_INVISIBLE) { + serializer.writeByte(Serializer::DotInvisible); + } else { + serializer.writeByte(Serializer::DotBlack); + } + } else { + serializer.writeByte(Serializer::DotNone); + } + if (val & Decoration::Gap) { + serializer.writeByte(Serializer::GapBreak); + } else { + serializer.writeByte(Serializer::GapNone); + } + } + + char startEnd = 0; + for (const Point& pos : _startpoints) { + if (pos.first == x && pos.second == y) { + startEnd |= Serializer::Start; + } + } + for (const Endpoint& endpoint : _endpoints) { + if (endpoint.GetX() == x && endpoint.GetY() == y) { + if (endpoint.GetDir() & Endpoint::LEFT) { + startEnd |= Serializer::EndLeft; + } + if (endpoint.GetDir() & Endpoint::RIGHT) { + startEnd |= Serializer::EndRight; + } + if (endpoint.GetDir() & Endpoint::UP) { + startEnd |= Serializer::EndTop; + } + if (endpoint.GetDir() & Endpoint::DOWN) { + startEnd |= Serializer::EndBottom; + } + } + } + serializer.writeByte(startEnd); + } + } + + serializer.writeInt(0); + serializer.writeByte(Serializer::NegationsCancelNegations | + Serializer::PrecisePolyominos | + Serializer::FlashForErrors); + + return serializer.str(); +} diff --git a/ext/wittle_generator/Panel.h b/ext/wittle_generator/Panel.h index 7444a9a..7f1588d 100644 --- a/ext/wittle_generator/Panel.h +++ b/ext/wittle_generator/Panel.h @@ -119,12 +119,12 @@ class Endpoint { _flags = flags; } - int GetX() { return _x; } + int GetX() const { return _x; } void SetX(int x) { _x = x; } - int GetY() { return _y; } + int GetY() const { return _y; } void SetY(int y) { _y = y; } - Direction GetDir() { return _dir; } - int GetFlags() { return _flags; } + Direction GetDir() const { return _dir; } + int GetFlags() const { return _flags; } void SetDir(Direction dir) { _dir = dir; } private: @@ -167,6 +167,8 @@ class Panel { int get(int x, int y) { return _grid[x][y]; } void set(int x, int y, int val) { _grid[x][y] = val; } + std::string Write(); + enum Style { SYMMETRICAL = 0x2, // Not on the town symmetry puzzles? IDK why. NO_BLINK = 0x4, diff --git a/ext/wittle_generator/Serializer.cpp b/ext/wittle_generator/Serializer.cpp new file mode 100644 index 0000000..420926f --- /dev/null +++ b/ext/wittle_generator/Serializer.cpp @@ -0,0 +1,135 @@ +#include "Serializer.h" + +#include +#include + +#include "Base64.h" + +void Serializer::writeByte(char val) { buffer_.push_back(val); } + +void Serializer::writeInt(int val) { + int b1 = (val & 0x000000FF) >> 0; + int b2 = (val & 0x0000FF00) >> 8; + int b3 = (val & 0x00FF0000) >> 16; + int b4 = (val & 0xFF000000) >> 24; + writeByte(b1); + writeByte(b2); + writeByte(b3); + writeByte(b4); +} + +void Serializer::writeLong(long val) { + long i1 = val & 0xFFFFFFFF; + long i2 = (val - i1) / 0x100000000; + writeInt(i1); + writeInt(i2); +} + +void Serializer::writeColor(int val) { + switch (val) { + case 0x1: { + // Black + writeByte(0xFF); + writeByte(0xFF); + writeByte(0xFF); + writeByte(0xFF); + break; + } + case 0x2: { + // White + writeByte(0x00); + writeByte(0x00); + writeByte(0x00); + writeByte(0xFF); + break; + } + case 0x3: { + // Red + writeByte(0xFF); + writeByte(0x00); + writeByte(0x00); + writeByte(0xFF); + break; + } + case 0x4: { + // Purple + writeByte(0x80); + writeByte(0x00); + writeByte(0x80); + writeByte(0xFF); + break; + } + case 0x5: { + // Green + writeByte(0x00); + writeByte(0xFF); + writeByte(0x00); + writeByte(0xFF); + break; + } + case 0x6: { + // Cyan + writeByte(0x00); + writeByte(0xFF); + writeByte(0xFF); + writeByte(0xFF); + break; + } + case 0x7: { + // Magenta + writeByte(0xFF); + writeByte(0x00); + writeByte(0xFF); + writeByte(0xFF); + break; + } + case 0x8: { + // Yellow + writeByte(0xFF); + writeByte(0xFF); + writeByte(0x00); + writeByte(0xFF); + break; + } + case 0x9: { + // Blue + writeByte(0x00); + writeByte(0x00); + writeByte(0xFF); + writeByte(0xFF); + break; + } + case 0xA: { + // Orange + writeByte(0xFF); + writeByte(0x8C); + writeByte(0x00); + writeByte(0xFF); + break; + } + case 0xF: { + // X???? + break; + } + } +} + +void Serializer::writeString(const std::string& val) { + writeInt(val.length()); + for (char ch : val) { + writeByte(ch); + } +} + +std::string Serializer::str() const { + int i = 0; + for (char ch : buffer_) { + std::cout << std::hex << static_cast(ch); + if (i++ % 4 == 3) { + std::cout << " "; + } + } + std::cout << std::endl; + + return "_" + macaron::Base64::Encode(buffer_); +} diff --git a/ext/wittle_generator/Serializer.h b/ext/wittle_generator/Serializer.h new file mode 100644 index 0000000..86b262d --- /dev/null +++ b/ext/wittle_generator/Serializer.h @@ -0,0 +1,76 @@ +#ifndef SERIALIZER_H_51D08D41 +#define SERIALIZER_H_51D08D41 + +#include + +constexpr int SERIALIZER_VERSION = 0; + +class Serializer { + public: + enum CellType : char { + Null = 0, + Line = 1, + Square = 2, + Star = 3, + Nega = 4, + Triangle = 5, + Poly = 6, + Ylop = 7, + Nonce = 8 + }; + + enum CellPart : char { + Start = 1, + EndLeft = 2, + EndRight = 4, + EndTop = 8, + EndBottom = 16 + }; + + enum GenericFlag : char { + Autosolved = 1, + Symmetrical = 2, + SymmetryX = 4, + SymmetryY = 8, + Pillar = 16 + }; + + enum LineColor : char { + LineNone = 0, + LineBlack = 1, + LineBlue = 2, + LineYellow = 3 + }; + + enum DotColor : char { + DotNone = 0, + DotBlack = 1, + DotBlue = 2, + DotYellow = 3, + DotInvisible = 4 + }; + + enum SettingsFlag : char { + NegationsCancelNegations = 1, + ShapelessZeroPoly = 2, + PrecisePolyominos = 4, + FlashForErrors = 8, + FatStartpoints = 16, + CustomMechanics = 32 + }; + + enum GapType : char { GapNone = 0, GapBreak = 1, GapFull = 2 }; + + void writeByte(char val); + void writeInt(int val); + void writeLong(long val); + void writeColor(int val); + void writeString(const std::string& val); + + std::string str() const; + + private: + std::string buffer_; +}; + +#endif /* end of include guard: SERIALIZER_H_51D08D41 */ diff --git a/ext/wittle_generator/Test.cpp b/ext/wittle_generator/Test.cpp index 6139215..1da6cd8 100644 --- a/ext/wittle_generator/Test.cpp +++ b/ext/wittle_generator/Test.cpp @@ -4,7 +4,12 @@ int main(int, char**) { Generate generator; generator.setSymbol(Decoration::Start, 0, 4 * 2); generator.setSymbol(Decoration::Exit, 4 * 2, 0); - generator.generate(4 * 2 + 1, 4 * 2 + 1, {{{Decoration::Triangle, 6}}}); + /*generator.generate(4 * 2 + 1, 4 * 2 + 1, + {{{Decoration::Triangle | Decoration::Orange, 6}}});*/ + generator.generate(4 * 2 + 1, 4 * 2 + 1, + {{{Decoration::Star | Decoration::Color::Magenta, 4}, + {Decoration::Stone | Decoration::Color::Black, 4}, + {Decoration::Stone | Decoration::Color::White, 4}}}); return 0; } -- cgit 1.4.1