diff --git a/.gitignore b/.gitignore index 9454a58..926f3f6 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ erl_crash.dump *.lock rel *.swp +.elixir_ls \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..7fd10c7 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,29 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "mix_task", + "name": "mix (Default task)", + "request": "launch", + "projectDir": "${workspaceRoot}" + }, + { + "type": "mix_task", + "name": "mix test", + "request": "launch", + "task": "test", + "taskArgs": [ + "--trace" + ], + "startApps": true, + "projectDir": "${workspaceRoot}", + "requireFiles": [ + "test/**/test_helper.exs", + "test/**/*_test.exs" + ] + } + ] +} \ No newline at end of file diff --git a/config/config.exs b/config/config.exs index a30b46c..7a1d705 100644 --- a/config/config.exs +++ b/config/config.exs @@ -7,13 +7,13 @@ config :servus, # Configuration for a connect-four game config :servus, -connect_four: %{ - adapters: [ - tcp: 3334, - web: 3335 - ], - players_per_game: 2, - implementation: ConnectFour -} + connect_four: %{ + adapters: [ + tcp: 3334, + web: 3335 + ], + players_per_game: 2, + implementation: ConnectFour + } -import_config "#{Mix.env}.exs" +import_config "#{Mix.env()}.exs" diff --git a/config/test.exs b/config/test.exs index 59b90b1..6dbcbb4 100644 --- a/config/test.exs +++ b/config/test.exs @@ -1,4 +1,4 @@ use Mix.Config config :logger, - level: :error + level: :debug diff --git a/lib/connect_four/connectfour.ex b/lib/connect_four/connectfour.ex index 78af0fe..a609582 100644 --- a/lib/connect_four/connectfour.ex +++ b/lib/connect_four/connectfour.ex @@ -1,14 +1,15 @@ defmodule ConnectFour do - use Servus.Game require Logger + require IO + use Servus.Game alias Servus.Serverutils def init(players) do - Logger.debug "Initializing game state machine" + Logger.debug("Initializing game state machine") [player2, player1] = players - {:ok, field_pid} = Gamefield.start_link + {:ok, field_pid} = Gamefield.start_link() fsm_state = %{player1: player1, player2: player2, field: field_pid} @@ -25,11 +26,13 @@ defmodule ConnectFour do decide what to do. """ def abort(player, state) do - Logger.warn "Player #{player.name} has aborted the game" + Logger.warn("Player #{player.name} has aborted the game") + cond do player.id == state.player1.id -> Serverutils.send(state.player2.socket, "abort", state.player1.id) {:stop, :shutdown, state} + player.id == state.player2.id -> Serverutils.send(state.player1.socket, "abort", state.player2.id) {:stop, :shutdown, state} @@ -40,14 +43,16 @@ defmodule ConnectFour do FSM is in state `p2`. Player 2 puts. Outcome: p1 state """ - def p2({id, "put", slot}, state) do + def handle_event(:cast, {id, "put", slot}, :p2, state) do cond do id != state.player2.id -> - Logger.warn "Not your turn" + Logger.warn("Not your turn") {:next_state, :p2, state} + slot in 0..7 -> Gamefield.update_field(state.field, slot, :p2) - if Gamefield.check_win_condition(state.field) do + + if Gamefield.check_win_condition(state.field) do # Send the final move to the loosingn player Serverutils.send(state.player1.socket, "set", slot) @@ -56,22 +61,23 @@ defmodule ConnectFour do Serverutils.send(state.player1.socket, "loose", nil) {:next_state, :win, state} else - #No win yet + # No win yet # Notify the other player about the put... Serverutils.send(state.player1.socket, "set", slot) - + # ...and give him the next turn. Serverutils.send(state.player1.socket, "turn", nil) {:next_state, :p1, state} end + true -> - Logger.warn "Invalid slot: #{slot}" + Logger.warn("Invalid slot: #{slot}") {:next_state, :p2, state} end end - def p2({_, "restart", _}, state) do - Logger.warn "Restart not allowed while game is ongoing" + def handle_event(:cast, {_, "restart", _}, :p2, state) do + Logger.warn("Restart not allowed while game is ongoing") {:next_state, :p2, state} end @@ -79,14 +85,16 @@ defmodule ConnectFour do FSM is in state `p1`. Player 1 puts. Outcome: p2 state """ - def p1({id, "put", slot}, state) do + def handle_event(:cast, {id, "put", slot}, :p1, state) do cond do id != state.player1.id -> - Logger.warn "Not your turn" + Logger.warn("Not your turn") {:next_state, :p1, state} + slot in 0..7 -> Gamefield.update_field(state.field, slot, :p1) - if Gamefield.check_win_condition(state.field) do + + if Gamefield.check_win_condition(state.field) do # Set the final move to the loosing player Serverutils.send(state.player2.socket, "set", slot) @@ -95,26 +103,27 @@ defmodule ConnectFour do Serverutils.send(state.player2.socket, "loose", nil) {:next_state, :win, state} else - #No win yet + # No win yet # Notify the other player about the put... Serverutils.send(state.player2.socket, "set", slot) - + # ...and give him the next turn. Serverutils.send(state.player2.socket, "turn", nil) {:next_state, :p2, state} end + true -> - Logger.warn "Invalid slot: #{slot}" + Logger.warn("Invalid slot: #{slot}") {:next_state, :p1, state} end end - def p1({_, "restart", _}, state) do - Logger.warn "Restart not allowed while game is ongoing" - {:next_state, :p2, state} + def handle_event(:cast, {_, "restart", _}, :p1, state) do + Logger.warn("Restart not allowed while game is ongoing") + {:next_state, :p1, state} end - def win({_, "restart", _}, state) do + def handle_event(:cast, {_, "restart", _}, :win, state) do Gamefield.reset_game(state.field) Serverutils.send(state.player1.socket, "reset", nil) Serverutils.send(state.player2.socket, "reset", nil) diff --git a/lib/connect_four/gamefield.ex b/lib/connect_four/gamefield.ex index 49cd092..caa7edc 100644 --- a/lib/connect_four/gamefield.ex +++ b/lib/connect_four/gamefield.ex @@ -11,6 +11,10 @@ defmodule Gamefield do GenServer.start_link(__MODULE__, produce_empty_field()) end + def init(init_arg) do + {:ok, init_arg} + end + @doc """ Reset the game field to empty state """ @@ -21,8 +25,8 @@ defmodule Gamefield do @doc """ Put a coin of `playerID` into `slot` """ - def update_field(server,slot,playerID) do - GenServer.call(server, {:updateField,slot, playerID}) + def update_field(server, slot, playerID) do + GenServer.call(server, {:updateField, slot, playerID}) end @doc """ @@ -38,23 +42,23 @@ defmodule Gamefield do # eight arrays. Each of the nested arrays contains eight times the # value `nil` (empty state). defp produce_empty_field do - for _ <- 0..7, do: (for _ <- 0..7, do: nil) + for _ <- 0..7, do: for(_ <- 0..7, do: nil) end # Implementation def handle_cast(:resetGame, _) do - {:noreply, produce_empty_field} + {:noreply, produce_empty_field()} end - def handle_call({:updateField, slot,playerID}, _, gamefield)do + def handle_call({:updateField, slot, playerID}, _, gamefield) do column = Enum.at(gamefield, slot) newColumn = put_coin(column, playerID, 0) newGameField = Listhelp.put_item(gamefield, slot, newColumn) {:reply, :ok, newGameField} end - def handle_call(:checkWinCondition,_,gamefield) do + def handle_call(:checkWinCondition, _, gamefield) do values = [ gamefield, Fieldchecker.rotate(gamefield), @@ -62,32 +66,35 @@ defmodule Gamefield do Fieldchecker.arrow_left(gamefield) ] - status = Enum.reduce values, false, fn (x, acc) -> - acc or check_field(x) - end + status = + Enum.reduce(values, false, fn x, acc -> + acc or check_field(x) + end) {:reply, status, gamefield} end - # Check the game field. If one of the columns contains - # a winning condition (four coins of the same type in + # a winning condition (four coins of the same type in # a row) then return true. defp check_field(field) do - values = Enum.map(field, fn(column) -> - case Fieldchecker.count(column) do - {_, 4} -> - true - _ -> - false - end - end) - - Enum.reduce(values, false, fn (x, acc) -> x or acc end) + values = + Enum.map(field, fn column -> + case Fieldchecker.count(column) do + {_, 4} -> + true + + _ -> + false + end + end) + + Enum.reduce(values, false, fn x, acc -> x or acc end) end # Put a coin of `player` in `column` defp put_coin(column, _, level) when level > 7, do: column + defp put_coin(column, player, level) do if Enum.at(column, level) == nil do Listhelp.put_item(column, level, player) diff --git a/lib/connect_four/listhelp.ex b/lib/connect_four/listhelp.ex index 381934e..a23b175 100644 --- a/lib/connect_four/listhelp.ex +++ b/lib/connect_four/listhelp.ex @@ -3,14 +3,14 @@ defmodule Listhelp do result end - defp put_item([h|t], result, index, cindex, item) do + defp put_item([h | t], result, index, cindex, item) do if index == cindex do put_item(t, [item | result], index, cindex + 1, item) else put_item(t, [h | result], index, cindex + 1, item) end end - + def put_item(list, index, item) do Enum.reverse(put_item(list, [], index, 0, item)) end @@ -25,13 +25,13 @@ defmodule Fieldchecker do {last, 4} end - defp count([nil|t], last, result) do + defp count([nil | t], last, _result) do count(t, last, 0) end - defp count([h|t], last, result) do + defp count([h | t], last, result) do if h == last do - count(t, last, result + 1) + count(t, last, result + 1) else count(t, h, 1) end @@ -64,16 +64,17 @@ defmodule Fieldchecker do [h7, g7, f7, e7, d7, c7, b7, a7] ] end + def arrow_right(field) do [ - [ _, _, _, a3, a4, a5, a6, a7], - [ _, _, b2, b3, b4, b5, b6, b7], - [ _, c1, c2, c3, c4, c5, c6, c7], + [_, _, _, a3, a4, a5, a6, a7], + [_, _, b2, b3, b4, b5, b6, b7], + [_, c1, c2, c3, c4, c5, c6, c7], [d0, d1, d2, d3, d4, d5, d6, d7], [e0, e1, e2, e3, e4, e5, e6, e7], - [f0, f1, f2, f3, f4, f5, f6, _], - [g0, g1, g2, g3, g4, g5, _, _], - [h0, h1, h2, h3, h4, _, _, _] + [f0, f1, f2, f3, f4, f5, f6, _], + [g0, g1, g2, g3, g4, g5, _, _], + [h0, h1, h2, h3, h4, _, _, _] ] = field [ @@ -91,26 +92,26 @@ defmodule Fieldchecker do def arrow_left(field) do [ - [a0, a1, a2, a3, a4, _, _, _], - [b0, b1, b2, b3, b4, b5, _, _], - [c0, c1, c2, c3, c4, c5, c6, _], + [a0, a1, a2, a3, a4, _, _, _], + [b0, b1, b2, b3, b4, b5, _, _], + [c0, c1, c2, c3, c4, c5, c6, _], [d0, d1, d2, d3, d4, d5, d6, d7], [e0, e1, e2, e3, e4, e5, e6, e7], - [ _, f1, f2, f3, f4, f5, f6, f7], - [ _, _, g2, g3, g4, g5, g6, g7], - [ _, _, _, h3, h4, h5, h6, h7] + [_, f1, f2, f3, f4, f5, f6, f7], + [_, _, g2, g3, g4, g5, g6, g7], + [_, _, _, h3, h4, h5, h6, h7] ] = field [ - [a4, b5, c6, d7], - [a3, b4, c5, d6, e7], - [a2, b3, c4, d5, e6, f7], - [a1, b2, c3, d4, e5, f6, g7], + [a4, b5, c6, d7], + [a3, b4, c5, d6, e7], + [a2, b3, c4, d5, e6, f7], + [a1, b2, c3, d4, e5, f6, g7], [a0, b1, c2, d3, e4, f5, g6, h7], - [b0, c1, d2, e3, f4, g5, h6], - [c0, d1, e2, f3, g4, h5], - [d0, e1, f2, g3, h4], - [e0, f1, g2, h3] + [b0, c1, d2, e3, f4, g5, h6], + [c0, d1, e2, f3, g4, h5], + [d0, e1, f2, g3, h4], + [e0, f1, g2, h3] ] end end diff --git a/lib/echo_module/echo.ex b/lib/echo_module/echo.ex index 07f90d7..7f0ba4d 100644 --- a/lib/echo_module/echo.ex +++ b/lib/echo_module/echo.ex @@ -1,16 +1,17 @@ defmodule Echo do - use Servus.Module + use Servus.Module require Logger - register "echo" + register("echo") - def startup do - Logger.info "Echo module registered" - [] # Return aodule state here + def startup() do + Logger.info("Echo module registered") + # Return aodule state here + [] end handle "echo", args, state do - Logger.info "Echo module called" + Logger.info("Echo module called") args end end diff --git a/lib/servus.ex b/lib/servus.ex index ccdbb31..170357c 100644 --- a/lib/servus.ex +++ b/lib/servus.ex @@ -1,14 +1,14 @@ defmodule Servus do - @moduledoc""" + @moduledoc """ The `Servus` Game Server A simple, modular and universal game backend """ - + use Application require Logger # Entry point def start(_type, _args) do - Servus.Supervisor.start_link + Servus.Supervisor.start_link() end end diff --git a/lib/servus/backend_supervisor.ex b/lib/servus/backend_supervisor.ex index d0fadb0..00c6cae 100644 --- a/lib/servus/backend_supervisor.ex +++ b/lib/servus/backend_supervisor.ex @@ -10,9 +10,10 @@ defmodule Servus.Backend.Supervisor do def start_link(adapters, queue_pid) do import Supervisor.Spec - children = Enum.map(adapters, fn {type, port} -> - worker(adapter_for(type), [port, queue_pid]) - end) + children = + Enum.map(adapters, fn {type, port} -> + worker(adapter_for(type), [port, queue_pid]) + end) Supervisor.start_link(children, strategy: :one_for_one) end diff --git a/lib/servus/client_handler.ex b/lib/servus/client_handler.ex index 3a7bab2..e7d48da 100644 --- a/lib/servus/client_handler.ex +++ b/lib/servus/client_handler.ex @@ -8,27 +8,29 @@ defmodule Servus.ClientHandler do alias Servus.Serverutils alias Servus.PlayerQueue require Logger + require IO def run(state) do case Serverutils.recv(state.socket) do {:ok, message} -> - data = Poison.decode message, as: %Servus.Message{} + data = Poison.decode(message, as: %Servus.Message{}) + case data do {:ok, %{type: "join", value: name}} -> # The special `join` message # Double join? if Map.has_key?(state, :player) do - Logger.warn "A player already joined on this connection" + Logger.warn("A player already joined on this connection") run(state) else - Logger.info "#{name} has joined the queue" + Logger.info("#{name} has joined the queue") # Create a new player and add it to the queue player = %{ - name: name, - socket: state.socket, - id: Serverutils.get_unique_id + name: name, + socket: state.socket, + id: Serverutils.get_unique_id() } PlayerQueue.push(state.queue, player) @@ -36,48 +38,62 @@ defmodule Servus.ClientHandler do # Store the player in the process state run(Map.put(state, :player, player)) end + {:ok, %{type: type, target: target, value: value}} -> - if target == nil do - # Generic message from client (player) - - # Check if the game state machine has already been started - # by querying it's pid from the PidStore. This is only - # required if it's not already stored in the process state - if not Map.has_key?(state, :fsm) do - pid = PidStore.get(state.player.id) + state = + if target == nil do + # Generic message from client (player) + + # Check if the game state machine has already been started + # by querying it's pid from the PidStore. This is only + # required if it's not already stored in the process state + state = + if not Map.has_key?(state, :fsm) do + pid = PidStore.get(state.player.id) + + if pid != nil do + Map.put(state, :fsm, pid) + else + state + end + else + state + end + + # Try to get the pid of the game state machine and send + # the command + pid = state.fsm + if pid != nil do - state = Map.put(state, :fsm, pid) + arguments = {state.player.id, type, value} + GenStateMachine.cast(pid, arguments) end - end - - # Try to get the pid of the game state machine and send - # the command - pid = state.fsm - if pid != nil do - :gen_fsm.send_event(pid, {state.player.id, type, value}) - end - else + state + else + # Call external module + pid = ModuleStore.get(target) - # Call external module - pid = ModuleStore.get(target) + # Check if the module is registered and available + # and if yes... + if pid != nil and Process.alive?(pid) do + # ...invoke a synchronous call and send the result back + # to the calles (client socket) + result = GenServer.call(pid, {type, value}) + Serverutils.send(state.socket, target, result) + end - # Check if the module is registered and available - # and if yes... - if pid != nil and Process.alive?(pid) do - # ...invoke a synchronous call and send the result back - # to the calles (client socket) - result = GenServer.call(pid, {type, value}) - Serverutils.send(state.socket, target, result) + state end - end run(state) + _ -> - Logger.warn "Invalid message format: #{message}" + Logger.warn("Invalid message format: #{message}") run(state) end - {:error, _} -> + + {:error, error} -> # Client has aborted the connection # De-register it's ID from the pid store if Map.has_key?(state, :player) do @@ -85,18 +101,21 @@ defmodule Servus.ClientHandler do PlayerQueue.remove(state.queue, state.player) pid = PidStore.get(state.player.id) + if pid != nil do # Notify the game logic about the player disconnect - :gen_fsm.send_all_state_event(pid, {:abort, state.player}) + GenStateMachine.cast(pid, {:abort, state.player}) # Remove the player from the registry PidStore.remove(state.player.id) - Logger.info "Removed player from pid store" + Logger.info("Removed player from pid store") end end - Logger.warn "Unexpected clientside abort" + Logger.warn("Unexpected clientside abort") + + Logger.error("Error => #{error}") end end end diff --git a/lib/servus/game.ex b/lib/servus/game.ex index b678e06..66a0ea0 100644 --- a/lib/servus/game.ex +++ b/lib/servus/game.ex @@ -5,8 +5,10 @@ defmodule Servus.Game do """ defmacro __using__(_) do quote do + use GenStateMachine + def start(players) do - {:ok, pid} = :gen_fsm.start(__MODULE__, players, []) + {:ok, pid} = GenStateMachine.start(__MODULE__, players, []) pid end @@ -14,7 +16,7 @@ defmodule Servus.Game do Forward all abort events (player aborts connection) to the appropriate handler function (abort). """ - def handle_event({:abort, player}, _, state) do + def handle_event(:cast, {:abort, player}, _, state) do abort(player, state) end @@ -32,10 +34,11 @@ defmodule Servus.Game do """ def abort(player, state) do # Default: stop game state machine on connection abort + # :gem_statem.close(self) {:stop, :shutdown, state} end - defoverridable [abort: 2] - end + defoverridable abort: 2 + end end end diff --git a/lib/servus/module.ex b/lib/servus/module.ex index 48847fe..6ce6d51 100644 --- a/lib/servus/module.ex +++ b/lib/servus/module.ex @@ -11,10 +11,10 @@ defmodule Servus.Module do def start_link(args) do GenServer.start_link(__MODULE__, [], args) end - + def init(args) do - __register__ - {:ok, startup} + __register__() + {:ok, startup()} end def terminate(reason, state) do @@ -26,7 +26,7 @@ defmodule Servus.Module do # Callbacks # ############### - def startup do + def startup() do # Override in user code [] end @@ -35,18 +35,18 @@ defmodule Servus.Module do # Override in user code end - def __register__ do + def __register__() do # Override or die raise "Did you forget to register module #{__MODULE__}?" end - defoverridable [__register__: 0, startup: 0, shutdown: 1] + defoverridable __register__: 0, startup: 0, shutdown: 1 end end defmacro register(name) do quote do - def __register__ do + def __register__() do Servus.ModuleStore.register(unquote(name), self()) end end @@ -59,17 +59,18 @@ defmodule Servus.Module do """ defmacro handle(action, args, state, handler) do handler_impl = Keyword.get(handler, :do, nil) - + # Normalize state # (let the user ignore the state with _ if he wants) - state = case state do - {:_, a, b} -> {:state, a, b} - _ -> state - end + state = + case state do + {:_, a, b} -> {:state, a, b} + _ -> state + end quote do def handle_call({unquote(action), unquote(args)}, _from, unquote(state)) do - result = (unquote(handler_impl)) + result = unquote(handler_impl) {:reply, result, unquote(state)} end end @@ -84,17 +85,18 @@ defmodule Servus.Module do """ defmacro handlep(action, args, state, handler) do handler_impl = Keyword.get(handler, :do, nil) - + # Normalize state # (let the user ignore the state with _ if he wants) - state = case state do - {:_, a, b} -> {:state, a, b} - _ -> state - end + state = + case state do + {:_, a, b} -> {:state, a, b} + _ -> state + end quote do def handle_call({:priv, unquote(action), unquote(args)}, _from, unquote(state)) do - result = (unquote(handler_impl)) + result = unquote(handler_impl) {:reply, result, unquote(state)} end end diff --git a/lib/servus/pidstore.ex b/lib/servus/pidstore.ex index 5647b28..24eac5b 100644 --- a/lib/servus/pidstore.ex +++ b/lib/servus/pidstore.ex @@ -3,19 +3,19 @@ defmodule Servus.PidStore do Stores the currently running game logic pids per player. """ def start_link do - Agent.start_link fn -> Map.new end, name: __MODULE__ + Agent.start_link(fn -> Map.new() end, name: __MODULE__) end def put(player, pid) do - Agent.update __MODULE__, fn dict -> Map.put(dict, player, pid) end + Agent.update(__MODULE__, fn dict -> Map.put(dict, player, pid) end) end def get(player) do - Agent.get __MODULE__, fn dict -> Map.get(dict, player) end + Agent.get(__MODULE__, fn dict -> Map.get(dict, player) end) end def remove(player) do - Agent.update __MODULE__, fn dict -> Map.delete(dict, player) end + Agent.update(__MODULE__, fn dict -> Map.delete(dict, player) end) end end @@ -24,14 +24,14 @@ defmodule Servus.ModuleStore do Stores the pids of running modules. """ def start_link do - Agent.start_link fn -> Map.new end, name: __MODULE__ + Agent.start_link(fn -> Map.new() end, name: __MODULE__) end def register(module, pid) do - Agent.update __MODULE__, fn dict -> Map.put(dict, module, pid) end + Agent.update(__MODULE__, fn dict -> Map.put(dict, module, pid) end) end def get(module) do - Agent.get __MODULE__, fn dict -> Map.get(dict, module) end + Agent.get(__MODULE__, fn dict -> Map.get(dict, module) end) end end diff --git a/lib/servus/queue.ex b/lib/servus/queue.ex index 47f292a..e7cecfa 100644 --- a/lib/servus/queue.ex +++ b/lib/servus/queue.ex @@ -3,16 +3,21 @@ defmodule Servus.PlayerQueue do require Logger alias Servus.PidStore + def init(init_arg) do + {:ok, init_arg} + end + @doc """ Start a new player queue for a backend of type `logic` allowing `players_per_game` players. """ def start_link(players_per_game, logic) do - {:ok, pid} = GenServer.start_link(__MODULE__, %{ - queue: [], - size: players_per_game, - logic: logic - }) + {:ok, pid} = + GenServer.start_link(__MODULE__, %{ + queue: [], + size: players_per_game, + logic: logic + }) pid end @@ -22,28 +27,30 @@ defmodule Servus.PlayerQueue do cond do state.size == length(state.queue) -> - pid = state.logic.start state.queue + pid = state.logic.start(state.queue) Enum.each(state.queue, fn player -> PidStore.put(player.id, pid) end) - Logger.info "Started a new #{state.logic} with #{state.size} players" + Logger.info("Started a new #{state.logic} with #{state.size} players") {:reply, :ok, %{state | :queue => []}} + true -> {:reply, :ok, state} end end def handle_call({:remove, player}, _, state) do - queue = Enum.reduce(state.queue, [], fn(candidate, acc) -> - if candidate.id == player.id do - acc - else - [candidate | acc] - end - end) + queue = + Enum.reduce(state.queue, [], fn candidate, acc -> + if candidate.id == player.id do + acc + else + [candidate | acc] + end + end) state = %{state | :queue => queue} diff --git a/lib/servus/serverutils.ex b/lib/servus/serverutils.ex index 909bf53..458f9eb 100644 --- a/lib/servus/serverutils.ex +++ b/lib/servus/serverutils.ex @@ -1,4 +1,6 @@ defmodule Servus.Serverutils.TCP do + require IO + @moduledoc """ Implementation of send/3 and recv/2 for TCP sockets """ @@ -9,7 +11,7 @@ defmodule Servus.Serverutils.TCP do """ def get_address(socket) do {:ok, {address, _}} = :inet.peername(socket) - address |> Tuple.to_list |> Enum.join(".") + address |> Tuple.to_list() |> Enum.join(".") end @doc """ @@ -17,12 +19,14 @@ defmodule Servus.Serverutils.TCP do the form `{"type": type,"value": value}` """ def send(socket, type, value) do - {:ok, json} = Poison.encode %{ - type: type, - value: value - } - - :gen_tcp.send socket, json + {:ok, json} = + Poison.encode(%{ + type: type, + value: value, + target: nil + }) + + :gen_tcp.send(socket, json) end @doc """ @@ -34,14 +38,16 @@ defmodule Servus.Serverutils.TCP do """ def recv(socket, opts) do result = :gen_tcp.recv(socket, 0, opts[:timeout]) + case result do {:ok, data} -> if opts[:parse] do - {:ok, msg} = Poison.decode data, as: %Servus.Message{} + {:ok, msg} = Poison.decode(data, as: %Servus.Message{}) msg else result end + _ -> result end @@ -66,12 +72,13 @@ defmodule Servus.Serverutils.Web do the form `{"type": type,"value": value}` """ def send(socket, type, value) do - {:ok, json} = Poison.encode %{ - type: type, - value: value - } + {:ok, json} = + Poison.encode(%{ + type: type, + value: value + }) - Socket.Web.send socket, {:text, json} + Socket.Web.send(socket, {:text, json}) end @doc """ @@ -80,18 +87,20 @@ defmodule Servus.Serverutils.Web do is available. """ def recv(socket, opts) do - result = Socket.Web.recv socket + result = Socket.Web.recv(socket) case result do {:ok, {:text, data}} -> if opts[:parse] do - {:ok, msg} = Poison.decode data, as: %Servus.Message{} + {:ok, msg} = Poison.decode(data, as: %Servus.Message{}) msg else {:ok, data} end + {:error, _reason} -> result + _ -> {:error, :unknown} end @@ -113,10 +122,10 @@ defmodule Servus.Serverutils do # IDs # ############################################### def get_unique_id do - :crypto.strong_rand_bytes(4) |> :crypto.bytes_to_integer + :crypto.strong_rand_bytes(4) |> :crypto.bytes_to_integer() end - # ############################################### + # ############################################### # Addresses # ############################################### @@ -127,8 +136,8 @@ defmodule Servus.Serverutils do def get_address(socket) do TCP.get_address(socket) end - # ############################################### + # ############################################### # Send / Receive # ############################################### @@ -145,19 +154,20 @@ defmodule Servus.Serverutils do :web -> Web.recv(socket.raw, opts) end end - # ############################################### + # ############################################### # Module call # ############################################### def call(target, type, value) do pid = ModuleStore.get(target) - + if pid != nil and Process.alive?(pid) do GenServer.call(pid, {:priv, type, value}) else :error end end + # ############################################### end diff --git a/lib/servus/socket_server.ex b/lib/servus/socket_server.ex index 43201da..3710fac 100644 --- a/lib/servus/socket_server.ex +++ b/lib/servus/socket_server.ex @@ -3,27 +3,28 @@ defmodule SocketServer do Manages the connections on a socket for a single game backend. Each game backend has it's own SocketServer """ - + require Logger alias Servus.Serverutils alias Servus.ClientHandler def start_link(port, queue_pid) do - {:ok, socket} = :gen_tcp.listen(port, [ - :binary, - packet: 4, - active: false, - reuseaddr: true - ]) - - Logger.info "Accepting tcp connections on port #{port}" + {:ok, socket} = + :gen_tcp.listen(port, [ + :binary, + packet: 4, + active: false, + reuseaddr: true + ]) + + Logger.info("Accepting tcp connections on port #{port}") - {:ok, spawn_link fn -> accept(socket, queue_pid) end} + {:ok, spawn_link(fn -> accept(socket, queue_pid) end)} end def accept(socket, queue_pid) do {:ok, client} = :gen_tcp.accept(socket) - Logger.info "Incomming TCP connection from #{Serverutils.get_address(client)}" + Logger.info("Incomming TCP connection from #{Serverutils.get_address(client)}") # Start a new client listener thread for the incoming connection Task.Supervisor.start_child(:client_handler, ClientHandler, :run, [ diff --git a/lib/servus/supervisor.ex b/lib/servus/supervisor.ex index 865ab45..6e307a5 100644 --- a/lib/servus/supervisor.ex +++ b/lib/servus/supervisor.ex @@ -5,7 +5,7 @@ defmodule Servus.Supervisor do """ @backends Application.get_env(:servus, :backends) - @modules Application.get_env(:servus, :modules) + @modules Application.get_env(:servus, :modules) def start_link do import Supervisor.Spec @@ -21,23 +21,26 @@ defmodule Servus.Supervisor do # Create a list of all the backends that have to be # started - backends = Enum.map(@backends, fn backend -> - backend = Application.get_env(:servus, backend) - players = backend[:players_per_game] - logic = backend[:implementation] - adapters = backend[:adapters] + backends = + Enum.map(@backends, fn backend -> + backend = Application.get_env(:servus, backend) + players = backend[:players_per_game] + logic = backend[:implementation] + adapters = backend[:adapters] - # Already create the player queue for the backend - queue_pid = PlayerQueue.start_link players, logic + # Already create the player queue for the backend + queue_pid = PlayerQueue.start_link(players, logic) - supervisor(Servus.Backend.Supervisor, [adapters, queue_pid], id: backend) - end) + supervisor(Servus.Backend.Supervisor, [adapters, queue_pid], id: backend) + end) # Create a list of all the modules that have to be # started - modules = Enum.map(@modules, fn module -> - worker(module, [[name: module]]) - end) + modules = + Enum.map(@modules, fn module -> + worker(module, [[name: module]]) + end) + # Start the common services and all backends Supervisor.start_link(children ++ backends ++ modules, strategy: :one_for_one) end diff --git a/lib/servus/web_socket_server.ex b/lib/servus/web_socket_server.ex index f18e78a..d122082 100644 --- a/lib/servus/web_socket_server.ex +++ b/lib/servus/web_socket_server.ex @@ -2,24 +2,24 @@ defmodule WebSocketServer do @moduledoc """ Same as socket_server but fur web- instead of tcp sockets. """ - + require Logger alias Servus.ClientHandler alias Servus.Serverutils def start_link(port, queue_pid) do - socket = Socket.Web.listen! port + socket = Socket.Web.listen!(port) - Logger.info "Accepting websocket connections on port #{port}" + Logger.info("Accepting websocket connections on port #{port}") - {:ok, spawn_link fn -> accept(socket, queue_pid) end} + {:ok, spawn_link(fn -> accept(socket, queue_pid) end)} end def accept(socket, queue_pid) do - client = Socket.Web.accept! socket - Socket.Web.accept! client + client = Socket.Web.accept!(socket) + Socket.Web.accept!(client) - Logger.info "Incomming WebSocket connection from #{Serverutils.get_address(client)}" + Logger.info("Incomming WebSocket connection from #{Serverutils.get_address(client)}") # Start a new client listener thread for the incoming connection Task.Supervisor.start_child(:client_handler, ClientHandler, :run, [ diff --git a/mix.exs b/mix.exs index 19b655d..2e68b82 100644 --- a/mix.exs +++ b/mix.exs @@ -2,10 +2,7 @@ defmodule Servus.Mixfile do use Mix.Project def project do - [app: :servus, - version: "0.0.1", - elixir: "~> 1.1", - deps: deps()] + [app: :servus, version: "0.0.1", elixir: "~> 1.10.2", deps: deps()] end # Configuration for the OTP application @@ -29,8 +26,9 @@ defmodule Servus.Mixfile do # Type `mix help deps` for more examples and options defp deps do [ - {:poison, "~> 2.1.0"}, - {:socket, "~> 0.3.4"} + {:poison, "~> 4.0"}, + {:socket, "~> 0.3.13"}, + {:gen_state_machine, "~> 2.1"} ] end end diff --git a/test/servus_test.exs b/test/servus_test.exs index 53e18c0..0df6f6f 100644 --- a/test/servus_test.exs +++ b/test/servus_test.exs @@ -1,5 +1,7 @@ defmodule ServusTest do use ExUnit.Case + require Logger + require IO alias Servus.Serverutils alias Servus.Message @@ -12,11 +14,18 @@ defmodule ServusTest do ] {:ok, socket_alice} = :gen_tcp.connect('localhost', 3334, connect_opts) + + Logger.info("socket_alice connected") + {:ok, socket_bob} = :gen_tcp.connect('localhost', 3334, connect_opts) - {:ok, [ - alice: %{raw: socket_alice, type: :tcp}, - bob: %{raw: socket_bob, type: :tcp} - ]} + + Logger.info("socket_bob connected") + + {:ok, + [ + alice: %{raw: socket_alice, type: :tcp}, + bob: %{raw: socket_bob, type: :tcp} + ]} end test "integration test (TCP)", context do @@ -31,127 +40,127 @@ defmodule ServusTest do assert :ok == Serverutils.send(context.bob, "join", "bob") assert( - %Message{type: "start", value: "bob", target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "start", value: "bob", target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) assert( - %Message{type: "start", value: "alice", target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "start", value: "alice", target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) # Bob turn assert :ok == Serverutils.send(context.bob, "put", 2) assert( - %Message{type: "set", value: 2, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "set", value: 2, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) # Alice turn assert :ok == Serverutils.send(context.alice, "put", 4) assert( - %Message{type: "set", value: 4, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "set", value: 4, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) # Bob turn assert :ok == Serverutils.send(context.bob, "put", 3) assert( - %Message{type: "set", value: 3, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "set", value: 3, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) - + # Alice turn assert :ok == Serverutils.send(context.alice, "put", 4) assert( - %Message{type: "set", value: 4, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "set", value: 4, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) # Bob turn assert :ok == Serverutils.send(context.bob, "put", 3) assert( - %Message{type: "set", value: 3, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "set", value: 3, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) # Alice turn assert :ok == Serverutils.send(context.alice, "put", 4) assert( - %Message{type: "set", value: 4, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "set", value: 4, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) # Bob turn assert :ok == Serverutils.send(context.bob, "put", 3) assert( - %Message{type: "set", value: 3, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "set", value: 3, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) # Alice turn assert :ok == Serverutils.send(context.alice, "put", 4) assert( - %Message{type: "win", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true, timeout: 100) + %Message{type: "win", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true, timeout: 100) ) assert( - %Message{type: "set", value: 4, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "set", value: 4, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) assert( - %Message{type: "loose", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true, timeout: 100) + %Message{type: "loose", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true, timeout: 100) ) end end diff --git a/test/servus_ws_test.exs b/test/servus_ws_test.exs index f73b645..bd2dd4e 100644 --- a/test/servus_ws_test.exs +++ b/test/servus_ws_test.exs @@ -5,13 +5,14 @@ defmodule ServusWSTest do alias Socket.Web setup_all do - socket_alice = Web.connect! "localhost", 3335 - socket_bob = Web.connect! "localhost", 3335 - - {:ok, [ - alice: %{raw: socket_alice, type: :web}, - bob: %{raw: socket_bob, type: :web} - ]} + socket_alice = Web.connect!("localhost", 3335) + socket_bob = Web.connect!("localhost", 3335) + + {:ok, + [ + alice: %{raw: socket_alice, type: :web}, + bob: %{raw: socket_bob, type: :web} + ]} end test "integration test (WebSocket)", context do @@ -26,127 +27,127 @@ defmodule ServusWSTest do assert :ok == Serverutils.send(context.bob, "join", "bob") assert( - %Message{type: "start", value: "bob", target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "start", value: "bob", target: nil} == + Serverutils.recv(context.alice, parse: true) ) assert( - %Message{type: "start", value: "alice", target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "start", value: "alice", target: nil} == + Serverutils.recv(context.bob, parse: true) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true) ) # Bob turn assert :ok == Serverutils.send(context.bob, "put", 2) assert( - %Message{type: "set", value: 2, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "set", value: 2, target: nil} == + Serverutils.recv(context.alice, parse: true) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true) ) # Alice turn assert :ok == Serverutils.send(context.alice, "put", 4) assert( - %Message{type: "set", value: 4, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "set", value: 4, target: nil} == + Serverutils.recv(context.bob, parse: true) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true) ) # Bob turn assert :ok == Serverutils.send(context.bob, "put", 3) assert( - %Message{type: "set", value: 3, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "set", value: 3, target: nil} == + Serverutils.recv(context.alice, parse: true) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true) ) - + # Alice turn assert :ok == Serverutils.send(context.alice, "put", 4) assert( - %Message{type: "set", value: 4, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "set", value: 4, target: nil} == + Serverutils.recv(context.bob, parse: true) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true) ) # Bob turn assert :ok == Serverutils.send(context.bob, "put", 3) assert( - %Message{type: "set", value: 3, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "set", value: 3, target: nil} == + Serverutils.recv(context.alice, parse: true) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true) ) # Alice turn assert :ok == Serverutils.send(context.alice, "put", 4) assert( - %Message{type: "set", value: 4, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "set", value: 4, target: nil} == + Serverutils.recv(context.bob, parse: true) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true) ) # Bob turn assert :ok == Serverutils.send(context.bob, "put", 3) assert( - %Message{type: "set", value: 3, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "set", value: 3, target: nil} == + Serverutils.recv(context.alice, parse: true) ) assert( - %Message{type: "turn", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "turn", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true) ) # Alice turn assert :ok == Serverutils.send(context.alice, "put", 4) assert( - %Message{type: "win", value: nil, target: nil} == - Serverutils.recv(context.alice, parse: true) + %Message{type: "win", value: nil, target: nil} == + Serverutils.recv(context.alice, parse: true) ) assert( - %Message{type: "set", value: 4, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "set", value: 4, target: nil} == + Serverutils.recv(context.bob, parse: true) ) assert( - %Message{type: "loose", value: nil, target: nil} == - Serverutils.recv(context.bob, parse: true) + %Message{type: "loose", value: nil, target: nil} == + Serverutils.recv(context.bob, parse: true) ) end end