1use rand::distributions::{Distribution, WeightedIndex};
9use rand::{Rng, SeedableRng};
10use rand_xoshiro::Xoshiro256PlusPlus;
11
12#[derive(Clone, Copy)]
13struct Direction(i32, i32);
14
15const DIRECTIONS: [Direction; 8] = [
16 Direction(1, 0), Direction(1, -1), Direction(0, -1), Direction(-1, -1), Direction(-1, 0), Direction(-1, 1), Direction(0, 1), Direction(1, 1), ];
25
26impl Direction {
27 fn to_angle(self) -> f64 {
28 match self {
29 Direction(1, 0) => 0.00,
30 Direction(1, -1) => 0.25,
31 Direction(0, -1) => 0.50,
32 Direction(-1, -1) => 0.75,
33 Direction(-1, 0) => 1.00,
34 Direction(-1, 1) => 1.25,
35 Direction(0, 1) => 1.50,
36 Direction(1, 1) => 1.75,
37 Direction(x, y) => {
38 (f64::atan2(y as f64, x as f64) - f64::atan2(0.00, 1.00)) / std::f64::consts::PI
39 }
40 }
41 }
42}
43
44fn angle_distance(a: f64, b: f64) -> f64 {
45 f64::min((a - b).abs(), 2.00 - (a - b).abs())
46}
47
48fn angle_to_direction_weights(angle: f64) -> [f64; 8] {
49 let weights = DIRECTIONS.map(|d| 2.00 - angle_distance(angle, d.to_angle()));
50 let least_likely = weights.iter().min_by(|a, b| a.total_cmp(b)).unwrap();
51 weights.map(|w| w - least_likely)
52}
53
54fn choose_direction(angle: f64, rng: &mut Xoshiro256PlusPlus) -> Direction {
55 let dist = WeightedIndex::new(angle_to_direction_weights(angle)).unwrap();
56 DIRECTIONS[dist.sample(rng)]
57}
58
59fn angle_displace_random(angle: f64, divergence: f64, rng: &mut Xoshiro256PlusPlus) -> f64 {
60 (angle + rng.gen_range(-divergence..divergence)).clamp(0.00, 2.00)
61}
62
63#[derive(Clone, Copy, Debug)]
64struct Position(usize, usize);
65
66impl Position {
67 fn move_in(&self, dir: Direction, size: usize) -> Self {
68 let x = ((self.0 as i32) + dir.0).clamp(0, (size - 1) as i32) as usize;
69 let y = ((self.1 as i32) + dir.1).clamp(0, (size - 1) as i32) as usize;
70 Self(x, y)
71 }
72}
73
74#[derive(Clone, Copy, Debug, PartialEq)]
75enum WalkerKind {
76 Short,
77 Long,
78}
79
80#[derive(Clone, Copy, Debug)]
81struct AngledRandomWalker {
82 kind: WalkerKind,
83 age: usize,
84 cumulative_age: usize,
85 generation: usize,
86 position: Position,
87 angle: f64,
88}
89
90pub enum Paint {
92 Age,
94 CumulativeAge,
96 Generation,
98 Constant,
100}
101
102pub enum InitialWalkers {
104 CardinalsAndOrdinals,
106 Custom(Vec<f64>),
108}
109
110pub struct SimulationParams {
112 pub size: usize,
114 pub max_long_age: usize,
116 pub max_short_age: usize,
118 pub max_generations: usize,
120 pub children: usize,
122 pub max_long_angle_divergence: f64,
124 pub max_short_angle_divergence: f64,
126 pub short_branch_frequency: usize,
128 pub paint: Paint,
130 pub initial_walkers: InitialWalkers,
132 pub seed: u64,
134}
135
136struct ShortWalkerSpawn {
137 position: Position,
138 angle: f64,
139 generation: usize,
140 cumulative_age: usize,
141}
142
143pub fn simulate(params: SimulationParams) -> Vec<Vec<u16>> {
145 let mut grid = vec![vec![0u16; params.size]; params.size];
146 let mut walkers: Vec<AngledRandomWalker> = match params.initial_walkers {
147 InitialWalkers::CardinalsAndOrdinals => (0..8)
148 .map(|n| AngledRandomWalker {
149 kind: WalkerKind::Long,
150 age: 0,
151 cumulative_age: 0,
152 generation: 0,
153 position: Position(params.size / 2, params.size / 2),
154 angle: 0.25 * n as f64,
155 })
156 .collect(),
157 InitialWalkers::Custom(angles) => angles
158 .into_iter()
159 .map(|angle| AngledRandomWalker {
160 kind: WalkerKind::Long,
161 age: 0,
162 cumulative_age: 0,
163 generation: 0,
164 position: Position(params.size / 2, params.size / 2),
165 angle: angle.clamp(0.00, 2.00),
166 })
167 .collect(),
168 };
169 let mut rng_direction_chooser = Xoshiro256PlusPlus::seed_from_u64(params.seed);
170 let mut rng_short_diverger = Xoshiro256PlusPlus::seed_from_u64(params.seed);
171 let mut rng_long_diverger = Xoshiro256PlusPlus::seed_from_u64(params.seed);
172 let mut short_walker_spawns: Vec<ShortWalkerSpawn> = Vec::new();
173 while !walkers.is_empty() {
174 let mut next_walkers: Vec<AngledRandomWalker> = Vec::new();
175 for walker in &walkers {
176 let max_age = match walker.kind {
177 WalkerKind::Short => params.max_short_age,
178 WalkerKind::Long => params.max_long_age,
179 };
180 if walker.age % params.short_branch_frequency == 0
181 && walker.kind != WalkerKind::Short
182 && walker.generation < params.max_generations
183 {
184 short_walker_spawns.push(ShortWalkerSpawn {
185 position: walker.position,
186 angle: walker.angle,
187 generation: walker.generation + 1,
188 cumulative_age: walker.cumulative_age + walker.age,
189 });
190 }
191 if walker.age > max_age {
192 if walker.generation < params.max_generations && walker.kind != WalkerKind::Short {
193 for _ in 0..params.children {
194 next_walkers.push(AngledRandomWalker {
195 kind: WalkerKind::Long,
196 age: 0,
197 cumulative_age: walker.cumulative_age + walker.age,
198 generation: walker.generation + 1,
199 position: walker.position,
200 angle: angle_displace_random(
201 walker.angle,
202 params.max_long_angle_divergence.clamp(0.00, 2.00),
203 &mut rng_long_diverger,
204 ),
205 });
206 }
207 }
208 } else {
209 let Position(x, y) = walker.position.move_in(
210 choose_direction(walker.angle, &mut rng_direction_chooser),
211 params.size,
212 );
213
214 grid[y][x] = match params.paint {
215 Paint::Age => walker.age + 1,
216 Paint::CumulativeAge => walker.cumulative_age + walker.age + 1,
217 Paint::Generation => walker.generation + 1,
218 Paint::Constant => 1,
219 } as u16;
220 next_walkers.push(AngledRandomWalker {
221 kind: walker.kind,
222 age: walker.age + 1,
223 cumulative_age: walker.cumulative_age,
224 generation: walker.generation,
225 position: Position(x, y),
226 angle: walker.angle,
227 });
228 }
229 }
230 walkers = next_walkers;
231 }
232 let mut short_walkers: Vec<AngledRandomWalker> = short_walker_spawns
233 .iter()
234 .map(
235 |ShortWalkerSpawn {
236 position,
237 angle,
238 generation,
239 cumulative_age,
240 }| AngledRandomWalker {
241 kind: WalkerKind::Short,
242 age: 0,
243 cumulative_age: *cumulative_age,
244 generation: *generation,
245 position: *position,
246 angle: angle_displace_random(
247 *angle,
248 params.max_short_angle_divergence,
249 &mut rng_short_diverger,
250 ),
251 },
252 )
253 .collect();
254 while !short_walkers.is_empty() {
255 let mut next_short_walkers: Vec<AngledRandomWalker> = Vec::new();
256 for walker in &short_walkers {
257 if walker.age <= params.max_short_age {
258 let Position(x, y) = walker.position.move_in(
259 choose_direction(walker.angle, &mut rng_direction_chooser),
260 params.size,
261 );
262
263 grid[y][x] = match params.paint {
264 Paint::Age => walker.age + 1,
265 Paint::CumulativeAge => walker.cumulative_age + walker.age + 1,
266 Paint::Generation => walker.generation + 1,
267 Paint::Constant => 1,
268 } as u16;
269 next_short_walkers.push(AngledRandomWalker {
270 kind: WalkerKind::Short,
271 age: walker.age + 1,
272 cumulative_age: walker.cumulative_age,
273 generation: walker.generation,
274 position: Position(x, y),
275 angle: walker.angle,
276 });
277 }
278 }
279 short_walkers = next_short_walkers;
280 }
281 grid
282}