diff options
Diffstat (limited to 'Source/Randomizer.cpp')
-rw-r--r-- | Source/Randomizer.cpp | 286 |
1 files changed, 104 insertions, 182 deletions
diff --git a/Source/Randomizer.cpp b/Source/Randomizer.cpp index b8462e5..8c4297d 100644 --- a/Source/Randomizer.cpp +++ b/Source/Randomizer.cpp | |||
@@ -2,15 +2,10 @@ | |||
2 | * TODO: Split out main() logic into another file, and move into separate functions for easier testing. Then write tests. | 2 | * TODO: Split out main() logic into another file, and move into separate functions for easier testing. Then write tests. |
3 | * BUGS: | 3 | * BUGS: |
4 | * Shipwreck vault fails, possibly because of dot_reflection? Sometimes? | 4 | * Shipwreck vault fails, possibly because of dot_reflection? Sometimes? |
5 | * Treehouse pivots *should* work, but I need to not copy style_flags. | ||
6 | This seems to cause crashes when pivots appear elsewhere in the world. | ||
7 | * Some panels are impossible casually: (idc, I think) | 5 | * Some panels are impossible casually: (idc, I think) |
8 | ** Town Stars, Invisible dots | 6 | ** Town Stars, Invisible dots |
9 | * Something is wrong with jungle re: softlocks | ||
10 | * FEATURES: | 7 | * FEATURES: |
11 | * SWAP_TARGETS should still require the full panel sequence (and have ways to prevent softlocks?) | 8 | * SWAP_TARGETS should still require the full panel sequence (and have ways to prevent softlocks?) |
12 | ** Think about: Jungle | ||
13 | ** Hard: Monastery | ||
14 | ** Do: Challenge | 9 | ** Do: Challenge |
15 | * Randomize audio logs | 10 | * Randomize audio logs |
16 | * Swap sounds in jungle (along with panels) -- maybe impossible | 11 | * Swap sounds in jungle (along with panels) -- maybe impossible |
@@ -28,212 +23,139 @@ | |||
28 | #include <chrono> | 23 | #include <chrono> |
29 | 24 | ||
30 | template <class T> | 25 | template <class T> |
31 | size_t find(const std::vector<T> &data, T search, size_t startIndex = 0) { | 26 | int find(const std::vector<T> &data, T search, size_t startIndex = 0) { |
32 | for (size_t i=startIndex ; i<data.size(); i++) { | 27 | for (size_t i=startIndex ; i<data.size(); i++) { |
33 | if (data[i] == search) return i; | 28 | if (data[i] == search) return static_cast<int>(i); |
34 | } | 29 | } |
35 | std::cout << "Couldn't find " << search << " in data!" << std::endl; | 30 | std::cout << "Couldn't find " << search << " in data!" << std::endl; |
36 | exit(-1); | 31 | exit(-1); |
37 | } | 32 | } |
38 | 33 | ||
39 | void Randomizer::Randomize(int seed) | 34 | void Randomizer::Randomize() |
40 | { | 35 | { |
41 | // Content swaps -- must happen before squarePanels | 36 | // Content swaps -- must happen before squarePanels |
42 | Randomize(tallUpDownPanels, SWAP_LINES | SWAP_STYLE); | 37 | _core.Randomize(tallUpDownPanels, SWAP_LINES|SWAP_LINES); |
43 | Randomize(upDownPanels, SWAP_LINES | SWAP_STYLE); | 38 | _core.Randomize(upDownPanels, SWAP_LINES|SWAP_LINES); |
44 | Randomize(leftForwardRightPanels, SWAP_LINES); | 39 | _core.Randomize(leftForwardRightPanels, SWAP_LINES|SWAP_LINES); |
45 | 40 | ||
46 | Randomize(squarePanels, SWAP_LINES | SWAP_STYLE); | 41 | _core.Randomize(squarePanels, SWAP_LINES|SWAP_LINES); |
47 | 42 | ||
48 | // Frame swaps -- must happen after squarePanels | 43 | // Individual area modifications |
49 | Randomize(burnablePanels, SWAP_LINES | SWAP_STYLE); | 44 | RandomizeTutorial(); |
45 | RandomizeSymmetry(); | ||
46 | RandomizeDesert(); | ||
47 | RandomizeQuarry(); | ||
48 | RandomizeTreehouse(); | ||
49 | RandomizeKeep(); | ||
50 | RandomizeShadows(); | ||
51 | RandomizeTown(); | ||
52 | RandomizeMonastery(); | ||
53 | RandomizeBunker(); | ||
54 | RandomizeJungle(); | ||
55 | RandomizeSwamp(); | ||
56 | RandomizeMountain(); | ||
57 | } | ||
50 | 58 | ||
51 | // Target swaps, can happen whenever | 59 | void Randomizer::RandomizeTutorial() { |
52 | Randomize(lasers, SWAP_TARGETS); | 60 | // Disable tutorial cursor speed modifications (not working?) |
53 | // Read the target of keep front laser, and write it to keep back laser. | 61 | _core.WritePanelData<float>(0x00295, CURSOR_SPEED_SCALE, {1.0}); |
54 | std::vector<int> keepFrontLaserTarget = ReadPanelData<int>(0x0360E, TARGET, 1); | 62 | _core.WritePanelData<float>(0x0C373, CURSOR_SPEED_SCALE, {1.0}); |
55 | WritePanelData<int>(0x03317, TARGET, keepFrontLaserTarget); | 63 | _core.WritePanelData<float>(0x00293, CURSOR_SPEED_SCALE, {1.0}); |
64 | _core.WritePanelData<float>(0x002C2, CURSOR_SPEED_SCALE, {1.0}); | ||
65 | } | ||
56 | 66 | ||
57 | std::vector<int> randomOrder; | 67 | void Randomizer::RandomizeSymmetry() { |
68 | } | ||
58 | 69 | ||
59 | /* Jungle | 70 | void Randomizer::RandomizeDesert() { |
60 | randomOrder = std::vector(junglePanels.size(), 0); | 71 | _core.Randomize(desertPanels, SWAP_LINES|SWAP_LINES); |
61 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
62 | // Randomize Waves 2-7 | ||
63 | // Waves 1 cannot be randomized, since no other panel can start on | ||
64 | randomizer.RandomizeRange(randomOrder, SWAP_NONE, 1, 7); | ||
65 | // Randomize Pitches 1-6 onto themselves | ||
66 | randomizer.RandomizeRange(randomOrder, SWAP_NONE, 7, 13); | ||
67 | randomizer.ReassignTargets(junglePanels, randomOrder); | ||
68 | */ | ||
69 | 72 | ||
70 | /* Bunker */ | 73 | // Turn off desert surface 8 |
71 | randomOrder = std::vector(bunkerPanels.size(), 0); | 74 | _core.WritePanelData<float>(0x09F94, POWER, {0.0, 0.0}); |
72 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | 75 | // Turn off desert flood final |
73 | // Randomize Tutorial 2-Advanced Tutorial 4 + Glass 1 | 76 | _core.WritePanelData<float>(0x18076, POWER, {0.0, 0.0}); |
74 | // Tutorial 1 cannot be randomized, since no other panel can start on | 77 | // Change desert floating target to desert flood final |
75 | // Glass 1 will become door + glass 1, due to the targetting system | 78 | _core.WritePanelData<int>(0x17ECA, TARGET, {0x18077}); |
76 | RandomizeRange(randomOrder, SWAP_NONE, 1, 10); | 79 | } |
77 | // Randomize Glass 1-3 into everything after the door | ||
78 | const size_t glassDoorIndex = find(randomOrder, 9) + 1; | ||
79 | RandomizeRange(randomOrder, SWAP_NONE, glassDoorIndex, 12); | ||
80 | ReassignTargets(bunkerPanels, randomOrder); | ||
81 | |||
82 | /* Shadows */ | ||
83 | randomOrder = std::vector(shadowsPanels.size(), 0); | ||
84 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
85 | RandomizeRange(randomOrder, SWAP_NONE, 0, 8); // Tutorial | ||
86 | RandomizeRange(randomOrder, SWAP_NONE, 8, 16); // Avoid | ||
87 | RandomizeRange(randomOrder, SWAP_NONE, 16, 21); // Follow | ||
88 | ReassignTargets(shadowsPanels, randomOrder); | ||
89 | // Turn off original starting panel | ||
90 | WritePanelData<float>(shadowsPanels[0], POWER, {0.0f, 0.0f}); | ||
91 | // Turn on new starting panel | ||
92 | WritePanelData<float>(shadowsPanels[randomOrder[0]], POWER, {1.0f, 1.0f}); | ||
93 | 80 | ||
94 | /* Monastery | 81 | void Randomizer::RandomizeQuarry() { |
95 | randomOrder = std::vector(monasteryPanels.size(), 0); | 82 | } |
96 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
97 | randomizer.RandomizeRange(randomOrder, SWAP_NONE, 2, 6); // outer 2 & 3, inner 1 | ||
98 | // Once outer 3 and right door are solved, inner 2-4 are accessible | ||
99 | int innerPanelsIndex = max(find(randomOrder, 2), find(randomOrder, 4)); | ||
100 | randomizer.RandomizeRange(randomOrder, SWAP_NONE, innerPanelsIndex, 9); // Inner 2-4 | ||
101 | 83 | ||
102 | randomizer.ReassignTargets(monasteryPanels, randomOrder); | 84 | void Randomizer::RandomizeTreehouse() { |
103 | */ | 85 | // Ensure that whatever pivot panels we have are flagged as "pivotable" |
86 | _core.WritePanelData<int>(0x17DD1, STYLE_FLAGS, {0x8000}); | ||
87 | _core.WritePanelData<int>(0x17CE3, STYLE_FLAGS, {0x8000}); | ||
88 | _core.WritePanelData<int>(0x17DB7, STYLE_FLAGS, {0x8000}); | ||
89 | _core.WritePanelData<int>(0x17E52, STYLE_FLAGS, {0x8000}); | ||
104 | } | 90 | } |
105 | 91 | ||
106 | Randomizer::Randomizer() | 92 | void Randomizer::RandomizeKeep() { |
107 | { | 93 | } |
108 | // Turn off desert surface 8 | ||
109 | WritePanelData<float>(0x09F94, POWER, {0.0, 0.0}); | ||
110 | // Turn off desert flood final | ||
111 | WritePanelData<float>(0x18076, POWER, {0.0, 0.0}); | ||
112 | // Change desert floating target to desert flood final | ||
113 | WritePanelData<int>(0x17ECA, TARGET, {0x18077}); | ||
114 | 94 | ||
95 | void Randomizer::RandomizeShadows() { | ||
115 | // Distance-gate shadows laser to prevent sniping through the bars | 96 | // Distance-gate shadows laser to prevent sniping through the bars |
116 | WritePanelData<float>(0x19650, MAX_BROADCAST_DISTANCE, {2.5}); | 97 | _core.WritePanelData<float>(0x19650, MAX_BROADCAST_DISTANCE, {2.5}); |
117 | // Change the shadows tutorial cable to only activate avoid | 98 | // Change the shadows tutorial cable to only activate avoid |
118 | WritePanelData<int>(0x319A8, CABLE_TARGET_2, {0}); | 99 | _core.WritePanelData<int>(0x319A8, CABLE_TARGET_2, {0}); |
119 | // Change shadows avoid 8 to power shadows follow | 100 | // Change shadows avoid 8 to power shadows follow |
120 | WritePanelData<int>(0x1972F, TARGET, {0x1C34C}); | 101 | _core.WritePanelData<int>(0x1972F, TARGET, {0x1C34C}); |
121 | |||
122 | // Distance-gate swamp snipe 1 to prevent RNG swamp snipe | ||
123 | WritePanelData<float>(0x17C05, MAX_BROADCAST_DISTANCE, {5.0}); | ||
124 | 102 | ||
125 | // Disable tutorial cursor speed modifications (not working?) | 103 | std::vector<int> randomOrder(shadowsPanels.size(), 0); |
126 | WritePanelData<float>(0x00295, CURSOR_SPEED_SCALE, {1.0}); | 104 | std::iota(randomOrder.begin(), randomOrder.end(), 0); |
127 | WritePanelData<float>(0x0C373, CURSOR_SPEED_SCALE, {1.0}); | 105 | _core.RandomizeRange(randomOrder, SWAP_NONE, 0, 8); // Tutorial |
128 | WritePanelData<float>(0x00293, CURSOR_SPEED_SCALE, {1.0}); | 106 | _core.RandomizeRange(randomOrder, SWAP_NONE, 8, 16); // Avoid |
129 | WritePanelData<float>(0x002C2, CURSOR_SPEED_SCALE, {1.0}); | 107 | _core.RandomizeRange(randomOrder, SWAP_NONE, 16, 21); // Follow |
108 | _core.ReassignTargets(shadowsPanels, randomOrder); | ||
109 | // Turn off original starting panel | ||
110 | _core.WritePanelData<float>(shadowsPanels[0], POWER, {0.0f, 0.0f}); | ||
111 | // Turn on new starting panel | ||
112 | _core.WritePanelData<float>(shadowsPanels[randomOrder[0]], POWER, {1.0f, 1.0f}); | ||
130 | } | 113 | } |
131 | 114 | ||
132 | void Randomizer::Randomize(std::vector<int>& panels, int flags) { | 115 | void Randomizer::RandomizeTown() { |
133 | return RandomizeRange(panels, flags, 0, panels.size()); | ||
134 | } | 116 | } |
135 | 117 | ||
136 | // Range is [start, end) | 118 | void Randomizer::RandomizeMonastery() { |
137 | void Randomizer::RandomizeRange(std::vector<int> &panels, int flags, size_t startIndex, size_t endIndex) { | 119 | std::vector<int> randomOrder(monasteryPanels.size(), 0); |
138 | if (panels.size() == 0) return; | 120 | std::iota(randomOrder.begin(), randomOrder.end(), 0); |
139 | if (startIndex >= endIndex) return; | 121 | _core.RandomizeRange(randomOrder, SWAP_NONE, 3, 9); // Outer 2 & 3, Inner 1-4 |
140 | if (endIndex >= panels.size()) endIndex = panels.size(); | 122 | _core.ReassignTargets(monasteryPanels, randomOrder); |
141 | for (size_t i = endIndex-1; i > startIndex+1; i--) { | ||
142 | const size_t target = rand() % (i - startIndex) + startIndex; | ||
143 | if (i != target) { | ||
144 | // std::cout << "Swapping panels " << std::hex << panels[i] << " and " << std::hex << panels[target] << std::endl; | ||
145 | SwapPanels(panels[i], panels[target], flags); | ||
146 | std::swap(panels[i], panels[target]); // Panel indices in the array | ||
147 | } | ||
148 | } | ||
149 | } | 123 | } |
150 | 124 | ||
151 | void Randomizer::SwapPanels(int panel1, int panel2, int flags) { | 125 | void Randomizer::RandomizeBunker() { |
152 | std::map<int, int> offsets; | 126 | std::vector<int> randomOrder(bunkerPanels.size(), 0); |
127 | std::iota(randomOrder.begin(), randomOrder.end(), 0); | ||
128 | // Randomize Tutorial 2-Advanced Tutorial 4 + Glass 1 | ||
129 | // Tutorial 1 cannot be randomized, since no other panel can start on | ||
130 | // Glass 1 will become door + glass 1, due to the targetting system | ||
131 | _core.RandomizeRange(randomOrder, SWAP_NONE, 1, 10); | ||
132 | // Randomize Glass 1-3 into everything after the door/glass 1 | ||
133 | const size_t glass1Index = find(randomOrder, 9); | ||
134 | _core.RandomizeRange(randomOrder, SWAP_NONE, glass1Index + 1, 12); | ||
135 | _core.ReassignTargets(bunkerPanels, randomOrder); | ||
136 | } | ||
153 | 137 | ||
154 | if (flags & SWAP_TARGETS) { | 138 | void Randomizer::RandomizeJungle() { |
155 | offsets[TARGET] = sizeof(int); | 139 | std::vector<int> randomOrder(junglePanels.size(), 0); |
156 | } | 140 | std::iota(randomOrder.begin(), randomOrder.end(), 0); |
157 | if (flags & SWAP_STYLE) { | 141 | // Randomize Waves 2-7 |
158 | offsets[STYLE_FLAGS] = sizeof(int); | 142 | // Waves 1 cannot be randomized, since no other panel can start on |
159 | } | 143 | _core.RandomizeRange(randomOrder, SWAP_NONE, 1, 7); |
160 | if (flags & SWAP_LINES) { | 144 | // Randomize Pitches 1-6 onto themselves |
161 | offsets[PATH_COLOR] = 16; | 145 | _core.RandomizeRange(randomOrder, SWAP_NONE, 8, 13); |
162 | offsets[REFLECTION_PATH_COLOR] = 16; | 146 | _core.ReassignTargets(junglePanels, randomOrder); |
163 | offsets[DOT_COLOR] = 16; | 147 | } |
164 | offsets[ACTIVE_COLOR] = 16; | ||
165 | offsets[BACKGROUND_REGION_COLOR] = 16; | ||
166 | offsets[SUCCESS_COLOR_A] = 16; | ||
167 | offsets[SUCCESS_COLOR_B] = 16; | ||
168 | offsets[STROBE_COLOR_A] = 16; | ||
169 | offsets[STROBE_COLOR_B] = 16; | ||
170 | offsets[ERROR_COLOR] = 16; | ||
171 | offsets[PATTERN_POINT_COLOR] = 16; | ||
172 | offsets[PATTERN_POINT_COLOR_A] = 16; | ||
173 | offsets[PATTERN_POINT_COLOR_B] = 16; | ||
174 | offsets[SYMBOL_A] = 16; | ||
175 | offsets[SYMBOL_B] = 16; | ||
176 | offsets[SYMBOL_C] = 16; | ||
177 | offsets[SYMBOL_D] = 16; | ||
178 | offsets[SYMBOL_E] = 16; | ||
179 | offsets[PUSH_SYMBOL_COLORS] = sizeof(int); | ||
180 | offsets[OUTER_BACKGROUND] = 16; | ||
181 | offsets[OUTER_BACKGROUND_MODE] = sizeof(int); | ||
182 | offsets[TRACED_EDGES] = 16; | ||
183 | offsets[AUDIO_PREFIX] = sizeof(void*); | ||
184 | // offsets[IS_CYLINDER] = sizeof(int); | ||
185 | // offsets[CYLINDER_Z0] = sizeof(float); | ||
186 | // offsets[CYLINDER_Z1] = sizeof(float); | ||
187 | // offsets[CYLINDER_RADIUS] = sizeof(float); | ||
188 | offsets[SPECULAR_ADD] = sizeof(float); | ||
189 | offsets[SPECULAR_POWER] = sizeof(int); | ||
190 | offsets[PATH_WIDTH_SCALE] = sizeof(float); | ||
191 | offsets[STARTPOINT_SCALE] = sizeof(float); | ||
192 | offsets[NUM_DOTS] = sizeof(int); | ||
193 | offsets[NUM_CONNECTIONS] = sizeof(int); | ||
194 | offsets[DOT_POSITIONS] = sizeof(void*); | ||
195 | offsets[DOT_FLAGS] = sizeof(void*); | ||
196 | offsets[DOT_CONNECTION_A] = sizeof(void*); | ||
197 | offsets[DOT_CONNECTION_B] = sizeof(void*); | ||
198 | offsets[DECORATIONS] = sizeof(void*); | ||
199 | offsets[DECORATION_FLAGS] = sizeof(void*); | ||
200 | offsets[DECORATION_COLORS] = sizeof(void*); | ||
201 | offsets[NUM_DECORATIONS] = sizeof(int); | ||
202 | offsets[REFLECTION_DATA] = sizeof(void*); | ||
203 | offsets[GRID_SIZE_X] = sizeof(int); | ||
204 | offsets[GRID_SIZE_Y] = sizeof(int); | ||
205 | offsets[SEQUENCE_LEN] = sizeof(int); | ||
206 | offsets[SEQUENCE] = sizeof(void*); | ||
207 | offsets[DOT_SEQUENCE_LEN] = sizeof(int); | ||
208 | offsets[DOT_SEQUENCE] = sizeof(void*); | ||
209 | offsets[DOT_SEQUENCE_LEN_REFLECTION] = sizeof(int); | ||
210 | offsets[DOT_SEQUENCE_REFLECTION] = sizeof(void*); | ||
211 | offsets[NUM_COLORED_REGIONS] = sizeof(int); | ||
212 | offsets[COLORED_REGIONS] = sizeof(void*); | ||
213 | offsets[PANEL_TARGET] = sizeof(void*); | ||
214 | offsets[SPECULAR_TEXTURE] = sizeof(void*); | ||
215 | } | ||
216 | 148 | ||
217 | for (auto const& [offset, size] : offsets) { | 149 | void Randomizer::RandomizeSwamp() { |
218 | std::vector<byte> panel1data = ReadPanelData<byte>(panel1, offset, size); | 150 | // Distance-gate swamp snipe 1 to prevent RNG swamp snipe |
219 | std::vector<byte> panel2data = ReadPanelData<byte>(panel2, offset, size); | 151 | _core.WritePanelData<float>(0x17C05, MAX_BROADCAST_DISTANCE, {15.0}); |
220 | WritePanelData<byte>(panel2, offset, panel1data); | ||
221 | WritePanelData<byte>(panel1, offset, panel2data); | ||
222 | } | ||
223 | } | 152 | } |
224 | 153 | ||
225 | void Randomizer::ReassignTargets(const std::vector<int>& panels, const std::vector<int>& order) { | 154 | void Randomizer::RandomizeMountain() { |
226 | // This list is offset by 1, so the target of the Nth panel is in position N (aka the N+1th element) | 155 | _core.Randomize(lasers, SWAP_TARGETS); |
227 | // The first panel may not have a wire to power it, so we use the panel ID itself. | 156 | _core.Randomize(pillars, SWAP_LINES|SWAP_LINES); |
228 | std::vector<int> targetToActivatePanel = {panels[0] + 1}; | ||
229 | for (const int panel : panels) { | ||
230 | int target = ReadPanelData<int>(panel, TARGET, 1)[0]; | ||
231 | targetToActivatePanel.push_back(target); | ||
232 | } | ||
233 | 157 | ||
234 | for (size_t i=0; i<order.size() - 1; i++) { | 158 | // Read the target of keep front laser, and write it to keep back laser. |
235 | // Set the target of order[i] to order[i+1], using the "real" target as determined above. | 159 | std::vector<int> keepFrontLaserTarget = _core.ReadPanelData<int>(0x0360E, TARGET, 1); |
236 | const int panelTarget = targetToActivatePanel[order[i+1]]; | 160 | _core.WritePanelData<int>(0x03317, TARGET, keepFrontLaserTarget); |
237 | WritePanelData<int>(panels[order[i]], TARGET, {panelTarget}); | 161 | } \ No newline at end of file |
238 | } | ||
239 | } | ||