diff --git a/conf.pest b/conf.pest index 3b8810b..cdad2d5 100644 --- a/conf.pest +++ b/conf.pest @@ -1,3 +1,5 @@ -world = { ASCII_DIGIT+ ~ " " ~ ASCII_DIGIT+ } -robot_init = { ASCII_DIGIT+ ~ " " ~ ASCII_DIGIT+ ~ " " ~ ("S" | "N" | "W" | "E") } -robot_instructions = { ASCII_ALPHA_UPPER+ } +World = { HeaderWorld ~ NEWLINE+ ~ Robot+ } +HeaderWorld = { ASCII_DIGIT+ ~ " " ~ ASCII_DIGIT+ } +Robot = { HeaderRobot ~ NEWLINE ~ Instructions ~ NEWLINE+ } +HeaderRobot = { ASCII_DIGIT+ ~ " " ~ ASCII_DIGIT+ ~ " " ~ ("S" | "N" | "W" | "E") } +Instructions = { (("F" | "L" | "R")+) | "" } diff --git a/src/main.rs b/src/main.rs index c1bba05..aabe45f 100644 --- a/src/main.rs +++ b/src/main.rs @@ -59,93 +59,91 @@ fn create_hash_map(pool: &Vec, hash: &mut HashMap String { +fn gen_world(pool: &mut Vec) -> Result { + let w = world::random_world(); let mut rng = rand::thread_rng(); - let n = rng.gen_range(5, 10); - let mut instructions = String::with_capacity(n); - const CHARSET: &[u8] = b"LRF"; - for _ in 0..n { - let l = rng.gen_range(0, CHARSET.len()); - instructions.push(CHARSET[l] as char); + let x = rng.gen_range(2, 10); + for i in 0..=x { + let r = robot::Robot::new_random(i + 1, w.x - 2, w.y - 2)?; + pool.push(r); } - - instructions + Ok(w) } /// Parse the config file, generate the world and robot pool. fn parse_config(conf: String, pool: &mut Vec) -> Result { - let mut lines: Vec<&str> = conf.split('\n').collect(); - let raw_world = match ConfParser::parse(Rule::world, lines.remove(0)) { - Ok(s) => s.as_str(), - Err(_) => return Err(String::from("World config is broken.")), + let pairs = match ConfParser::parse(Rule::World, &conf) { + Ok(p) => p, + Err(_) => return Err(String::from("Config is broken.")), }; - let mut w: Vec = Vec::with_capacity(2); - for n in raw_world.split_whitespace() { - let v: i32 = n.parse::().unwrap(); - w.push(v); + + let mut world = world::World { x: 0, y: 0 }; + let mut id = 1; + for pair in pairs { + for inner in pair.into_inner() { + match inner.as_rule() { + Rule::HeaderWorld => { + let mut w: Vec = Vec::with_capacity(2); + for n in inner.as_str().split_whitespace() { + let v: i32 = n.parse::().unwrap(); + w.push(v); + } + world = world::World { x: w[0], y: w[1] }; + } + Rule::Robot => { + let mut vpos: Vec = Vec::new(); + let mut vor: Vec = Vec::new(); + let mut vinst: Vec> = Vec::new(); + for inner_robot in inner.into_inner() { + match inner_robot.as_rule() { + Rule::HeaderRobot => { + let mut setup = inner_robot.as_str().split_whitespace(); + let rx = setup.next().unwrap(); + let ry = setup.next().unwrap(); + let ro = setup.next().unwrap(); + vpos.push(robot::Position { + x: rx.parse::().unwrap(), + y: ry.parse::().unwrap(), + }); + vor.push(match ro { + "N" => robot::Orientation::N, + "E" => robot::Orientation::E, + "S" => robot::Orientation::S, + _ => robot::Orientation::W, + }) + } + Rule::Instructions => { + if !(inner_robot.as_str() == "") { + let instructions = inner_robot.as_str(); + vinst.push(robot::instructions_from_string( + instructions.chars().rev().collect::(), + )?); + } else { + let instructions = robot::gen_random_instructions(); + vinst.push(robot::instructions_from_string( + instructions.chars().rev().collect::(), + )?); + } + let r = robot::Robot::new( + id, + vor.pop().unwrap(), + vpos.pop().unwrap(), + vinst.pop().unwrap(), + ); + match check_map(&r, &world) { + Ok(()) => pool.push(r), + Err(err) => return Err(err), + } + id += 1; + } + _ => unreachable!(), + } + } + } + _ => unreachable!(), + }; + } } - let world = world::World { x: w[0], y: w[1] }; - lines.remove(0); - let mut r_id: u32 = 0; - loop { - r_id += 1; - if lines.len() == 0 { - break; - } - let raw_setup = match ConfParser::parse(Rule::robot_init, lines.remove(0)) { - Ok(s) => s.as_str(), - Err(_) => return Err(String::from("Robot setup is broken.")), - }; - - let rand_instructions = gen_random_instructions(); - let l = lines.remove(0); - - let instructions = match ConfParser::parse(Rule::robot_instructions, l) { - Ok(s) => s.as_str(), - Err(_) => rand_instructions.as_str(), - }; - - let mut setup = raw_setup.split_whitespace(); - let pos_x = setup.next().unwrap(); - let pos_y = setup.next().unwrap(); - let orientation = setup.next().unwrap(); - // Convert values of the setup line - let r_x = pos_x.parse::().unwrap(); - let r_y = pos_y.parse::().unwrap(); - let r_o = match orientation { - "N" => robot::Orientation::N, - "E" => robot::Orientation::E, - "S" => robot::Orientation::S, - "W" => robot::Orientation::W, - _ => { - return Err(String::from( - "The third token of the setup line do not match any orientations !", - )) - } - }; - // Convert instructions line. - let inst: Vec = instructions.chars().rev().collect(); - if !robot::is_instructions(&inst) { - return Err(String::from("Invalid instructions !")); - } - - let r = robot::Robot::new(r_id, r_o, robot::Position { x: r_x, y: r_y }, inst); - // Load robot inside the pool. - match check_map(&r, &world) { - Ok(()) => pool.push(r), - Err(err) => return Err(err), - } - - if lines.len() == 0 { - break; - } - if l.len() == 0 { - continue; - } - lines.remove(0); - } - Ok(world) } @@ -213,15 +211,27 @@ fn main() -> Result<(), Box> { .takes_value(false) .help("Generate random world"), ) + .arg( + Arg::with_name("random") + .short("r") + .long("random") + .takes_value(false) + .help("Generate random world"), + ) .get_matches(); - let raw_conf = open_file(matches.value_of("file").unwrap_or("two_robots.txt"))?; - let mut robot_pool: Vec = Vec::new(); - let mut world: world::World = parse_config(raw_conf, &mut robot_pool)?; - world = match matches.is_present("random-world") { - false => world, - true => world::random_world(), + let world = match matches.is_present("random") { + true => gen_world(&mut robot_pool)?, + false => { + let raw_conf = open_file(matches.value_of("file").unwrap_or("two_robots.txt"))?; + let mut world: world::World = parse_config(raw_conf, &mut robot_pool)?; + world = match matches.is_present("random-world") { + false => world, + true => world::random_world(), + }; + world + } }; let mut hash: HashMap = HashMap::new(); @@ -284,7 +294,7 @@ mod tests { 0, robot::Orientation::N, robot::Position { x: 2, y: 3 }, - vec!['F'], + vec![robot::Instruction::F], ); let w = world::World { x: 10, y: 10 }; @@ -298,7 +308,7 @@ mod tests { 0, robot::Orientation::N, robot::Position { x: 2, y: 4 }, - vec!['F'], + vec![robot::Instruction::F], ); let w = world::World { x: 3, y: 3 }; @@ -312,7 +322,7 @@ mod tests { 0, robot::Orientation::N, robot::Position { x: 2, y: 3 }, - vec!['F'], + vec![robot::Instruction::F], ); let mut h: HashMap = HashMap::new(); diff --git a/src/robot.rs b/src/robot.rs index 42e3adc..aa96587 100644 --- a/src/robot.rs +++ b/src/robot.rs @@ -9,37 +9,41 @@ pub struct Robot { pub id: u32, pub o: Orientation, pub p: Position, - i: Vec, + pub i: Vec, } impl Robot { /// Create new `Robot` with given id, `Orientation`, `Position` and instructions. - pub fn new(id: u32, o: Orientation, p: Position, i: Vec) -> Robot { + pub fn new(id: u32, o: Orientation, p: Position, i: Vec) -> Robot { Robot { id, o, p, i } } + /// Create new random `Robot`. + pub fn new_random(id: u32, posx_max: i32, posy_max: i32) -> Result { + let mut rng = rand::thread_rng(); + let x = rng.gen_range(2, posx_max); + let y = rng.gen_range(2, posy_max); + let inst = gen_random_instructions(); + let instructions: Vec = instructions_from_string(inst)?; + let o: Orientation = rand::random(); + Ok(Robot { + id, + o, + p: Position { x, y }, + i: instructions, + }) + } /// Apply given instruction to a `Robot`. pub fn execute_instruction(&mut self) { match self.i.pop() { Some(instruction) => match instruction { - 'L' => match self.o { - Orientation::N => self.o = Orientation::W, - Orientation::E => self.o = Orientation::N, - Orientation::S => self.o = Orientation::E, - Orientation::W => self.o = Orientation::S, - }, - 'R' => match self.o { - Orientation::N => self.o = Orientation::E, - Orientation::E => self.o = Orientation::S, - Orientation::S => self.o = Orientation::W, - Orientation::W => self.o = Orientation::N, - }, - 'F' => match self.o { + Instruction::L => self.o = turn_left(&self.o), + Instruction::R => self.o = turn_right(&self.o), + Instruction::F => match self.o { Orientation::N => self.p.y += 1, Orientation::E => self.p.x += 1, Orientation::S => self.p.y -= 1, Orientation::W => self.p.x -= 1, }, - _ => (), // never happens 😉 }, None => (), } @@ -55,6 +59,44 @@ pub enum Orientation { W, } +fn turn_left(o: &Orientation) -> Orientation { + match o { + Orientation::N => Orientation::W, + Orientation::E => Orientation::N, + Orientation::S => Orientation::E, + Orientation::W => Orientation::S, + } +} +fn turn_right(o: &Orientation) -> Orientation { + match o { + Orientation::N => Orientation::E, + Orientation::E => Orientation::S, + Orientation::S => Orientation::W, + Orientation::W => Orientation::N, + } +} + +/// Enum to store all possible instructions. +#[derive(Debug, Eq, PartialEq)] +pub enum Instruction { + L, + R, + F, +} + +pub fn instructions_from_string(s: String) -> Result, String> { + let mut v: Vec = Vec::new(); + for c in s.chars() { + match c { + 'L' => v.push(Instruction::L), + 'R' => v.push(Instruction::R), + 'F' => v.push(Instruction::F), + _ => return Err(String::from("Not an instruction.")), + } + } + Ok(v) +} + impl Distribution for Standard { /// Generating random orientation. fn sample(&self, rng: &mut R) -> Orientation { @@ -74,17 +116,18 @@ pub struct Position { pub y: i32, } -/// Check if instructions list is valid. -pub fn is_instructions(v: &Vec) -> bool { - for c in v { - match c { - 'F' => continue, - 'R' => continue, - 'L' => continue, - _ => return false, - } +/// Generate random instructions. +pub fn gen_random_instructions() -> String { + let mut rng = rand::thread_rng(); + let n = rng.gen_range(5, 10); + let mut instructions = String::with_capacity(n); + const CHARSET: &[u8] = b"LRF"; + for _ in 0..n { + let l = rng.gen_range(0, CHARSET.len()); + instructions.push(CHARSET[l] as char); } - true + + instructions } /// Check if a robot is piouff. @@ -136,13 +179,15 @@ mod tests { 0, Orientation::N, Position { x: 1, y: 2 }, - vec!['L', 'L', 'F', 'R'], + vec![Instruction::L, Instruction::F, Instruction::R], ); assert_eq!(r.id, 0); assert!(matches!(r.o, Orientation::N)); assert_eq!(r.p.x, 1); assert_eq!(r.p.y, 2); - assert_eq!(r.i, vec!['L', 'L', 'F', 'R']); + assert_eq!(r.i[0], Instruction::L); + assert_eq!(r.i[1], Instruction::F); + assert_eq!(r.i[2], Instruction::R); } #[test] @@ -151,7 +196,12 @@ mod tests { 0, Orientation::N, Position { x: 1, y: 2 }, - vec!['R', 'F', 'L', 'F'], + vec![ + Instruction::R, + Instruction::F, + Instruction::L, + Instruction::F, + ], ); let mut hash = std::collections::HashMap::new(); //hash.insert(&r.p, &r.id); // first insert while initializing. @@ -166,26 +216,18 @@ mod tests { assert_eq!(r.p.y, 3); } - #[test] - fn is_instructions_test() { - let v = vec!['F', 'R', 'L', 'F']; - assert!(is_instructions(&v)); - } - - #[test] - #[should_panic] - fn is_instructions_test_fail() { - let v = vec!['F', 'R', 'L', 'Z']; - assert!(is_instructions(&v)); - } - #[test] fn test_piouf() { let mut r: Robot = Robot::new( 0, Orientation::N, Position { x: 1, y: 2 }, - vec!['R', 'F', 'L', 'F'], + vec![ + Instruction::R, + Instruction::F, + Instruction::L, + Instruction::F, + ], ); r.i.pop(); r.i.pop();