From 9129c83803c384f0653486925ca8d55439069fc9 Mon Sep 17 00:00:00 2001 From: Ian Whitney Date: Mon, 27 Jun 2016 10:05:30 -0500 Subject: [PATCH] Implement Robot Simulator Tests come from https://github.com/exercism/x-common/blob/183934754b1847612809db410a8aeb640678f188/robot-simulator.json As discussed in https://github.com/exercism/xrust/issues/117 we are providing a stub implementation so that ignored tests do not fail when a student hasn't yet implemented the functionality they exercise. A lot of excellent feedback and help provided by - jonasbb - petertseng - steveklabnik - Ryman - Dr-Emann Full details of the design discussion at https://github.com/exercism/xrust/pull/146 -- Some details that people may care about: ---- Robots are immutable because: - Immutability is the default in Rust - No other problem (that I know of) really features Rust's default immutability - Immutability is not my natural inclination I figure if I don't expect immutability then other programmers with an OO-focused background probably don't expect immutability. So a problem that forces immutability may make me (and them) think about immutability. Which I think is good. Immutability brings other benefits, though they aren't exposed by the tests. It would be very easy to trace your Robot's path, for example. Re-winding to a specific point on the path would be trivial. All very nice, but not part of the test suite. ---- The example code has the `build` function because: There's still some awkwardness around the `new` function, since it takes x/y and all the internals use Position. I have to preserve the `new(x, y, direction)` function signature because of the tests (and because the tests should follow the example of Queen Attack, which also passes `x` and `y` in as separate parameters. But that function becomes a pain once I'm working inside the Robot and I have a Position. The new `build` function creates a robot using a Position and Direction, so it can be easily used by all of the Robot's internals. And `new` now just wraps around `build`. This is kind of the exact opposite way I'd normally do this. I'd expect a `build` function to be the public API, do the data manipulation and then call a private `new` function. But if I were to do that here then all of the tests would contain: ``` Robot::build(x, y, &direction) ``` Which I think would be non-idiomatic and weird to students. ---- --- config.json | 1 + exercises/robot-simulator/Cargo.lock | 4 + exercises/robot-simulator/Cargo.toml | 3 + exercises/robot-simulator/example.rs | 101 ++++++++++++ exercises/robot-simulator/src/lib.rs | 44 ++++++ .../robot-simulator/tests/robot-simulator.rs | 147 ++++++++++++++++++ 6 files changed, 300 insertions(+) create mode 100644 exercises/robot-simulator/Cargo.lock create mode 100644 exercises/robot-simulator/Cargo.toml create mode 100644 exercises/robot-simulator/example.rs create mode 100644 exercises/robot-simulator/src/lib.rs create mode 100644 exercises/robot-simulator/tests/robot-simulator.rs diff --git a/config.json b/config.json index 059600eec..5e3b91fc9 100644 --- a/config.json +++ b/config.json @@ -22,6 +22,7 @@ "roman-numerals", "hexadecimal", "grade-school", + "robot-simulator", "queen-attack", "sublist", "allergies", diff --git a/exercises/robot-simulator/Cargo.lock b/exercises/robot-simulator/Cargo.lock new file mode 100644 index 000000000..05f6c5b20 --- /dev/null +++ b/exercises/robot-simulator/Cargo.lock @@ -0,0 +1,4 @@ +[root] +name = "robot-simulator" +version = "0.0.0" + diff --git a/exercises/robot-simulator/Cargo.toml b/exercises/robot-simulator/Cargo.toml new file mode 100644 index 000000000..af4017bd7 --- /dev/null +++ b/exercises/robot-simulator/Cargo.toml @@ -0,0 +1,3 @@ +[package] +name = "robot-simulator" +version = "0.0.0" diff --git a/exercises/robot-simulator/example.rs b/exercises/robot-simulator/example.rs new file mode 100644 index 000000000..f20eb1ab2 --- /dev/null +++ b/exercises/robot-simulator/example.rs @@ -0,0 +1,101 @@ +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum Direction { + North, + East, + South, + West, +} + +impl Direction { + pub fn previous_clockwise(&self) -> Self { + match *self { + Direction::North => Direction::West, + Direction::East => Direction::North, + Direction::South => Direction::East, + Direction::West => Direction::South, + } + } + + pub fn next_clockwise(&self) -> Self { + match *self { + Direction::North => Direction::East, + Direction::East => Direction::South, + Direction::South => Direction::West, + Direction::West => Direction::North, + } + } +} + +#[derive(Clone, Copy)] +struct Position { + x: i32, + y: i32, +} + +impl Position { + fn new(x: i32, y: i32) -> Self { + Position { x: x, y: y } + } + + fn advance(&self, direction: &Direction) -> Self { + match *direction { + Direction::North => Self::new(self.x, self.y + 1), + Direction::South => Self::new(self.x, self.y - 1), + Direction::East => Self::new(self.x + 1, self.y), + Direction::West => Self::new(self.x - 1, self.y), + } + } +} + +#[derive(Clone)] +pub struct Robot { + position: Position, + direction: Direction, +} + +impl Robot { + pub fn new(x: i32, y: i32, d: Direction) -> Self { + Robot::build(Position::new(x, y), d) + } + + fn build(position: Position, direction: Direction) -> Self { + Robot { + position: position, + direction: direction, + } + } + + pub fn turn_right(&self) -> Self { + Self::build(self.position, self.direction.next_clockwise()) + } + + pub fn turn_left(&self) -> Self { + Self::build(self.position, self.direction.previous_clockwise()) + } + + pub fn advance(&self) -> Self { + Self::build(self.position.advance(&self.direction), self.direction) + } + + pub fn instructions(&self, instructions: &str) -> Self { + instructions.chars().fold(self.clone(), + |robot, instruction| robot.execute(instruction)) + } + + pub fn position(&self) -> (i32, i32) { + (self.position.x, self.position.y) + } + + pub fn direction(&self) -> &Direction { + &self.direction + } + + fn execute(self, command: char) -> Self { + match command { + 'R' => self.turn_right(), + 'L' => self.turn_left(), + 'A' => self.advance(), + _ => self, + } + } +} diff --git a/exercises/robot-simulator/src/lib.rs b/exercises/robot-simulator/src/lib.rs new file mode 100644 index 000000000..d99a28609 --- /dev/null +++ b/exercises/robot-simulator/src/lib.rs @@ -0,0 +1,44 @@ +// The code below is a stub. Just enough to satisfy the compiler. +// In order to pass the tests you can add-to or change any of this code. + +#[derive(PartialEq, Debug)] +pub enum Direction { + North, + East, + South, + West, +} + +pub struct Robot; + +impl Robot { + #[allow(unused_variables)] + pub fn new(x: isize, y: isize, d: Direction) -> Self { + unimplemented!() + } + + pub fn turn_right(self) -> Self { + unimplemented!() + } + + pub fn turn_left(self) -> Self { + unimplemented!() + } + + pub fn advance(self) -> Self { + unimplemented!() + } + + #[allow(unused_variables)] + pub fn instructions(self, instructions: &str) -> Self { + unimplemented!() + } + + pub fn position(&self) -> (isize, isize) { + unimplemented!() + } + + pub fn direction(&self) -> &Direction { + unimplemented!() + } +} diff --git a/exercises/robot-simulator/tests/robot-simulator.rs b/exercises/robot-simulator/tests/robot-simulator.rs new file mode 100644 index 000000000..81528ba76 --- /dev/null +++ b/exercises/robot-simulator/tests/robot-simulator.rs @@ -0,0 +1,147 @@ +extern crate robot_simulator; + +use robot_simulator::*; + +#[test] +fn robots_are_created_with_position_and_direction() { + let robot = Robot::new(0, 0, Direction::North); + assert_eq!((0, 0), robot.position()); + assert_eq!(&Direction::North, robot.direction()); +} + +#[test] +#[ignore] +fn positions_can_be_negative() { + let robot = Robot::new(-1, -1, Direction::South); + assert_eq!((-1, -1), robot.position()); + assert_eq!(&Direction::South, robot.direction()); +} + +#[test] +#[ignore] +fn turning_right_does_not_change_position() { + let robot = Robot::new(0, 0, Direction::North).turn_right(); + assert_eq!((0, 0), robot.position()); +} + +#[test] +#[ignore] +fn turning_right_from_north_points_the_robot_east() { + let robot = Robot::new(0, 0, Direction::North).turn_right(); + assert_eq!(&Direction::East, robot.direction()); +} + +#[test] +#[ignore] +fn turning_right_from_east_points_the_robot_south() { + let robot = Robot::new(0, 0, Direction::East).turn_right(); + assert_eq!(&Direction::South, robot.direction()); +} + +#[test] +#[ignore] +fn turning_right_from_south_points_the_robot_west() { + let robot = Robot::new(0, 0, Direction::South).turn_right(); + assert_eq!(&Direction::West, robot.direction()); +} + +#[test] +#[ignore] +fn turning_right_from_west_points_the_robot_north() { + let robot = Robot::new(0, 0, Direction::West).turn_right(); + assert_eq!(&Direction::North, robot.direction()); +} + +#[test] +#[ignore] +fn turning_left_does_not_change_position() { + let robot = Robot::new(0, 0, Direction::North).turn_left(); + assert_eq!((0, 0), robot.position()); +} + +#[test] +#[ignore] +fn turning_left_from_north_points_the_robot_west() { + let robot = Robot::new(0, 0, Direction::North).turn_left(); + assert_eq!(&Direction::West, robot.direction()); +} + +#[test] +#[ignore] +fn turning_left_from_west_points_the_robot_south() { + let robot = Robot::new(0, 0, Direction::West).turn_left(); + assert_eq!(&Direction::South, robot.direction()); +} + +#[test] +#[ignore] +fn turning_left_from_south_points_the_robot_east() { + let robot = Robot::new(0, 0, Direction::South).turn_left(); + assert_eq!(&Direction::East, robot.direction()); +} + +#[test] +#[ignore] +fn turning_left_from_east_points_the_robot_north() { + let robot = Robot::new(0, 0, Direction::East).turn_left(); + assert_eq!(&Direction::North, robot.direction()); +} + +#[test] +#[ignore] +fn advance_does_not_change_the_direction() { + let robot = Robot::new(0, 0, Direction::North).advance(); + assert_eq!(&Direction::North, robot.direction()); +} + +#[test] +#[ignore] +fn advance_increases_the_y_coordinate_by_one_when_facing_north() { + let robot = Robot::new(0, 0, Direction::North).advance(); + assert_eq!((0, 1), robot.position()); +} + +#[test] +#[ignore] +fn advance_decreases_the_y_coordinate_by_one_when_facing_south() { + let robot = Robot::new(0, 0, Direction::South).advance(); + assert_eq!((0, -1), robot.position()); +} + +#[test] +#[ignore] +fn advance_increases_the_x_coordinate_by_one_when_facing_east() { + let robot = Robot::new(0, 0, Direction::East).advance(); + assert_eq!((1, 0), robot.position()); +} + +#[test] +#[ignore] +fn advance_decreases_the_x_coordinate_by_one_when_facing_west() { + let robot = Robot::new(0, 0, Direction::West).advance(); + assert_eq!((-1, 0), robot.position()); +} + +#[test] +#[ignore] +fn follow_instructions_to_move_west_and_north() { + let robot = Robot::new(0, 0, Direction::North).instructions("LAAARALA"); + assert_eq!((-4, 1), robot.position()); + assert_eq!(&Direction::West, robot.direction()); +} + +#[test] +#[ignore] +fn follow_instructions_to_move_west_and_south() { + let robot = Robot::new(2, -7, Direction::East).instructions("RRAAAAALA"); + assert_eq!((-3, -8), robot.position()); + assert_eq!(&Direction::South, robot.direction()); +} + +#[test] +#[ignore] +fn follow_instructions_to_move_east_and_north() { + let robot = Robot::new(8, 4, Direction::South).instructions("LAAARRRALLLL"); + assert_eq!((11, 5), robot.position()); + assert_eq!(&Direction::North, robot.direction()); +}