2019-11-15 20:43:59 +01:00
|
|
|
import enum
|
2019-11-16 03:19:50 +01:00
|
|
|
from copy import deepcopy
|
|
|
|
from typing import List
|
2019-11-15 20:29:06 +01:00
|
|
|
|
2019-11-16 04:25:28 +01:00
|
|
|
from pydantic import BaseModel, root_validator, validator
|
2019-11-15 20:29:06 +01:00
|
|
|
|
2019-11-15 21:04:16 +01:00
|
|
|
|
|
|
|
class Vector(BaseModel):
|
|
|
|
x: int = 0
|
|
|
|
y: int = 0
|
2019-11-15 20:34:34 +01:00
|
|
|
|
2019-11-16 04:24:02 +01:00
|
|
|
@validator("x")
|
|
|
|
def _x_must_be_positive(x):
|
|
|
|
if x < 0:
|
|
|
|
raise ValueError("x must be positive")
|
|
|
|
return x
|
|
|
|
|
|
|
|
@validator("y")
|
|
|
|
def _y_must_be_positive(y):
|
|
|
|
if y < 0:
|
|
|
|
raise ValueError("y must be positive")
|
|
|
|
return y
|
|
|
|
|
2019-11-15 20:34:34 +01:00
|
|
|
|
2019-11-15 20:43:59 +01:00
|
|
|
class Direction(enum.Enum):
|
|
|
|
NORTH = "N"
|
|
|
|
SOUTH = "S"
|
|
|
|
EAST = "E"
|
|
|
|
WEST = "W"
|
|
|
|
|
|
|
|
|
2019-11-16 03:19:50 +01:00
|
|
|
class ObstacleError(RuntimeError):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2019-11-15 21:29:37 +01:00
|
|
|
DIRECTIONS = [Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST]
|
|
|
|
|
|
|
|
|
2019-11-15 21:04:16 +01:00
|
|
|
class Rover(BaseModel):
|
|
|
|
pos: Vector = Vector(x=0, y=0)
|
|
|
|
planet_size: Vector = Vector(x=100, y=100)
|
2019-11-15 20:43:59 +01:00
|
|
|
dir: Direction = Direction.NORTH
|
2019-11-15 21:12:07 +01:00
|
|
|
|
2019-11-16 04:25:28 +01:00
|
|
|
@root_validator()
|
|
|
|
def _validate_pos(cls, values) -> None:
|
|
|
|
pos, planet_size = values.get("pos"), values.get("planet_size")
|
|
|
|
if pos.x > planet_size.x:
|
|
|
|
raise ValueError(
|
|
|
|
f"pos.x (= {pos.x}) should be under planet_size.x (= {planet_size.x})"
|
|
|
|
)
|
|
|
|
if pos.y > planet_size.y:
|
|
|
|
raise ValueError(
|
|
|
|
f"pos.y (= {pos.y}) should be under planet_size.y (= {planet_size.y})"
|
|
|
|
)
|
|
|
|
return values
|
|
|
|
|
2019-11-16 04:27:41 +01:00
|
|
|
def _translate(self, value) -> None:
|
2019-11-15 21:12:07 +01:00
|
|
|
if self.dir == Direction.NORTH:
|
2019-11-15 21:20:42 +01:00
|
|
|
self.pos.y += value
|
2019-11-15 21:12:07 +01:00
|
|
|
elif self.dir == Direction.SOUTH:
|
2019-11-15 21:20:42 +01:00
|
|
|
self.pos.y -= value
|
2019-11-15 21:12:07 +01:00
|
|
|
elif self.dir == Direction.EAST:
|
2019-11-15 21:20:42 +01:00
|
|
|
self.pos.x += value
|
2019-11-15 21:12:07 +01:00
|
|
|
elif self.dir == Direction.WEST:
|
2019-11-15 21:20:42 +01:00
|
|
|
self.pos.x -= value
|
2019-11-15 21:12:51 +01:00
|
|
|
|
|
|
|
if self.pos.x < 0:
|
|
|
|
self.pos.x += self.planet_size.x
|
|
|
|
if self.pos.y < 0:
|
|
|
|
self.pos.y += self.planet_size.y
|
2019-11-15 21:14:42 +01:00
|
|
|
|
|
|
|
self.pos.x %= self.planet_size.x
|
|
|
|
self.pos.y %= self.planet_size.y
|
2019-11-15 21:20:42 +01:00
|
|
|
|
2019-11-16 04:27:41 +01:00
|
|
|
def forward(self) -> None:
|
2019-11-15 21:20:42 +01:00
|
|
|
self._translate(1)
|
|
|
|
|
2019-11-16 04:27:41 +01:00
|
|
|
def backward(self) -> None:
|
2019-11-15 21:20:42 +01:00
|
|
|
self._translate(-1)
|
2019-11-15 21:29:37 +01:00
|
|
|
|
2019-11-16 04:27:41 +01:00
|
|
|
def _turn(self, value) -> None:
|
2019-11-15 21:29:37 +01:00
|
|
|
index: int = DIRECTIONS.index(self.dir)
|
|
|
|
self.dir = DIRECTIONS[(index + value) % len(DIRECTIONS)]
|
|
|
|
|
2019-11-16 04:27:41 +01:00
|
|
|
def turn_left(self) -> None:
|
2019-11-15 21:29:37 +01:00
|
|
|
self._turn(-1)
|
|
|
|
|
2019-11-16 04:27:41 +01:00
|
|
|
def turn_right(self) -> None:
|
2019-11-15 21:29:37 +01:00
|
|
|
self._turn(1)
|
2019-11-15 21:29:57 +01:00
|
|
|
|
|
|
|
|
|
|
|
class Commander(BaseModel):
|
|
|
|
rover: Rover = Rover()
|
2019-11-16 03:19:50 +01:00
|
|
|
obstacles: List[Vector] = []
|
2019-11-16 03:02:53 +01:00
|
|
|
|
2019-11-16 04:26:24 +01:00
|
|
|
@root_validator()
|
|
|
|
def _rover_should_not_start_on_obstacle(cls, values):
|
|
|
|
rover, obstacles = values.get("rover"), values.get("obstacles")
|
|
|
|
if rover.pos in obstacles:
|
|
|
|
raise ValueError(f"Rover should not start on obstacle ({rover.pos})")
|
|
|
|
return values
|
|
|
|
|
2019-11-16 04:27:41 +01:00
|
|
|
def parse_execute(self, commands: str) -> None:
|
2019-11-28 16:21:40 +01:00
|
|
|
for i, command in enumerate(commands):
|
2019-11-16 03:19:50 +01:00
|
|
|
save: Vector = deepcopy(self.rover.pos)
|
2019-11-16 03:02:53 +01:00
|
|
|
if command == "F":
|
|
|
|
self.rover.forward()
|
|
|
|
elif command == "B":
|
|
|
|
self.rover.backward()
|
|
|
|
elif command == "L":
|
|
|
|
self.rover.turn_left()
|
|
|
|
elif command == "R":
|
|
|
|
self.rover.turn_right()
|
2019-11-28 16:21:40 +01:00
|
|
|
else:
|
|
|
|
raise ValueError(f"Unknown command: '{command}' (index {i})")
|
2019-11-16 03:19:50 +01:00
|
|
|
if self.rover.pos in self.obstacles:
|
|
|
|
self.rover.pos = save
|
|
|
|
raise ObstacleError
|