wave_function_collapse/abstractions/
proximity_graph.rs

1// this abstraction is a web of nodes that have a center and specific states are expected to be closer to each other than further away
2// you can imagine a game needing points of interest that are nearby each other - you would not want quest-adjacent locations to be physically distant
3
4use std::collections::HashMap;
5use std::hash::Hash;
6use serde::{Deserialize, Serialize};
7use crate::wave_function::collapsable_wave_function::collapsable_wave_function::CollapsableWaveFunction;
8use crate::wave_function::collapsable_wave_function::sequential_collapsable_wave_function::SequentialCollapsableWaveFunction;
9use crate::wave_function::{Node, NodeStateCollection, NodeStateProbability, WaveFunction};
10
11pub struct Distance {
12    // the center of the point that the values are quantifiable
13    center: f32,
14    // the distance from the center that they are reasonably still the same
15    width: f32,
16}
17
18impl Distance {
19    pub fn new(center: f32, width: f32) -> Self {
20        Self {
21            center,
22            width,
23        }
24    }
25}
26
27pub enum Proximity {
28    // this indicates that more than one cannot exist
29    ExclusiveExistence,
30    // the values are different from each other in a quantifiable way
31    SomeDistanceAway {
32        // the distance between one thing and another
33        distance: Distance,
34    },
35    // the values are not related at all and are unquantifiably different
36    InAnotherDimensionEntirely,
37}
38
39#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Serialize)]
40enum NodeState<TValue>
41where
42    TValue: HasProximity,
43{
44    // the states that make up the web states
45    Primary {
46        state: TValue,
47    },
48    // the states that ensure that there is exactly one instance of the primary state equivalent
49    Secondary {
50        state: TValue,
51        node_index: usize,
52    },
53}
54
55impl<'de, TValue> Deserialize<'de> for NodeState<TValue>
56where
57    TValue: HasProximity, // Ensure TValue implements Deserialize
58{
59    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60    where
61        D: serde::Deserializer<'de>,
62    {
63        // Define a visitor struct that will help us with deserialization
64        struct NodeStateVisitor<TValue>(std::marker::PhantomData<TValue>);
65
66        // Implement Visitor for NodeStateVisitor
67        impl<'de, TValue> serde::de::Visitor<'de> for NodeStateVisitor<TValue>
68        where
69            TValue: HasProximity,
70        {
71            type Value = NodeState<TValue>;
72
73            fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
74                formatter.write_str("a valid NodeState variant")
75            }
76
77            fn visit_map<V>(self, mut map: V) -> Result<Self::Value, V::Error>
78            where
79                V: serde::de::MapAccess<'de>,
80            {
81                let mut state: Option<TValue> = None;
82                let mut node_index: Option<usize> = None;
83
84                // Iterate over the key-value pairs in the map
85                while let Some(key) = map.next_key::<String>()? {
86                    match key.as_str() {
87                        "state" => {
88                            if state.is_some() {
89                                return Err(serde::de::Error::duplicate_field("state"));
90                            }
91                            state = Some(map.next_value()?);
92                        }
93                        "node_index" => {
94                            if node_index.is_some() {
95                                return Err(serde::de::Error::duplicate_field("node_index"));
96                            }
97                            node_index = Some(map.next_value()?);
98                        }
99                        _ => {
100                            return Err(serde::de::Error::unknown_field(&key, &["state", "node_index"]));
101                        }
102                    }
103                }
104
105                // Determine which variant to return based on the presence of fields
106                match (state, node_index) {
107                    (Some(state), None) => Ok(NodeState::Primary { state }),
108                    (Some(state), Some(node_index)) => Ok(NodeState::Secondary { state, node_index }),
109                    _ => Err(serde::de::Error::missing_field("state")),
110                }
111            }
112        }
113
114        // Call the deserializer with the visitor
115        deserializer.deserialize_struct(
116            "NodeState",
117            &["state", "node_index"],
118            NodeStateVisitor(std::marker::PhantomData),
119        )
120    }
121}
122
123pub trait HasProximity: Eq + Hash + Clone + std::fmt::Debug + Ord + Serialize + for<'de> Deserialize<'de> {
124    fn get_proximity(&self, other: &Self) -> Proximity where Self: Sized;
125}
126
127#[derive(std::fmt::Debug, Clone)]
128pub struct ProximityGraphNode<T: Clone> {
129    proximity_graph_node_id: String,
130    distance_per_proximity_graph_node_id: HashMap<String, f32>,
131    tag: T,
132}
133
134impl<T: Clone> ProximityGraphNode<T> {
135    pub fn new(proximity_graph_node_id: String, distance_per_proximity_graph_node_id: HashMap<String, f32>, tag: T) -> Self {
136        Self {
137            proximity_graph_node_id,
138            distance_per_proximity_graph_node_id,
139            tag,
140        }
141    }
142    pub fn get_id(&self) -> &String {
143        &self.proximity_graph_node_id
144    }
145    pub fn get_tag(&self) -> &T {
146        &self.tag
147    }
148}
149
150#[derive(std::fmt::Debug, Clone)]
151pub enum ProximityGraphError {
152    FailedToMapValuesToNodesAtAnyDistance,
153    TestError,
154}
155
156pub struct ProximityGraph<T: Clone> {
157    nodes: Vec<ProximityGraphNode<T>>,
158}
159
160impl<T: Clone> ProximityGraph<T> {
161    pub fn new(nodes: Vec<ProximityGraphNode<T>>) -> Self {
162        Self {
163            nodes,
164        }
165    }
166    pub fn get_value_per_proximity_graph_node_id<TValue: HasProximity>(&self, values: Vec<TValue>, maximum_acceptable_distance_variance_factor: f32, acceptable_distance_variance_factor_difference: f32) -> Result<HashMap<String, TValue>, ProximityGraphError> {
167
168        // iterate over the construction and collapsing of the wave function until the best solution is found
169        // first start with the maximum distance being acceptable to ensure that the values can collapse at all
170        // if they can collapse, then begin to binary-search for the optimal configuration by restricting what is an acceptable maximum proximity
171        //      ex: divide in half first, too low? then make it 75% of original maximum, still too low? make it between 75%-100%, etc.
172
173        let mut distance_variance_factor = 0.0;
174        let mut distance_variance_factor_minimum = 0.0;
175        let mut distance_variance_factor_maximum = 0.0;
176        let mut best_collapsed_wave_function = None;
177        let mut is_distance_variance_factor_acceptable = false;
178        let mut iterations = 0;
179        while best_collapsed_wave_function.is_none() || !is_distance_variance_factor_acceptable {
180            //{
181            //    let best_is_what = if best_collapsed_wave_function.is_some() {
182            //        "some"
183            //    }
184            //    else {
185            //        "none"
186            //    };
187            //    println!("best is {} from {} to {} while at {}", best_is_what, distance_variance_factor_minimum, distance_variance_factor_maximum, distance_variance_factor);
188            //}
189            let primary_node_state_ratio_per_node_state_id = {
190                let node_state_ids = values.iter()
191                    .map(|value| {
192                        NodeState::Primary {
193                            state: value.clone(),
194                        }
195                    })
196                    .collect::<Vec<NodeState<TValue>>>();
197                NodeStateProbability::get_equal_probability(&node_state_ids)
198            };
199
200            let (nodes, node_state_collections) = {
201                let mut nodes = Vec::new();
202                let mut node_state_collections = Vec::new();
203
204                // create primary nodes
205                for proximity_graph_node in self.nodes.iter() {
206                    // setup the NodeStateCollections per neighbor
207                    let mut node_state_collection_ids_per_neighbor_node_id: HashMap<String, Vec<String>> = HashMap::new();
208                    for (neighbor_proximity_graph_node_id, neighbor_distance) in proximity_graph_node.distance_per_proximity_graph_node_id.iter() {
209                        let neighbor_distance = *neighbor_distance;
210
211                        let mut node_state_collection_ids: Vec<String> = Vec::new();
212                        if &proximity_graph_node.proximity_graph_node_id != neighbor_proximity_graph_node_id {
213                            // collect up each node state
214                            for (current_value_index, current_value) in values.iter().enumerate() {
215                                let current_node_state = NodeState::Primary {
216                                    state: current_value.clone(),
217                                };
218                                let mut other_node_states = Vec::new();
219                                for other_value in values.iter() {
220                                    match current_value.get_proximity(other_value) {
221                                        Proximity::ExclusiveExistence => {
222                                            // do not add the current node state as being able to be in the same final result as this other node state
223                                        },
224                                        Proximity::SomeDistanceAway { distance } => {
225                                            let distance_variance = distance.center * distance_variance_factor;
226                                            let from_distance = distance.center - distance_variance - distance.width;
227                                            let to_distance = distance.center + distance_variance + distance.width;
228
229                                            //println!("checking that {} is between {} and {}", normalized_neighbor_distance, from_distance, to_distance);
230                                            if from_distance <= neighbor_distance && neighbor_distance <= to_distance {
231                                                // this neighbor is within range of being in this other state
232                                                let other_node_state = NodeState::Primary {
233                                                    state: other_value.clone(),
234                                                };
235                                                other_node_states.push(other_node_state);
236                                            }
237                                        },
238                                        Proximity::InAnotherDimensionEntirely => {
239                                            // this neighbor being in this other state has no affect on the current node's state
240                                            let other_node_state = NodeState::Primary {
241                                                state: other_value.clone(),
242                                            };
243                                            other_node_states.push(other_node_state);
244                                        },
245                                    }
246                                }
247
248                                // store the results
249                                let node_state_collection_id: String = format!("primary_{}_{}_{}", proximity_graph_node.proximity_graph_node_id, neighbor_proximity_graph_node_id, current_value_index);
250
251                                let node_state_collection = NodeStateCollection::new(
252                                    node_state_collection_id.clone(),
253                                    current_node_state,
254                                    other_node_states,
255                                );
256                                node_state_collections.push(node_state_collection);
257
258                                node_state_collection_ids.push(node_state_collection_id);
259                            }
260                        }
261
262                        let neighbor_node_id = format!("primary_{}", neighbor_proximity_graph_node_id);
263                        node_state_collection_ids_per_neighbor_node_id.insert(neighbor_node_id, node_state_collection_ids);
264                    }
265
266                    let node = Node::new(
267                        format!("primary_{}", proximity_graph_node.proximity_graph_node_id),
268                        primary_node_state_ratio_per_node_state_id.clone(),
269                        node_state_collection_ids_per_neighbor_node_id,
270                    );
271                    nodes.push(node);
272                }
273
274                // create secondary nodes
275                for (value_index, value) in values.iter().enumerate() {
276                    if let Proximity::ExclusiveExistence = value.get_proximity(&value) {
277                        // this value needs to only exist exactly once
278                        let secondary_node_state_ratio_per_node_state_id = {
279                            let mut node_states = Vec::new();
280                            for (node_index, _) in self.nodes.iter().enumerate() {
281                                node_states.push(
282                                    NodeState::Secondary {
283                                        node_index,
284                                        state: value.clone(),
285                                    }
286                                );
287                            };
288                            NodeStateProbability::get_equal_probability(&node_states)
289                        };
290                        let node_state_collection_ids_per_neighbor_node_id = {
291                            let mut node_state_collection_ids_per_neighbor_node_id = HashMap::new();
292
293                            // set the active primary node state
294
295                            for (proximity_graph_node_index, proximity_graph_node) in self.nodes.iter().enumerate() {
296                                let node_state_collection_id = format!("secondary_{}_{}", value_index, proximity_graph_node.proximity_graph_node_id);
297                                let node_state_collection = NodeStateCollection::new(
298                                    node_state_collection_id.clone(),
299                                    NodeState::Secondary {
300                                        node_index: proximity_graph_node_index,
301                                        state: value.clone(),
302                                    },
303                                    vec![NodeState::Primary {
304                                        state: value.clone(),
305                                    }],
306                                );
307                                node_state_collections.push(node_state_collection);
308                                let neighbor_node_id = format!("primary_{}", proximity_graph_node.proximity_graph_node_id);
309                                node_state_collection_ids_per_neighbor_node_id.insert(neighbor_node_id, vec![node_state_collection_id]);
310                            }
311
312                            node_state_collection_ids_per_neighbor_node_id
313
314                            // TODO consider migrating all state logic from primary and secondary layers into secondary layer only
315                        };
316                        let node = Node::new(
317                            format!("secondary_{}", value_index),
318                            secondary_node_state_ratio_per_node_state_id,
319                            node_state_collection_ids_per_neighbor_node_id,
320                        );
321                        nodes.push(node);
322                    }
323                }
324
325                // return results
326                (nodes, node_state_collections)
327            };
328
329            //println!("nodes: {}", nodes.len());
330            //println!("node_state_collections: {}", node_state_collections.len());
331
332            let wave_function = WaveFunction::new(nodes, node_state_collections);
333            let mut collapsable_wave_function = wave_function.get_collapsable_wave_function::<SequentialCollapsableWaveFunction<NodeState<TValue>>>(None);
334            match collapsable_wave_function.collapse() {
335                Ok(collapsed_wave_function) => {
336                    // store this as the best collapsed wave function
337                    best_collapsed_wave_function = Some(collapsed_wave_function);
338
339                    // we need to reduce the variances to better isolate an ideal solution
340                    distance_variance_factor_maximum = distance_variance_factor;
341                    distance_variance_factor = (distance_variance_factor_maximum + distance_variance_factor_minimum) * 0.5;
342
343                    if distance_variance_factor_maximum - distance_variance_factor_minimum <= acceptable_distance_variance_factor_difference {
344                        is_distance_variance_factor_acceptable = true;
345                        //println!("collapsed and found at ({}-{}) at {}", distance_variance_factor_minimum, distance_variance_factor_maximum, distance_variance_factor);
346                    }
347                    else {
348                        //println!("collapsed but {} - {} is not less than {}", distance_variance_factor_maximum, distance_variance_factor_minimum, acceptable_distance_variance_factor_difference);
349                    }
350                },
351                Err(_) => {
352                    // expand or retract the distance variance
353                    // if the distance variance is beyond some measure of the maximum value proximity versus the maximum node distance, return Err
354                    if distance_variance_factor_maximum == 0.0 {
355                        // if we haven't expanded yet, let's start at the maximum acceptable variance
356                        distance_variance_factor_maximum = maximum_acceptable_distance_variance_factor;
357                        distance_variance_factor = maximum_acceptable_distance_variance_factor;
358                    }
359                    else if distance_variance_factor_maximum == maximum_acceptable_distance_variance_factor {
360                        // if we just tried the maximum acceptable distance difference factor, we will never find an acceptable factor
361                        return Err(ProximityGraphError::FailedToMapValuesToNodesAtAnyDistance);
362                    }
363                    else {
364                        distance_variance_factor_minimum = distance_variance_factor;
365                        distance_variance_factor = (distance_variance_factor_maximum + distance_variance_factor_minimum) * 0.5;
366                    }
367
368                    if distance_variance_factor_maximum - distance_variance_factor_minimum <= acceptable_distance_variance_factor_difference {
369                        is_distance_variance_factor_acceptable = true;
370                        //println!("not collapsed and found at ({}-{}) at {}", distance_variance_factor_minimum, distance_variance_factor_maximum, distance_variance_factor);
371                    }
372                    else {
373                        //println!("not collapsed but {} - {} is not less than {}", distance_variance_factor_maximum, distance_variance_factor_minimum, acceptable_distance_variance_factor_difference);
374                    }
375                },
376            }
377
378            //return Err(ProximityGraphError::TestError);
379        
380            iterations += 1;
381            if iterations > 10 {
382                break;
383            }
384        }
385        
386        let best_collapsed_wave_function = best_collapsed_wave_function.expect("We should have already failed when both extremes were tested earlier in the logic.");
387        let mut value_per_proximity_graph_node_id = HashMap::new();
388        for (node_id, node_state) in best_collapsed_wave_function.node_state_per_node_id {
389            match node_state {
390                NodeState::Primary { state } => {
391                    if let Some(proximity_graph_node_id) = node_id.strip_prefix("primary_") {
392                        value_per_proximity_graph_node_id.insert(String::from(proximity_graph_node_id), state);
393                    }
394                    else {
395                        panic!("Unexpected non-primary node ID when node state is in a primary state.");
396                    }
397                },
398                NodeState::Secondary { state: _, node_index: _ } => {
399                    if let Some(_) = node_id.strip_prefix("primary_") {
400                        panic!("Unexpected secondary node state tied to a primary node.");
401                    }
402                },
403            }
404        }
405        Ok(value_per_proximity_graph_node_id)
406    }
407}
408
409#[cfg(test)]
410mod proximity_graph_tests {
411    // TODO create unit tests
412
413    use std::collections::HashMap;
414
415    use serde::{Deserialize, Serialize};
416
417    use super::{Distance, HasProximity, Proximity, ProximityGraph, ProximityGraphNode};
418
419    fn get_x_by_y_grid_proximity_graph(x: usize, y: usize) -> ProximityGraph<(usize, usize)> {
420        let mut proximity_graph_nodes = Vec::new();
421        for i in 0..x {
422            for j in 0..y {
423                let mut distance_per_proximity_graph_node_id = HashMap::new();
424                for i_other in 0..x {
425                    for j_other in 0..y {
426                        if i != i_other || j != j_other {
427                            let other_proximity_graph_node_id = format!("node_{}_{}", i_other, j_other);
428                            let distance = (
429                                if i < i_other {
430                                    i_other - i
431                                }
432                                else {
433                                    i - i_other
434                                } + if j < j_other {
435                                    j_other - j
436                                }
437                                else {
438                                    j - j_other
439                                }
440                            ) as f32;
441                            distance_per_proximity_graph_node_id.insert(other_proximity_graph_node_id, distance);
442                        }
443                    }
444                }
445                let proximity_graph_node = ProximityGraphNode {
446                    proximity_graph_node_id: format!("node_{}_{}", i, j),
447                    distance_per_proximity_graph_node_id,
448                    tag: (i, j),
449                };
450                proximity_graph_nodes.push(proximity_graph_node);
451            }
452        }
453        ProximityGraph::new(proximity_graph_nodes)
454    }
455
456    fn get_values(total_values: usize) -> Vec<IceCreamShop> {
457        let mut values = Vec::with_capacity(total_values);
458        for index in 0..total_values {
459            match index {
460                0 => values.push(IceCreamShop::AppleCream),
461                1 => values.push(IceCreamShop::BananaBoost),
462                2 => values.push(IceCreamShop::CaramelJuice),
463                3 => values.push(IceCreamShop::DarkDestiny),
464                4 => values.push(IceCreamShop::EternalJoy),
465                _ => values.push(IceCreamShop::None),
466            }
467        }
468        values
469    }
470
471    fn println_value_per_proximity_graph_node_id(x: usize, y: usize, value_per_proximity_graph_node_id: &HashMap<String, IceCreamShop>) {
472        let mut character_per_y_per_x = HashMap::new();
473        for i in 0..x {
474            let mut character_per_y = HashMap::new();
475            for j in 0..y {
476                character_per_y.insert(j, None);
477            }
478            character_per_y_per_x.insert(i, character_per_y);
479        }
480        for (proximity_graph_node_id, ice_cream_shop) in value_per_proximity_graph_node_id.iter() {
481            let x_and_y: Vec<&str> = proximity_graph_node_id.strip_prefix("node_")
482                .unwrap()
483                .split('_')
484                .collect();
485            let x: usize = x_and_y[0].parse().unwrap();
486            let y: usize = x_and_y[1].parse().unwrap();
487            let character = match ice_cream_shop {
488                IceCreamShop::AppleCream => "A",
489                IceCreamShop::BananaBoost => "B",
490                IceCreamShop::CaramelJuice => "C",
491                IceCreamShop::DarkDestiny => "D",
492                IceCreamShop::EternalJoy => "E",
493                IceCreamShop::None => "_",
494            };
495            *character_per_y_per_x.get_mut(&x)
496                .unwrap()
497                .get_mut(&y)
498                .unwrap() = Some(character);
499        }
500
501        for j in 0..y {
502            let mut line = String::new();
503            for i in 0..x {
504                let character = character_per_y_per_x.get(&i)
505                    .unwrap()
506                    .get(&j)
507                    .unwrap()
508                    .unwrap();
509                line.push_str(character);
510            }
511            println!("{}", line);
512        }
513    }
514
515    #[derive(Clone, std::fmt::Debug, Hash, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
516    enum IceCreamShop {
517        AppleCream,
518        BananaBoost,
519        CaramelJuice,
520        DarkDestiny,
521        EternalJoy,
522        None,
523    }
524
525    impl HasProximity for IceCreamShop {
526        fn get_proximity(&self, other: &Self) -> Proximity where Self: Sized {
527            match self {
528                Self::AppleCream => {
529                    match other {
530                        Self::AppleCream => Proximity::ExclusiveExistence,
531                        Self::BananaBoost => Proximity::SomeDistanceAway {
532                            distance: Distance {
533                                center: 4.0,
534                                width: 0.0,
535                            }
536                        },
537                        Self::CaramelJuice => Proximity::SomeDistanceAway {
538                            distance: Distance {
539                                center: 8.0,
540                                width: 0.0,
541                            },
542                        },
543                        Self::DarkDestiny => Proximity::SomeDistanceAway {
544                            distance: Distance {
545                                center: 1.0,
546                                width: 0.0,
547                            },
548                        },
549                        Self::EternalJoy => Proximity::SomeDistanceAway {
550                            distance: Distance {
551                                center: 4.0,
552                                width: 0.0,
553                            },
554                        },
555                        Self::None => Proximity::InAnotherDimensionEntirely,
556                    }
557                },
558                Self::BananaBoost => {
559                    match other {
560                        Self::AppleCream => Proximity::SomeDistanceAway {
561                            distance: Distance {
562                                center: 4.0,
563                                width: 0.0,
564                            },
565                        },
566                        Self::BananaBoost => Proximity::ExclusiveExistence,
567                        Self::CaramelJuice => Proximity::SomeDistanceAway {
568                            distance: Distance {
569                                center: 4.0,
570                                width: 0.0,
571                            },
572                        },
573                        Self::DarkDestiny => Proximity::SomeDistanceAway {
574                            distance: Distance {
575                                center: 5.0,
576                                width: 0.0,
577                            },
578                        },
579                        Self::EternalJoy => Proximity::SomeDistanceAway {
580                            distance: Distance {
581                                center: 8.0,
582                                width: 0.0,
583                            },
584                        },
585                        Self::None => Proximity::InAnotherDimensionEntirely,
586                    }
587                },
588                Self::CaramelJuice => {
589                    match other {
590                        Self::AppleCream => Proximity::SomeDistanceAway {
591                            distance: Distance {
592                                center: 8.0,
593                                width: 0.0,
594                            },
595                        },
596                        Self::BananaBoost => Proximity::SomeDistanceAway {
597                            distance: Distance {
598                                center: 4.0,
599                                width: 0.0,
600                            },
601                        },
602                        Self::CaramelJuice => Proximity::ExclusiveExistence,
603                        Self::DarkDestiny => Proximity::SomeDistanceAway {
604                            distance: Distance {
605                                center: 7.0,
606                                width: 0.0,
607                            },
608                        },
609                        Self::EternalJoy => Proximity::SomeDistanceAway {
610                            distance: Distance {
611                                center: 4.0,
612                                width: 0.0,
613                            },
614                        },
615                        Self::None => Proximity::InAnotherDimensionEntirely,
616                    }
617                },
618                Self::DarkDestiny => {
619                    match other {
620                        Self::AppleCream => Proximity::SomeDistanceAway {
621                            distance: Distance {
622                                center: 1.0,
623                                width: 0.0,
624                            },
625                        },
626                        Self::BananaBoost => Proximity::SomeDistanceAway {
627                            distance: Distance {
628                                center: 5.0,
629                                width: 0.0,
630                            },
631                        },
632                        Self::CaramelJuice => Proximity::SomeDistanceAway {
633                            distance: Distance {
634                                center: 7.0,
635                                width: 0.0,
636                            },
637                        },
638                        Self::DarkDestiny => Proximity::ExclusiveExistence,
639                        Self::EternalJoy => Proximity::SomeDistanceAway {
640                            distance: Distance {
641                                center: 3.0,
642                                width: 0.0,
643                            },
644                        },
645                        Self::None => Proximity::InAnotherDimensionEntirely,
646                    }
647                },
648                Self::EternalJoy => {
649                    match other {
650                        Self::AppleCream => Proximity::SomeDistanceAway {
651                            distance: Distance {
652                                center: 4.0,
653                                width: 0.0,
654                            },
655                        },
656                        Self::BananaBoost => Proximity::SomeDistanceAway {
657                            distance: Distance {
658                                center: 8.0,
659                                width: 0.0,
660                            },
661                        },
662                        Self::CaramelJuice => Proximity::SomeDistanceAway {
663                            distance: Distance {
664                                center: 4.0,
665                                width: 0.0,
666                            },
667                        },
668                        Self::DarkDestiny => Proximity::SomeDistanceAway {
669                            distance: Distance {
670                                center: 3.0,
671                                width: 0.0,
672                            },
673                        },
674                        Self::EternalJoy => Proximity::ExclusiveExistence,
675                        Self::None => Proximity::InAnotherDimensionEntirely,
676                    }
677                },
678                Self::None => {
679                    match other {
680                        Self::AppleCream => Proximity::InAnotherDimensionEntirely,
681                        Self::BananaBoost => Proximity::InAnotherDimensionEntirely,
682                        Self::CaramelJuice => Proximity::InAnotherDimensionEntirely,
683                        Self::DarkDestiny => Proximity::InAnotherDimensionEntirely,
684                        Self::EternalJoy => Proximity::InAnotherDimensionEntirely,
685                        Self::None => Proximity::InAnotherDimensionEntirely,
686                    }
687                },
688            }
689        }
690    }
691
692    #[test]
693    fn test_w7b0_get_x_by_y_grid_proximity_graph() {
694        let proximity_graph = get_x_by_y_grid_proximity_graph(2, 2);
695        assert_eq!(4, proximity_graph.nodes.len());
696        for index in 0..4 {
697            assert_eq!(3, proximity_graph.nodes[index].distance_per_proximity_graph_node_id.keys().len());
698        }
699        println!("{:?}", proximity_graph.nodes);
700    }
701
702    #[test_case::test_case(5, 5, 0.0, 0.0)]
703    #[test_case::test_case(4, 4, 1.0, 0.1)]
704    #[test_case::test_case(3, 3, 2.0, 0.1)]
705    fn test_h2s7_icecream_shops_in_grid(x: usize, y: usize, maximum_acceptable_distance_variance_factor: f32, acceptable_distance_variance_factor_difference: f32) {
706        let proximity_graph = get_x_by_y_grid_proximity_graph(x, y);
707        let values = get_values(x * y);
708        let value_per_proximity_graph_node_id = proximity_graph.get_value_per_proximity_graph_node_id(values, maximum_acceptable_distance_variance_factor, acceptable_distance_variance_factor_difference).expect("Failed to get value per proximity graph node ID.");
709        println_value_per_proximity_graph_node_id(x, y, &value_per_proximity_graph_node_id);
710        println!("{:?}", value_per_proximity_graph_node_id);
711        assert_eq!(IceCreamShop::AppleCream, *value_per_proximity_graph_node_id.get("node_0_0").unwrap());
712        assert_eq!(IceCreamShop::BananaBoost, *value_per_proximity_graph_node_id.get(format!("node_{}_0", x - 1).as_str()).unwrap());
713        assert_eq!(IceCreamShop::CaramelJuice, *value_per_proximity_graph_node_id.get(format!("node_{}_{}", x - 1, y - 1).as_str()).unwrap());
714        assert_eq!(IceCreamShop::DarkDestiny, *value_per_proximity_graph_node_id.get("node_0_1").unwrap());
715        assert_eq!(IceCreamShop::EternalJoy, *value_per_proximity_graph_node_id.get(format!("node_0_{}", y - 1).as_str()).unwrap());
716    }
717
718    #[test_case::test_case(6, 6, 0.0, 0.0)]
719    fn test_y7c4_icecream_shops_in_grid(x: usize, y: usize, maximum_acceptable_distance_variance_factor: f32, acceptable_distance_variance_factor_difference: f32) {
720        let proximity_graph = get_x_by_y_grid_proximity_graph(x, y);
721        let values = get_values(x * y);
722        let value_per_proximity_graph_node_id = proximity_graph.get_value_per_proximity_graph_node_id(values, maximum_acceptable_distance_variance_factor, acceptable_distance_variance_factor_difference).expect("Failed to get value per proximity graph node ID.");
723        println_value_per_proximity_graph_node_id(x, y, &value_per_proximity_graph_node_id);
724        println!("{:?}", value_per_proximity_graph_node_id);
725        assert_eq!(IceCreamShop::AppleCream, *value_per_proximity_graph_node_id.get("node_0_0").unwrap());
726        assert_eq!(IceCreamShop::BananaBoost, *value_per_proximity_graph_node_id.get("node_4_0").unwrap());
727        assert_eq!(IceCreamShop::CaramelJuice, *value_per_proximity_graph_node_id.get("node_4_4").unwrap());
728        assert_eq!(IceCreamShop::DarkDestiny, *value_per_proximity_graph_node_id.get("node_0_1").unwrap());
729        assert_eq!(IceCreamShop::EternalJoy, *value_per_proximity_graph_node_id.get("node_0_4").unwrap());
730    }
731
732    #[test_case::test_case(4, 4, 0.5, 0.1)]
733    #[test_case::test_case(3, 3, 1.0, 0.1)]
734    fn test_o1n6_icecream_shops_in_grid(x: usize, y: usize, maximum_acceptable_distance_variance_factor: f32, acceptable_distance_variance_factor_difference: f32) {
735        let proximity_graph = get_x_by_y_grid_proximity_graph(x, y);
736        let values = get_values(x * y);
737        let error = proximity_graph.get_value_per_proximity_graph_node_id(values, maximum_acceptable_distance_variance_factor, acceptable_distance_variance_factor_difference);
738        assert!(error.is_err());
739    }
740}