-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Track remote players in a local cache
- Loading branch information
Showing
7 changed files
with
231 additions
and
23 deletions.
There are no files selected for viewing
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"lib/*.ex": { "alternate": "test/{}_test.exs" }, | ||
"test/*_test.exs": { "alternate": "lib/{}.ex" } | ||
} |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
defmodule Gossip.Players do | ||
@moduledoc """ | ||
Track remote players as they sign in and out on Gossip | ||
""" | ||
|
||
use GenServer | ||
|
||
alias Gossip.Players.Implementation | ||
|
||
@type who_list() :: %{ | ||
Gossip.game_name() => [Gossip.player_name], | ||
} | ||
|
||
@refresh_minutes 1 | ||
|
||
@doc """ | ||
See who is signed into remote games | ||
""" | ||
@spec who() :: who_list() | ||
def who() do | ||
GenServer.call(__MODULE__, {:who}) | ||
end | ||
|
||
@doc """ | ||
A player has signed into a remote game | ||
""" | ||
def sign_in(game_name, player_name) do | ||
GenServer.cast(__MODULE__, {:sign_in, game_name, player_name}) | ||
end | ||
|
||
@doc """ | ||
A player has signed out of a remote game | ||
""" | ||
def sign_out(game_name, player_name) do | ||
GenServer.cast(__MODULE__, {:sign_out, game_name, player_name}) | ||
end | ||
|
||
@doc """ | ||
Update the local player list after a `players/status` event comes in | ||
""" | ||
@spec player_list(Gossip.game_name, [Gossip.player_name]) :: :ok | ||
def player_list(game_name, players) do | ||
GenServer.cast(__MODULE__, {:player_list, game_name, players}) | ||
end | ||
|
||
@doc """ | ||
For tests only - resets the player list state | ||
""" | ||
@spec reset() :: :ok | ||
def reset() do | ||
GenServer.call(__MODULE__, {:reset}) | ||
end | ||
|
||
@doc false | ||
def start_link(_) do | ||
GenServer.start_link(__MODULE__, [], name: __MODULE__) | ||
end | ||
|
||
@doc false | ||
def init(_) do | ||
schedule_refresh_list() | ||
{:ok, %{games: %{}}} | ||
end | ||
|
||
def handle_call({:reset}, _from, state) do | ||
{:reply, :ok, %{state | games: %{}}} | ||
end | ||
|
||
def handle_call({:who}, _from, state) do | ||
{:reply, state.games, state} | ||
end | ||
|
||
def handle_cast({:player_list, game_name, players}, state) do | ||
{:ok, state} = Implementation.player_list(state, game_name, players) | ||
{:noreply, state} | ||
end | ||
|
||
def handle_cast({:sign_in, game_name, player_name}, state) do | ||
{:ok, state} = Implementation.sign_in(state, game_name, player_name) | ||
{:noreply, state} | ||
end | ||
|
||
def handle_cast({:sign_out, game_name, player_name}, state) do | ||
{:ok, state} = Implementation.sign_out(state, game_name, player_name) | ||
{:noreply, state} | ||
end | ||
|
||
def handle_info({:refresh_list}, state) do | ||
Gossip.request_players_online() | ||
schedule_refresh_list() | ||
{:noreply, state} | ||
end | ||
|
||
defp schedule_refresh_list() do | ||
Process.send_after(self(), {:refresh_list}, :timer.minutes(@refresh_minutes)) | ||
end | ||
|
||
defmodule Implementation do | ||
@moduledoc false | ||
|
||
@doc false | ||
def player_list(state, game_name, players) do | ||
games = Map.put(state.games, game_name, players) | ||
{:ok, %{state | games: games}} | ||
end | ||
|
||
@doc false | ||
def sign_in(state, game_name, player_name) do | ||
players = Map.get(state.games, game_name, []) | ||
players = [player_name | players] | ||
players = Enum.sort(players) | ||
games = Map.put(state.games, game_name, players) | ||
{:ok, %{state | games: games}} | ||
end | ||
|
||
@doc false | ||
def sign_out(state, game_name, player_name) do | ||
players = | ||
state.games | ||
|> Map.get(game_name, []) | ||
|> List.delete(player_name) | ||
|
||
case Enum.empty?(players) do | ||
true -> | ||
games = Map.delete(state.games, game_name) | ||
{:ok, %{state | games: games}} | ||
|
||
false -> | ||
games = Map.put(state.games, game_name, players) | ||
{:ok, %{state | games: games}} | ||
end | ||
end | ||
end | ||
end |
This file contains 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
This file contains 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
This file contains 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
defmodule Gossip.PlayersTest do | ||
use ExUnit.Case, async: false | ||
|
||
alias Gossip.Players | ||
|
||
setup [:reset] | ||
|
||
describe "sign in" do | ||
test "adds the player to the current list" do | ||
Players.sign_in("ExVenture", "player") | ||
|
||
assert Players.who() == %{"ExVenture" => ["player"]} | ||
end | ||
|
||
test "a second player - list is sorted" do | ||
Players.sign_in("ExVenture", "player1") | ||
Players.sign_in("ExVenture", "player2") | ||
|
||
assert Players.who() == %{"ExVenture" => ["player1", "player2"]} | ||
end | ||
end | ||
|
||
describe "sign out" do | ||
test "removes the player to the current list" do | ||
Players.sign_in("ExVenture", "player1") | ||
Players.sign_in("ExVenture", "player2") | ||
Players.sign_out("ExVenture", "player1") | ||
|
||
assert Players.who() == %{"ExVenture" => ["player2"]} | ||
end | ||
|
||
test "if last player on remote game signs out, clear the game from the list" do | ||
Players.sign_in("ExVenture", "player") | ||
Players.sign_out("ExVenture", "player") | ||
|
||
assert Players.who() == %{} | ||
end | ||
end | ||
|
||
describe "new full game list" do | ||
test "updates the local list" do | ||
Players.player_list("ExVenture", ["player"]) | ||
|
||
assert Players.who() == %{"ExVenture" => ["player"]} | ||
end | ||
end | ||
|
||
def reset(_) do | ||
Players.reset() | ||
end | ||
end |