Files
exercism/elixir/robot-simulator/lib/robot_simulator.ex
2021-01-19 09:13:47 -06:00

91 lines
2.4 KiB
Elixir

defmodule RobotSimulator do
defmodule Robot, do: defstruct([:position, :direction])
@directions [:north, :east, :south, :west]
@turns %{"R" => 1, "L" => -1}
defguardp is_direction(direction) when direction in [:north, :east, :south, :west]
defguardp is_position(position)
when is_tuple(position) and tuple_size(position) == 2 and
is_integer(elem(position, 0)) and is_integer(elem(position, 1))
@doc """
Create a Robot Simulator given an initial direction and position.
Valid directions are: `:north`, `:east`, `:south`, `:west`
"""
@spec create(direction :: atom, position :: {integer, integer}) :: any
def create(direction \\ :north, position \\ {0, 0})
def create(direction, _position) when not is_direction(direction),
do: {:error, "invalid direction"}
def create(_direction, position) when not is_position(position),
do: {:error, "invalid position"}
def create(direction, position) do
%Robot{position: position, direction: direction}
end
@doc """
Simulate the robot's movement given a string of instructions.
Valid instructions are: "R" (turn right), "L", (turn left), and "A" (advance)
"""
@spec simulate(robot :: any, instructions :: String.t()) :: any
def simulate(robot, ""), do: robot
def simulate(
%Robot{position: position, direction: direction} = robot,
<<head::bytes-size(1)>> <> tail
) do
case head do
"A" ->
simulate(%Robot{robot | position: get_change(position, direction)}, tail)
"L" ->
simulate(%Robot{robot | direction: get_turn(head, direction)}, tail)
"R" ->
simulate(%Robot{robot | direction: get_turn(head, direction)}, tail)
_ ->
{:error, "invalid instruction"}
end
end
@doc """
Return the robot's direction.
Valid directions are: `:north`, `:east`, `:south`, `:west`
"""
@spec direction(robot :: any) :: atom
def direction(robot) do
robot.direction
end
@doc """
Return the robot's position.
"""
@spec position(robot :: any) :: {integer, integer}
def position(robot) do
robot.position
end
def get_turn(turn, direction) do
@directions
|> Enum.fetch(rem(Enum.find_index(@directions, &(&1 == direction)) + @turns[turn], 4))
|> elem(1)
end
def get_change({x, y}, direction) do
case direction do
:north -> {x, y + 1}
:east -> {x + 1, y}
:south -> {x, y - 1}
:west -> {x - 1, y}
end
end
end