Skip to content

Commit 42cd1de

Browse files
committed
Repo backup
0 parents  commit 42cd1de

40 files changed

+1205
-0
lines changed

.formatter.exs

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# Used by "mix format"
2+
[
3+
inputs: ["{mix,.formatter}.exs", "{config,lib,test}/**/*.{ex,exs}"]
4+
]

.gitignore

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# The directory Mix will write compiled artifacts to.
2+
/_build/
3+
4+
# If you run "mix test --cover", coverage assets end up here.
5+
/cover/
6+
7+
# The directory Mix downloads your dependencies sources to.
8+
/deps/
9+
10+
# Where third-party dependencies like ExDoc output generated docs.
11+
/doc/
12+
13+
# Ignore .fetch files in case you like to edit your project deps locally.
14+
/.fetch
15+
16+
# If the VM crashes, it generates a dump, let's ignore it too.
17+
erl_crash.dump
18+
19+
# Also ignore archive artifacts (built via "mix archive.build").
20+
*.ez
21+
22+
# Ignore package tarball (built via "mix hex.build").
23+
toy_robot-*.tar
24+
25+
# Temporary files, for example, from tests.
26+
/tmp/

.idea/.gitignore

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/libraries/benchfella.xml

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/libraries/earmark.xml

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/libraries/ex_doc.xml

+7
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/libraries/uuid.xml

+18
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/misc.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.idea/modules.xml

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# ToyRobot
2+
3+
**TODO: Add description**
4+
5+
## Installation
6+
7+
If [available in Hex](https://hex.pm/docs/publish), the package can be installed
8+
by adding `toy_robot` to your list of dependencies in `mix.exs`:
9+
10+
```elixir
11+
def deps do
12+
[
13+
{:toy_robot, "~> 0.1.0"}
14+
]
15+
end
16+
```
17+
18+
Documentation can be generated with [ExDoc](https://github.com/elixir-lang/ex_doc)
19+
and published on [HexDocs](https://hexdocs.pm). Once published, the docs can
20+
be found at <https://hexdocs.pm/toy_robot>.
21+

lib/toy_robot.ex

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
defmodule ToyRobot do
2+
@moduledoc """
3+
Documentation for `ToyRobot`.
4+
"""
5+
6+
@doc """
7+
Hello world.
8+
9+
## Examples
10+
11+
iex> ToyRobot.hello()
12+
:world
13+
14+
"""
15+
def hello do
16+
:world
17+
end
18+
end

lib/toy_robot/application.ex

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
defmodule ToyRobot.Application do
2+
use Application
3+
4+
def start(_type, _args) do
5+
children = [
6+
ToyRobot.Game.PlayerSupervisor
7+
]
8+
opts = [strategy: :one_for_one, name: ToyRobot.Supervisor]
9+
Supervisor.start_link(children, opts)
10+
end
11+
end

lib/toy_robot/cli.ex

+15
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
defmodule ToyRobot.CLI do
2+
def main([file_name]) do
3+
if File.exists?(file_name) do
4+
file_name
5+
|> File.stream!()
6+
|> Enum.map(&String.trim/1)
7+
|> ToyRobot.CommandInterpreter.interpret()
8+
|> ToyRobot.CommandRunner.run()
9+
else
10+
IO.puts "The file #{file_name} does not exist"
11+
end
12+
end
13+
14+
def main(_), do: IO.puts("Usage: toy_robot commands.txt")
15+
end

lib/toy_robot/command_interpreter.ex

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
defmodule ToyRobot.CommandInterpreter do
2+
@doc """
3+
Interprets commands from a commands list and prepares them for execution.
4+
5+
## Examples
6+
iex> alias ToyRobot.CommandInterpreter
7+
ToyRobot.CommandInterpreter
8+
iex> commands = ["PLACE 1,2,NORTH", "MOVE", "LEFT", "RIGHT", "REPORT", "SPIN", "EXTERMINATE", "PLACE 1, 2, NORTH", "mOvE"]
9+
["PLACE 1,2,NORTH", "MOVE", "LEFT", "RIGHT", "REPORT", "SPIN", "EXTERMINATE", "PLACE 1, 2, NORTH", "mOvE"]
10+
iex> commands |> CommandInterpreter.interpret
11+
[
12+
{:place, %{x: 1, y: 2, f: :north}},
13+
:move,
14+
:turn_left,
15+
:turn_right,
16+
:report,
17+
{:invalid, "SPIN"},
18+
{:invalid, "EXTERMINATE"},
19+
{:invalid, "PLACE 1, 2, NORTH"},
20+
{:invalid, "mOvE"}
21+
]
22+
"""
23+
def interpret(commands_list) do
24+
commands_list |> Enum.map(&do_interpret/1)
25+
end
26+
27+
defp do_interpret(("PLACE" <> _rest) = command) do
28+
format = ~r/\APLACE (\d+),(\d+),(NORTH|EAST|SOUTH|WEST)\z/
29+
case Regex.run(format, command) do
30+
[_command, x, y, f] -> {
31+
:place,
32+
%{
33+
x: String.to_integer(x),
34+
y: String.to_integer(y),
35+
f: f |> String.downcase |> String.to_atom
36+
}
37+
}
38+
nil -> {:invalid, command}
39+
end
40+
41+
end
42+
43+
defp do_interpret(command) do
44+
case command do
45+
"MOVE" -> :move
46+
"LEFT" -> :turn_left
47+
"RIGHT" -> :turn_right
48+
"REPORT" -> :report
49+
invalid_command -> {:invalid, invalid_command}
50+
end
51+
end
52+
end

lib/toy_robot/command_runner.ex

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
defmodule ToyRobot.CommandRunner do
2+
alias ToyRobot.SimulationDriver
3+
alias ToyRobot.Table
4+
5+
def run([{:place, placement} | rest]) do
6+
table = %Table{north_boundary: 4, east_boundary: 4}
7+
case SimulationDriver.place(table, placement) do
8+
{:ok, simulation} -> run(rest, simulation)
9+
{:error, :invalid_placement} -> run(rest)
10+
end
11+
end
12+
13+
def run([]), do: nil # Empty case
14+
def run([_command | rest]), do: run(rest) # Consumes everything until the first valid place command is encountered
15+
16+
defp run([:move | rest], simulation) do
17+
case simulation |> SimulationDriver.move do
18+
{:ok, new_simulation} -> run(rest, new_simulation)
19+
{:error, :at_table_boundary} -> run(rest, simulation)
20+
end
21+
end
22+
23+
defp run([:turn_left | rest], simulation) do
24+
{:ok, simulation} = simulation |> SimulationDriver.turn_left
25+
run(rest, simulation)
26+
end
27+
28+
defp run([:turn_right | rest], simulation) do
29+
{:ok, simulation} = simulation |> SimulationDriver.turn_right
30+
run(rest, simulation)
31+
end
32+
33+
defp run([:report | rest], simulation) do
34+
%{x: x, y: y, f: f} = SimulationDriver.report(simulation)
35+
facing = f |> Atom.to_string |> String.upcase
36+
IO.puts("The robot is at (#{x}, #{y}) and is facing #{facing}")
37+
run(rest, simulation)
38+
end
39+
40+
defp run([{:invalid, _} | rest], simulation) do
41+
run(rest, simulation)
42+
end
43+
44+
defp run([], simulation) do
45+
simulation
46+
end
47+
end

lib/toy_robot/game/game.ex

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
defmodule ToyRobot.Game do
2+
alias ToyRobot.Game.Server
3+
4+
def start([north_boundary: north_boundary, east_boundary: east_boundary]) do
5+
Server.start_link([north_boundary: north_boundary, east_boundary: east_boundary])
6+
end
7+
8+
def place(game, position, name) do
9+
# This is to see if the position is within bounds, so no data races should be occuring with valid_position alone.
10+
# But if we use position_available, a data race can occur if place(game, position, name) is called from multiple processes,
11+
# i.e. if we try to place multiple players in the game, its possible we get :ok for both valid_position and position_available
12+
# Eventually, both processes will call :place, which could potentially put multiple players on same coordinates (even though it is serialized).
13+
with :ok <- game |> valid_position(position),
14+
:ok <- game |> position_available(position) do
15+
GenServer.call(game, {:place, position, name})
16+
else
17+
error -> error
18+
end
19+
end
20+
21+
def move(game, name) do
22+
# One guy stands on (0, 1, :east). Other stands on(0, 3, :west)
23+
# next_position should return (0, 2) for both and that position can be available for both,
24+
# since move(game, name) is not serialized call.
25+
next_pos = next_position(game, name)
26+
game
27+
|> position_available(next_pos)
28+
|> case do
29+
:ok -> GenServer.call(game, {:move, name})
30+
error -> error
31+
end
32+
end
33+
34+
defp next_position(game, name) do
35+
GenServer.call(game, {:next_position, name})
36+
end
37+
38+
defp position_available(game, position) do
39+
GenServer.call(game, {:position_available, position})
40+
end
41+
42+
defp valid_position(game, position), do: GenServer.call(game, {:valid_position, position})
43+
44+
def player_count(game) do
45+
GenServer.call(game, :player_count)
46+
end
47+
48+
def report(game, name) do
49+
GenServer.call(game, {:report, name})
50+
end
51+
end

lib/toy_robot/game/player.ex

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
defmodule ToyRobot.Game.Player do
2+
use GenServer
3+
alias ToyRobot.Game.Players
4+
alias ToyRobot.SimulationDriver
5+
6+
def start_link([registry_id: registry_id, table: table, robot: robot, name: name]) do
7+
name = process_name(registry_id, name)
8+
GenServer.start_link(__MODULE__, [registry_id: registry_id, table: table, robot: robot, name: name], name: name)
9+
end
10+
11+
def process_name(registry_id, name), do: {:via, Registry, {registry_id, name}}
12+
13+
def report(player_pid) do
14+
GenServer.call(player_pid, :report)
15+
end
16+
17+
def move(player_pid) do
18+
GenServer.cast(player_pid, :move)
19+
end
20+
21+
def next_position(player_pid) do
22+
GenServer.call(player_pid, :next_position)
23+
end
24+
25+
@impl GenServer
26+
def init([registry_id: registry_id, table: table, robot: robot, name: name]) do
27+
robot =
28+
registry_id
29+
|> Players.all
30+
|> Players.except(name)
31+
|> Players.positions
32+
|> Players.change_position_if_occupied(table, robot)
33+
simulation = %SimulationDriver{
34+
robot: robot,
35+
table: table
36+
}
37+
{:ok, simulation}
38+
end
39+
40+
def init([table: table, robot: robot]) do
41+
simulation = %SimulationDriver{
42+
robot: robot,
43+
table: table
44+
}
45+
{:ok, simulation}
46+
end
47+
48+
@impl GenServer
49+
def handle_call(:report, _from, simulation) do
50+
{:reply, simulation |> SimulationDriver.report, simulation}
51+
end
52+
53+
@impl GenServer
54+
def handle_call(:next_position, _from, simulation) do
55+
next_position = simulation |> SimulationDriver.next_position()
56+
{:reply, next_position, simulation}
57+
end
58+
59+
@impl GenServer
60+
def handle_cast(:move, simulation) do
61+
{:ok, new_simulation} = simulation |> SimulationDriver.move
62+
{:noreply, new_simulation}
63+
end
64+
end

0 commit comments

Comments
 (0)