dancing_droids/src/robot.rs
2020-11-08 21:22:08 +01:00

239 lines
6.3 KiB
Rust

use rand::{
distributions::{Distribution, Standard},
Rng,
};
/// A Robot *aka droid* is represented here.
/// Each robot must have a unique id.
pub struct Robot {
pub id: u32,
pub o: Orientation,
pub p: Position,
pub i: Vec<Instruction>,
}
impl Robot {
/// Create new `Robot` with given id, `Orientation`, `Position` and instructions.
pub fn new(id: u32, o: Orientation, p: Position, i: Vec<Instruction>) -> Robot {
Robot { id, o, p, i }
}
/// Create new random `Robot`.
pub fn new_random(id: u32, posx_max: i32, posy_max: i32) -> Result<Robot, String> {
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<Instruction> = 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 {
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,
},
},
None => (),
}
}
}
/// Enum to store all possible orientations.
#[derive(Debug)]
pub enum Orientation {
N,
E,
S,
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<Vec<Instruction>, String> {
let mut v: Vec<Instruction> = 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<Orientation> for Standard {
/// Generating random orientation.
fn sample<R: Rng + ?Sized>(&self, rng: &mut R) -> Orientation {
match rng.gen_range(0, 3) {
0 => Orientation::N,
1 => Orientation::E,
2 => Orientation::S,
_ => Orientation::W,
}
}
}
/// Struct to store robot position.
#[derive(PartialEq, Eq, Hash)]
pub struct Position {
pub x: i32,
pub y: i32,
}
/// 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);
}
instructions
}
/// Check if a robot is piouff.
pub fn is_piouff(r: &Robot) -> bool {
r.i.len() == 0
}
/// Print robots id, position and instructions.
pub fn print_robots(robot_pool: &Vec<Robot>) -> String {
let mut res = format!("Robots [\n");
for r in robot_pool {
res = format!(
"{}{{ id = {}, x = {}; y = {}; orientation: {}, instructions: {:?}, }},\n",
res,
r.id,
r.p.x,
r.p.y,
match r.o {
Orientation::N => "Norts",
Orientation::S => "South",
Orientation::E => "East",
Orientation::W => "West",
},
r.i
);
}
res = format!("{}]\n", res);
res
}
#[cfg(test)]
mod tests {
use super::*;
use std::any::type_name;
fn type_of<T>(_: T) -> &'static str {
type_name::<T>()
}
#[test]
fn test_rand_orientation() {
let o: Orientation = rand::random();
assert_eq!(type_of(o), "dancing_droid::robot::Orientation");
}
#[test]
fn test_new_robot() {
let r: Robot = Robot::new(
0,
Orientation::N,
Position { x: 1, y: 2 },
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[0], Instruction::L);
assert_eq!(r.i[1], Instruction::F);
assert_eq!(r.i[2], Instruction::R);
}
#[test]
fn test_execute_instruction() {
let mut r: Robot = Robot::new(
0,
Orientation::N,
Position { x: 1, y: 2 },
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.
hash.remove(&r.p); // remove before execute_instruction().
r.execute_instruction();
hash.insert(&r.p, &r.id); // second insert after moving.
assert_eq!(r.p.x, 1);
assert_eq!(r.p.y, 3);
r.execute_instruction();
r.execute_instruction();
assert_eq!(r.p.x, 0);
assert_eq!(r.p.y, 3);
}
#[test]
fn test_piouf() {
let mut r: Robot = Robot::new(
0,
Orientation::N,
Position { x: 1, y: 2 },
vec![
Instruction::R,
Instruction::F,
Instruction::L,
Instruction::F,
],
);
r.i.pop();
r.i.pop();
r.i.pop();
r.i.pop();
assert!(is_piouff(&r));
}
}