Skip to content

Commit

Permalink
WIP: Multiplayer prototype
Browse files Browse the repository at this point in the history
  • Loading branch information
radar committed Aug 6, 2019
1 parent 175f80c commit 1055504
Show file tree
Hide file tree
Showing 8 changed files with 230 additions and 15 deletions.
51 changes: 51 additions & 0 deletions lib/toy_robot/game/game.ex
@@ -0,0 +1,51 @@
defmodule ToyRobot.Game do
alias ToyRobot.Game.{Player, PlayerSupervisor, Table}

def new_table(%{name: name, north_boundary: north_boundary, east_boundary: east_boundary}) do
ToyRobot.Game.Table.start_link(
name: name,
north_boundary: north_boundary,
east_boundary: east_boundary
)
end

def place(table, name, position) do
if Table.position_taken?(table, position) do
{:error, :occupied}
else
{:ok, _robot} = PlayerSupervisor.start_child(table, position, table |> player_name(name))

:ok
end
end

def move(table, name) do
next_move = table |> process_name(name) |> Player.next_move()

if Table.position_taken?(table, next_move) do
{:error, :occupied}
else
%{robot: robot} = table |> process_name(name) |> Player.move()
player_name = table |> player_name(name)
Table.update_position(table, player_name, robot)
robot
end
end

def turn_right(table, name) do
%{robot: robot} = table |> process_name(name) |> Player.turn_right()
robot
end

def report(table, name) do
table |> process_name(name) |> Player.report()
end

defp player_name(table, name) do
Table.name(table) <> "_" <> name
end

def process_name(table, name) do
table |> player_name(name) |> Player.process_name()
end
end
51 changes: 38 additions & 13 deletions lib/toy_robot/game/player.ex
@@ -1,26 +1,33 @@
defmodule ToyRobot.Game.Player do
use GenServer

alias ToyRobot.{Robot, Simulation, Table}
alias ToyRobot.{Robot, Simulation}
alias ToyRobot.Game.Table

def start(robot) do
GenServer.start(__MODULE__, robot)
end

def start_link([position: position, name: name]) do
GenServer.start_link(__MODULE__, position, name: process_name(name))
def start_link(table: table, position: position, name: name) do
GenServer.start_link(__MODULE__, [name: name, table: table, position: position], name: process_name(name))
end

def init(position) do
simulation = %Simulation{
table: %Table{
north_boundary: 4,
east_boundary: 4,
},
robot: struct(Robot, position)
}
def init(name: name, table: table, position: position) do
if Table.position_taken?(table, position) do
init(
name: name,
table: table,
position: position |> Map.merge(Table.random_position(table))
)
else
simulation = %Simulation{
table: ToyRobot.Game.Table.table(table),
robot: struct(Robot, position)
}
Table.update_position(table, name, position)

{:ok, simulation}
{:ok, simulation}
end
end

def process_name(name) do
Expand All @@ -35,12 +42,30 @@ defmodule ToyRobot.Game.Player do
GenServer.call(player, :move)
end

def turn_right(player) do
GenServer.call(player, :turn_right)
end

def next_move(player) do
GenServer.call(player, :next_move)
end

def handle_call(:report, _from, simulation) do
{:reply, simulation |> Simulation.report, simulation}
{:reply, simulation |> Simulation.report(), simulation}
end

def handle_call(:move, _from, simulation) do
{:ok, new_simulation} = simulation |> Simulation.move()
{:reply, new_simulation, new_simulation}
end

def handle_call(:turn_right, _from, simulation) do
{:ok, new_simulation} = simulation |> Simulation.turn_right()
{:reply, new_simulation, new_simulation}
end

def handle_call(:next_move, _from, simulation) do
next_move = simulation |> Simulation.next_move()
{:reply, next_move, simulation}
end
end
4 changes: 2 additions & 2 deletions lib/toy_robot/game/player_supervisor.ex
Expand Up @@ -12,10 +12,10 @@ defmodule ToyRobot.Game.PlayerSupervisor do
DynamicSupervisor.init(strategy: :one_for_one)
end

def start_child(position, name) do
def start_child(table, position, name) do
DynamicSupervisor.start_child(
__MODULE__,
{Player, [position: position, name: name]}
{Player, [table: table, position: position, name: name]}
)
end

Expand Down
73 changes: 73 additions & 0 deletions lib/toy_robot/game/table.ex
@@ -0,0 +1,73 @@
defmodule ToyRobot.Game.Table do
use GenServer

alias ToyRobot.Table
alias ToyRobot.Game.PlayerSupervisor

def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end

def init(name: name, north_boundary: north_boundary, east_boundary: east_boundary) do
table = %Table{
north_boundary: north_boundary,
east_boundary: east_boundary
}

{:ok, %{name: name, table: table, players: %{}}}
end

def position_taken?(table, position) do
GenServer.call(table, {:position_taken?, position |> coordinates})
end

def update_position(table, name, position) do
GenServer.call(table, {:update_position, name, position})
end

def name(table) do
GenServer.call(table, :name)
end

def table(table) do
GenServer.call(table, :table)
end

def random_position(table) do
GenServer.call(table, :random_position)
end

def handle_call({:position_taken?, position}, _from, %{players: players} = state) do
taken = position in (players |> positions)
{:reply, taken, state}
end

def handle_call({:update_position, name, position}, _from, %{players: players} = state) do
players = players |> Map.put(name, position)
{:reply, :ok, %{state | players: players}}
end

def handle_call(:table, _from, %{table: table} = state) do
{:reply, table, state}
end

def handle_call(:name, _from, %{name: name} = state) do
{:reply, name, state}
end

def handle_call(
:random_position,
_from,
%{
table: %Table{north_boundary: north_boundary, east_boundary: east_boundary},
players: players
} = state
) do
pick_a_number = fn max -> 0..max |> Enum.random() end

{:reply, %{north: pick_a_number.(north_boundary), east: pick_a_number.(east_boundary)}, state}
end

defp coordinates(position), do: position |> Map.take([:north, :east])
defp positions(players), do: players |> Map.values() |> Enum.map(&coordinates/1)
end
4 changes: 4 additions & 0 deletions lib/toy_robot/simulation.ex
Expand Up @@ -155,4 +155,8 @@ defmodule ToyRobot.Simulation do
%Robot{north: 0, east: 0, facing: :north}
"""
def report(%Simulation{robot: robot}), do: robot

def next_move(%Simulation{robot: robot}) do
robot |> Robot.move()
end
end
13 changes: 13 additions & 0 deletions multi/1.exs
@@ -0,0 +1,13 @@
alias ToyRobot.Game

# Task #1: Cannot start on an already occupied square
{:ok, table} = ToyRobot.Game.new_table(%{
name: "Playground",
north_boundary: 4,
east_boundary: 4,
})

izzy_origin = %{north: 0, east: 0, facing: :north}
:ok = Game.place(table, "Izzy", izzy_origin)
davros_origin = %{north: 0, east: 0, facing: :south}
{:error, :occupied} = Game.place(table, "Davros", davros_origin)
21 changes: 21 additions & 0 deletions multi/2.exs
@@ -0,0 +1,21 @@
alias ToyRobot.Game

{:ok, table} = ToyRobot.Game.new_table(%{
name: "Playground",
north_boundary: 4,
east_boundary: 4,
})

# Task #2: Cannot move onto an occupied square
izzy_origin = %{north: 0, east: 0, facing: :north}
:ok = Game.place(table, "Izzy", izzy_origin)
davros_origin = %{north: 1, east: 0, facing: :south}
:ok = Game.place(table, "Davros", davros_origin)
# Izzy occupies the square
{:error, :occupied} = Game.move(table, "Davros")
# Izzy moves off that square
%{north: 0, east: 0, facing: :east} = Game.turn_right(table, "Izzy")
%{north: 0, east: 1, facing: :east} = Game.move(table, "Izzy")

# Davros can move onto it
%{north: 0, east: 0, facing: :south} = Game.move(table, "Davros")
28 changes: 28 additions & 0 deletions multi/3.exs
@@ -0,0 +1,28 @@
alias ToyRobot.Game

# Task #3: Cannot respawn on an occupied square
{:ok, table} = ToyRobot.Game.new_table(%{
name: "Playground",
north_boundary: 4,
east_boundary: 4,
})

izzy_origin = %{north: 0, east: 1, facing: :north}
:ok = Game.place(table, "Izzy", izzy_origin)

davros_origin = %{north: 1, east: 1, facing: :west}
:ok = Game.place(table, "Davros", davros_origin)
# Davros moves off starting square, moving west
%{north: 1, east: 0} = Game.move(table, "Davros")

# Izzy moves onto Davros's origin square
%{north: 1, east: 1, facing: :north} = Game.move(table, "Izzy")

# Davros moves off the edge of the table, falling to his doom.
%{north: 1, east: 0} = Game.move(table, "Davros")

# # Davros's origin is taken, so he should start in a random position
Game.report(table, "Davros") |> IO.inspect(label: "Davros")

# Ensure both robots (and the supervisor) are currently still functional
2 = DynamicSupervisor.which_children(ToyRobot.Game.PlayerSupervisor) |> Enum.count

0 comments on commit 1055504

Please sign in to comment.