Skip to content

Commit

Permalink
Merge branch 'feature/monitoring-refactor'
Browse files Browse the repository at this point in the history
  • Loading branch information
bigardone committed May 15, 2016
2 parents 1722d95 + ce19dc4 commit 417f8e9
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 38 deletions.
66 changes: 46 additions & 20 deletions lib/battleship/game.ex
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ defmodule Battleship.Game do
require Logger
alias Battleship.{Game}
alias Battleship.Game.Board
alias Battleship.Game.Supervisor, as: GameSupervisor

defstruct [
id: nil,
Expand All @@ -25,12 +26,30 @@ defmodule Battleship.Game do

def join(id, player_id, pid), do: try_call(id, {:join, player_id, pid})

@doc """
Returns the game's state
"""
def get_data(id), do: try_call(id, :get_data)

@doc """
Returns the game's state for a given player. This means it will
hide ships positions from the opponent's board.
"""
def get_data(id, player_id), do: try_call(id, {:get_data, player_id})

@doc """
Adds new chat message to the game's state
"""
def add_message(id, player_id, text), do: try_call(id, {:add_message, player_id, text})

@doc """
Fires a shot into the opponent's board for the given coordinates
"""
def player_shot(id, player_id, x: x, y: y), do: try_call(id, {:player_shot, player_id, x: x, y: y})

@doc """
Called when a player leaves the game
"""
def player_left(id, player_id), do: try_call(id, {:player_left, player_id})

# SERVER
Expand All @@ -53,7 +72,8 @@ defmodule Battleship.Game do
Process.monitor(pid)
Process.flag(:trap_exit, true)

create_board(player_id)
{:ok, board_pid} = create_board(player_id)
Process.monitor(board_pid)

game = game
|> add_player(player_id)
Expand Down Expand Up @@ -109,23 +129,37 @@ defmodule Battleship.Game do
@doc """
Handles exit messages from linked game channels processes, destroying boards and
sopping the game process.
- {:DOWN, _ref, :process, _pid, _reason}
- {:EXIT, _pid, {:shutdown, :closed}}
- {:DOWN, _ref, :process, _pid, _reason}
- {:EXIT, _pid, {:shutdown, :closed}}
"""
def handle_info(message, game) do
Logger.debug "Handling message #{inspect message} in Game server"
def handle_info({:DOWN, _ref, :process, _pid, reason}, game) do
Logger.debug "Handling :DOWM in Game server with reason #{reason}"

stop(game)
Battleship.Game.Event.game_stopped(game.id)

{:stop, :normal, game}
end
# def handle_info({:EXIT, _pid, {:shutdown, :closed}}, game) do
# Logger.debug "Handling :EXIT message in Game server"
#
# stop(game)
# end

@doc """
Creates a new Board for a given Player
"""
def terminate(_reason, game) do
Logger.debug "Terminating Game process #{game.id}"

for player <- [game.attacker, game.defender], do: destroy_board(player)

Battleship.Game.Event.game_over

:ok
end

# Creates a new Board for a given Player
defp create_board(player_id), do: Board.create(player_id)

@doc """
Generates global reference
"""
# Generates global reference
defp ref(id), do: {:global, {:game, id}}

defp add_player(%__MODULE__{attacker: nil} = game, player_id), do: %{game | attacker: player_id}
Expand All @@ -136,14 +170,6 @@ defmodule Battleship.Game do
defp destroy_board(nil), do: :ok
defp destroy_board(player_id), do: Board.destroy(player_id)

defp stop(game) do
for player <- [game.attacker, game.defender], do: destroy_board(player)

Battleship.Game.Event.game_over

{:stop, :normal, game}
end

defp udpate_turns(game, player_id, x: x, y: y, result: result) do
%{game | turns: [%{player_id: player_id, x: x, y: y, result: result} | game.turns]}
end
Expand Down
12 changes: 10 additions & 2 deletions lib/battleship/game/board.ex
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,14 @@ defmodule Battleship.Game.Board do
Destroys an existing Player board
"""
def destroy(player_id) do
Logger.debug "Stopping board for player #{player_id}"
case GenServer.whereis(ref(player_id)) do
nil ->
Logger.debug "Attempt to destroy unesxisting Board for player #{player_id}"
pid ->
Logger.debug "Stopping board for player #{player_id}"

Agent.stop(ref(player_id), :normal, :infinity)
Agent.stop(pid, :normal, :infinity)
end
end

@doc """
Expand Down Expand Up @@ -121,6 +126,7 @@ defmodule Battleship.Game.Board do
{:ok, result}
end

# Generates global reference name for the board process
defp ref(player_id), do: {:global, {:board, player_id}}

# Checks if a similar ship has been already placed
Expand Down Expand Up @@ -167,13 +173,15 @@ defmodule Battleship.Game.Board do
|> Enum.reduce(%{}, fn item, acc -> Map.put(acc, item, @grid_value_water) end)
end

# Builds cells for a given row
defp build_rows(y, rows) do
row = 0..@size - 1
|> Enum.reduce(rows, fn x, col -> [Enum.join([y, x], "") | col] end)

[row | rows]
end

# Returns shot result depending on the cell's current value
defp shot_result(current_value) when current_value == @grid_value_ship, do: @grid_value_ship_hit
defp shot_result(current_value) when current_value == @grid_value_ship_hit, do: @grid_value_ship_hit
defp shot_result(_current_value), do: @grid_value_water_hit
Expand Down
1 change: 1 addition & 0 deletions lib/battleship/game/event.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ defmodule Battleship.Game.Event do
def game_created, do: GenEvent.notify(__MODULE__, :game_created)
def player_joined, do: GenEvent.notify(__MODULE__, :player_joined)
def game_over, do: GenEvent.notify(__MODULE__, :game_over)
def game_stopped(game_id), do: GenEvent.notify(__MODULE__, {:game_stopped, game_id})
def player_shot, do: GenEvent.notify(__MODULE__, :player_shot)
end
8 changes: 6 additions & 2 deletions lib/battleship/game/event_handler.ex
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
defmodule Battleship.Game.EventHandler do
use GenEvent
alias Battleship.LobbyChannel
alias Battleship.{LobbyChannel, GameChannel}

def handle_event({:game_stopped, game_id}, state) do
GameChannel.broadcast_stop(game_id)

{:ok, state}
end
def handle_event(:game_created, state), do: broadcast_update(state)
def handle_event(:player_joined, state), do: broadcast_update(state)
def handle_event(:game_over, state), do: broadcast_update(state)
def handle_event(:player_shot, state), do: broadcast_update(state)

def handle_event(_, state), do: {:ok, state}

defp broadcast_update(state) do
Expand Down
10 changes: 10 additions & 0 deletions lib/battleship/game/supervisor.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ defmodule Battleship.Game.Supervisor do
@moduledoc """
Game processes supervisor
"""
require Logger

use Supervisor
alias Battleship.{Game}

Expand Down Expand Up @@ -29,6 +31,14 @@ defmodule Battleship.Game.Supervisor do
|> Enum.map(&game_data/1)
end

def stop_game(game_id) do
Logger.debug "Stopping game #{game_id} in supervisor"

pid = GenServer.whereis({:global, {:game, game_id}})

Supervisor.terminate_child(__MODULE__, pid)
end

defp game_data({_id, pid, _type, _modules}) do
pid
|> GenServer.call(:get_data)
Expand Down
21 changes: 17 additions & 4 deletions test/lib/game_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,30 @@ defmodule Battleship.GameTest do
assert %Board{player_id: ^defender_id} = Agent.get({:global, {:board, defender_id}}, &(&1))
end

test "closes game when player goes down", %{attacker_id: attacker_id} do
test "terminates game when player goes down", %{attacker_id: attacker_id} do
{:ok, pid} = GameSupervisor.create_game("new-game")

ref = Process.monitor(pid)

spawn fn ->
Game.join("new-game", attacker_id, self)
end

assert catch_exit(Agent.get({:global, {:board, 1}}, &(&1)))
assert catch_exit(Agent.get({:global, {:board, attacker_id}}, &(&1)))
assert_receive {:DOWN, ^ref, :process, ^pid, :normal}
end

test "terminates game when a board goes down", %{attacker_id: attacker_id} do
{:ok, pid} = GameSupervisor.create_game("new-game")
Process.monitor(pid)
Game.join("new-game", attacker_id, self)

{:global, {:board, attacker_id}}
|> GenServer.whereis
|> Process.exit(:kill)

assert_receive {:DOWN, _ref, :process, pid, :normal}
end

test "updates turns after a shot", %{id: id, attacker_id: attacker_id, defender_id: defender_id} do
GameSupervisor.create_game(id)

Expand All @@ -54,7 +65,9 @@ defmodule Battleship.GameTest do
%Ship{x: 1, y: 0, size: 4, orientation: :vertical},
%Ship{x: 2, y: 0, size: 3, orientation: :vertical},
%Ship{x: 3, y: 0, size: 2, orientation: :vertical},
%Ship{x: 4, y: 0, size: 1, orientation: :vertical}
%Ship{x: 4, y: 0, size: 2, orientation: :vertical},
%Ship{x: 5, y: 0, size: 1, orientation: :vertical},
%Ship{x: 6, y: 0, size: 1, orientation: :vertical}
]

Game.join(id, attacker_id, self)
Expand Down
28 changes: 20 additions & 8 deletions web/channels/game_channel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ defmodule Battleship.GameChannel do
use Phoenix.Channel
alias Battleship.{Game, Ship}
alias Battleship.Game.Board
alias Battleship.Game.Supervisor, as: GameSupervisor
require Logger

def join("game:" <> game_id, _message, socket) do
Logger.debug "Joining Game channel", game_id: game_id
Logger.debug "Joining Game channel #{game_id}", game_id: game_id

player_id = socket.assigns.player_id

case Game.join(game_id, player_id, socket.channel_pid) do
{:ok, pid} ->
Process.link(pid)
Process.monitor(pid)

{:ok, assign(socket, :game_id, game_id)}
{:error, reason} ->
Expand All @@ -23,7 +24,8 @@ defmodule Battleship.GameChannel do
end

def handle_in("game:joined", _message, socket) do
Logger.debug "Broadcasting player joined"
Logger.debug "Broadcasting player joined #{socket.assigns.game_id}"

player_id = socket.assigns.player_id
board = Board.get_opponents_data(player_id)

Expand All @@ -41,7 +43,7 @@ defmodule Battleship.GameChannel do
end

def handle_in("game:send_message", %{"text" => text}, socket) do
Logger.debug "Handling send_message on GameChannel"
Logger.debug "Handling send_message on GameChannel #{socket.assigns.game_id}"

player_id = socket.assigns.player_id
message = %{player_id: player_id, text: text}
Expand All @@ -52,7 +54,7 @@ defmodule Battleship.GameChannel do
end

def handle_in("game:place_ship", %{"ship" => ship}, socket) do
Logger.debug "Handling place_ship on GameChannel"
Logger.debug "Handling place_ship on GameChannel #{socket.assigns.game_id}"

player_id = socket.assigns.player_id
game_id = socket.assigns.game_id
Expand All @@ -78,7 +80,7 @@ defmodule Battleship.GameChannel do
end

def handle_in("game:shoot", %{"y" => y, "x" => x}, socket) do
Logger.debug "Handling shoot on GameChannel"
Logger.debug "Handling shoot on GameChannel #{socket.assigns.game_id}"

player_id = socket.assigns.player_id
game_id = socket.assigns.game_id
Expand All @@ -100,20 +102,30 @@ defmodule Battleship.GameChannel do
end

def terminate(reason, socket) do
Logger.debug"Terminating GameChannel #{inspect reason}"
Logger.debug"Terminating GameChannel #{socket.assigns.game_id} #{inspect reason}"

player_id = socket.assigns.player_id
game_id = socket.assigns.game_id

case Game.player_left(game_id, player_id) do
{:ok, game} ->

GameSupervisor.stop_game(game_id)

broadcast(socket, "game:over", %{game: %{game | channels: nil}})
broadcast(socket, "game:player_left", %{player_id: player_id})

:ok

_ ->
:ok
end
end

def handle_info(_, socket), do: {:noreply, socket}

def broadcast_stop(game_id) do
Logger.debug "Broadcasting game:stopped from GameChannel #{game_id}"

Battleship.Endpoint.broadcast("game:#{game_id}", "game:stopped", %{})
end
end
3 changes: 3 additions & 0 deletions web/channels/lobby_channel.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ defmodule Battleship.LobbyChannel do
@moduledoc """
Lobby channel
"""
require Logger

use Battleship.Web, :channel
alias Battleship.Game.Supervisor, as: GameSupervisor
Expand All @@ -22,6 +23,8 @@ defmodule Battleship.LobbyChannel do
end

def broadcast_current_games do
Logger.debug "Broadcasting current games from LobbyChannel"

Battleship.Endpoint.broadcast("lobby", "update_games", %{games: GameSupervisor.current_games})
end
end
5 changes: 5 additions & 0 deletions web/static/js/actions/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@ export function joinGame(socket, playerId, gameId) {
playerId: payload.player_id,
});
});

channel.on(`game:stopped`, (payload) => {
dispatch(resetGame());
dispatch(push('/game_error'));
});
};
}

Expand Down
3 changes: 3 additions & 0 deletions web/static/js/reducers/game.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,9 @@ export default function reducer(state = initialState, action = {}) {
return { ...state, error: action.error };

case Constants.GAME_RESET:
let { gameChannel } = state;
if (gameChannel != null) gameChannel.leave();

return { ...initialState };

default:
Expand Down
2 changes: 2 additions & 0 deletions web/static/js/routes/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import MainLayout from '../layouts/main';
import HomeIndexView from '../views/home';
import GameShowView from '../views/game/show';
import NotFoundView from '../views/errors/not_found';
import GameErrorView from '../views/errors/game_error';

export default function configRoutes(store) {
return (
<Route component={MainLayout}>
<Route path="/" component={HomeIndexView}/>
<Route path="/game/:id" component={GameShowView}/>
<Route path="/not_found" component={NotFoundView} />
<Route path="/game_error" component={GameErrorView} />
<Route path="*" component={NotFoundView} />
</Route>
);
Expand Down
Loading

0 comments on commit 417f8e9

Please sign in to comment.