昨天用 tailwind 畫好首頁
現在要做開始遊戲前的等待對手頁面
在這之前,我覺得差不多可以把 game 搬到 phoenix 裡面了
就..把他移動到 lib/card/game.ex
但我們還是要調整一下 module 名稱
從 Game
變成 Card.Game
,同時裡面有一些用 %Game{}
的地方,
也要改,不過既然是在裡面用的 我們可以寫 %__MODULE__{}
就好
接著我們需要另一個 struct 來儲存房間狀態
這個也是 lib/card/room.ex
defmodule Card.Room do
defstruct id: nil, game: nil, game_pid: nil, host_ready: false, guest_ready: false
end
這個 struct 就可以替換掉我們昨天暫時寫的 "房間狀態"
在昨天的 lib/card_web/live/page_live/index.ex 最後兩個方法
def handle_event("start_game", _params, socket) do
room_id = random_room_id()
:ets.insert_new(:rooms, {room_id, %Card.Room{id: room_id}})
{:noreply, push_redirect(socket, to: "/#{room_id}")}
end
# 這個跟昨天比少了 "room_" 我發現那個好像有點沒意義
defp random_room_id(), do: String.trim(to_string(:rand.uniform), "0.")
打點好之後我們就可以來 router 幫路徑補上
scope "/", CardWeb do
pipe_through :browser
live "/", PageLive.Index, :index
live "/:id/host", GameLive.Invite, :host
live "/:id/guest", GameLive.Invite, :guest
end
這邊會分成兩個,host 結尾的是我們要從 page index 導出去的連結
guest 是給我們複製貼給對手用的
寫好之後在終端機打 mix phx.routes
他就會回傳我們現在設定好的連結,跟 router helper 名稱
page_index_path GET / Phoenix.LiveView.Plug :index
game_invite_path GET /:id/host Phoenix.LiveView.Plug :host
game_invite_path GET /:id/guest Phoenix.LiveView.Plug :guest
下略...
我們在 page index 按下開始遊戲按鈕後,就可以使用 Routes.game_invite_path(socket, :host, room_id)
這樣:
def handle_event("start_game", _params, socket) do
room_id = random_room_id()
:ets.insert_new(:rooms, {room_id, %Card.Room{id: room_id}})
{:noreply, push_redirect(socket, to: Routes.game_invite_path(socket, :host, room_id))}
end
有了連結接著就是做出等待邀請的頁面了lib/card_web/live/game_live/invite.ex
一不注意就一次打完了... 只好加說明在裡面
defmodule CardWeb.GameLive.Invite do
use CardWeb, :live_view
def mount(%{"id" => id} = params, session, socket) do
# 這邊是拿網址裡面的 id 去 ETS 的 rooms 表格找找看有沒有紀錄,沒有的話就轉到首頁
# 有的話就在 socket 的 assigns 裡面加入 room, player, invite_path(要邀請 guest 的連結)
case :ets.lookup(:rooms, id) do
[{_, room}] ->
{:ok, assign(socket, %{room: room, player: socket.assigns.live_action, invite_path: Routes.game_invite_url(socket, :guest, id)})}
_ ->
{:ok, push_redirect(socket, to: "/")}
end
end
def render(assigns) do
# 因為剛剛已經有把要用的東西加進 assigns 裡面,這邊就可以直接用 @ 取值
~H"""
<div class="flex flex-col items-center h-screen">
<header class="text-blue-500 m-4">
<a href="/" class="text-5xl font-serif">
? CardyTotala
</a>
</header>
<div class="flex-grow flex flex-col items-center justify-center">
<%=# 如果玩家是 host 而且 guest 還沒準備好的話 顯示邀請連結 %>
<%= if @player == :host && !@room.guest_ready do %>
<.invite path={@invite_path}/>
<% end %>
</div>
</div>
"""
end
def invite(assigns) do
~H"""
<div class="flex flex-col p-4 bg-grey-50 shadow-lg text-center mt-8">
<div class="bg-blue-300 w-40 h-8 rounded-xl transform skew-x-12 rotate-6 translate-y-6 translate-x-40"></div>
<div class="transform">
<label for="invite_url" class="text-2xl font-serif">Guest Link</label><br>
<input id="invite_url" class="w-96 text-center text-gray-500" type="text" value={@path} readonly>
</div>
<p class="text-lg text-gray-600">To begin the game, send this link to your component.</p>
</div>
"""
end
end
剛剛上面有 <.invite /> 這種東西,這個是 heex 專用的 component 寫法,不是 html
是 phoenix 1.6 剛加進去的,我們看到另一個方法叫 invite(assigns) 就可以猜到
他就是去執行並 render 裡面的東西取代掉 <.invite />
另外在 heex 裡面不能在html tag 上使用 #{}
以前是這樣
# text_color = "text-red-600"
~L"""
<p class="#{text_color}">
"""
但 heex 不行這樣用
現在要改用
# text_color = "text-red-600"
~H"""
<p class={text_color}>
"""
所以現在邀請頁面長這樣