# thekeele's solution

## to Robot Simulator in the Elixir Track

Published at May 20 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

@valid_directions [:north, :east, :south, :west]

@enforce_keys [:direction, :position]
defstruct [:direction, :position]
@type t :: %__MODULE__{direction: atom, position: {integer, integer}}

@spec create(direction :: atom, position :: {integer, integer}) :: __MODULE__.t
def create(direction \\ :north, position \\ {0, 0})

def create(direction, position = {x, y})
when is_integer(x) and is_integer(y) do
if direction in @valid_directions do
%__MODULE__{direction: direction, position: position}
else
{:error, "invalid direction"}
end
end

def create(_, _), do: {:error, "invalid position"}

@spec simulate(robot :: __MODULE__.t, instructions :: String.t()) :: __MODULE__.t
def simulate(robot, instructions) do
steps = String.codepoints(instructions)

if Enum.all?(steps, & &1 in ["R", "L", "A"]) do
do_simulate(robot, steps)
else
{:error, "invalid instruction"}
end
end

defp do_simulate(robot, []),
do: robot

defp do_simulate(robot, [step | steps]) when step in ["R", "L"],
do: robot |> change_direction(step, robot.direction) |> do_simulate(steps)

defp do_simulate(robot, ["A" | steps]),
do: robot |> change_position(robot.direction, robot.position) |> do_simulate(steps)

defp change_direction(robot, "R", :north), do: %__MODULE__{robot | direction: :east}
defp change_direction(robot, "R", :east), do: %__MODULE__{robot | direction: :south}
defp change_direction(robot, "R", :south), do: %__MODULE__{robot | direction: :west}
defp change_direction(robot, "R", :west), do: %__MODULE__{robot | direction: :north}
defp change_direction(robot, "L", :north), do: %__MODULE__{robot | direction: :west}
defp change_direction(robot, "L", :east), do: %__MODULE__{robot | direction: :north}
defp change_direction(robot, "L", :south), do: %__MODULE__{robot | direction: :east}
defp change_direction(robot, "L", :west), do: %__MODULE__{robot | direction: :south}

defp change_position(robot, :north, {x, y}), do: %__MODULE__{robot | position: {x, y + 1}}
defp change_position(robot, :east, {x, y}), do: %__MODULE__{robot | position: {x + 1, y}}
defp change_position(robot, :south, {x, y}), do: %__MODULE__{robot | position: {x, y - 1}}
defp change_position(robot, :west, {x, y}), do: %__MODULE__{robot | position: {x - 1, y}}

@spec direction(robot :: __MODULE__.t) :: atom
def direction(robot), do: robot.direction

@spec position(robot :: __MODULE__.t) :: {integer, integer}
def position(robot), do: robot.position
end``````