use gur::cur::{Cur, CurBuilder}; use gur::prelude::*; use rand::prelude::*; use std::io; use std::io::{BufRead, BufReader, BufWriter, Write}; #[derive(Clone)] struct Array(Vec); impl Array { fn new(size: usize) -> Self { Array((0..size as u32).collect()) } fn get(&self) -> &[u32] { &self.0 } fn shuffle(&mut self, rng: &mut R) { self.0.shuffle(rng); } fn sort(&mut self) { self.0.sort(); } } struct App<'a> { data: Cur<'a, Array>, rng: ThreadRng, } impl<'a> App<'a> { fn new() -> Self { Self { data: CurBuilder::new().build(Array::new(10)), rng: rand::thread_rng(), } } fn reset(&mut self, size: usize) { self.data = CurBuilder::new().build(Array::new(size)) } fn shuffle(&mut self) { // NOTE // This method uses try_edit() to store the updated array as a snapshot // because of non-reproducibility of the rng. self.data .try_edit(|mut data| { data.shuffle(&mut self.rng); Ok(data) }) .unwrap(); } fn sort(&mut self) { self.data.edit(|mut data| { data.sort(); data }); } fn undo(&mut self, count: usize) { self.data.undo_multi(count); } fn redo(&mut self, count: usize) { self.data.redo_multi(count); } fn get(&self) -> &[u32] { self.data.get() } } const COMMAND_HELP: &str = "COMMANDS :h | Print command help. :i SIZE | Initialize (Reset) the array by SIZE. :t | Shuffle the array. :s | Sort the array. :u [COUNT] | Undo COUNT changes. :r [COUNT] | Redo COUNT changes. :q | Quit the program."; fn main() -> Result<(), Box> { println!( "[Undo demonstration] Array shuffling and sorting.\n\n{}", COMMAND_HELP ); let mut app = App::new(); let mut stdin = BufReader::new(io::stdin().lock()); let print = |app: &mut App| -> io::Result<()> { let mut stdout = BufWriter::new(io::stdout().lock()); writeln!(stdout, "Data: {:?}", app.get())?; stdout.flush()?; Ok(()) }; print(&mut app)?; let mut command_str = String::new(); loop { command_str.clear(); write!(&mut io::stdout(), ":")?; io::stdout().flush()?; if 0 == stdin.read_line(&mut command_str)? { return Ok(()); } match Command::parse(&command_str)? { Command::Help => writeln!(&mut io::stdout(), "{}", COMMAND_HELP)?, Command::Quit => return Ok(()), Command::Reset(size) => { app.reset(size); print(&mut app)?; } Command::Shuffle => { app.shuffle(); print(&mut app)?; } Command::Sort => { app.sort(); print(&mut app)?; } Command::Undo(count) => { app.undo(count); print(&mut app)?; } Command::Redo(count) => { app.redo(count); print(&mut app)?; } } } } enum Command { Help, Reset(usize), Shuffle, Sort, Undo(usize), Redo(usize), Quit, } impl Command { fn parse(command_str: &str) -> Result> { let command_str = command_str .trim_start_matches(Self::is_whitespace) .trim_end_matches(&['\r', '\n']); let (name, param) = if let Some((name, param)) = command_str.split_once(Self::is_whitespace) { (name, param) } else { (command_str, "") }; match name { "h" => Ok(Command::Help), "q" => Ok(Command::Quit), "i" => Self::parse_reset(param), "t" => Ok(Command::Shuffle), "s" => Ok(Command::Sort), "u" => Self::parse_undo(param), "r" => Self::parse_redo(param), _ => Ok(Command::Help), } } fn parse_undo(param: &str) -> Result> { let count = Self::parse_usize(param)?; Ok(Command::Undo(count.unwrap_or(1))) } fn parse_redo(param: &str) -> Result> { let count = Self::parse_usize(param)?; Ok(Command::Redo(count.unwrap_or(1))) } fn parse_reset(param: &str) -> Result> { let count = Self::parse_usize(param)?; Ok(Command::Reset(count.unwrap_or(1))) } fn parse_usize(param: &str) -> Result, Box> { if let None = param.find(|c: char| !Self::is_whitespace(c)) { return Ok(None); } let num = param.parse::()?; Ok(Some(num)) } fn is_whitespace(c: char) -> bool { c.is_ascii_whitespace() } }