Last active
November 21, 2018 23:47
-
-
Save amuino/44b5cb330ce637eb5b19a24c9ed0abed to your computer and use it in GitHub Desktop.
Exercism Robot Simulator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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