diff --git a/src/lib.rs b/src/lib.rs index 3af0741..fc6a443 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,42 +1,127 @@ /// A trait for graph-like structures, where you can get a node by a unique value and find its neighbors. /// -/// **Node** represents some settled data type +/// **Node** represents some settled data type. The graph should likely contain `Option` +/// elements prepopulated with `None` values. For example `[[None; WIDTH]; HEIGHT]` for a 2D grid. /// /// **NodeId** represents something that can be used to find an Node inside the graph, such as /// position, index, or key to a hashmap. It should allow O(1) access to the Node. pub trait GraphLike { + /// Number of steps to backtrack after encountering an impossible node. + /// + /// Used in the default implementation of `full_generation` + const BACKTRACK_AMOUNT: usize = 5; + /// Maximum number of times we try to retry before making progress. If exceeded, we give up. + /// + /// Used in the default implementation of `full_generation` + const MAX_RETRIES: usize = 5; + fn get_node(&self, id: NodeId) -> Option<&Node>; - fn set_node(&mut self, id: NodeId, node: Node); + fn set_node(&mut self, id: NodeId, node: Option); fn iter_ids(&self) -> impl Iterator; fn iter_neighbor_ids(&self, id: NodeId) -> impl Iterator; - fn iter_nodes<'a>(&'a self) -> impl Iterator)> where Node: 'a, NodeId: Copy { + fn iter_nodes<'a>(&'a self) -> impl Iterator)> + where + Node: 'a, + NodeId: Copy, + { self.iter_ids().map(|id| (id, self.get_node(id))) } - fn iter_empty(&self) -> impl Iterator where NodeId: Copy { + fn iter_empty(&self) -> impl Iterator + where + NodeId: Copy, + { self.iter_ids().filter(|id| self.get_node(*id).is_none()) } - fn iter_neighbour_nodes(&self, id: NodeId) -> Vec<(NodeId, Option<&Node>)> where NodeId: Copy { - self.iter_neighbor_ids(id).map(|id| (id, self.get_node(id))).collect() + fn iter_neighbour_nodes(&self, id: NodeId) -> Vec<(NodeId, Option<&Node>)> + where + NodeId: Copy, + { + self.iter_neighbor_ids(id) + .map(|id| (id, self.get_node(id))) + .collect() } - /// TODO: Replace return type with a result that explains the reason for failure. This could - /// help with rollback to recover from impossible generation - fn single_step(&mut self, rules: &impl Ruleset) -> Option where NodeId: Copy, Self: Sized { - let id = rules.find_lowest_entropy(self)?; - let node = rules.choose(self, id).expect("Ruleset::choose should also return Some if Ruleset::entropy returns Some"); - let empty_ids = self.iter_empty(); - - None + fn single_step( + &mut self, + rules: &impl Ruleset, + retry_count: Option, + ) -> Option + where + NodeId: Copy, + Self: Sized, + { + let id = rules.find_lowest_entropy(self, retry_count)?; + let node = rules + .choose(self, id, retry_count) + .expect("Ruleset::choose should also return Some if Ruleset::entropy returns Some"); + self.set_node(id, Some(node)); + Some(id) + } + + fn full_generation(&mut self, rules: &impl Ruleset) -> Result<(), ()> + where + NodeId: Copy, + Self: Sized, + { + // Ordered list of all the nodes that we have generated. Used for backtracking. + let mut steps: Vec = vec![]; + // Number of times we have backtracked from the same point + let mut retries: usize = 0; + // The biggest number of steps we have gotten se far + let mut most_steps = 0; + while self.iter_empty().next().is_some() { + match self.single_step(rules, Some(retries)) { + Some(id) => { + steps.push(id); + if steps.len() > most_steps { + most_steps = steps.len(); + retries = 0; + } + } + None => { + // Backtracking + let reverse_steps = + steps.split_off(steps.len().saturating_sub(Self::BACKTRACK_AMOUNT)); + for step in reverse_steps.into_iter().rev() { + self.set_node(step, None); + } + // Keep track of retries + retries += 1; + most_steps = steps.len(); + if retries >= Self::MAX_RETRIES { + return Err(()); + } + } + } + } + Ok(()) } } pub trait Ruleset { - fn entropy(&self, graph: &impl GraphLike, id: NodeId) -> Option where NodeId: Copy; - fn choose(&self, graph: &impl GraphLike, id: NodeId) -> Option where NodeId: Copy; + fn entropy(&self, graph: &impl GraphLike, id: NodeId) -> Option + where + NodeId: Copy; + fn choose( + &self, + graph: &impl GraphLike, + id: NodeId, + _retry_count: Option, + ) -> Option + where + NodeId: Copy; - fn find_lowest_entropy(&self, graph: &impl GraphLike) -> Option where NodeId: Copy { - graph.iter_empty() + fn find_lowest_entropy( + &self, + graph: &impl GraphLike, + _retry_count: Option, + ) -> Option + where + NodeId: Copy, + { + graph + .iter_empty() .map(|id| (id, self.entropy(graph, id))) // NOTE: Option implements PartialOrd // None is considered smaller than any Some value