├── .gitignore ├── src └── com │ └── thoughtworks │ └── rover │ ├── commands │ ├── ICommand.java │ ├── MoveCommand.java │ ├── RotateLeftCommand.java │ └── RotateRightCommand.java │ ├── universe │ ├── Plateau.java │ ├── Direction.java │ └── Coordinates.java │ ├── MarsRover.java │ └── parser │ └── StringCommandParser.java ├── test ├── commands │ ├── MoveCommandTest.java │ ├── RotateLeftCommandTest.java │ └── RotateRightCommandTest.java ├── universe │ ├── PlateauTest.java │ ├── CoordinateTest.java │ └── DirectionTest.java ├── parser │ └── StringCommandParserTest.java └── MarsRoverTest.java ├── LICENSE.md └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.iml 3 | out/ 4 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/commands/ICommand.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover.commands; 2 | 3 | import com.thoughtworks.rover.MarsRover; 4 | 5 | public interface ICommand { 6 | 7 | public void execute(final MarsRover rover); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/commands/MoveCommand.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover.commands; 2 | 3 | import com.thoughtworks.rover.MarsRover; 4 | 5 | public class MoveCommand implements ICommand { 6 | 7 | @Override 8 | public void execute(final MarsRover rover) { 9 | rover.move(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/commands/RotateLeftCommand.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover.commands; 2 | 3 | import com.thoughtworks.rover.MarsRover; 4 | 5 | public class RotateLeftCommand implements ICommand { 6 | 7 | @Override 8 | public void execute(final MarsRover rover) { 9 | rover.turnLeft(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/commands/RotateRightCommand.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover.commands; 2 | 3 | import com.thoughtworks.rover.MarsRover; 4 | 5 | public class RotateRightCommand implements ICommand { 6 | 7 | @Override 8 | public void execute(final MarsRover rover) { 9 | rover.turnRight(); 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/universe/Plateau.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover.universe; 2 | 3 | public class Plateau { 4 | 5 | private Coordinates topRightCoordinates = new Coordinates(0, 0); 6 | private Coordinates bottomLeftCoordinates = new Coordinates(0, 0); 7 | 8 | public Plateau(final int topRightXCoordinate, final int topRightYCoordinate) { 9 | this.topRightCoordinates = this.topRightCoordinates.newCoordinatesFor(topRightXCoordinate, topRightYCoordinate); 10 | } 11 | 12 | public boolean hasWithinBounds(final Coordinates coordinates) { 13 | return this.bottomLeftCoordinates.hasOutsideBounds(coordinates) && this.topRightCoordinates.hasWithinBounds(coordinates); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /test/commands/MoveCommandTest.java: -------------------------------------------------------------------------------- 1 | package commands; 2 | 3 | import com.thoughtworks.rover.MarsRover; 4 | import com.thoughtworks.rover.commands.MoveCommand; 5 | import com.thoughtworks.rover.universe.Coordinates; 6 | import com.thoughtworks.rover.universe.Direction; 7 | import com.thoughtworks.rover.universe.Plateau; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | public class MoveCommandTest { 12 | 13 | @Test 14 | public void testThatMoveCommandMovesTheNavigableObject() { 15 | //Given 16 | MoveCommand command = new MoveCommand(); 17 | Plateau plateau = new Plateau(5,5); 18 | Coordinates startingPosition = new Coordinates(1,2); 19 | MarsRover rover = new MarsRover(plateau, Direction.N, startingPosition); 20 | 21 | //When 22 | command.execute(rover); 23 | 24 | //Then 25 | Assert.assertEquals("1 3 N", rover.currentLocation()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /test/commands/RotateLeftCommandTest.java: -------------------------------------------------------------------------------- 1 | package commands; 2 | 3 | import com.thoughtworks.rover.MarsRover; 4 | import com.thoughtworks.rover.commands.RotateLeftCommand; 5 | import com.thoughtworks.rover.universe.Coordinates; 6 | import com.thoughtworks.rover.universe.Direction; 7 | import com.thoughtworks.rover.universe.Plateau; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | public class RotateLeftCommandTest { 12 | 13 | @Test 14 | public void testThatRotateLeftCommandRotatesTheNavigableObjectLeft() { 15 | //Given 16 | RotateLeftCommand command = new RotateLeftCommand(); 17 | Plateau plateau = new Plateau(5,5); 18 | Coordinates startingPosition = new Coordinates(1,2); 19 | MarsRover rover = new MarsRover(plateau, Direction.N, startingPosition); 20 | 21 | //When 22 | command.execute(rover); 23 | 24 | //Then 25 | Assert.assertEquals("1 2 W", rover.currentLocation()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /test/commands/RotateRightCommandTest.java: -------------------------------------------------------------------------------- 1 | package commands; 2 | 3 | import com.thoughtworks.rover.MarsRover; 4 | import com.thoughtworks.rover.commands.RotateRightCommand; 5 | import com.thoughtworks.rover.universe.Coordinates; 6 | import com.thoughtworks.rover.universe.Direction; 7 | import com.thoughtworks.rover.universe.Plateau; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | public class RotateRightCommandTest { 12 | 13 | @Test 14 | public void testThatRotateRightCommandRotatesTheNavigableObjectRight() { 15 | //Given 16 | RotateRightCommand command = new RotateRightCommand(); 17 | Plateau plateau = new Plateau(5,5); 18 | Coordinates startingPosition = new Coordinates(1,2); 19 | MarsRover rover = new MarsRover(plateau, Direction.N, startingPosition); 20 | 21 | //When 22 | command.execute(rover); 23 | 24 | //Then 25 | Assert.assertEquals("1 2 E", rover.currentLocation()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Full License Text 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2014 Priyank Gupta 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a 7 | copy of this software and associated documentation files (the 8 | "Software"), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included 15 | in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 18 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/universe/Direction.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover.universe; 2 | 3 | public enum Direction { 4 | 5 | N(0,1) { 6 | @Override 7 | public Direction left() { 8 | return W; 9 | } 10 | 11 | @Override 12 | public Direction right() { 13 | return E; 14 | } 15 | }, 16 | 17 | S(0,-1) { 18 | @Override 19 | public Direction right() { 20 | return W; 21 | } 22 | 23 | @Override 24 | public Direction left() { 25 | return E; 26 | } 27 | }, 28 | 29 | E(1,0) { 30 | @Override 31 | public Direction right() { 32 | return S; 33 | } 34 | 35 | @Override 36 | public Direction left() { 37 | return N; 38 | } 39 | }, 40 | 41 | W(-1,0) { 42 | @Override 43 | public Direction right() { 44 | return N; 45 | } 46 | 47 | @Override 48 | public Direction left() { 49 | return S; 50 | } 51 | }; 52 | 53 | private final int stepSizeOnXAxis; 54 | private final int stepSizeOnYAxis; 55 | 56 | Direction(final int stepSizeOnXAxis, final int stepSizeOnYAxis) { 57 | this.stepSizeOnXAxis = stepSizeOnXAxis; 58 | this.stepSizeOnYAxis = stepSizeOnYAxis; 59 | } 60 | 61 | public abstract Direction right(); 62 | public abstract Direction left(); 63 | 64 | public int stepSizeForXAxis() { 65 | return this.stepSizeOnXAxis; 66 | } 67 | 68 | public int stepSizeForYAxis() { 69 | return this.stepSizeOnYAxis; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/MarsRover.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover; 2 | 3 | import com.thoughtworks.rover.commands.ICommand; 4 | import com.thoughtworks.rover.parser.StringCommandParser; 5 | import com.thoughtworks.rover.universe.Coordinates; 6 | import com.thoughtworks.rover.universe.Direction; 7 | import com.thoughtworks.rover.universe.Plateau; 8 | 9 | import java.util.List; 10 | 11 | public class MarsRover { 12 | 13 | private Coordinates currentCoordinates; 14 | private Direction currentDirection; 15 | private Plateau plateau; 16 | 17 | 18 | public MarsRover(final Plateau plateau, final Direction direction, final Coordinates coordinates) { 19 | this.plateau = plateau; 20 | this.currentDirection = direction; 21 | this.currentCoordinates = coordinates; 22 | } 23 | 24 | public void run(final String commandString) { 25 | List roverCommands = new StringCommandParser(commandString).toCommands(); 26 | for (ICommand command : roverCommands) { 27 | command.execute(this); 28 | } 29 | } 30 | 31 | public String currentLocation() { 32 | return currentCoordinates.toString() + " " + currentDirection.toString(); 33 | } 34 | 35 | public void turnRight() { 36 | this.currentDirection = this.currentDirection.right(); 37 | } 38 | 39 | public void turnLeft() { 40 | this.currentDirection = this.currentDirection.left(); 41 | } 42 | 43 | public void move() { 44 | Coordinates positionAfterMove = currentCoordinates.newCoordinatesForStepSize(currentDirection.stepSizeForXAxis(), currentDirection.stepSizeForYAxis()); 45 | 46 | //ignores the command if rover is being driven off plateau 47 | if(plateau.hasWithinBounds(positionAfterMove)) 48 | currentCoordinates = currentCoordinates.newCoordinatesFor(currentDirection.stepSizeForXAxis(), currentDirection.stepSizeForYAxis()); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /test/universe/PlateauTest.java: -------------------------------------------------------------------------------- 1 | package universe; 2 | 3 | import com.thoughtworks.rover.universe.Coordinates; 4 | import com.thoughtworks.rover.universe.Plateau; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | public class PlateauTest { 9 | 10 | @Test 11 | public void locationWithCoordinateWithinBoundsIsIdentified() { 12 | //Given 13 | Plateau mars = new Plateau(5,5); 14 | 15 | //When 16 | Coordinates plateauCoordinates = new Coordinates(5, 0); 17 | 18 | //Then 19 | Assert.assertTrue(mars.hasWithinBounds(plateauCoordinates)); 20 | } 21 | 22 | @Test 23 | public void locationWithPositiveXCoordinateOutsideBoundsIsIdentified() { 24 | //Given 25 | Plateau mars = new Plateau(5,5); 26 | 27 | //When 28 | Coordinates coordinates = new Coordinates(6, 0); 29 | 30 | //Then 31 | Assert.assertFalse(mars.hasWithinBounds(coordinates)); 32 | } 33 | 34 | @Test 35 | public void locationWithNegativeXCoordinateOutsideBoundsIsIdentified() { 36 | //Given 37 | Plateau mars = new Plateau(5,5); 38 | 39 | //When 40 | Coordinates coordinates = new Coordinates(-1, 0); 41 | 42 | 43 | //Then 44 | Assert.assertFalse(mars.hasWithinBounds(coordinates)); 45 | } 46 | 47 | @Test 48 | public void locationWithPositiveYCoordinateOutsideBoundsIsIdentified() { 49 | //Given 50 | Plateau mars = new Plateau(5,5); 51 | 52 | //When 53 | Coordinates coordinates = new Coordinates(0, 6); 54 | 55 | 56 | //Then 57 | Assert.assertFalse(mars.hasWithinBounds(coordinates)); 58 | } 59 | 60 | @Test 61 | public void locationWithNegativeYCoordinateOutsideBoundsIsIdentified() { 62 | //Given 63 | Plateau mars = new Plateau(5,5); 64 | 65 | //When 66 | Coordinates coordinates = new Coordinates(0, -1); 67 | 68 | 69 | //Then 70 | Assert.assertFalse(mars.hasWithinBounds(coordinates)); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/parser/StringCommandParser.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover.parser; 2 | 3 | import com.thoughtworks.rover.commands.ICommand; 4 | import com.thoughtworks.rover.commands.MoveCommand; 5 | import com.thoughtworks.rover.commands.RotateLeftCommand; 6 | import com.thoughtworks.rover.commands.RotateRightCommand; 7 | 8 | import java.util.*; 9 | 10 | public class StringCommandParser { 11 | 12 | public static final String BY_EACH_CHARACTER = ""; 13 | public static final int START_INDEX = 0; 14 | 15 | private static Map stringToCommandMap = new HashMap() {{ 16 | put("L", new RotateLeftCommand()); 17 | put("R", new RotateRightCommand()); 18 | put("M", new MoveCommand()); 19 | }}; 20 | 21 | private String commandString; 22 | 23 | public StringCommandParser(final String commandString) { 24 | this.commandString = commandString; 25 | } 26 | 27 | public List toCommands() { 28 | if(isNullOrEmpty(commandString)) return new ArrayList(); 29 | return buildCommandsList(commandString); 30 | } 31 | 32 | private List buildCommandsList(final String commandString) { 33 | List commands = new ArrayList(); 34 | 35 | for(String commandCharacter : commandCharactersFrom(commandString)) { 36 | if (commandCharacter == null) break; 37 | ICommand mappedCommand = lookupEquivalentCommand(commandCharacter.toUpperCase()); 38 | commands.add(mappedCommand); 39 | } 40 | 41 | return commands; 42 | } 43 | 44 | private boolean isNullOrEmpty(final String commandString) { 45 | return (null == commandString || commandString.trim().length() == 0); 46 | } 47 | 48 | private String[] commandCharactersFrom(final String commandString) { 49 | return Arrays.copyOfRange(commandString.split(BY_EACH_CHARACTER), START_INDEX, commandString.length() + 1); 50 | } 51 | 52 | private ICommand lookupEquivalentCommand(final String commandString) { 53 | return stringToCommandMap.get(commandString); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/com/thoughtworks/rover/universe/Coordinates.java: -------------------------------------------------------------------------------- 1 | package com.thoughtworks.rover.universe; 2 | 3 | public class Coordinates { 4 | 5 | private int xCoordinate; 6 | private int yCoordinate; 7 | 8 | public Coordinates(final int xCoordinate, final int yCoordinate) { 9 | this.xCoordinate = xCoordinate; 10 | this.yCoordinate = yCoordinate; 11 | } 12 | 13 | public Coordinates newCoordinatesFor(final int xCoordinateStepValue, final int yCoordinateStepValue) { 14 | return new Coordinates(this.xCoordinate + xCoordinateStepValue, this.yCoordinate + yCoordinateStepValue); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | StringBuilder coordinateOutput = new StringBuilder(); 20 | coordinateOutput.append(xCoordinate); 21 | coordinateOutput.append(" "); 22 | coordinateOutput.append(yCoordinate); 23 | return coordinateOutput.toString(); 24 | } 25 | 26 | public boolean hasWithinBounds(final Coordinates coordinates) { 27 | return isXCoordinateWithinBounds(coordinates.xCoordinate) && isYCoordinateWithinBounds(coordinates.yCoordinate); 28 | } 29 | 30 | public boolean hasOutsideBounds(final Coordinates coordinates) { 31 | return isXCoordinateInOutsideBounds(coordinates.xCoordinate) && isYCoordinateInOutsideBounds(coordinates.yCoordinate); 32 | } 33 | 34 | private boolean isXCoordinateInOutsideBounds(final int xCoordinate) { 35 | return xCoordinate >= this.xCoordinate; 36 | } 37 | 38 | private boolean isYCoordinateInOutsideBounds(final int yCoordinate) { 39 | return yCoordinate >= this.yCoordinate; 40 | } 41 | 42 | private boolean isYCoordinateWithinBounds(final int yCoordinate) { 43 | return yCoordinate <= this.yCoordinate; 44 | } 45 | 46 | private boolean isXCoordinateWithinBounds(final int xCoordinate) { 47 | return xCoordinate <= this.xCoordinate; 48 | } 49 | 50 | public Coordinates newCoordinatesForStepSize(final int xCoordinateStepValue, final int yCoordinateStepValue) { 51 | return new Coordinates(xCoordinate+xCoordinateStepValue, yCoordinate+yCoordinateStepValue); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /test/parser/StringCommandParserTest.java: -------------------------------------------------------------------------------- 1 | package parser; 2 | 3 | import com.thoughtworks.rover.commands.ICommand; 4 | import com.thoughtworks.rover.commands.MoveCommand; 5 | import com.thoughtworks.rover.commands.RotateLeftCommand; 6 | import com.thoughtworks.rover.commands.RotateRightCommand; 7 | import com.thoughtworks.rover.parser.StringCommandParser; 8 | import org.junit.Assert; 9 | import org.junit.Test; 10 | 11 | import java.util.List; 12 | 13 | public class StringCommandParserTest { 14 | 15 | @Test 16 | public void stringLMapsToRotateLeftCommand() { 17 | //Given 18 | StringCommandParser parser = new StringCommandParser("L"); 19 | 20 | //When 21 | List commands = parser.toCommands(); 22 | 23 | //Then 24 | Assert.assertTrue(commands.get(0) instanceof RotateLeftCommand); 25 | Assert.assertEquals(1, commands.size()); 26 | } 27 | 28 | @Test 29 | public void stringRMapsToRotateRightCommand() { 30 | //Given 31 | StringCommandParser parser = new StringCommandParser("R"); 32 | 33 | //When 34 | List commands = parser.toCommands(); 35 | 36 | //Then 37 | Assert.assertTrue(commands.get(0) instanceof RotateRightCommand); 38 | } 39 | 40 | @Test 41 | public void stringMMapsToMoveCommand() { 42 | //Given 43 | StringCommandParser parser = new StringCommandParser("M"); 44 | 45 | //When 46 | List commands = parser.toCommands(); 47 | 48 | //Then 49 | Assert.assertTrue(commands.get(0) instanceof MoveCommand); 50 | } 51 | 52 | 53 | @Test 54 | public void emptyStringResultsInEmptyCommandList() { 55 | //Given 56 | StringCommandParser parser = new StringCommandParser(""); 57 | 58 | //When 59 | List commands = parser.toCommands(); 60 | 61 | //Then 62 | Assert.assertEquals(0, commands.size()); 63 | } 64 | 65 | 66 | @Test 67 | public void nullStringResultsInEmptyCommandList() { 68 | //Given 69 | StringCommandParser parser = new StringCommandParser(null); 70 | 71 | //When 72 | List commands = parser.toCommands(); 73 | 74 | //Then 75 | Assert.assertEquals(0, commands.size()); 76 | } 77 | 78 | @Test 79 | public void stringToCommandMappingIsCaseInsensitive() { 80 | //Given 81 | StringCommandParser parser = new StringCommandParser("mM"); 82 | 83 | //When 84 | List commands = parser.toCommands(); 85 | 86 | //Then 87 | Assert.assertTrue(commands.get(0) instanceof MoveCommand); 88 | Assert.assertTrue(commands.get(1) instanceof MoveCommand); 89 | } 90 | 91 | @Test 92 | public void multiCommandStringIsMappedToCommandsInSameOrder() { 93 | //Given 94 | StringCommandParser parser = new StringCommandParser("MRL"); 95 | 96 | //When 97 | List commands = parser.toCommands(); 98 | 99 | //Then 100 | Assert.assertTrue(commands.get(0) instanceof MoveCommand); 101 | Assert.assertTrue(commands.get(1) instanceof RotateRightCommand); 102 | Assert.assertTrue(commands.get(2) instanceof RotateLeftCommand); 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /test/universe/CoordinateTest.java: -------------------------------------------------------------------------------- 1 | package universe; 2 | 3 | import com.thoughtworks.rover.universe.Coordinates; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | public class CoordinateTest { 8 | 9 | @Test 10 | public void xCoordinatesAreIncrementedForPositiveValue() { 11 | //Given 12 | Coordinates boundaryCoordinates = new Coordinates(2,3); 13 | 14 | //When 15 | boundaryCoordinates = boundaryCoordinates.newCoordinatesFor(1, 0); 16 | 17 | //Then 18 | Assert.assertEquals("3 3", boundaryCoordinates.toString()); 19 | } 20 | 21 | @Test 22 | public void xCoordinatesAreDecrementedForNegativeValue() { 23 | //Given 24 | Coordinates boundaryCoordinates = new Coordinates(2,3); 25 | 26 | //When 27 | boundaryCoordinates = boundaryCoordinates.newCoordinatesFor(-1, 0); 28 | 29 | //Then 30 | Assert.assertEquals("1 3", boundaryCoordinates.toString()); 31 | } 32 | 33 | @Test 34 | public void yCoordinatesAreIncrementedForPositiveValue() { 35 | //Given 36 | Coordinates boundaryCoordinates = new Coordinates(2,3); 37 | 38 | //When 39 | boundaryCoordinates = boundaryCoordinates.newCoordinatesFor(0, 1); 40 | 41 | //Then 42 | Assert.assertEquals("2 4", boundaryCoordinates.toString()); 43 | } 44 | 45 | @Test 46 | public void yCoordinatesAreDecrementedForNegativeValue() { 47 | //Given 48 | Coordinates boundaryCoordinates = new Coordinates(2,3); 49 | 50 | //When 51 | boundaryCoordinates = boundaryCoordinates.newCoordinatesFor(0, -1); 52 | 53 | //Then 54 | Assert.assertEquals("2 2", boundaryCoordinates.toString()); 55 | } 56 | 57 | @Test 58 | public void pointWithXCoordinateWithinBoundaryAreIdentified() { 59 | //Given 60 | Coordinates boundaryCoordinates = new Coordinates(5,5); 61 | 62 | //When 63 | Coordinates internalPoint = new Coordinates(4,5); 64 | 65 | //Then 66 | Assert.assertTrue(boundaryCoordinates.hasWithinBounds(internalPoint)); 67 | } 68 | 69 | 70 | @Test 71 | public void pointWithYCoordinateWithinBoundaryAreIdentified() { 72 | //Given 73 | Coordinates boundaryCoordinates = new Coordinates(5,5); 74 | 75 | //When 76 | Coordinates internalPoint = new Coordinates(5,4); 77 | 78 | //Then 79 | Assert.assertTrue(boundaryCoordinates.hasWithinBounds(internalPoint)); 80 | } 81 | 82 | 83 | @Test 84 | public void pointsWithXCoordinateOutsideBoundaryAreIdentified() { 85 | //Given 86 | Coordinates boundaryCoordinates = new Coordinates(5,5); 87 | 88 | //When 89 | Coordinates externalPoint = new Coordinates(8,5); 90 | 91 | //Then 92 | Assert.assertTrue(boundaryCoordinates.hasOutsideBounds(externalPoint)); 93 | } 94 | 95 | 96 | @Test 97 | public void pointsWithYCoordinateOutsideBoundaryAreIdentified() { 98 | //Given 99 | Coordinates boundaryCoordinates = new Coordinates(5,5); 100 | 101 | //When 102 | Coordinates externalPoint = new Coordinates(5,8); 103 | 104 | //Then 105 | Assert.assertTrue(boundaryCoordinates.hasOutsideBounds(externalPoint)); 106 | } 107 | 108 | @Test 109 | public void immutableCoordinatesAreCreatedForNewStepSize() { 110 | //Given 111 | Coordinates boundaryCoordinates = new Coordinates(5,5); 112 | 113 | //When 114 | Coordinates newBoundary = boundaryCoordinates.newCoordinatesForStepSize(1, -1); 115 | 116 | //Then 117 | Assert.assertEquals("6 4", newBoundary.toString()); 118 | Assert.assertEquals("5 5", boundaryCoordinates.toString()); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Introduction 2 | -------------------- 3 | 4 | This problem used to be a coding assignment at ThoughtWorks' interview coding round. With this repository and a series of blog posts here I use this problem to demonstrate application of OO concepts and design patterns. 5 | 6 | Feel free to use it in any way you like. Reach out to me at "priyaaank [at] gmail" for any feedback or comments. 7 | 8 | The complete write-up rationalizing the code is in a series of blog (mentioned below): 9 | 10 | [Decoding ThoughtWorks' coding problem](http://priyaaank.tumblr.com/post/95095165285/decoding-thoughtworks-coding-problems) 11 | 12 | [Objects that talk domain](http://priyaaank.tumblr.com/post/95095193545/objects-that-talk-domain) 13 | 14 | [Objects that are loose and discrete](http://priyaaank.tumblr.com/post/95095211355/objects-that-are-loose-discrete) 15 | 16 | [Design patterns for win](http://priyaaank.tumblr.com/post/95095221320/design-patterns-for-win) 17 | 18 | [Reinforced design with TDD](http://priyaaank.tumblr.com/post/95095229180/reinforced-design-with-tdd) 19 | 20 | And other personal musings at my [blog](http://priyaaank.tumblr.com/) 21 | 22 | 23 | ### Mars Rovers thoughtworks puzzles 24 | -------------------- 25 | 26 | A squad of robotic rovers are to be landed by NASA on a plateau on Mars. This plateau, which is curiously rectangular, must be navigated by the rovers so that their on-board cameras can get a complete view of the surrounding terrain to send back to Earth. 27 | A rover's position and location is represented by a combination of x and y co-ordinates and a letter representing one of the four cardinal compass points. The plateau is divided up into a grid to simplify navigation. An example position might be 0, 0, N, which means the rover is in the bottom left corner and facing North. 28 | In order to control a rover, NASA sends a simple string of letters. The possible letters are 'L', 'R' and 'M'. 'L' and 'R' makes the rover spin 90 degrees left or right respectively, without moving from its current spot. 'M' means move forward one grid point, and maintain the same heading. 29 | 30 | Assume that the square directly North from (x, y) is (x, y+1). 31 | 32 | #### INPUT: 33 | 34 | The first line of input is the upper-right coordinates of the plateau, the lower-left coordinates are assumed to be 0,0. 35 | 36 | The rest of the input is information pertaining to the rovers that have been deployed. Each rover has two lines of input. The first line gives the rover's position, and the second line is a series of instructions telling the rover how to explore the plateau. 37 | The position is made up of two integers and a letter separated by spaces, corresponding to the x and y co-ordinates and the rover's orientation. 38 | 39 | Each rover will be finished sequentially, which means that the second rover won't start to move until the first one has finished moving. 40 | 41 | #### OUTPUT: 42 | 43 | The output for each rover should be its final co-ordinates and heading. 44 | 45 | #### INPUT AND OUTPUT: 46 | 47 | ##### Test Input: 48 | 5 5 49 | 50 | 1 2 N 51 | 52 | LMLMLMLMM 53 | 54 | 3 3 E 55 | 56 | MMRMMRMRRM 57 | 58 | ##### Expected Output: 59 | 60 | 1 3 N 61 | 62 | 5 1 E 63 | 64 | 65 | ### LICENSE 66 | 67 | Full License Text 68 | The MIT License (MIT) 69 | 70 | Copyright (c) 2014 Priyank Gupta 71 | 72 | Permission is hereby granted, free of charge, to any person obtaining a 73 | copy of this software and associated documentation files (the 74 | "Software"), to deal in the Software without restriction, including 75 | without limitation the rights to use, copy, modify, merge, publish, 76 | distribute, sublicense, and/or sell copies of the Software, and to 77 | permit persons to whom the Software is furnished to do so, subject to 78 | the following conditions: 79 | 80 | The above copyright notice and this permission notice shall be included 81 | in all copies or substantial portions of the Software. 82 | 83 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 84 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 85 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 86 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 87 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 88 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 89 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 90 | -------------------------------------------------------------------------------- /test/MarsRoverTest.java: -------------------------------------------------------------------------------- 1 | import com.thoughtworks.rover.MarsRover; 2 | import com.thoughtworks.rover.universe.Coordinates; 3 | import com.thoughtworks.rover.universe.Direction; 4 | import com.thoughtworks.rover.universe.Plateau; 5 | import org.junit.Assert; 6 | import org.junit.Test; 7 | 8 | public class MarsRoverTest { 9 | 10 | @Test 11 | public void canProvideCurrentLocationAsString() { 12 | //Given 13 | Plateau plateau = new Plateau(5,5); 14 | Coordinates startingPosition = new Coordinates(3,3); 15 | 16 | //When 17 | MarsRover marsRover = new MarsRover(plateau, Direction.N, startingPosition); 18 | 19 | //then 20 | Assert.assertEquals("3 3 N", marsRover.currentLocation()); 21 | } 22 | 23 | @Test 24 | public void canRotateLeft() { 25 | //Given 26 | Plateau plateau = new Plateau(5,5); 27 | Coordinates startingPosition = new Coordinates(1,2); 28 | MarsRover marsRover = new MarsRover(plateau, Direction.N, startingPosition); 29 | 30 | //When 31 | marsRover.turnLeft(); 32 | 33 | //then 34 | Assert.assertEquals("1 2 W", marsRover.currentLocation()); 35 | } 36 | 37 | @Test 38 | public void canRotateRight() { 39 | //Given 40 | Plateau plateau = new Plateau(5,5); 41 | Coordinates startingPosition = new Coordinates(1,2); 42 | MarsRover marsRover = new MarsRover(plateau, Direction.N, startingPosition); 43 | 44 | //When 45 | marsRover.turnRight(); 46 | 47 | //then 48 | Assert.assertEquals("1 2 E", marsRover.currentLocation()); 49 | } 50 | 51 | @Test 52 | public void canMove() { 53 | //Given 54 | Plateau plateau = new Plateau(5,5); 55 | Coordinates startingPosition = new Coordinates(1,2); 56 | MarsRover marsRover = new MarsRover(plateau, Direction.N, startingPosition); 57 | 58 | //When 59 | marsRover.move(); 60 | 61 | //then 62 | Assert.assertEquals("1 3 N", marsRover.currentLocation()); 63 | } 64 | 65 | @Test 66 | public void canRunCommandToRotateRight() { 67 | //Given 68 | Plateau plateau = new Plateau(5,5); 69 | Coordinates startingPosition = new Coordinates(1,2); 70 | MarsRover marsRover = new MarsRover(plateau, Direction.N, startingPosition); 71 | 72 | //When 73 | marsRover.run("R"); 74 | 75 | //then 76 | Assert.assertEquals("1 2 E", marsRover.currentLocation()); 77 | } 78 | 79 | @Test 80 | public void canRunCommandToRotateLeft() { 81 | //Given 82 | Plateau plateau = new Plateau(5,5); 83 | Coordinates startingPosition = new Coordinates(1,2); 84 | MarsRover marsRover = new MarsRover(plateau, Direction.N, startingPosition); 85 | 86 | //When 87 | marsRover.run("L"); 88 | 89 | //then 90 | Assert.assertEquals("1 2 W", marsRover.currentLocation()); 91 | } 92 | 93 | @Test 94 | public void canRunCommandToMove() { 95 | //Given 96 | Plateau plateau = new Plateau(5,5); 97 | Coordinates startingPosition = new Coordinates(1,2); 98 | MarsRover marsRover = new MarsRover(plateau, Direction.N, startingPosition); 99 | 100 | //When 101 | marsRover.run("M"); 102 | 103 | //then 104 | Assert.assertEquals("1 3 N", marsRover.currentLocation()); 105 | } 106 | 107 | @Test 108 | public void canRunCommandWithMultipleInstructions() { 109 | //Given 110 | Plateau plateau = new Plateau(5,5); 111 | Coordinates startingPosition = new Coordinates(3,3); 112 | MarsRover marsRover = new MarsRover(plateau, Direction.E, startingPosition); 113 | 114 | //When 115 | marsRover.run("MMRMMRMRRM"); 116 | 117 | //then 118 | Assert.assertEquals("5 1 E", marsRover.currentLocation()); 119 | } 120 | 121 | @Test 122 | public void wontDriveOffPlateau() { 123 | //Given 124 | Plateau plateau = new Plateau(5,5); 125 | Coordinates startingPosition = new Coordinates(3,3); 126 | MarsRover marsRover = new MarsRover(plateau, Direction.N, startingPosition); 127 | 128 | //When 129 | marsRover.run("MMMMMMMMMMR"); 130 | 131 | //then 132 | Assert.assertEquals("3 5 E", marsRover.currentLocation()); 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /test/universe/DirectionTest.java: -------------------------------------------------------------------------------- 1 | package universe; 2 | 3 | import com.thoughtworks.rover.universe.Direction; 4 | import org.junit.Assert; 5 | import org.junit.Test; 6 | 7 | public class DirectionTest { 8 | 9 | @Test 10 | public void westIsOnLeftOfNorth() { 11 | //Given 12 | Direction north = Direction.N; 13 | 14 | //When 15 | Direction west = north.left(); 16 | 17 | //Then 18 | Assert.assertEquals(Direction.W, west); 19 | } 20 | 21 | @Test 22 | public void eastIsOnRightOfNorth() { 23 | //Given 24 | Direction north = Direction.N; 25 | 26 | //When 27 | Direction east = north.right(); 28 | 29 | //Then 30 | Assert.assertEquals(Direction.E, east); 31 | } 32 | 33 | @Test 34 | public void northIsOnRightOfWest() { 35 | //Given 36 | Direction west = Direction.W; 37 | 38 | //When 39 | Direction north = west.right(); 40 | 41 | //Then 42 | Assert.assertEquals(Direction.N, north); 43 | } 44 | 45 | @Test 46 | public void southIsOnLeftOfWest() { 47 | //Given 48 | Direction west = Direction.W; 49 | 50 | //When 51 | Direction south = west.left(); 52 | 53 | //Then 54 | Assert.assertEquals(Direction.S, south); 55 | } 56 | 57 | @Test 58 | public void eastIsOnLeftOfSouth() { 59 | //Given 60 | Direction south = Direction.S; 61 | 62 | //When 63 | Direction east = south.left(); 64 | 65 | //Then 66 | Assert.assertEquals(Direction.E, east); 67 | } 68 | 69 | @Test 70 | public void westIsOnRightOfSouth() { 71 | //Given 72 | Direction south = Direction.S; 73 | 74 | //When 75 | Direction west = south.right(); 76 | 77 | //Then 78 | Assert.assertEquals(Direction.W, west); 79 | } 80 | 81 | @Test 82 | public void northIsOnLeftOfEast() { 83 | //Given 84 | Direction east = Direction.E; 85 | 86 | //When 87 | Direction north = east.left(); 88 | 89 | //Then 90 | Assert.assertEquals(Direction.N, north); 91 | } 92 | 93 | @Test 94 | public void southIsOnRightOfEast() { 95 | //Given 96 | Direction east = Direction.E; 97 | 98 | //When 99 | Direction south = east.right(); 100 | 101 | //Then 102 | Assert.assertEquals(Direction.S, south); 103 | } 104 | 105 | @Test 106 | public void eastIsOneStepForwardOnXAxis() { 107 | //Given 108 | Direction east = Direction.E; 109 | 110 | //When 111 | int stepSize = east.stepSizeForXAxis(); 112 | 113 | //Then 114 | Assert.assertEquals(1, stepSize); 115 | } 116 | 117 | @Test 118 | public void eastIsStationaryOnYAxis() { 119 | //Given 120 | Direction east = Direction.E; 121 | 122 | //When 123 | int stepSize = east.stepSizeForYAxis(); 124 | 125 | //Then 126 | Assert.assertEquals(0, stepSize); 127 | } 128 | 129 | @Test 130 | public void westIsOneStepBackOnXAxis() { 131 | //Given 132 | Direction west = Direction.W; 133 | 134 | //When 135 | int stepSize = west.stepSizeForXAxis(); 136 | 137 | //Then 138 | Assert.assertEquals(-1, stepSize); 139 | } 140 | 141 | 142 | @Test 143 | public void westIsStationaryOnYAxis() { 144 | //Given 145 | Direction west = Direction.W; 146 | 147 | //When 148 | int stepSize = west.stepSizeForYAxis(); 149 | 150 | //Then 151 | Assert.assertEquals(0, stepSize); 152 | } 153 | 154 | @Test 155 | public void northIsOneStepForwardOnYAxis() { 156 | //Given 157 | Direction north = Direction.N; 158 | 159 | //When 160 | int stepSize = north.stepSizeForYAxis(); 161 | 162 | //Then 163 | Assert.assertEquals(1, stepSize); 164 | } 165 | 166 | 167 | @Test 168 | public void northIsStationaryOnXAxis() { 169 | //Given 170 | Direction north = Direction.N; 171 | 172 | //When 173 | int stepSize = north.stepSizeForXAxis(); 174 | 175 | //Then 176 | Assert.assertEquals(0, stepSize); 177 | } 178 | 179 | @Test 180 | public void southIsOneStepBackOnYAxis() { 181 | //Given 182 | Direction south = Direction.S; 183 | 184 | //When 185 | int stepSize = south.stepSizeForYAxis(); 186 | 187 | //Then 188 | Assert.assertEquals(-1, stepSize); 189 | } 190 | 191 | @Test 192 | public void southIsStationaryOnXAxis() { 193 | //Given 194 | Direction south = Direction.S; 195 | 196 | //When 197 | int stepSize = south.stepSizeForXAxis(); 198 | 199 | //Then 200 | Assert.assertEquals(0, stepSize); 201 | } 202 | } 203 | --------------------------------------------------------------------------------