summary refs log tree commit diff stats
path: root/generator
diff options
context:
space:
mode:
authorStar Rauchenberger <fefferburbia@gmail.com>2024-12-06 17:33:40 -0500
committerStar Rauchenberger <fefferburbia@gmail.com>2024-12-06 17:35:35 -0500
commitd1024b559c44a143eca214fb8732001080e8b037 (patch)
tree1c6ad799195b9f917047aa11b72e1f7dc0ecb013 /generator
parentad8243c74c1d718b94a2a4bf4f0fa56d4c9dbb45 (diff)
downloadlingo-randomizer-d1024b559c44a143eca214fb8732001080e8b037.tar.gz
lingo-randomizer-d1024b559c44a143eca214fb8732001080e8b037.tar.bz2
lingo-randomizer-d1024b559c44a143eca214fb8732001080e8b037.zip
Change output format to zstd compressed Godot serialized variant
Diffstat (limited to 'generator')
-rw-r--r--generator/CMakeLists.txt10
-rw-r--r--generator/generator.cpp159
-rw-r--r--generator/godot_variant.cpp69
-rw-r--r--generator/godot_variant.h28
4 files changed, 217 insertions, 49 deletions
diff --git a/generator/CMakeLists.txt b/generator/CMakeLists.txt index fb5dc3c..b6d3c42 100644 --- a/generator/CMakeLists.txt +++ b/generator/CMakeLists.txt
@@ -1,11 +1,15 @@
1cmake_minimum_required (VERSION 3.1) 1cmake_minimum_required (VERSION 3.1)
2project (generator) 2project (generator)
3 3
4find_package(PkgConfig)
5pkg_check_modules(libzstd libzstd REQUIRED)
6
4add_subdirectory(vendor/fmt) 7add_subdirectory(vendor/fmt)
5 8
6include_directories(vendor/hkutil vendor/fmt/include) 9include_directories(vendor/hkutil vendor/fmt/include ${libzstd_INCLUDE_DIRS})
10link_directories(${libzstd_LIBRARY_DIRS})
7 11
8add_executable(generator generator.cpp main.cpp) 12add_executable(generator generator.cpp main.cpp godot_variant.cpp)
9set_property(TARGET generator PROPERTY CXX_STANDARD 17) 13set_property(TARGET generator PROPERTY CXX_STANDARD 17)
10set_property(TARGET generator PROPERTY CXX_STANDARD_REQUIRED ON) 14set_property(TARGET generator PROPERTY CXX_STANDARD_REQUIRED ON)
11target_link_libraries(generator fmt) 15target_link_libraries(generator fmt ${libzstd_LIBRARIES})
diff --git a/generator/generator.cpp b/generator/generator.cpp index 7263591..7016885 100644 --- a/generator/generator.cpp +++ b/generator/generator.cpp
@@ -3,6 +3,7 @@
3#include <fmt/core.h> 3#include <fmt/core.h>
4#include <hkutil/progress.h> 4#include <hkutil/progress.h>
5#include <hkutil/string.h> 5#include <hkutil/string.h>
6#include <zstd.h>
6 7
7#include <algorithm> 8#include <algorithm>
8#include <filesystem> 9#include <filesystem>
@@ -10,13 +11,17 @@
10#include <list> 11#include <list>
11#include <regex> 12#include <regex>
12#include <set> 13#include <set>
14#include <sstream>
13#include <stdexcept> 15#include <stdexcept>
14#include <string> 16#include <string>
15#include <unordered_map> 17#include <unordered_map>
16#include <unordered_set> 18#include <unordered_set>
17#include <vector> 19#include <vector>
18 20
21#include "godot_variant.h"
22
19constexpr int MIN_FREQUENCY = 2000000; 23constexpr int MIN_FREQUENCY = 2000000;
24constexpr int kCompressionLevel = 3;
20 25
21namespace { 26namespace {
22 27
@@ -877,7 +882,7 @@ void generator::run() {
877 } 882 }
878 883
879 // Orange addition 884 // Orange addition
880 std::vector<std::string> orange_addition; 885 std::vector<GodotVariant> orange_addition;
881 { 886 {
882 hatkirby::progress ppgs("Generating orange addition puzzles...", 887 hatkirby::progress ppgs("Generating orange addition puzzles...",
883 wanderlust_.size()); 888 wanderlust_.size());
@@ -894,8 +899,12 @@ void generator::run() {
894 continue; 899 continue;
895 } 900 }
896 if (wanderlust_.count(cipher - cipher2)) { 901 if (wanderlust_.count(cipher - cipher2)) {
897 orange_addition.push_back(fmt::format( 902 std::vector<GodotVariant> addition_entry;
898 "[{},{},{}]", form_id2, wanderlust_[cipher - cipher2], form_id)); 903 addition_entry.emplace_back(static_cast<int32_t>(form_id2));
904 addition_entry.emplace_back(
905 static_cast<int32_t>(wanderlust_[cipher - cipher2]));
906 addition_entry.emplace_back(static_cast<int32_t>(form_id));
907 orange_addition.emplace_back(addition_entry);
899 } 908 }
900 } 909 }
901 } 910 }
@@ -995,83 +1004,141 @@ void generator::run() {
995 std::cout << "Yellow top yellow middle combos: " 1004 std::cout << "Yellow top yellow middle combos: "
996 << combos_[kYellowTop][kYellowMiddle].size() << std::endl; 1005 << combos_[kYellowTop][kYellowMiddle].size() << std::endl;
997 1006
998 std::vector<std::string> form_entry; 1007 // 0: forms
1008 // 1: paintings
1009 // 2: wanderlust
1010 // 3: addition
1011 // 4: walls
1012 // 5: combos
1013 std::vector<GodotVariant> full_variant;
1014
1015 std::vector<GodotVariant> form_entry;
999 form_entry.reserve(forms_.size()); 1016 form_entry.reserve(forms_.size());
1000 for (const Form& form : forms_) { 1017 for (const Form& form : forms_) {
1001 if (form.puzzles.empty()) { 1018 if (form.puzzles.empty()) {
1002 form_entry.push_back(fmt::format("\"{}\"", form.text)); 1019 form_entry.emplace_back(form.text);
1003 } else { 1020 } else {
1004 std::vector<std::string> entry_per_type; 1021 std::map<GodotVariant, GodotVariant> entry_per_type;
1005 for (const auto& [puzzle_type, puzzles] : form.puzzles) { 1022 for (const auto& [puzzle_type, puzzles] : form.puzzles) {
1006 std::vector<std::string> entry_per_puzzle; 1023 std::vector<GodotVariant> entry_per_puzzle;
1007 for (size_t puzzle : puzzles) { 1024 for (size_t puzzle : puzzles) {
1008 entry_per_puzzle.push_back(std::to_string(puzzle)); 1025 entry_per_puzzle.emplace_back(puzzle);
1009 } 1026 }
1010 entry_per_type.push_back( 1027 entry_per_type.emplace(
1011 fmt::format("{}:[{}]", static_cast<int>(puzzle_type), 1028 std::piecewise_construct,
1012 hatkirby::implode(entry_per_puzzle, ","))); 1029 std::forward_as_tuple(static_cast<int32_t>(puzzle_type)),
1030 std::forward_as_tuple(entry_per_puzzle));
1031 }
1032 std::vector<GodotVariant> pair_entry;
1033 pair_entry.emplace_back(form.text);
1034 if (!entry_per_type.empty()) {
1035 pair_entry.emplace_back(entry_per_type);
1013 } 1036 }
1014 form_entry.push_back(fmt::format("[\"{}\",{{{}}}]", form.text, 1037 form_entry.emplace_back(pair_entry);
1015 hatkirby::implode(entry_per_type, ",")));
1016 } 1038 }
1017 } 1039 }
1040 full_variant.emplace_back(form_entry);
1018 1041
1019 std::ofstream output_file(outputPath_); 1042 std::vector<GodotVariant> painting_entries;
1020 output_file << "extends Node\n\nvar forms = ["
1021 << hatkirby::implode(form_entry, ",") << "]" << std::endl;
1022
1023 std::vector<std::string> painting_entries;
1024 { 1043 {
1025 std::list<std::string> paintings(readFile(datadirPath_ / "paintings.txt")); 1044 std::list<std::string> paintings(readFile(datadirPath_ / "paintings.txt"));
1026 for (const std::string& line : paintings) { 1045 for (const std::string& line : paintings) {
1027 auto parts = hatkirby::split<std::vector<std::string>>(line, ","); 1046 auto parts = hatkirby::split<std::vector<std::string>>(line, ",");
1028 painting_entries.push_back( 1047 std::vector<GodotVariant> painting_entry;
1029 fmt::format("[\"{}\",\"{}\"]", parts[0], parts[1])); 1048 painting_entry.emplace_back(parts[0]);
1049 painting_entry.emplace_back(parts[1]);
1050 painting_entries.emplace_back(painting_entry);
1030 } 1051 }
1031 } 1052 }
1032 output_file << "var paintings = [" << hatkirby::implode(painting_entries, ",") 1053 full_variant.emplace_back(painting_entries);
1033 << "]" << std::endl;
1034 1054
1035 std::vector<std::string> cipher_lines; 1055 std::vector<GodotVariant> wanderlust_entries;
1036 for (const auto& [cipher, form_id] : wanderlust_) { 1056 for (const auto& [cipher, form_id] : wanderlust_) {
1037 cipher_lines.push_back(std::to_string(form_id)); 1057 wanderlust_entries.emplace_back(static_cast<int32_t>(form_id));
1038 } 1058 }
1039 output_file << "var wanderlust = [" << hatkirby::implode(cipher_lines, ",") 1059 full_variant.emplace_back(wanderlust_entries);
1040 << "]" << std::endl; 1060
1041 output_file << "var addition = [" << hatkirby::implode(orange_addition, ",") 1061 full_variant.emplace_back(orange_addition);
1042 << "]" << std::endl;
1043 1062
1044 std::vector<std::string> walls_entries; 1063 std::vector<GodotVariant> walls_entries;
1045 { 1064 {
1046 std::list<std::string> walls(readFile(datadirPath_ / "walls.txt")); 1065 std::list<std::string> walls(readFile(datadirPath_ / "walls.txt"));
1047 for (const std::string& line : walls) { 1066 for (const std::string& line : walls) {
1048 auto parts = hatkirby::split<std::vector<std::string>>(line, ","); 1067 auto parts = hatkirby::split<std::vector<std::string>>(line, ",");
1049 walls_entries.push_back( 1068 std::vector<GodotVariant> walls_entry;
1050 fmt::format("[\"{}\",\"{}\"]", parts[0], parts[1])); 1069 walls_entry.emplace_back(parts[0]);
1070 walls_entry.emplace_back(parts[1]);
1071 walls_entries.emplace_back(walls_entry);
1051 } 1072 }
1052 } 1073 }
1053 output_file << "var walls_puzzles = [" 1074 full_variant.emplace_back(walls_entries);
1054 << hatkirby::implode(walls_entries, ",") << "]" << std::endl;
1055 1075
1056 std::vector<std::string> combo_entries; 1076 std::map<GodotVariant, GodotVariant> combo_entries;
1057 for (const auto& [left_type, left_join] : combos_) { 1077 for (const auto& [left_type, left_join] : combos_) {
1058 std::vector<std::string> left_entries; 1078 std::map<GodotVariant, GodotVariant> left_entries;
1059 for (const auto& [right_type, choices] : left_join) { 1079 for (const auto& [right_type, choices] : left_join) {
1060 std::vector<std::string> choice_entries; 1080 std::vector<GodotVariant> choice_entries;
1061 for (const auto& [hint1, hint2, answer] : choices) { 1081 for (const auto& [hint1, hint2, answer] : choices) {
1062 choice_entries.push_back( 1082 std::vector<GodotVariant> choice_entry;
1063 fmt::format("[{},{},{}]", hint1, hint2, answer)); 1083 choice_entry.emplace_back(hint1);
1084 choice_entry.emplace_back(hint2);
1085 choice_entry.emplace_back(answer);
1086 choice_entries.emplace_back(choice_entry);
1064 } 1087 }
1065 left_entries.push_back( 1088 left_entries.emplace(
1066 fmt::format("{}:[{}]", static_cast<int>(right_type), 1089 std::piecewise_construct,
1067 hatkirby::implode(choice_entries, ","))); 1090 std::forward_as_tuple(static_cast<int32_t>(right_type)),
1091 std::forward_as_tuple(choice_entries));
1068 } 1092 }
1069 combo_entries.push_back(fmt::format("{}:{{{}}}", 1093 combo_entries.emplace(
1070 static_cast<int>(left_type), 1094 std::piecewise_construct,
1071 hatkirby::implode(left_entries, ","))); 1095 std::forward_as_tuple(static_cast<int32_t>(left_type)),
1096 std::forward_as_tuple(left_entries));
1097 }
1098 full_variant.emplace_back(combo_entries);
1099
1100 std::ostringstream serialized_variant_buffer;
1101 GodotVariant(full_variant).Serialize(serialized_variant_buffer);
1102
1103 std::string serialized_variant = serialized_variant_buffer.str();
1104 std::ostringstream variant_file_buffer;
1105 int32_t variant_len = serialized_variant.size();
1106 variant_file_buffer.write(reinterpret_cast<char*>(&variant_len), 4);
1107 variant_file_buffer.write(serialized_variant.data(),
1108 serialized_variant.size());
1109
1110 std::string variant_file = variant_file_buffer.str();
1111
1112 size_t max_output_size = ZSTD_compressBound(variant_file.size());
1113 std::string compressed_variant(max_output_size, 0);
1114 size_t compressed_size = ZSTD_compress(
1115 compressed_variant.data(), max_output_size, variant_file.data(),
1116 variant_file.size(), kCompressionLevel);
1117
1118 // Write Godot's weird gzip container
1119 {
1120 std::ofstream output_file(outputPath_, std::ios::binary);
1121
1122 int32_t bytes = 0x46504347;
1123 output_file.write(reinterpret_cast<char*>(&bytes), 4);
1124
1125 bytes = 0x00000002; // ZSTD
1126 output_file.write(reinterpret_cast<char*>(&bytes), 4);
1127
1128 bytes = variant_file.size() + 1;
1129 output_file.write(reinterpret_cast<char*>(&bytes), 4);
1130
1131 bytes = variant_file.size();
1132 output_file.write(reinterpret_cast<char*>(&bytes), 4);
1133
1134 bytes = compressed_size;
1135 output_file.write(reinterpret_cast<char*>(&bytes), 4);
1136
1137 output_file.write(compressed_variant.data(), compressed_size);
1138
1139 bytes = 0x46504347;
1140 output_file.write(reinterpret_cast<char*>(&bytes), 4);
1072 } 1141 }
1073 output_file << "var combos = {" << hatkirby::implode(combo_entries, ",")
1074 << "}" << std::endl;
1075} 1142}
1076 1143
1077size_t generator::LookupOrCreatePronunciation(const std::string& phonemes) { 1144size_t generator::LookupOrCreatePronunciation(const std::string& phonemes) {
diff --git a/generator/godot_variant.cpp b/generator/godot_variant.cpp new file mode 100644 index 0000000..dd1ad33 --- /dev/null +++ b/generator/godot_variant.cpp
@@ -0,0 +1,69 @@
1#include "godot_variant.h"
2
3#include <stdexcept>
4
5bool GodotVariant::operator<(const GodotVariant& rhs) const {
6 if (value.index() != rhs.value.index()) {
7 return value.index() < rhs.value.index();
8 }
9
10 if (const int32_t* pval = std::get_if<int32_t>(&value)) {
11 return *pval < std::get<int32_t>(rhs.value);
12 } else if (const std::string* pval = std::get_if<std::string>(&value)) {
13 return *pval < std::get<std::string>(rhs.value);
14 } else if (const std::map<GodotVariant, GodotVariant>* pval =
15 std::get_if<std::map<GodotVariant, GodotVariant>>(&value)) {
16 return *pval < std::get<std::map<GodotVariant, GodotVariant>>(rhs.value);
17 } else if (const std::vector<GodotVariant>* pval =
18 std::get_if<std::vector<GodotVariant>>(&value)) {
19 return *pval < std::get<std::vector<GodotVariant>>(rhs.value);
20 }
21
22 throw std::invalid_argument("Incomparable variants.");
23}
24
25void GodotVariant::Serialize(std::basic_ostream<char>& output) const {
26 if (const int32_t* pval = std::get_if<int32_t>(&value)) {
27 uint32_t typebits = 2;
28 output.write(reinterpret_cast<char*>(&typebits), 4);
29 output.write(reinterpret_cast<const char*>(pval), 4);
30 } else if (const std::string* pval = std::get_if<std::string>(&value)) {
31 uint32_t typebits = 4;
32 output.write(reinterpret_cast<char*>(&typebits), 4);
33
34 uint32_t lengthbits = pval->length();
35 output.write(reinterpret_cast<char*>(&lengthbits), 4);
36
37 output.write(pval->data(), pval->length());
38
39 if (pval->length() % 4 != 0) {
40 int padding = 4 - (pval->length() % 4);
41 for (int i = 0; i < padding; i++) {
42 output.put(0);
43 }
44 }
45 } else if (const std::map<GodotVariant, GodotVariant>* pval =
46 std::get_if<std::map<GodotVariant, GodotVariant>>(&value)) {
47 uint32_t typebits = 18;
48 output.write(reinterpret_cast<char*>(&typebits), 4);
49
50 uint32_t lengthbits = pval->size() & 0x7fffffff;
51 output.write(reinterpret_cast<char*>(&lengthbits), 4);
52
53 for (const auto& mapping : *pval) {
54 mapping.first.Serialize(output);
55 mapping.second.Serialize(output);
56 }
57 } else if (const std::vector<GodotVariant>* pval =
58 std::get_if<std::vector<GodotVariant>>(&value)) {
59 uint32_t typebits = 19;
60 output.write(reinterpret_cast<char*>(&typebits), 4);
61
62 uint32_t lengthbits = pval->size() & 0x7fffffff;
63 output.write(reinterpret_cast<char*>(&lengthbits), 4);
64
65 for (const auto& entry : *pval) {
66 entry.Serialize(output);
67 }
68 }
69}
diff --git a/generator/godot_variant.h b/generator/godot_variant.h new file mode 100644 index 0000000..6f2651a --- /dev/null +++ b/generator/godot_variant.h
@@ -0,0 +1,28 @@
1#ifndef GODOT_VARIANT_H_84682779
2#define GODOT_VARIANT_H_84682779
3
4#include <cctype>
5#include <map>
6#include <ostream>
7#include <string>
8#include <variant>
9#include <vector>
10
11struct GodotVariant {
12 using variant_type = std::variant<std::monostate, int32_t, std::string,
13 std::map<GodotVariant, GodotVariant>,
14 std::vector<GodotVariant> >;
15
16 variant_type value;
17
18 GodotVariant(int32_t v) : value(v) {}
19 GodotVariant(std::string v) : value(std::move(v)) {}
20 GodotVariant(std::map<GodotVariant, GodotVariant> v) : value(std::move(v)) {}
21 GodotVariant(std::vector<GodotVariant> v) : value(std::move(v)) {}
22
23 bool operator<(const GodotVariant& rhs) const;
24
25 void Serialize(std::basic_ostream<char>& output) const;
26};
27
28#endif /* end of include guard: GODOT_VARIANT_H_84682779 */