Mars Rover Coordinates problem - Solution

I got an interesting problem by email recently where someone had asked me to solve the given below problem:-

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 therovers so that their on-board cameras can get a complete view of the surrounding terrain to send back to Earth.

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.

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.

Assume that the square directly North from (x, y) is (x, y+1).

INPUT:
The first line of input is the upper-right coordinates of the plateau, the lower-left coordinates are assumed to be 0,0.

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.

The position is made up of two integers and a letter separated by spaces, corresponding to the x and  co-ordinates and the rover’s orientation.

Each rover will be finished sequentially, which means that the second rover won’t start to move until the first one has finished moving.

OUTPUT
The output for each rover should be its final co-ordinates and heading.

I worked out a simple solution for this problem. Of course there is a lot of room for improvement as this solution is the bare minimum one. The rover parses the instructions one at a time. Here are improvements I have planned to be implemented in later versions

  • Understand multiple instruction and process them at once to improve performance
  • Identify cyclic instructions that have no net effect on coordinates and ignore them
  • Move multiple rover objects at same time.
  • Prevent two rovers from taking at same positions at the same time.

Heres what I have for now.

The Rover class stores the state of each Rover on the grid.

 ///
    /// This class is responsible for maintaining the state of each Rover
    /// and processing the instructions given to it.
    ///
    class Rover
    {
        //private objects : Coordinates, Direction, Instruction Array
        private Coordinates _rovCor;
        private Direction _rovDir;
        private string _instructionArray;
 
        //readonly properties for above objects
        public int X { get { return _rovCor.X; } }
        public int Y { get { return _rovCor.Y; } }
        public Direction RoverDirection { get { return _rovDir; } }
 
        ///
        /// Constructor. Passes to Struct coorinates for getting coordinate object
        ///
        public Rover(int _xCor, int _yCor, char _startDir, string _instructionArray)
        {
            this._rovCor = new Coordinates(_xCor, _yCor);
            this._rovDir = GetDirection(_startDir);
            this._instructionArray = _instructionArray;
        }
 
        //Parses The Start Position into Direction Enum
        private Direction GetDirection(char _startDir)
        {
            switch (_startDir)
            {
                case 'N':
                    return Direction.North;
                case 'S':
                    return Direction.South;
                case 'E':
                    return Direction.East;
                case 'W':
                    return Direction.West;
                default:
                    throw new Exception("Unknown Direction Supplied. Accepted values are N,S,E,W");
            }
        }
 
        //Processes the Instruction Array
        public void ProcessInstructionArray()
        {
            foreach (char _instruction in _instructionArray)
            {
                ProcessInstruction(_instruction);
            }
        }
 
        //Processes individual instructions. delegates tasks to other methods
        //after checking instruction type.
        private void ProcessInstruction(char _instruction)
        {
            if ((_instruction == 'L') || (_instruction == 'R'))
            {
                ProcessDirectionInstruction(_instruction);
            }
            else if (_instruction == 'M')
                ProcessMoveInstruction();
            else
                throw new Exception("Invalid Instruction Processed. Please supply only L,R and M");
        }
 
        //Move Instruction is processed by the Coordinate Object
        private void ProcessMoveInstruction()
        {
            this._rovCor.UpdateCoordinates(_rovDir);
        }
 
        //This Processes the direction of the Rover. Instructions L and R
        //are processed by this method.
        private void ProcessDirectionInstruction(char _instruction)
        {
            int _rovDirInt = (int)this._rovDir;
 
            if (_instruction == 'L')
            {
                if (_rovDirInt == 0)
                    _rovDirInt = 4;
                this._rovDir = (Direction)(_rovDirInt - 1);
            }
            else
            {
                if (_rovDirInt == 3)
                    _rovDirInt = -1;
                this._rovDir = (Direction)(_rovDirInt + 1);
            }
 
        }
    }

The Rover class stores its coordinates and Directions as a struct and an enum. Here is the code for them.

    ///
    /// This enumeration is for the Direction the Rover is currently facing in.
    ///
    enum Direction { North, East, South, West, };
 
    ///
    /// This is Stuct which holds the current coordinates of the Rover.
    /// It is also used to hold the max coordinates of the Grid.
    ///
    struct Coordinates
    {
        private int _xCor;
        private int _yCor;
 
        public int X { get { return _xCor; } }
 
        public int Y { get { return _yCor; } }
 
        public Coordinates(int _xCor, int _yCor)
        {
            this._xCor = _xCor;
            this._yCor = _yCor;
        }
 
        public void UpdateCoordinates(Direction _dirObj)
        {
            switch (_dirObj)
            {
                case Direction.North:
                    this._yCor++;
                    break;
                case Direction.South:
                    this._yCor--;
                    break;
                case Direction.East:
                    this._xCor++;
                    break;
                case Direction.West:
                    this._xCor--;
                    break;
            }
        }
    }

The Grid class contains a generic list of all the rovers on the grid. It moves each rover in the list and makes sure the rover doesn’t cross the boundaries. If it does then an exception is thrown.

    ///
    /// This class is responsible for holding all the Rovers
    /// and also making sure no rovers cross the grid boundaries.
    /// The ToString() method is overridden to get the state of
    /// all rovers on the grid in an easy to read string format
    ///
    class Grid
    {
        //Maximum coordinates for the grid
        private Coordinates _maxCoordinates;
 
        //Collection Object to hold all rovers on grid
        private List _allRoversOnGrid;
 
        //Constructor to intialize Rover collection and max coordinates
        public Grid(Coordinates _maxCor)
        {
            this._maxCoordinates = _maxCor;
            _allRoversOnGrid = new List();
        }
 
        //Rovers can only be added to the collection. Nothing else
        //is exposed outside
        public void AddToRoverCollection(Rover _roverObj)
        {
            _allRoversOnGrid.Add(_roverObj);
        }
 
        //This method processes all the rovers on the grid and their instruction arrays
        public void MoveAllRoversOnGrid()
        {
            foreach (Rover _tempRov in _allRoversOnGrid)
            {
                _tempRov.ProcessInstructionArray();
                this.DoIntegrityCheck(_tempRov);
            }
        }
 
        //Making sure the Rover object stays within the grid.
        private void DoIntegrityCheck(Rover _tempRov)
        {
            if ((_tempRov.X > _maxCoordinates.X) || (_tempRov.Y > _maxCoordinates.Y))
            {
                throw new Exception("Rover Failed Grid Integrity. Moved out of Grid Boundaries");
            }
        }
 
        //Overriding the ToString method to get all rovers states
        public override string ToString()
        {
            StringBuilder _AllRoversState = new StringBuilder();
            _AllRoversState.Append("**********************************");
            _AllRoversState.Append(Environment.NewLine);
            _AllRoversState.Append("Number of Rovers currently on Grid: " + this._allRoversOnGrid.Count);
            _AllRoversState.Append(Environment.NewLine);
            _AllRoversState.Append(String.Format("Max Coordinates: X: {0},Y: {1}", _maxCoordinates.X, _maxCoordinates.Y));
            _AllRoversState.Append(Environment.NewLine);
            _AllRoversState.Append("Each Rover state as below: ");
            _AllRoversState.Append(Environment.NewLine);
            foreach (Rover _tempRov in _allRoversOnGrid)
            {
                _AllRoversState.Append(String.Format("Rover Details -- X:{0},Y:{1},Direction:{2}", _tempRov.X, _tempRov.Y, _tempRov.RoverDirection));
                _AllRoversState.Append(Environment.NewLine);
            }
            _AllRoversState.Append("**********************************");
            return _AllRoversState.ToString();
        }
 
    }

Finally, the code to call the Grid class:-

    class Program
    {
        static void Main(string[] args)
        {
 
            //Creating Grid Object
            Grid _testGrid = new Grid(new Coordinates(5,5));
            //Adding two rovers as per sample input
            _testGrid.AddToRoverCollection(new Rover(1, 2, 'N', "LMLMLMLMM"));
            _testGrid.AddToRoverCollection(new Rover(3, 3, 'E', "MMRMMRMRRM"));
            //move all rovers
            _testGrid.MoveAllRoversOnGrid();
            //Get Rover Final state using Grids ToString() method.
            Console.WriteLine(_testGrid.ToString());
        }
    }

To download the solution click here.

Leave a Reply

 

 

 

You can use these HTML tags

<a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong> <pre lang="" line="" escaped="">