Created
November 3, 2023 23:54
-
-
Save ludolara/66ad645eed4c2c34e479459154f6faf7 to your computer and use it in GitHub Desktop.
Mars Rover Kata (https://kata-log.rocks/mars-rover-kata)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import unittest | |
class MarsRover(): | |
def __init__(self, facing, x, y, planet_map) -> None: | |
self.facing: str = facing | |
self.x: int = x | |
self.y: int = y | |
self.planet_map: Planet = planet_map | |
self.compass: list[str] = ["N", "E", "S", "W"] | |
self.obstacle_detected: bool = False | |
def process_commands(self, commands) -> bool: | |
if not isinstance(commands, str): | |
return False | |
for command in commands: | |
if command not in ("f", "b", "r", "l"): | |
return False | |
if command in ("f", "b") and not self.move(command): | |
return False | |
if command in ("r", "l"): | |
self.turn(command) | |
return True | |
def move(self, step: str) -> bool: | |
delta_x, delta_y = self._calculate_movement_delta(step) | |
new_x, new_y = self._calculate_wrapped_position(delta_x, delta_y) | |
if self._detects_obstacle(new_x, new_y): | |
self.obstacle_detected = True | |
return False | |
self.x, self.y = new_x, new_y | |
return True | |
def _calculate_movement_delta(self, step: str) -> tuple[int, int]: | |
delta = 1 if step == "f" else -1 | |
return { | |
"N": (0, delta), | |
"E": (delta, 0), | |
"S": (0, -delta), | |
"W": (-delta, 0) | |
}[self.facing] | |
def _calculate_wrapped_position(self, delta_x: int, delta_y: int) -> tuple[int, int]: | |
new_x = (self.x + delta_x) % self.planet_map.width | |
new_y = (self.y + delta_y) % self.planet_map.height | |
return new_x, new_y | |
def _detects_obstacle(self, x: int, y: int) -> bool: | |
return self.planet_map.planet_map[y][x] == "*" | |
def turn(self, direction) -> None: | |
int_facing = self.compass.index(self.facing) | |
if direction == "r": | |
new_facing = (int_facing+1) % len(self.compass) | |
elif direction == "l": | |
new_facing = (int_facing-1) % len(self.compass) | |
self.facing = self.compass[new_facing] | |
class Planet(): | |
def __init__(self, width, height) -> None: | |
self.width = width | |
self.height = height | |
self.planet_map = [["-" for _x in range(width)] for _y in range(height)] | |
def add_obstacle(self, x, y): | |
self.planet_map[y][x] = "*" | |
class TestMarsRoverInitialState(unittest.TestCase): | |
def setUp(self): | |
self.mars_map = Planet(3, 3) | |
self.rover = MarsRover("N", 1, 2, self.mars_map) | |
def test_rover_facing_specify_initial_direction(self): | |
self.assertEqual(self.rover.facing, "N") | |
def test_rover_coordinate_x_specified_initial_x(self): | |
self.assertEqual(self.rover.x, 1) | |
def test_rover_coordinate_y_specified_initial_y(self): | |
self.assertEqual(self.rover.y, 2) | |
def test_rover_planet_map_specified_initial_planet_map(self): | |
self.assertEqual(self.rover.planet_map.width, 3) | |
self.assertEqual(self.rover.planet_map.height, 3) | |
def test_rover_compass_initial_default(self): | |
self.assertEqual(self.rover.compass, ["N", "E", "S", "W"]) | |
class TestMarsRover(unittest.TestCase): | |
def setUp(self): | |
self.mars_map = Planet(3, 3) | |
self.mars_map.add_obstacle(0,2) | |
self.rover = MarsRover("N", 0, 0, self.mars_map) | |
def test_valid_str_commands(self): | |
is_valid = self.rover.process_commands("lfrb") | |
self.assertTrue(is_valid) | |
def test_invalid_int_commands(self): | |
is_valid = self.rover.process_commands(1) | |
self.assertFalse(is_valid) | |
def test_invalid_str_command(self): | |
self.assertFalse(self.rover.process_commands("x")) | |
def test_command_sequence_without_obstacle(self): | |
self.assertTrue(self.rover.process_commands("lffrbb")) | |
def test_command_sequence_with_obstacle(self): | |
self.assertFalse(self.rover.process_commands("ff")) | |
class TestMarsRoverMove(unittest.TestCase): | |
def setUp(self): | |
self.mars_map = Planet(3, 3) | |
self.rover = MarsRover("N", 0, 0, self.mars_map) | |
def test_move_forward_from_north_increments_y(self): | |
self.rover.move("f") | |
self.assertEqual(self.rover.y, 1) | |
def test_move_backward_from_north_wraps_y_to_other_edge(self): | |
self.rover.move("b") | |
self.assertEqual(self.rover.y, 2) | |
def test_move_forward_from_east_increments_x(self): | |
self.rover.facing = "E" | |
self.rover.move("f") | |
self.assertEqual(self.rover.x, 1) | |
def test_move_backward_from_east_wraps_x_to_other_edge(self): | |
self.rover.facing = "E" | |
self.rover.move("b") | |
self.assertEqual(self.rover.x, 2) | |
def test_move_forward_from_south_wraps_y_to_other_edge(self): | |
self.rover.facing = "S" | |
self.rover.move("f") | |
self.assertEqual(self.rover.y, 2) | |
def test_move_backward_from_south_increments_y(self): | |
self.rover.facing = "S" | |
self.rover.move("b") | |
self.assertEqual(self.rover.y, 1) | |
def test_move_forward_from_west_wraps_x_to_other_edge(self): | |
self.rover.facing = "W" | |
self.rover.move("f") | |
self.assertEqual(self.rover.x, 2) | |
def test_move_backward_from_west_increments_x(self): | |
self.rover.facing = "W" | |
self.rover.move("b") | |
self.assertEqual(self.rover.x, 1) | |
class TestMarsRoverTurn(unittest.TestCase): | |
def setUp(self): | |
self.mars_map = Planet(3, 3) | |
self.rover = MarsRover("N", 0, 0, self.mars_map) | |
def test_turn_left_from_north_to_west(self): | |
self.rover.turn("l") | |
self.assertEqual(self.rover.facing, "W") | |
def test_turn_right_from_north_to_east(self): | |
self.rover.turn("r") | |
self.assertEqual(self.rover.facing, "E") | |
def test_turn_left_from_west_to_south(self): | |
self.rover.facing = "W" | |
self.rover.turn("l") | |
self.assertEqual(self.rover.facing, "S") | |
def test_turn_right_from_west_to_north(self): | |
self.rover.facing = "W" | |
self.rover.turn("r") | |
self.assertEqual(self.rover.facing, "N") | |
class TestPlanet(unittest.TestCase): | |
def setUp(self): | |
self.planet = Planet(3, 3) | |
def test_planet_creation(self): | |
expected_map = [ | |
["-", "-", "-"], | |
["-", "-", "-"], | |
["-", "-", "-"] | |
] | |
self.assertEqual(self.planet.planet_map, expected_map) | |
def test_add_obstacle(self): | |
self.planet.add_obstacle(1, 1) | |
expected_map_with_obstacle = [ | |
["-", "-", "-"], | |
["-", "*", "-"], | |
["-", "-", "-"] | |
] | |
self.assertEqual(self.planet.planet_map, expected_map_with_obstacle) | |
# 2 |*|-|-| | |
# 1 |-|-|-| | |
# 0 |^|-|-| | |
# 0 1 2 | |
if __name__ == '__main__': | |
unittest.main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment