Home Posts Post Search Tag Search

PokerLive - 03 - Room Lobby
Published on: 2026-03-28 Tags: elixir, Blog, LiveView, Game Site, Phoenix, Poker, GenServer, Player, Room, Supervisor Tree

Okay so now I wanted to work on the lobby for the Rooms this is where a use can see all the active rooms and then be able to pick on or create their own if they are logged in. I like to keep everything I can separated so that you can keep context and domains to what they should be doing only.


First let’s set up the component.ex that will house the different things that we want to have in the lobby.ex. This will be the instructions for the rooms. The table for all the live games that are being played. As well as a final block for creating your own game. Later we can work on making sure that there can only be one room created person. But for right now we have all that we need.

defmodule GameSiteWeb.MultiPokerLive.Component do
  use GameSiteWeb, :live_view
  use Phoenix.Component

  def instructions(assigns) do
    ~H"""
    <h2 class="text-xl font-semibold mb-2">Poker Game Overview</h2>
    <ul class="list-disc list-inside mt-2 space-y-1 text-gray-700">
      <li>Here you can create or join a room to play some poker</li>
      <li>You must be logged in to create a room.</li>
      <li>If you want to keep track of your chip count you need to create an account.</li>
      <li>There will be a max of 6 players per room.</li>
      <li>You will start with 1000 chips if you are a new player.</li>
    </ul>
    """
  end

  attr(:rooms, :map, required: true)

  def live_games(assigns) do
    ~H"""
    <.table id="rooms" rows={@rooms}>
      <:col :let={room} label="Room ID">
        <span class="font-mono text-sm">{room.display_id}</span>
      </:col>

      <:col :let={room} label="Players">
        {room.player_count}
      </:col>

      <:col :let={room} label="Status">
        <span class={[
          "inline-flex rounded-full px-2 py-1 text-xs font-semibold",
          room.room_status == :waiting && "bg-yellow-100 text-yellow-800",
          room.room_status == :in_progress && "bg-green-100 text-green-800"
        ]}>
          {room.room_status}
        </span>
      </:col>

      <:action :let={room}>
        <.link
          navigate={~p"/multi-poker/#{room.room_id}"}
          class="rounded-md bg-indigo-600 px-3 py-2 text-sm font-semibold text-white hover:bg-indigo-500"
        >
          Join
        </.link>
      </:action>
    </.table>
    """
  end

  attr(:current_user, :map, required: false)

  def new_game(assigns) do
    ~H"""
    <div class="rounded-xl border border-zinc-200 bg-white p-6 shadow-sm">
      <h2 class="mb-2 text-xl font-semibold text-zinc-800">Can't find a game you like?</h2>

      <%= if @current_user == nil do %>
        <p class="mb-4 text-sm text-zinc-600">
          Log in or create an account to make your own room.
        </p>

        <div class="flex gap-3">
          <.link
            navigate={~p"/users/log_in"}
            class="rounded-md bg-indigo-600 px-4 py-2 text-sm font-semibold text-white hover:bg-indigo-500"
          >
            Log in
          </.link>

          <.link
            navigate={~p"/users/register"}
            class="rounded-md border border-zinc-300 px-4 py-2 text-sm font-semibold text-zinc-700 hover:bg-zinc-50"
          >
            Register
          </.link>
        </div>
      <% else %>
        <p class="mb-4 text-sm text-zinc-600">
          Start a new room and invite other players to join.
        </p>

        <button
          type="button"
          phx-click="create_room"
          class="rounded-md bg-emerald-600 px-4 py-2 text-sm font-semibold text-white hover:bg-emerald-500"
        >
          Create Room
        </button>
      <% end %>
    </div>
    """
  end
end

Next we can set up the actual page. This will leverage the component.ex that we created earlier to keep the render very slim. The mount will only populate the Rooms if it is connected and then we have the event to create a room and the direct them to the created room.


I wanted to also take a min to go over the list_rooms and then the list_room_summaries. The first one uses Registry to pull all the pids and their ids from the Supervisor, the normal output for the return value is a list that looks like

[
  {key, pid, value}
  ...
]
# so looking at the code for it we can see that we are using this syntax for the select()
{
  {:"$1", :"$2", :"$3"},  # match
  [],                     # guards
  [{{:"$1", :"$2"}}]      # return
}

defmodule GameSiteWeb.MultiPokerLive.Lobby do
  use GameSiteWeb, :live_view

  alias GameSite.MultiPoker.Room
  alias GameSite.MultiPoker
  alias GameSiteWeb.MultiPokerLive.Component

  @registry GameSite.MultiPoker.RoomRegistry

  @impl true
  def render(assigns) do
    ~H"""
    <Component.instructions />
    <Component.live_games rooms={@rooms} />
    <Component.new_game current_user={@current_user} />
    """
  end

  @impl true
  def mount(_params, _session, socket) do
    rooms =
      if connected?(socket) do
        list_room_summaries()
      else
        []
      end

    {:ok, assign(socket, :rooms, rooms)}
  end

  @impl true
  def handle_event("create_room", _params, socket) do
    current_user = socket.assigns.current_user
    {_, room_id} = MultiPoker.create_room(current_user.id)

    socket =
      assign(socket, :rooms, list_room_summaries())

    {:noreply, redirect(socket, to: "/multi-poker/#{room_id}")}
  end

  defp list_rooms do
    Registry.select(@registry, [
      {
        {:"$1", :"$2", :"$3"},
        [],
        [{{:"$1", :"$2"}}]
      }
    ])
  end

  defp list_room_summaries do
    list_rooms()
    |> Enum.with_index(1)
    |> Enum.map(fn {{room_id, pid}, display_id} ->
      state = Room.get_state(pid)

      %{
        room_id: room_id,
        player_count: map_size(state.players),
        room_status: state.room_status,
        display_id: display_id
      }
    end)
  end
end

No we can set up a holder for the room where all the games will be played. We will fill this in later but I wanted you to have a page to render while we worked on the code.

defmodule GameSiteWeb.MultiPokerLive do
  use GameSiteWeb, :live_view

  def render(assigns) do
    ~H"""
    The Games will go here.
    """
  end

  def mount(_params, _session, socket) do
    {:ok, assign(socket, :room, nil)}
  end
end

lastly we can set up the routes for the Lobby and Room This is pretty simple as it just makes the routes for the different modules to render and mount.

  scope "/", GameSiteWeb do
    pipe_through :browser

    live_session :games,
      on_mount: [{GameSiteWeb.UserAuth, :mount_current_user}] do
      ...
      live "/multi-poker", MultiPokerLive.Lobby
      live "/multi-poker/:room", MultiPokerLive
      ...
    end
  end