# paulfioravanti's solution

## to Robot Simulator in the Elixir Track

Published at Jul 29 2019 · 0 comments
Instructions
Test suite
Solution

#### Note:

This exercise has changed since this solution was written.

Write a robot simulator.

A robot factory's test facility needs a program to verify robot movements.

The robots have three possible movements:

• turn right
• turn left

Robots are placed on a hypothetical infinite grid, facing a particular direction (north, east, south, or west) at a set of {x,y} coordinates, e.g., {3,8}, with coordinates increasing to the north and east.

The robot then receives a number of instructions, at which point the testing facility verifies the robot's new position, and in which direction it is pointing.

• The letter-string "RAALAL" means:
• Turn right
• Turn left
• Turn left yet again
• Say a robot starts at {7, 3} facing north. Then running this stream of instructions should leave it at {9, 4} facing west.

## Running tests

Execute the tests with:

``````\$ elixir robot_simulator_test.exs
``````

### Pending tests

In the test suites, all but the first test have been skipped.

Once you get a test passing, you can unskip the next one by commenting out the relevant `@tag :pending` with a `#` symbol.

For example:

``````# @tag :pending
test "shouting" do
assert Bob.hey("WATCH OUT!") == "Whoa, chill out!"
end
``````

Or, you can enable all the tests by commenting out the `ExUnit.configure` line in the test suite.

``````# ExUnit.configure exclude: :pending, trace: true
``````

If you're stuck on something, it may help to look at some of the available resources out there where answers might be found.

## Source

Inspired by an interview question at a famous company.

## Submitting Incomplete Solutions

It's possible to submit an incomplete solution so you can see how others have completed the exercise.

### robot_simulator_test.exs

``````if !System.get_env("EXERCISM_TEST_EXAMPLES") do
end

ExUnit.start()
ExUnit.configure(exclude: :pending, trace: true)

defmodule RobotSimulatorTest do
use ExUnit.Case

test "create has sensible defaults" do
robot = RobotSimulator.create()
assert RobotSimulator.position(robot) == {0, 0}
assert RobotSimulator.direction(robot) == :north
end

@tag :pending
test "create works with valid arguments" do
robot = RobotSimulator.create(:north, {0, 0})
assert RobotSimulator.position(robot) == {0, 0}
assert RobotSimulator.direction(robot) == :north

robot = RobotSimulator.create(:south, {-10, 0})
assert RobotSimulator.position(robot) == {-10, 0}
assert RobotSimulator.direction(robot) == :south

robot = RobotSimulator.create(:east, {0, 10})
assert RobotSimulator.position(robot) == {0, 10}
assert RobotSimulator.direction(robot) == :east

robot = RobotSimulator.create(:west, {100, -100})
assert RobotSimulator.position(robot) == {100, -100}
assert RobotSimulator.direction(robot) == :west
end

@tag :pending
test "create errors if invalid direction given" do
position = {0, 0}
invalid_direction = {:error, "invalid direction"}

assert RobotSimulator.create(:invalid, position) == invalid_direction
assert RobotSimulator.create(0, position) == invalid_direction
assert RobotSimulator.create("east", position) == invalid_direction
end

@tag :pending
test "create errors if invalid position given" do
direction = :north
invalid_position = {:error, "invalid position"}

assert RobotSimulator.create(direction, {0, 0, 0}) == invalid_position
assert RobotSimulator.create(direction, {0, :invalid}) == invalid_position
assert RobotSimulator.create(direction, {"0", 0}) == invalid_position

assert RobotSimulator.create(direction, "invalid") == invalid_position
assert RobotSimulator.create(direction, 0) == invalid_position
assert RobotSimulator.create(direction, [0, 0]) == invalid_position
assert RobotSimulator.create(direction, nil) == invalid_position
end

@tag :pending
test "simulate robots" do
robot1 = RobotSimulator.create(:north, {0, 0}) |> RobotSimulator.simulate("LAAARALA")
assert RobotSimulator.direction(robot1) == :west
assert RobotSimulator.position(robot1) == {-4, 1}

robot2 = RobotSimulator.create(:east, {2, -7}) |> RobotSimulator.simulate("RRAAAAALA")
assert RobotSimulator.direction(robot2) == :south
assert RobotSimulator.position(robot2) == {-3, -8}

robot3 = RobotSimulator.create(:south, {8, 4}) |> RobotSimulator.simulate("LAAARRRALLLL")
assert RobotSimulator.direction(robot3) == :north
assert RobotSimulator.position(robot3) == {11, 5}
end

@tag :pending
test "simulate errors on invalid instructions" do
assert RobotSimulator.create() |> RobotSimulator.simulate("UUDDLRLRBASTART") ==
{:error, "invalid instruction"}
end
end``````
``````defmodule RobotSimulator do
defstruct direction: :north, position: {0, 0}

@bearings [:north, :east, :south, :west]
@instructions %{
"L" => :turn_left,
"R" => :turn_right
}

alias __MODULE__, as: RobotSimulator

defguardp invalid_direction?(direction) when not (direction in @bearings)

defguardp invalid_position?(position)
when not is_tuple(position) or
tuple_size(position) != 2 or
not is_integer(elem(position, 0)) or
not 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 \\ nil, position \\ nil)
def create(nil, nil), do: %RobotSimulator{}

def create(direction, _position) when invalid_direction?(direction) do
{:error, "invalid direction"}
end

def create(_direction, position) when invalid_position?(position) do
{:error, "invalid position"}
end

def create(direction, position) do
%RobotSimulator{direction: direction, position: position}
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, instructions) do
instructions
|> String.graphemes()
|> Enum.map(&parse_instruction/1)
|> Enum.reduce(robot, &apply(RobotSimulator, &1, [&2]))
catch
:invalid_instruction ->
{:error, "invalid instruction"}
end

@doc """
Return the robot's direction.

Valid directions are: `:north`, `:east`, `:south`, `:west`
"""
@spec direction(robot :: any) :: atom
def direction(%RobotSimulator{direction: direction}), do: direction

@doc """
Return the robot's position.
"""
@spec position(robot :: any) :: {integer, integer}
def position(%RobotSimulator{position: position}), do: position

defp parse_instruction(instruction) do
case Map.fetch(@instructions, instruction) do
{:ok, instruction} ->
instruction

:error ->
throw(:invalid_instruction)
end
end

def turn_left(robot) do
@bearings
|> rotate(-1)
|> turn(robot)
end

def turn_right(robot) do
@bearings
|> rotate(1)
|> turn(robot)
end

{x, y} = robot.position

new_position =
case robot.direction do
:north ->
{x, y + 1}

:east ->
{x + 1, y}

:south ->
{x, y - 1}

:west ->
{x - 1, y}
end

%RobotSimulator{robot | position: new_position}
end

defp turn(new_bearings, robot) do
index =
@bearings
|> Enum.find_index(&(&1 == robot.direction))

new_direction =
new_bearings
|> Enum.fetch!(index)

%RobotSimulator{robot | direction: new_direction}
end

defp rotate(list, 0), do: list

defp rotate([head | tail], count) when count > 0 do
rotate(tail ++ [head], count - 1)
end

defp rotate(list, count) when count < 0 do
list
|> Enum.reverse()
|> rotate(abs(count))
|> Enum.reverse()
end
end``````