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