Skip to content

Instantly share code, notes, and snippets.

@amuino
Last active November 21, 2018 23:47
Show Gist options
  • Save amuino/44b5cb330ce637eb5b19a24c9ed0abed to your computer and use it in GitHub Desktop.
Save amuino/44b5cb330ce637eb5b19a24c9ed0abed to your computer and use it in GitHub Desktop.
Exercism Robot Simulator
defmodule RobotSimulator do
@directions [:north, :east, :south, :west]
@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 = {x, y})
when direction in @directions and is_integer(x) and is_integer(y) do
{direction, position}
end
def create(direction, _) when direction in @directions, do: {:error, "invalid position"}
def create(_, _), do: {:error, "invalid direction"}
@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)
def simulate(robot, ""), do: robot
def simulate({dir = :north, {x, y}}, "A" <> rest), do: simulate({dir, {x, y + 1}}, rest)
def simulate({dir = :east, {x, y}}, "A" <> rest), do: simulate({dir, {x + 1, y}}, rest)
def simulate({dir = :south, {x, y}}, "A" <> rest), do: simulate({dir, {x, y - 1}}, rest)
def simulate({dir = :west, {x, y}}, "A" <> rest), do: simulate({dir, {x - 1, y}}, rest)
def simulate({:north, {x, y}}, "R" <> rest), do: simulate({:east, {x, y}}, rest)
def simulate({:east, {x, y}}, "R" <> rest), do: simulate({:south, {x, y}}, rest)
def simulate({:south, {x, y}}, "R" <> rest), do: simulate({:west, {x, y}}, rest)
def simulate({:west, {x, y}}, "R" <> rest), do: simulate({:north, {x, y}}, rest)
def simulate({:north, {x, y}}, "L" <> rest), do: simulate({:west, {x, y}}, rest)
def simulate({:west, {x, y}}, "L" <> rest), do: simulate({:south, {x, y}}, rest)
def simulate({:south, {x, y}}, "L" <> rest), do: simulate({:east, {x, y}}, rest)
def simulate({:east, {x, y}}, "L" <> rest), do: simulate({:north, {x, y}}, rest)
def simulate(_, _), do: {:error, "invalid instruction"}
@doc """
Return the robot's direction.
Valid directions are: `:north`, `:east`, `:south`, `:west`
"""
@spec direction(robot :: any) :: atom
def direction({direction, _}), do: direction
@doc """
Return the robot's position.
"""
@spec position(robot :: any) :: {integer, integer}
def position({_, position}), do: position
end
defmodule RobotSimulator do
@directions [:north, :east, :south, :west]
@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 = {x, y})
when direction in @directions and is_integer(x) and is_integer(y) do
{direction, position}
end
def create(direction, _) when direction in @directions, do: {:error, "invalid position"}
def create(_, _), do: {:error, "invalid direction"}
@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)
def simulate(robot, ""), do: robot
def simulate(robot, "R" <> rest), do: robot |> rotate(+1) |> simulate(rest)
def simulate(robot, "L" <> rest), do: robot |> rotate(-1) |> simulate(rest)
def simulate(robot, "A" <> rest), do: robot |> advance(+1) |> simulate(rest)
def simulate(_, _), do: {:error, "invalid instruction"}
defp rotate({direction, position}, turn) do
current_dir_index = Enum.find_index(@directions, &(&1 == direction))
new_dir = Enum.fetch!(@directions, rem(current_dir_index + turn, 4))
{new_dir, position}
end
defp advance({dir = :north, {x, y}}, steps), do: {dir, {x, y + steps}}
defp advance({dir = :east, {x, y}}, steps), do: {dir, {x + steps, y}}
defp advance({dir = :south, {x, y}}, steps), do: {dir, {x, y - steps}}
defp advance({dir = :west, {x, y}}, steps), do: {dir, {x - steps, y}}
@doc """
Return the robot's direction.
Valid directions are: `:north`, `:east`, `:south`, `:west`
"""
@spec direction(robot :: any) :: atom
def direction({direction, _}), do: direction
@doc """
Return the robot's position.
"""
@spec position(robot :: any) :: {integer, integer}
def position({_, position}), do: position
end
defmodule RobotSimulator do
@directions [:north, :east, :south, :west]
@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 = {x, y})
when direction in @directions and is_integer(x) and is_integer(y) do
spawn_link(RobotSimulator.Implementation, :start, [direction, position])
end
def create(direction, _) when direction in @directions, do: {:error, "invalid position"}
def create(_, _), do: {:error, "invalid direction"}
def simulate(robot, instructions), do: rpc(robot, {:simulate, instructions})
def direction(robot), do: rpc(robot, {:direction})
def position(robot), do: rpc(robot, {:position})
defp rpc(robot, msg) do
ref = make_ref()
send(robot, {self(), ref, msg})
receive do
{^ref, response} -> response
m -> exit("Unexpected message #{inspect(m)}")
end
end
defmodule Implementation do
def start(direction, position), do: loop({direction, position})
defp loop(robot) do
robot =
receive do
{pid, msg_id, {:simulate, instructions}} ->
case simulate(robot, instructions) do
{:error, reason} ->
reply({:error, reason}, pid, msg_id)
robot
new_robot ->
reply(self(), pid, msg_id)
new_robot
end
{pid, msg_id, {:direction}} ->
robot |> direction |> reply(pid, msg_id)
robot
{pid, msg_id, {:position}} ->
robot |> position |> reply(pid, msg_id)
robot
m ->
exit("Unexpected message #{inspect(m)}")
end
loop(robot)
end
defp reply(response, pid, msg_id) do
send(pid, {msg_id, response})
response
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)
def simulate(robot, ""), do: robot
def simulate(robot, "R" <> rest), do: robot |> rotate(+1) |> simulate(rest)
def simulate(robot, "L" <> rest), do: robot |> rotate(-1) |> simulate(rest)
def simulate(robot, "A" <> rest), do: robot |> advance(+1) |> simulate(rest)
def simulate(_, _), do: {:error, "invalid instruction"}
@directions [:north, :east, :south, :west]
defp rotate({direction, position}, turn) do
current_dir_index = Enum.find_index(@directions, &(&1 == direction))
new_dir = Enum.fetch!(@directions, rem(current_dir_index + turn, 4))
{new_dir, position}
end
defp advance({dir = :north, {x, y}}, steps), do: {dir, {x, y + steps}}
defp advance({dir = :east, {x, y}}, steps), do: {dir, {x + steps, y}}
defp advance({dir = :south, {x, y}}, steps), do: {dir, {x, y - steps}}
defp advance({dir = :west, {x, y}}, steps), do: {dir, {x - steps, y}}
@doc """
Return the robot's direction.
Valid directions are: `:north`, `:east`, `:south`, `:west`
"""
@spec direction(robot :: any) :: atom
def direction({direction, _}), do: direction
@doc """
Return the robot's position.
"""
@spec position(robot :: any) :: {integer, integer}
def position({_, position}), do: position
end
end
defmodule RobotSimulator do
@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}) do
case GenServer.start_link(RobotSimulator.Implementation, [direction, position]) do
{:ok, robot} -> robot
error = {:error, _} -> error
end
end
def simulate(robot, instructions), do: GenServer.call(robot, {:simulate, instructions})
def direction(robot), do: GenServer.call(robot, {:direction})
def position(robot), do: GenServer.call(robot, {:position})
defmodule Implementation do
@directions [:north, :east, :south, :west]
use GenServer
def init([direction, position = {x, y}])
when direction in @directions and is_integer(x) and is_integer(y) do
{:ok, {direction, position}}
end
def init([direction, _]) when direction in @directions, do: {:stop, "invalid position"}
def init([_, _]), do: {:stop, "invalid direction"}
def handle_call({:simulate, instructions}, _from, state) do
{response, new_state} =
case simulate(state, instructions) do
error = {:error, _} -> {error, state}
new_robot -> {self(), new_robot}
end
{:reply, response, new_state}
end
def handle_call({:direction}, _from, state), do: {:reply, direction(state), state}
def handle_call({:position}, _from, state), do: {:reply, position(state), state}
@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)
def simulate(robot, ""), do: robot
def simulate(robot, "R" <> rest), do: robot |> rotate(+1) |> simulate(rest)
def simulate(robot, "L" <> rest), do: robot |> rotate(-1) |> simulate(rest)
def simulate(robot, "A" <> rest), do: robot |> advance(+1) |> simulate(rest)
def simulate(_, _), do: {:error, "invalid instruction"}
defp rotate({direction, position}, turn) do
current_dir_index = Enum.find_index(@directions, &(&1 == direction))
new_dir = Enum.fetch!(@directions, rem(current_dir_index + turn, 4))
{new_dir, position}
end
defp advance({dir = :north, {x, y}}, steps), do: {dir, {x, y + steps}}
defp advance({dir = :east, {x, y}}, steps), do: {dir, {x + steps, y}}
defp advance({dir = :south, {x, y}}, steps), do: {dir, {x, y - steps}}
defp advance({dir = :west, {x, y}}, steps), do: {dir, {x - steps, y}}
@doc """
Return the robot's direction.
Valid directions are: `:north`, `:east`, `:south`, `:west`
"""
@spec direction(robot :: any) :: atom
def direction({direction, _}), do: direction
@doc """
Return the robot's position.
"""
@spec position(robot :: any) :: {integer, integer}
def position({_, position}), do: position
end
end
defmodule RobotSimulator do
@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}) do
case GenServer.start_link(RobotSimulator.Server, [direction, position]) do
{:ok, robot} -> robot
error = {:error, _} -> error
end
end
def simulate(robot, instructions), do: GenServer.call(robot, {:simulate, instructions})
def direction(robot), do: GenServer.call(robot, {:direction})
def position(robot), do: GenServer.call(robot, {:position})
defmodule Server do
@directions [:north, :east, :south, :west]
use GenServer
def init([direction, position = {x, y}])
when direction in @directions and is_integer(x) and is_integer(y) do
{:ok, {direction, position}}
end
def init([direction, _]) when direction in @directions, do: {:stop, "invalid position"}
def init([_, _]), do: {:stop, "invalid direction"}
def handle_call({:simulate, instructions}, _from, state) do
{response, new_state} =
case RobotSimulator.Implementation.simulate(state, instructions) do
error = {:error, _} -> {error, state}
new_robot -> {self(), new_robot}
end
{:reply, response, new_state}
end
def handle_call({:direction}, _from, state),
do: {:reply, RobotSimulator.Implementation.direction(state), state}
def handle_call({:position}, _from, state),
do: {:reply, RobotSimulator.Implementation.position(state), state}
end
defmodule Implementation do
@directions [:north, :east, :south, :west]
@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)
def simulate(robot, ""), do: robot
def simulate(robot, "R" <> rest), do: robot |> rotate(+1) |> simulate(rest)
def simulate(robot, "L" <> rest), do: robot |> rotate(-1) |> simulate(rest)
def simulate(robot, "A" <> rest), do: robot |> advance(+1) |> simulate(rest)
def simulate(_, _), do: {:error, "invalid instruction"}
defp rotate({direction, position}, turn) do
current_dir_index = Enum.find_index(@directions, &(&1 == direction))
new_dir = Enum.fetch!(@directions, rem(current_dir_index + turn, 4))
{new_dir, position}
end
defp advance({dir = :north, {x, y}}, steps), do: {dir, {x, y + steps}}
defp advance({dir = :east, {x, y}}, steps), do: {dir, {x + steps, y}}
defp advance({dir = :south, {x, y}}, steps), do: {dir, {x, y - steps}}
defp advance({dir = :west, {x, y}}, steps), do: {dir, {x - steps, y}}
@doc """
Return the robot's direction.
Valid directions are: `:north`, `:east`, `:south`, `:west`
"""
@spec direction(robot :: any) :: atom
def direction({direction, _}), do: direction
@doc """
Return the robot's position.
"""
@spec position(robot :: any) :: {integer, integer}
def position({_, position}), do: position
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment