Skip to content

Commit

Permalink
[GH-498] Add quick game endpoint (#501)
Browse files Browse the repository at this point in the history
* Add new join_quick_game/3 function to game_launcher API

* Add new endpoint for quick games and also its websocket handler

* Refactor the fill with bots and game creation behavior

* Fix bug in list appending action

* Create required callback for quick game ws handler

* Add quick_game entrypoint in game_client app and also improve its code and view

* Modify core components styles for game client

* Format elixir code
  • Loading branch information
Nico-Sanchez authored Apr 23, 2024
1 parent 5dd7000 commit ec59584
Show file tree
Hide file tree
Showing 10 changed files with 110 additions and 48 deletions.
48 changes: 32 additions & 16 deletions apps/arena/lib/arena/game_launcher.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ defmodule Arena.GameLauncher do
GenServer.call(__MODULE__, {:join, client_id, character_name, player_name})
end

def join_quick_game(client_id, character_name, player_name) do
GenServer.call(__MODULE__, {:join_quick_game, client_id, character_name, player_name})
end

# Callbacks
@impl true
def init(_) do
Expand All @@ -52,6 +56,13 @@ defmodule Arena.GameLauncher do
}}
end

@impl true
def handle_call({:join_quick_game, client_id, character_name, player_name}, {from_pid, _}, state) do
create_game_for_clients([{client_id, character_name, player_name, from_pid}])

{:reply, :ok, state}
end

@impl true
def handle_info(:launch_game?, %{clients: clients} = state) do
Process.send_after(self(), :launch_game?, 300)
Expand All @@ -66,22 +77,7 @@ defmodule Arena.GameLauncher do

def handle_info(:start_game, state) do
{game_clients, remaining_clients} = Enum.split(state.clients, @clients_needed)

bot_clients = get_bot_clients(@clients_needed - Enum.count(state.clients))

{:ok, game_pid} =
GenServer.start(Arena.GameUpdater, %{
clients: game_clients ++ bot_clients
})

game_id = game_pid |> :erlang.term_to_binary() |> Base58.encode()

spawn_bot_for_player(bot_clients, game_id)

Enum.each(game_clients, fn {_client_id, _character_name, _player_name, from_pid} ->
Process.send(from_pid, {:join_game, game_id}, [])
Process.send(from_pid, :leave_waiting_game, [])
end)
create_game_for_clients(game_clients)

{:noreply, %{state | clients: remaining_clients}}
end
Expand Down Expand Up @@ -116,4 +112,24 @@ defmodule Arena.GameLauncher do
send(self(), {:spawn_bot_for_player, bot_client, game_id})
end)
end

# Receives a list of clients.
# Fills the given list with bots clients, creates a game and tells every client to join that game.
defp create_game_for_clients(clients) do
bot_clients = get_bot_clients(@clients_needed - Enum.count(clients))

{:ok, game_pid} =
GenServer.start(Arena.GameUpdater, %{
clients: clients ++ bot_clients
})

game_id = game_pid |> :erlang.term_to_binary() |> Base58.encode()

spawn_bot_for_player(bot_clients, game_id)

Enum.each(clients, fn {_client_id, _character_name, _player_name, from_pid} ->
Process.send(from_pid, {:join_game, game_id}, [])
Process.send(from_pid, :leave_waiting_game, [])
end)
end
end
53 changes: 53 additions & 0 deletions apps/arena/lib/arena/quick_game_handler.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
defmodule Arena.QuickGameHandler do
@moduledoc """
Module that handles cowboy websocket requests
"""
alias Arena.GameLauncher
alias Arena.Serialization.GameState

@behaviour :cowboy_websocket

@impl true
def init(req, _opts) do
client_id = :cowboy_req.binding(:client_id, req)
character_name = :cowboy_req.binding(:character_name, req)
player_name = :cowboy_req.binding(:player_name, req)
{:cowboy_websocket, req, %{client_id: client_id, character_name: character_name, player_name: player_name}}
end

@impl true
def websocket_init(state) do
GameLauncher.join_quick_game(state.client_id, state.character_name, state.player_name)

game_state =
GameState.encode(%GameState{
game_id: nil,
players: %{},
projectiles: %{}
})

{:reply, {:binary, game_state}, state}
end

@impl true
def websocket_info(:leave_waiting_game, state) do
{:stop, state}
end

@impl true
def websocket_info({:join_game, game_id}, state) do
game_state =
GameState.encode(%GameState{
game_id: game_id,
players: %{},
projectiles: %{}
})

{:reply, {:binary, game_state}, state}
end

@impl true
def websocket_handle(:ping, state) do
{:reply, {:pong, ""}, state}
end
end
9 changes: 4 additions & 5 deletions apps/game_client/assets/js/hooks/game_queue.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ export const GameQueue = function () {
let player_id = document.getElementById("board_game").dataset.playerId
let character = document.getElementById("board_game").dataset.character
let player_name = document.getElementById("board_game").dataset.playerName
let player = new Player(getQueueSocketUrl(player_id, character, player_name))
let game_mode = document.getElementById("board_game").dataset.gameMode
let player = new Player(getQueueSocketUrl(player_id, character, player_name, game_mode))

player.socket.addEventListener("message", (event) => {
game_state = messages.GameState.deserializeBinary(event.data);
Expand All @@ -17,12 +18,10 @@ export const GameQueue = function () {
};
}

function getQueueSocketUrl(player_id, character, player_name) {
function getQueueSocketUrl(player_id, character, player_name, game_mode) {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
const host = getHost()
const path = '/join'

return `${protocol}//${host}${path}/${player_id}/${character}/${player_name}`
return `${protocol}//${host}/${game_mode}/${player_id}/${character}/${player_name}`
}

// TODO: This will work for while the Arena is using the default wss port and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ defmodule GameClientWeb.CoreComponents do
<select
id={@id}
name={@name}
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm"
class="mt-2 block w-full rounded-md border border-gray-300 bg-white shadow-sm focus:border-zinc-400 focus:ring-0 sm:text-sm text-black font-semibold"
multiple={@multiple}
{@rest}
>
Expand Down Expand Up @@ -394,7 +394,7 @@ defmodule GameClientWeb.CoreComponents do

def label(assigns) do
~H"""
<label for={@for} class="block text-sm font-semibold leading-6 text-zinc-800">
<label for={@for} class="block text-sm font-semibold leading-6">
<%= render_slot(@inner_block) %>
</label>
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ defmodule GameClientWeb.PageController do
def home(conn, _params) do
render(conn, :home)
end

def select_character(conn, %{"character" => character, "game_mode" => game_mode}) do
redirect(conn, to: ~p"/board/#{Ecto.UUID.generate()}/#{character}/player_name/#{game_mode}")
end
end
Original file line number Diff line number Diff line change
@@ -1,15 +1,5 @@
<ul>
<li>
Mocked Muflus: <a href={"/board/#{Ecto.UUID.generate()}/muflus/player_name"} class="text-yellow-400">link</a>
</li>
<li>
Mocked Uma: <a href={"/board/#{Ecto.UUID.generate()}/uma/player_name"} class="text-yellow-400">link</a>
</li>
<li>
Mocked H4ck: <a href={"/board/#{Ecto.UUID.generate()}/h4ck/player_name"} class="text-yellow-400">link</a>
</li>
<li>
Mocked Valtimer: <a href={"/board/#{Ecto.UUID.generate()}/valtimer/player_name"} class="text-yellow-400">link</a>
</li>
</ul>

<.form :let={f} for={%{}} action={~p"/"}>
<.input field={f[:character]} name="character" label="Select a Character" type="select" options={["muflus", "h4ck", "uma", "valtimer"]} value=""/>
<.button type="submit" name="game_mode" value="join">Play</.button>
<.button type="submit" name="game_mode" value="quick_game">Quick Game</.button>
</.form>
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@ defmodule GameClientWeb.BoardLive.GameQueue do
require Logger
use GameClientWeb, :live_view

def mount(%{"player_id" => player_id, "character" => character, "player_name" => player_name}, _session, socket) do
{:ok, assign(socket, player_id: player_id, character: character, player_name: player_name)}
def mount(
%{"player_id" => player_id, "character" => character, "player_name" => player_name, "game_mode" => game_mode},
_session,
socket
) do
{:ok, assign(socket, player_id: player_id, character: character, player_name: player_name, game_mode: game_mode)}
end

def handle_event("join_game", %{"game_id" => game_id, "player_id" => player_id}, socket) do
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div id="board_game" phx-hook="GameQueue" data-player-id={@player_id} data-character={@character} data-player-name={@player_name} class="max-w-full max-h-[40rem] overflow-scroll"></div>
<div id="board_game" phx-hook="GameQueue" data-player-id={@player_id} data-character={@character} data-player-name={@player_name} data-game-mode={@game_mode} class="max-w-full max-h-[40rem] overflow-scroll"></div>

WAITING FOR A GAME TO START...
9 changes: 2 additions & 7 deletions apps/game_client/lib/game_client_web/router.ex
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,9 @@ defmodule GameClientWeb.Router do
pipe_through :browser

get "/", PageController, :home
end

scope "/", GameClientWeb do
pipe_through :browser
# pipe_through [:browser, :game]

post "/", PageController, :select_character
live "/board/play/:game_id/:player_id", BoardLive.Show
live "/board/:player_id/:character/:player_name", BoardLive.GameQueue
live "/board/:player_id/:character/:player_name/:game_mode", BoardLive.GameQueue
end

# Other scopes may use custom stacks.
Expand Down
1 change: 1 addition & 0 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ dispatch = [
_: [
{"/play/:game_id/:client_id", Arena.GameSocketHandler, []},
{"/join/:client_id/:character_name/:player_name", Arena.SocketHandler, []},
{"/quick_game/:client_id/:character_name/:player_name", Arena.QuickGameHandler, []},
{:_, Plug.Cowboy.Handler, {ArenaWeb.Endpoint, []}}
]
]
Expand Down

0 comments on commit ec59584

Please sign in to comment.