top of page
Subscribe to my blog • Don’t miss out!

Thanks for subscribing!

sadiahzahoor

Mars Rover Challenge

Updated: Jul 20, 2023

Have you ever found yourself in a situation where you stumbled upon an opportunity completely by chance? That's exactly what happened to me last week when I unknowingly entered a room filled with software engineers, ready to take on a coding challenge in a workshop organised by Everest Academy in Melbourne. The purpose of this workshop was to practice Hardcore Test-Driven Development (TDD) by incorporating peer programming. I learned the importance of writing comprehensive test cases, the art of refactoring to improve code quality, and the immense value of working alongside a programming partner. Yet, with every passing moment, my anticipation grew, and I couldn't wait to dive into the actual coding task itself.

Challenge: Develop an API that translates the commands sent from Earth to instructions that are understood by a Mars Rover.

Well, I was quite excited to complete this coding task and am eager to share my code in this blog post.

Note: At each step, the highlighted text represents the new added code that's relevant to the step.


The coding task had the following requirements:

  1. You are given the initial starting point (x,y) of a rover and the direction (N.S, E,W) it is facing.

  2. The rover receives a character array of commands.

  3. Implement commands that move the rover forward/backward (f, b).

  4. Implement commands that turn the rover left/right (I,r).

  5. Implement wrapping from one edge of the grid to another.

  6. 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, aborts the sequence and reports the obstacle.



STEP 1: You are given the initial starting point (x,y) of a rover and the direction (N.S, E,W) it is facing.

Input:


from typing import List

class Rover:
    def __init__(self):
        self.x = 0
        self.y = 0
        self.direction = 'N'
        self.directions = ['N', 'W', 'S', 'E']
        print("We are currently at coordinate ", (self.x, self.y), " and facing direction", self.direction, ".\n")

mars_rover = Rover()


Output:

We are currently at coordinate  (0, 0)  and facing direction N .


STEP 2: Implement commands that turn the rover left/right (I,r).

Input:



from typing import List

class Rover:
    def __init__(self):
        self.x = 0
        self.y = 0
        self.direction = 'N'
        self.directions = ['N', 'W', 'S', 'E']
        print("We are currently at coordinate ", (self.x, self.y), " and facing direction", self.direction, ".\n"
                  "Instructions:\n"
                 " 1. Please enter a list of 'l's and 'r's separated by comma's to rotate left or right respectively.\n "
                 "Example: instance.rotate(['l', 'r', 'l', 'l'])\n")


    def rotate(self, inputs: List):
        output = ""

        # Validate the inputs
        while not all(char == 'l' or char == 'r' for char in inputs):
            error = "Error: Invalid input. Only 'l' and 'r' are allowed."
            return error

        direction_mapping = {'N': 0, 'W': 1, 'S': 2, 'E': 3}
        anticlockwise_rotations = inputs.count('l')
        clockwise_rotations = inputs.count('r')
        net_rotations = direction_mapping[self.direction] + anticlockwise_rotations - clockwise_rotations

        if net_rotations < 0:
            net_rotations = net_rotations + 4

        self.direction = self.directions[net_rotations % 4]
        output += f"We are now at position {(self.x, self.y)} and facing direction {self.direction}."
        return output

mars_rover = Rover()
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.rotate(['l', 'l', 'r']))

Output:


We are currently at coordinate  (0, 0)  and facing direction N .
Instructions:
 1. Please enter a list of 'l's and 'r's separated by comma's to rotate left or right respectively.
 Example: instance.rotate(['l', 'r', 'l', 'l'])

We are now at position (0, 0) and facing direction W.
We are now at position (0, 0) and facing direction S.
We are now at position (0, 0) and facing direction E.

STEP 3: Implement commands that move the rover forward/backward (f, b).

Input:


from typing import List

class Rover:
    def __init__(self):
        self.x = 0
        self.y = 0
        self.direction = 'N'
        self.directions = ['N', 'W', 'S', 'E']
        print("We are currently at coordinate ", (self.x, self.y), " and facing direction", self.direction, ".\n"
                  "Instructions:\n"
                  " 1. Please enter a list of 'l's and 'r's separated by comma's to rotate left or right respectively.\n "
                  "Example: instance.rotate(['l', 'r', 'l', 'l'])\n"
                  " 2. Please enter a list of 'f's and 'b's separated by comma's to move forward or backward
                    respectively.\n"
                  " Example: instance.steps(['f', 'f', 'b', 'b'])\n")

    def rotate(self, inputs: List):
        output = ""

        # Validate the inputs
        while not all(char == 'l' or char == 'r' for char in inputs):
            error = "Error: Invalid input. Only 'l' and 'r' are allowed."
            return error

        direction_mapping = {'N': 0, 'W': 1, 'S': 2, 'E': 3}
        anticlockwise_rotations = inputs.count('l')
        clockwise_rotations = inputs.count('r')
        net_rotations = direction_mapping[self.direction] + anticlockwise_rotations - clockwise_rotations

        if net_rotations < 0:
            net_rotations = net_rotations + 4

        self.direction = self.directions[net_rotations % 4]
        output += f"We are now at position {(self.x, self.y)} and facing direction {self.direction}."
        return output

    def steps(self, inputs: List):
        output = ""
        while not all(char == 'f' or char == 'b' for char in inputs):
            error = "Error: Invalid input. Only 'f' and 'b' are allowed."
            return error
        forward_steps = inputs.count('f')
        backward_steps = inputs.count('b')
        steps = forward_steps - backward_steps

        direction_steps = {'N': (self.x, self.y + steps),
                                         'W': (self.x - steps, self.y),
                                           'S': (self.x, self.y - steps),
                                           'E': (self.x + steps, self.y)}

        self.x = direction_steps[self.direction][0]
        self.y = direction_steps[self.direction][1]

        output += f"We are now at position {(self.x, self.y)} and facing direction {self.direction}."
        return output

mars_rover = Rover()
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.steps(['f', 'f', 'f', 'f', 'f', 'f', 'f']))
print(mars_rover.rotate(['l']))

Output:


We are currently at coordinate  (0, 0)  and facing direction N .
Instructions:
 1. Please enter a list of 'l's and 'r's separated by comma's to rotate left or right respectively.
 Example: instance.rotate(['l', 'r', 'l', 'l'])
 2. Please enter a list of 'f's and 'b's separated by comma's to move forward or backward respectively.
 Example: instance.steps(['f', 'f', 'b', 'b'])

We are now at position (0, 0) and facing direction W.
We are now at position (0, 0) and facing direction S.
We are now at position (0, 0) and facing direction E.
We are now at position (7, 0) and facing direction E.
We are now at position (7, 0) and facing direction N.

STEP 4: Implement wrapping from one edge of the grid to another. (Choose 5 x 5 grid)

Input:


from typing import List

class Rover:
    def __init__(self):
        self.x = 0
        self.y = 0
        self.direction = 'N'
        self.directions = ['N', 'W', 'S', 'E']
        print("We are currently at coordinate ", (self.x, self.y), " and facing direction", self.direction, ".\n"
                  "Instructions:\n"
                  " 1. Please enter a list of 'l's and 'r's separated by comma's to rotate left or right respectively.\n "
                  "Example: instance.rotate(['l', 'r', 'l', 'l'])\n"
                  " 2. Please enter a list of 'f's and 'b's separated by comma's to move forward or backward
                    respectively.\n"
                  " Example: instance.steps(['f', 'f', 'b', 'b'])\n"
                  " 3. We are wrapped around in a 5 x 5 grid centered around origin (0,0).\n")

    def rotate(self, inputs: List):
        output = ""

        # Validate the inputs
        while not all(char == 'l' or char == 'r' for char in inputs):
            error = "Error: Invalid input. Only 'l' and 'r' are allowed."
            return error

        direction_mapping = {'N': 0, 'W': 1, 'S': 2, 'E': 3}
        anticlockwise_rotations = inputs.count('l')
        clockwise_rotations = inputs.count('r')
        net_rotations = direction_mapping[self.direction] + anticlockwise_rotations - clockwise_rotations

        if net_rotations < 0:
            net_rotations = net_rotations + 4

        self.direction = self.directions[net_rotations % 4]
        output += f"We are now at position {(self.x % 5, self.y % 5)} and facing direction {self.direction}."
        return output

    def steps(self, inputs: List):
        output = ""
        while not all(char == 'f' or char == 'b' for char in inputs):
            error = "Error: Invalid input. Only 'f' and 'b' are allowed."
            return error
        forward_steps = inputs.count('f')
        backward_steps = inputs.count('b')
        steps = forward_steps - backward_steps

        direction_steps = {'N': (self.x, self.y + steps),
                                         'W': (self.x - steps, self.y),
                                           'S': (self.x, self.y - steps),
                                           'E': (self.x + steps, self.y)}

        self.x = direction_steps[self.direction][0]
        self.y = direction_steps[self.direction][1]

        output += f"We are now at position {(self.x % 5, self.y % 5)} and facing direction {self.direction}."
        return output

mars_rover = Rover()
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.steps(['f', 'f', 'f', 'f', 'f', 'f', 'f']))
print(mars_rover.rotate(['l']))

Output:


We are currently at coordinate  (0, 0)  and facing direction N .
Instructions:
 1. Please enter a list of 'l's and 'r's separated by comma's to rotate left or right respectively.
 Example: instance.rotate(['l', 'r', 'l', 'l'])
 2. Please enter a list of 'f's and 'b's separated by comma's to move forward or backward respectively.
 3. We are wrapped around in a 5 x 5 grid centered around origin (0,0).

We are now at position (0, 0) and facing direction W.
We are now at position (0, 0) and facing direction S.
We are now at position (0, 0) and facing direction E.
We are now at position (2, 0) and facing direction E.
We are now at position (2, 0) and facing direction N.

STEP 5: 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, aborts the sequence and reports the obstacle. (Choose coordinates (2, 3), (4, 1) as obstacles)

Input:


from typing import List

class Rover:
    def __init__(self):
        self.x = 0
        self.y = 0
        self.direction = 'N'
        self.directions = ['N', 'W', 'S', 'E']
        self.obstacles = [(2, 3), (4, 1)]
        print("We are currently at coordinate ", (self.x, self.y), " and facing direction", self.direction, ".\n"
              "Instructions:\n"
              " 1. Please enter a list of 'l's and 'r's separated by comma's to rotate left or right respectively.\n "
              "Example: instance.rotate(['l', 'r', 'l', 'l'])\n"
              " 2. Please enter a list of 'f's and 'b's separated by comma's to move forward or backward 
               respectively.\n"
              " Example: instance.steps(['f', 'f', 'b', 'b'])\n"
              " 3. We are wrapped around in a 5 x 5 grid centered around origin (0,0).")

    def rotate(self, inputs: List):
        output = ""

        # Validate the inputs
        while not all(char == 'l' or char == 'r' for char in inputs):
            error = "Error: Invalid input. Only 'l' and 'r' are allowed."
            return error

        direction_mapping = {'N': 0, 'W': 1, 'S': 2, 'E': 3}
        anticlockwise_rotations = inputs.count('l')
        clockwise_rotations = inputs.count('r')
        net_rotations = direction_mapping[self.direction] + anticlockwise_rotations - clockwise_rotations

        if net_rotations < 0:
            net_rotations = net_rotations + 4

        self.direction = self.directions[net_rotations % 4]
        output += f"We are now at position {(self.x % 5, self.y % 5)} and facing direction {self.direction}."
        return output

    def steps(self, inputs: List):
        output = ""
        while not all(char == 'f' or char == 'b' for char in inputs):
            error = "Error: Invalid input. Only 'f' and 'b' are allowed."
            return error
        forward_steps = inputs.count('f')
        backward_steps = inputs.count('b')
        steps = forward_steps - backward_steps

        direction_steps = {'N': (self.x, self.y + steps),
                                         'W': (self.x - steps, self.y),
                                           'S': (self.x, self.y - steps),
                                           'E': (self.x + steps, self.y)}

        self.x = direction_steps[self.direction][0]
        self.y = direction_steps[self.direction][1]

        output += f"We are now at position {(self.x % 5, self.y % 5)} and facing direction {self.direction}."
        return output

    def steps_with_obstacle_detection(self, inputs: List):
        output = " "
        while not all(char == 'f' or char == 'b' for char in inputs):
            error = "Error: Invalid input. Only 'f' and 'b' are allowed."
            return error
        forward_steps = inputs.count('f')
        backward_steps = inputs.count('b')
        steps = forward_steps - backward_steps

        for _ in range(0, abs(steps)):
            direction_steps = {'N': (self.x, self.y + 1),
                                             'W': (self.x - 1, self.y),
                                               'S': (self.x, self.y - 1),
                                               'E': (self.x + 1, self.y)}
            x = direction_steps[self.direction][0] % 5
            y = direction_steps[self.direction][1] % 5
            obstacle_detected = False
            for i in self.obstacles:
                if i == (x, y):
                    obstacle_detected = True
                break
            if obstacle_detected:
                print(f"Warning: Obstacle detected at {(x, y)}. Move aborted.")
                break
            else:
                self.x = x
                self.y = y
        output += f"We are now at position {(self.x, self.y)} and facing direction {self.direction}."
        return output


mars_rover = Rover()
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.rotate(['l', 'l', 'r']))
print(mars_rover.steps(['f', 'f', 'f', 'f', 'f', 'f', 'f']))
print(mars_rover.rotate(['l']))
print(mars_rover.steps_with_obstacle_detection(['f', 'f', 'f', 'f']))

Output:


We are currently at coordinate  (0, 0)  and facing direction N .
Instructions:
 1. Please enter a list of 'l's and 'r's separated by comma's to rotate left or right respectively.
 Example: instance.rotate(['l', 'r', 'l', 'l'])
 2. Please enter a list of 'f's and 'b's separated by comma's to move forward or backward respectively.
 Example: instance.steps(['f', 'f', 'b', 'b'])
 3. We are wrapped around in a 5 x 5 grid centered around origin (0,0).

We are now at position (0, 0) and facing direction W.
We are now at position (0, 0) and facing direction S.
We are now at position (0, 0) and facing direction E.
We are now at position (2, 0) and facing direction E.
We are now at position (2, 0) and facing direction N.
Warning: Obstacle detected at (2, 3). Move aborted.
We are now at position (2, 2) and facing direction N.

That's done! So, we now have an API that follows my instructions on Mars (at least hypothetically). If you want more explanations and have any further suggestions, leave them in the comments. I am excited to continue embracing these challenges in my future coding endeavours and am confident in the transformative power these workshops hold.


Author:

Sadiah Z.

Comments


bottom of page