Java Tutorial Through Katas: Mars Rover

A programming kata is an exercise which helps a programmer hone his skills through practice and repetition.

This article is part of the series Java Tutorial Through Katas.

The article assumes that the reader already has experience with Java, that he is familiar with the basics of unit tests and that he knows how to run them from his favorite IDE (mine is IntelliJ IDEA).

Tests that prove that the solution is correct are displayed below. Recommended way to solve this kata is to use test-driven development approach (write the implementation for the first test, confirm that it passes and move to the next). Once all of the tests pass, the kata can be considered solved. For more information about best practices, please read the Test Driven Development (TDD): Best Practices Using Java Examples.

One possible solution is provided below the tests. Try to solve the kata by yourself first.

Mars Rover

Develop an api that moves a rover around on a grid.

Rules:

  • You are given the initial starting point (x,y) of a rover and the direction (N,S,E,W) it is facing.
  • The rover receives a character array of commands.
  • Implement commands that move the rover forward/backward (f,b).
  • Implement commands that turn the rover left/right (l,r).
  • Implement wrapping from one edge of the grid to another. (planets are spheres after all)
  • Implement obstacle detection before each move to a new square. If a given sequence of commands encounters an obstacle, the rover moves up to the last possible point and reports the obstacle.

Tests

Following is a set of unit tests that can be used to solve this kata in the TDD fashion.

package com.technologyconversations.kata.marsrover;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static org.assertj.core.api.Assertions.*;
/*
Source: http://dallashackclub.com/rover
Develop an api that moves a rover around on a grid.
* You are given the initial starting point (x,y) of a rover and the direction (N,S,E,W) it is facing.
* - The rover receives a character array of commands.
* - Implement commands that move the rover forward/backward (f,b).
* - Implement commands that turn the rover left/right (l,r).
* - Implement wrapping from one edge of the grid to another. (planets are spheres after all)
* - Implement obstacle detection before each move to a new square.
* If a given sequence of commands encounters an obstacle, the rover moves up to the last possible point and reports the obstacle.
*/
public class RoverSpec {
private Rover rover;
private Coordinates roverCoordinates;
private final Direction direction = Direction.NORTH;
private Point x;
private Point y;
private List<Obstacle> obstacles;
@Before
public void beforeRoverTest() {
x = new Point(1, 9);
y = new Point(2, 9);
obstacles = new ArrayList<Obstacle>();
roverCoordinates = new Coordinates(x, y, direction, obstacles);
rover = new Rover(roverCoordinates);
}
@Test
public void newInstanceShouldSetRoverCoordinatesAndDirection() {
assertThat(rover.getCoordinates()).isEqualToComparingFieldByField(roverCoordinates);
}
@Test
public void receiveSingleCommandShouldMoveForwardWhenCommandIsF() throws Exception {
int expected = y.getLocation() + 1;
rover.receiveSingleCommand('F');
assertThat(rover.getCoordinates().getY().getLocation()).isEqualTo(expected);
}
@Test
public void receiveSingleCommandShouldMoveBackwardWhenCommandIsB() throws Exception {
int expected = y.getLocation() - 1;
rover.receiveSingleCommand('B');
assertThat(rover.getCoordinates().getY().getLocation()).isEqualTo(expected);
}
@Test
public void receiveSingleCommandShouldTurnLeftWhenCommandIsL() throws Exception {
rover.receiveSingleCommand('L');
assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.WEST);
}
@Test
public void receiveSingleCommandShouldTurnRightWhenCommandIsR() throws Exception {
rover.receiveSingleCommand('R');
assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.EAST);
}
@Test
public void receiveSingleCommandShouldIgnoreCase() throws Exception {
rover.receiveSingleCommand('r');
assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.EAST);
}
@Test(expected = Exception.class)
public void receiveSingleCommandShouldThrowExceptionWhenCommandIsUnknown() throws Exception {
rover.receiveSingleCommand('X');
}
@Test
public void receiveCommandsShouldBeAbleToReceiveMultipleCommands() throws Exception {
int expected = x.getLocation() + 1;
rover.receiveCommands("RFR");
assertThat(rover.getCoordinates().getX().getLocation()).isEqualTo(expected);
assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.SOUTH);
}
@Test
public void receiveCommandShouldWhatFromOneEdgeOfTheGridToAnother() throws Exception {
int expected = x.getMaxLocation() + x.getLocation() - 2;
rover.receiveCommands("LFFF");
assertThat(rover.getCoordinates().getX().getLocation()).isEqualTo(expected);
}
@Test
public void receiveCommandsShouldStopWhenObstacleIsFound() throws Exception {
int expected = x.getLocation() + 1;
rover.getCoordinates().setObstacles(Arrays.asList(new Obstacle(expected + 1, y.getLocation())));
rover.getCoordinates().setDirection(Direction.EAST);
rover.receiveCommands("FFFRF");
assertThat(rover.getCoordinates().getX().getLocation()).isEqualTo(expected);
assertThat(rover.getCoordinates().getDirection()).isEqualTo(Direction.EAST);
}
@Test
public void positionShouldReturnXYAndDirection() throws Exception {
rover.receiveCommands("LFFFRFF");
assertThat(rover.getPosition()).isEqualTo("8 X 4 N");
}
@Test
public void positionShouldReturnNokWhenObstacleIsFound() throws Exception {
rover.getCoordinates().setObstacles(Arrays.asList(new Obstacle(x.getLocation() + 1, y.getLocation())));
rover.getCoordinates().setDirection(Direction.EAST);
rover.receiveCommands("F");
assertThat(rover.getPosition()).endsWith(" NOK");
}
}
view raw RoverSpec.java hosted with ❤ by GitHub

One possible solution is following.

package com.technologyconversations.kata.marsrover;
/*
Method receiveCommands should be used to transmit commands to the rover.
*/
public class Rover {
private Coordinates coordinates;
public void setCoordinates(Coordinates value) {
coordinates = value;
}
public Coordinates getCoordinates() {
return coordinates;
}
public Rover(Coordinates coordinatesValue) {
setCoordinates(coordinatesValue);
}
public void receiveCommands(String commands) throws Exception {
for (char command : commands.toCharArray()) {
if (!receiveSingleCommand(command)) {
break;
}
}
}
public boolean receiveSingleCommand(char command) throws Exception {
switch(Character.toUpperCase(command)) {
case 'F':
return getCoordinates().moveForward();
case 'B':
return getCoordinates().moveBackward();
case 'L':
getCoordinates().changeDirectionLeft();
return true;
case 'R':
getCoordinates().changeDirectionRight();
return true;
default:
throw new Exception("Command " + command + " is unknown.");
}
}
public String getPosition() {
return getCoordinates().toString();
}
}
view raw Rover.java hosted with ❤ by GitHub

Full source is located in the GitHub repo mars-rover-kata-java. Above code presents only the code of the main class. There are several other classes/objects with their corresponding specification. Besides tests and implementation, repository includes build.gradle that can be used, among other things, to download AssertJ dependencies and run tests. README.md contains short instructions how to set up the project.

What was your solution? Post it as a comment so that we can compare different ways to solve this kata.

Test-Driven Java Development

7429OS_Test-Driven Java DevelopmentTest-Driven Java Development book wrote by Alex Garcia and me has been published by Packt Publishing. It was a long, demanding, but very rewarding journey that resulted in a very comprehensive hands-on material for all Java developers interested in learning or improving their TDD skills.

If you liked this article I am sure that you’ll find this book very useful. It contains extensive tutorials, guidelines and exercises for all Java developers eager to learn how to successfully apply TDD practices.

You can download a sample or purchase your own copy directly from Packt or Amazon.

8 thoughts on “Java Tutorial Through Katas: Mars Rover

  1. Javier

    Hi,
    I was playing around this Kata to practice more TDD and DDD. It is a similar to yours solution but probably decoupling some responsabilities from Coordinates.

    At the end, adding obstacles in my solution, I am not sure if make sense that Terrain is a VO instead an entity containing one or multiple Rovers.

    Feedback is much appreciate to the solution on goo.gl/p61Aal
    https://github.com/mustaine/katas/tree/master/mars-rover

    Reply
    1. Viktor Farcic Post author

      I really liked how you decoupled responsibilities. The code is very clean and easy to understand (which is one of the things I value the most).

      The only negative comment would be that you might have a bit more of code duplication than I’d like. For example, Direction.java could be refactored to something with less repeated code.

      Overall, I think it’s a great solution. I’m currently writing a book on TDD, that will, among other things, have Mars Rover exercise. Would you mind if I put a link to your solution?

      Reply
      1. Javier

        Feel free to do it, it would be an honor xD

        Definetly I will look at your suggestion. I will try to do a refactoring with it and I was thinking also to decouple the command processor as should not be part of the Rover as I understand the problem.

        Reply
  2. Pingback: Data-driven tests in Junit5.0.0-SNAPSHOT | Markus Gärtner

  3. Balazs Gabonyi

    Hi,
    When I first read the specification I was confused and I am still a bit confused.
    I may be wrong, so please correct me if I am.

    My problem is with the teminology, CoordiantesSpec and PointSpec class names for me are misleading. Let me explain.

    An N dimensional space can be spanned with N (ortogonal) basis vectors.
    A point can be defined as the linear combination of these basis vectors.
    So the usual notation is a list of the coefficients of these vectors called coordiantes.

    To sum it up: In my understading a Point is made up from a set of N coordinates such as
    Point point = new Point (1,2);

    But am I right that in this specification Point is instead meant to be the implementation of a single coordiante with its maximum value?

    My suggestion would be composing a Point from 2 Coordiantes and the Coordinate class should hold the maximum value.

    Am I completely wrong or mistaken?

    Reply
    1. cugx

      Balazs,
      I completely agree with you.
      No matter the cardinality of the space the rover is operating in, its coordinate is always a point.
      If its world is 2-dimensional, you’ll be able to describe its coordinates with two numbers;
      if it’s 3-dimensional, 3 numbers would be required; and so on.

      Unfortunately, the authors decided to put the rover in two places at the same time on a two dimensional map (c.f. the @Before annotated method)
      – once at (1,9)
      – once at (2,9)
      those positions seem to be mutated independently depending the direction and command

      It is either the authors meant to say that the rover is a photon or something else that allows one instance to manifest itself in two different places at the same time, or the example is incorrect if designed to be applied in real life.

      Reply
  4. Marit

    Dependencies for maven:

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.assertj</groupId>
            <artifactId>assertj-core</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>
    
    Reply

Leave a Reply