drunken_diver/
lib.rs

1/*! The mental model of this library one should have
2  is that of a drunken diver descending into the depths of the ocean
3  while leaving behind a trail of notes 
4  each with a stylized depiction of a direction (left or right), 
5  allowing those that come afterwards to swim the same path the diver took.
6
7  Please see the examples directory for examples.
8  */
9
10// This file implements the main algorithm.
11
12mod minutiae;
13use minutiae::{Note, Direction, Style, DSPair};
14
15
16
17/// A row of `Note`s.
18/// If the diver is in this row, it contains
19/// the current position of the diver.
20/// Otherwise, it contains the diver's position when it left the row.
21///
22// `usize` should be within `0` and `WIDTH`.
23// NB: We consider `0` to be the leftmost position and `WIDTH` to be the rightmost position.
24#[derive(PartialEq)]
25pub struct Row<const WIDTH: usize>([Note ; WIDTH], usize);
26
27impl <const WIDTH: usize> Row<WIDTH> {
28
29    const EMPTY_ARR: [ Note ; WIDTH ] = [ Note::Empty ; WIDTH ];
30    
31    // NB: The goal of `WIDTH/8` is to increase the margin size
32    // e.g. with `0..0`, we get
33    // |<
34    // |<
35    // |<
36    // etc.
37    //
38    // while with `WIDTH/8` we increase the minimum of the sum 
39    // of the margins of both sides in proportion to the width
40    // of the route. Note that `8` is a magic number,
41    // but it is meant to strike a balance between `4`
42    // which seems to allow notes to easily encompass the center of
43    // the route, and `16` which seems a little too small.
44    const MIN_MARGIN: usize = WIDTH/8;
45
46    fn new(loc: usize) -> Row<WIDTH> { Row(Row::<WIDTH>::EMPTY_ARR, loc) }
47
48    fn set_note(&mut self, note: Note) { self.0[self.1] = note; }
49    fn is_note_empty(&self) -> bool { self.0[self.1] == Note::Empty }
50
51    // Get the location of the diver or where it left the row.
52    fn get_loc(&self) -> &usize { &self.1 }
53
54    fn is_empty(&self) -> bool { self.0 == Row::<WIDTH>::EMPTY_ARR }
55
56    fn is_rightmost(&self) -> bool { self.1 + 1 >= WIDTH } 
57    fn is_leftmost(&self) -> bool { self.1 <= 0 }
58
59    fn go_rightmost(&mut self) { self.1 = WIDTH.saturating_sub(1) }
60    fn go_leftmost(&mut self) { self.1 = 0 }
61
62    fn go_right_unchecked(&mut self) { self.1 += 1 }
63    fn go_left_unchecked(&mut self) { self.1 -= 1 }
64
65    fn go_right_wrapping(&mut self) { 
66        if self.is_rightmost() { self.go_leftmost() } else { self.go_right_unchecked() } 
67    }
68    fn go_left_wrapping(&mut self) {
69        if self.is_leftmost() { self.go_rightmost() } else { self.go_left_unchecked() }
70    }
71
72    /// Moves the diver rightward.
73    /// Returns whether the diver should go down to a new row.
74    /// true - yes, the diver should go down
75    /// false - no, the diver shouldn't go down
76    fn journey_right(&mut self) -> bool {
77        // examine the current location. Is it empty? Then settle there.
78        // Otherwise, go to the right, wrapping.
79        for _ in 0..Row::<WIDTH>::MIN_MARGIN {
80            if self.is_note_empty() { return false }
81            else { self.go_right_wrapping() }
82        }
83        // examine the current location. Is it empty? Then settle there.
84        // Is it at the end? Then go down a row from before you started the journey.
85        // Otherwise, go right.
86        loop {
87            if self.is_note_empty() { return false }
88            else if self.is_rightmost() { return true } // end of the line
89            else { self.go_right_unchecked(); }
90        }
91    }
92
93    /// See `journey_right`.
94    fn journey_left(&mut self) -> bool {
95        for _ in 0..Row::<WIDTH>::MIN_MARGIN {
96            if self.is_note_empty() { return false }
97            else { self.go_left_wrapping() }
98        }
99        loop {
100            if self.is_note_empty() { return false }
101            else if self.is_leftmost() { return true } // end of the line
102            else { self.go_left_unchecked(); }
103        }
104    }
105}
106
107
108impl <const WIDTH: usize> std::fmt::Display for Row<WIDTH> {
109    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
110        for note in self.0.iter() { write!(f, "{}", note)? }
111        Ok(())
112    }
113}
114
115
116/// An iterator over the rows a diver descends through.
117pub struct Dive<T, const WIDTH: usize> {
118    iter: T,
119    row: Row<WIDTH>,
120    buffered: Option<(Direction, Style)>,
121}
122
123impl <T: Iterator<Item=u8>, const WIDTH: usize> From<T> for Dive<T, WIDTH> {
124    fn from(it: T) -> Dive<T, WIDTH> {
125        Dive {
126            iter: it,
127            row: Row::new(WIDTH/2),
128            buffered: None,
129        }
130    }
131}
132
133impl <T: Iterator<Item=u8>, const WIDTH: usize> Dive<T, WIDTH> {
134
135    /// Makes the diver move towards a direction, eventually settling.
136    ///
137    /// Returns None if the diver stayed on the same row.
138    /// Returns the old row if the diver left the row.
139    fn settle(&mut self, d: Direction, home: usize) -> Option<Row<WIDTH>> {
140        let go_down = match d {
141            Direction::Right => { self.row.journey_right() },
142            Direction::Left => { self.row.journey_left() }
143        };
144
145        if go_down {
146           return Some(std::mem::replace(&mut self.row, Row::new(home)))
147        }
148        else { None }
149    }
150
151    /// When the diver decides to `go`, it first leaves behind
152    /// a `Note` at that location detailing the `Direction` `d` it went.
153    /// The `Note` has a `Style`.
154    ///
155    /// Let `home` be the starting position of the diver.
156    /// Repeat MIN_MARGIN times:
157    ///     The diver goes one space in direction `d`,
158    ///     wrapping to the other side of the row if necessary.
159    ///     Then, if the space is empty, return `None`,
160    ///     as the diver did not go down a row.
161    /// Repeat:
162    ///     If going one space in direction `d` would put the diver
163    ///     past the edges of the row,
164    ///     create a new row with the diver starting at `home`,
165    ///     then return the old row.
166    ///     Otherwise, the diver goes one space in direction `d`.
167    ///     If the space is empty, return `None`.
168    ///     
169    fn go(&mut self, d: Direction, s: Style) -> Option<Row<WIDTH>> {
170        self.row.set_note(Note::Full(d, s));
171        self.settle(d, *self.row.get_loc())
172    }
173}
174
175impl <T: Iterator<Item=u8>, const WIDTH: usize> Iterator for Dive<T, WIDTH> {
176    type Item = Row<WIDTH>;
177
178    /// `go` on the buffer if a value is present.
179    /// If the diver descends, return the obtained row.
180    /// 
181    /// Repeat:
182    ///     Retrieve the next value in `iter`.
183    ///     Process the `u8`  into two `u4`'s, which convert to
184    ///     two tuples of `Direction` and `Style`.
185    ///     `go` on the first tuple. If the diver descends, buffer the second tuple
186    ///     and return the obtained row.
187    ///     Otherwise, `go`on the second tuple. If the diver descends,
188    ///     return the obtained row.
189    fn next(&mut self) -> Option<Self::Item> {
190        macro_rules! go_and_return_on_descent {
191            ($direction:ident, $style:ident $(,  $b:block)?) => {
192                if let Some(row) = self.go($direction, $style) {
193                    $($b)?
194                    return Some(row)
195                }
196            }
197        }
198
199        if let Some((d, s)) = self.buffered.take() {
200            go_and_return_on_descent!(d, s)
201        }
202
203        while let Some(byte) = self.iter.next() {
204                let DSPair([(d1, s1), (d2, s2)]) = DSPair::from(byte);
205                go_and_return_on_descent!(d1, s1, { self.buffered = Some((d2, s2)); });
206                go_and_return_on_descent!(d2, s2)
207        }
208        if self.row.is_empty() { return None }
209        else {
210            let orig = *self.row.get_loc();
211            return Some(std::mem::replace(&mut self.row, Row::new(orig)))
212        }
213    }
214}
215
216
217/// A completed route the diver took.
218///
219/// This is the main way to print the art the diver generates.
220pub struct Route<const WIDTH: usize> {
221    route: Vec<Row<WIDTH>>,
222    end: usize,
223}
224
225
226impl <T: Iterator<Item=u8>, const WIDTH: usize> From<Dive<T, WIDTH>> for Route<WIDTH> {
227    fn from(it: Dive<T, WIDTH>) -> Route<WIDTH> {
228        let vec: Vec<_> = it.collect(); 
229        Route{ end: vec.last().map(|Row(_, v)| *v).unwrap_or(WIDTH/2), route: vec }
230    }
231
232}
233
234impl <const WIDTH: usize> std::fmt::Display for Route<WIDTH> {
235    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
236        write!(f, "+{}v{}+\n", "-".repeat((WIDTH/2).saturating_sub(1)), "-".repeat(WIDTH - (WIDTH/2)))?;
237        for row in &self.route {
238            write!(f, "|{}|\n", row)?
239        }
240        write!(f, "+{}v{}+\n", "-".repeat(self.end), "-".repeat((WIDTH - self.end).saturating_sub(1)))
241    }
242}
243
244