diff options
-rw-r--r-- | generator/CMakeLists.txt | 10 | ||||
-rw-r--r-- | generator/generator.cpp | 159 | ||||
-rw-r--r-- | generator/godot_variant.cpp | 69 | ||||
-rw-r--r-- | generator/godot_variant.h | 28 |
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 @@ | |||
1 | cmake_minimum_required (VERSION 3.1) | 1 | cmake_minimum_required (VERSION 3.1) |
2 | project (generator) | 2 | project (generator) |
3 | 3 | ||
4 | find_package(PkgConfig) | ||
5 | pkg_check_modules(libzstd libzstd REQUIRED) | ||
6 | |||
4 | add_subdirectory(vendor/fmt) | 7 | add_subdirectory(vendor/fmt) |
5 | 8 | ||
6 | include_directories(vendor/hkutil vendor/fmt/include) | 9 | include_directories(vendor/hkutil vendor/fmt/include ${libzstd_INCLUDE_DIRS}) |
10 | link_directories(${libzstd_LIBRARY_DIRS}) | ||
7 | 11 | ||
8 | add_executable(generator generator.cpp main.cpp) | 12 | add_executable(generator generator.cpp main.cpp godot_variant.cpp) |
9 | set_property(TARGET generator PROPERTY CXX_STANDARD 17) | 13 | set_property(TARGET generator PROPERTY CXX_STANDARD 17) |
10 | set_property(TARGET generator PROPERTY CXX_STANDARD_REQUIRED ON) | 14 | set_property(TARGET generator PROPERTY CXX_STANDARD_REQUIRED ON) |
11 | target_link_libraries(generator fmt) | 15 | target_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 | |||
19 | constexpr int MIN_FREQUENCY = 2000000; | 23 | constexpr int MIN_FREQUENCY = 2000000; |
24 | constexpr int kCompressionLevel = 3; | ||
20 | 25 | ||
21 | namespace { | 26 | namespace { |
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 | ||
1077 | size_t generator::LookupOrCreatePronunciation(const std::string& phonemes) { | 1144 | size_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 | |||
5 | bool 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 | |||
25 | void 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 | |||
11 | struct 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 */ | ||