labirust/
executor.rs

1//! ## Executor
2//!
3//! This module contains the definition of an [`Executor`], used to run an [`Algorithm`] and have a graphical output in the terminal.
4//! This type is supposed to be created using the builder pattern (c.f. [`Executor`]`::build`).
5
6use std::{
7    collections::{HashMap, HashSet},
8    thread,
9    time::Duration,
10};
11
12use crate::{Algorithm, Maze, Pos};
13
14use self::builder::{
15    maze_state::{BuildableMazeState, Unprovided},
16    new_builder, DynExecutorBuilder, ExecutorBuilder,
17};
18
19/// A guess to pass to the current [`Executor`] at the end of every `progress` call.
20pub struct Guess(Vec<Pos>);
21
22/// An insight given to the [`Algorithm`] on every `progress` call.
23/// On the first time about the starting point and every consecutive call about the tail of the previous guess.
24pub struct Insight<'p> {
25    position: Pos,
26    paths: &'p [Pos],
27}
28
29impl<'p> Insight<'p> {
30    fn new(position: Pos, paths: &'p [Pos]) -> Self {
31        Self { paths, position }
32    }
33
34    fn from_position(position: Pos, maze: &'p Maze) -> Self {
35        let paths = maze.paths_from(position);
36        Self::new(position, paths)
37    }
38
39    /// The position of the insight.
40    pub fn position(&self) -> Pos {
41        self.position
42    }
43
44    /// the paths from that position.
45    pub fn paths(&self) -> &[Pos] {
46        self.paths
47    }
48}
49
50/// A context given to the [`Algorithm`] on every `progress` call, provide informations about the maze and method to create a [`Guess`].
51pub struct Context<'m> {
52    maze: &'m Maze,
53}
54
55impl<'m> Context<'m> {
56    fn new(maze: &'m Maze) -> Self {
57        Self { maze }
58    }
59
60    /// Constructor for [`Guess`].
61    /// Takes a path, that is a vector of positions from the starting point to the position to discover on the next call to `progress`.
62    pub fn guess(&self, pos: Vec<Pos>) -> Guess {
63        Guess(pos)
64    }
65
66    /// Returns the position of the `start` of the [`Maze`].
67    pub fn start(&self) -> Pos {
68        self.maze.start()
69    }
70
71    /// Returns the position of the `end` of the [`Maze`].
72    pub fn end(&self) -> Pos {
73        self.maze.end()
74    }
75
76    /// Returns the `width` of the [`Maze`].
77    pub fn width(&self) -> isize {
78        self.maze.width()
79    }
80
81    /// Returns the `height` of the [`Maze`].
82    pub fn height(&self) -> isize {
83        self.maze.width()
84    }
85
86    /// Returns a tuple containing both the `width` and `height` of the [`Maze`].
87    pub fn size(&self) -> (isize, isize) {
88        self.maze.size()
89    }
90}
91
92mod builder;
93
94/// A structure holding a [`Maze`] and iteratively solving it with a provided [`Algorithm`].
95pub struct Executor {
96    delay: Duration,
97    maze: Maze,
98    algorithm: Box<dyn Algorithm>,
99}
100
101impl Executor {
102    /// Constructor.
103    fn new(maze: Maze, algorithm: Box<dyn Algorithm>, delay: Duration) -> Self {
104        Self {
105            maze,
106            algorithm,
107            delay,
108        }
109    }
110
111    pub fn build<'f, A, F, MS>(algorithm: A, builder: F) -> Self
112    where
113        A: Algorithm + 'static,
114        MS: BuildableMazeState,
115        F: Fn(ExecutorBuilder<Unprovided>) -> ExecutorBuilder<MS>,
116    {
117        let operation = builder;
118        let builder = (operation)(new_builder());
119        let (maze, delay) = builder.build();
120        let algorithm = Box::new(algorithm);
121        Self::new(maze, algorithm, delay)
122    }
123
124    pub fn build_dyn<F>(algorithm: Box<dyn Algorithm>, builder: F) -> Self
125    where
126        F: Fn(DynExecutorBuilder) -> DynExecutorBuilder,
127    {
128        let operation = builder;
129        let builder = (operation)(DynExecutorBuilder::new());
130        let (maze, delay) = builder.build();
131        Self::new(maze, algorithm, delay)
132    }
133
134    /// Submit the maze to the [`Algorithm`] and iteratively progress through the maze driven by said algorithm.
135    pub fn run(&mut self) {
136        let Self {
137            maze,
138            algorithm,
139            delay,
140        } = self;
141        let mut insight = Insight::from_position(maze.start(), &maze);
142        let mut tick = 0;
143        let mut tried = HashSet::new();
144        loop {
145            let mut context = Context::new(maze);
146            let Guess(guess) = algorithm.progress(&insight, &mut context);
147            // TODO:
148            // - extract metrics from the context
149            // - check if path is actually a path
150            guess.iter().for_each(|&p| {
151                tried.insert(p);
152            });
153            let tail = *guess.last().expect("returned an empty path");
154
155            // draw
156            Self::draw(maze, &tried, tick, &guess);
157            thread::sleep(*delay);
158            tick += 1;
159
160            // check for next iteration
161            if maze.is_end(tail) {
162                break;
163            } else {
164                insight = Insight::from_position(tail, maze)
165            }
166        }
167    }
168
169    fn draw(maze: &Maze, tried: &HashSet<Pos>, tick: usize, path: &Vec<Pos>) {
170        let mut overlay = HashMap::new();
171        for position in tried {
172            overlay.insert(*position, '░');
173        }
174        for position in path {
175            overlay.insert(*position, '█');
176        }
177        overlay.insert(maze.start(), 'S');
178        overlay.insert(maze.end(), 'E');
179        overlay.insert(*path.last().unwrap(), 'G');
180
181        let grid = maze.display(Some(overlay));
182        let text = format!("tick {tick}:\n{grid}\n");
183
184        // DIRTY!
185        // print the frame on top of the previous one
186        if tick > 0 {
187            let count = text.lines().count();
188            let up = termion::cursor::Up(count as u16);
189            print!("{up}")
190        }
191
192        print!("{text}");
193    }
194}