この記事は、「fukuoka.ex Elixir/Phoenix Advent Calendar 2021」の18日目になります。17日目は @pojiro さんの[ ]話でした。
東京だけど fukuoka.ex の YOSUKENAKAO.me です。
The Waggleという会社でScrumとElixirと研修講師をやってます。
Scrum開発で学ぶElixir研修を2020年にテストケースとしてローンチし、9名の方がわずか1ヶ月でElixirでモジュール開発から、簡単な地図アプリケーションをデプロイするまで成長しました。
企業向けの研修ですが、個人でも受けたいという方がいらしたらご連絡お待ちしてます。
ご要望が多ければ個人向けも提供したいと考えております。
なお、研修講師になりたい人も 絶賛募集中です。
こちらは、Elixirだけでなく、JavaやPHP、C、C#, Python、機械学習、統計などの分野でも募集しています。
自身のスキルの棚卸しや、コーチングやファシリテーションのスキルなども身につきますので、自身のスキルアップとしてチャレンジしたい方もいましたらこちらのページの下記にあるお問い合わせフォームまでご連絡ください。
この記事では、LiveViewを使って簡単にタイピングゲーム擬きを作成していきます。の後編です。
前編は、「Elixir Advent Calendar 2021」の15日目で掲載中です。
前編を見てない方はそちらからどうぞLiveViewを使って簡単にステートフルなタイピングゲームアプリを作ろう! 前編
ページリロードを抑制
Key入力をするたびにページリロードが入ると、忙しないので以下のハンドルイベント関数を追加し、単純にkeyを押しただけではページのリロードが起きないように変更します。
# page-activeの時は何もしない
def handle_event("page-active", %{}, socket) do
{:noreply, socket}
end
# page-inactiveの時は何もしない
def handle_event("page-inactive", %{}, socket) do
{:noreply, socket}
end
表示するワードのリストを追加
mount関数にwordをリストから追加するように準備します。
def mount(_params, _session, socket) do
data = ["Test Case", "Analyze the business issues.", "Grow as a Team.", "Develop a plan to solve the business issues."]
{:ok, assign(socket, word: Enum.at(data, 0) )}
end
シフト操作の処理
大文字変換をする際に"Shift"を押す必要があります。
"Shift"を押しながらkey入力する必要があるので、"Shift"を受け付けてもwordの更新をしないように
変更します。
ついでに、後々、誤入力の判定もしたいので、"Shift"以外で先頭の文字と一致しなかった場合のルートをOtherとして確保しておきます。
@impl true
def handle_event("typing", %{"key" => key, "word" => word, "char" => char}, socket) do
word = if char == key do
[_head | tail] = String.graphemes(word)
List.to_string(tail)
else
case key do
"Shift" -> word
_Other -> word
end
end
{:noreply, assign(socket, word: word) }
end
リスト情報の更新
ここで、word
の情報が最後の文字まで消えた後、次のリストの文字に更新をしたいので、カウンタを使って管理していきたいと思います。
そこで、Agentを使ってカウントを持たせてみます。
typing_game_web/util/counter.ex
を作成します。
Agentについて詳しく知りたい方はこちらを読んでみてください。
今回は、長くなるので割愛します。
defmodule TypingGameWeb.Util.Counter do
use Agent
def start_link(initial_value) do
{status, pid} = Agent.start_link(fn -> initial_value end, name: __MODULE__)
case status do
:ok -> pid
:error -> "error"
end
end
def value do
Agent.get(__MODULE__, & &1)
end
def increment do
Agent.update(__MODULE__, &(&1 + 1))
end
def stop(pid) do
Agent.stop(pid)
end
end
続いて、Agentを起動するタイミングを作りたいと思います。
mountに作成してしまうと、更新のタイミングで上書きされてしまうので、スタートボタンを作り、ボタンをクリックしたタイミングで作成するように変更していきます。
<section class="phx-hero">
<div class="container"
phx-window-focus="page-active"
phx-window-blur="page-inactive"
phx-window-keyup="typing"
phx-value-word={ @word }
phx-value-char={ String.at(@word, 0) }
phx-value-data={ @data }
>
<%= if @word == "" do %>
<button phx-click="start", phx-value-data={ @data }>開始する</button>
<%= else %>
<p><%= @word %></p>
<% end %>
</div>
</section>
これで、スタートボタンをクリックして、ゲームスタートができるといった表現ができました。では実際にそのように@word
が初期値 "" となるように変更していきましょう。
def mount(_params, _session, socket) do
{:ok, assign(socket, word: "" , data: [], count: 0 )}
end
handle_event
で start をキャッチして Counterを開始する部分を実装します。ここで、mountにあったdataのリストを実装する形に変更します。
def handle_event("start", %{}, socket) do
data = ["Test Case", "Analyze the business issues.", "Grow as a Team.", "Develop a plan to solve the business issues."]
pid = Counter.start_link(0)
{:noreply, assign(socket, word: Enum.at(data, 0), data: data,
count: Counter.value(), pid: pid
)}
end
wordの文字列が1になったらカウントに1追加して、次のリストの文字列をwordにマッチさせる処理を追加して、カウンタの値を加算していきます。
本来は、0になったらの条件で進めたいのですが、現時点では0の判定にすると判定前に
エラーで終了してしまうので1の判定にしておきます。
1で判定の中に入ったら、Agentで管理してるカウンタを加算して
dataに束縛してる値から2つ目の要素を取り出してwordに再束縛します。
この時、dataはsocketにあるので、socket.assigns.dataで取得する
事が可能です。
次に、dataのリストの値とカウンタの値が同じになったら、Agentを止めたいので
その判定処理を入れておきます。
@impl true
def handle_event("typing", %{"key" => key, "word" => word, "char" => char}, socket) do
word = if 1 == String.length(word) do
Counter.increment()
socket.assigns.data |> Enum.at(Counter.value())
else
if char == key do
[_head | tail] = String.graphemes(word)
IO.inspect "#{String.length(word)}"
List.to_string(tail)
else
case key do
"Shift" -> word
"Enter" -> word
_Other -> word
end
end
end
if Enum.count(socket.assigns.data) == Counter.value() do
Counter.stop(socket.assigns.pid)
end
{:noreply, assign(socket, word: word) }
end
これで、一通り、タイピングゲーム擬きの形ができました。
あとは、これまでにやったことを応用して、タイプミスをカウントするAgentを用意して、_Other のケースでカウントアップしたり、data に取り込むリストをCSV読み込みで準備できるようにしたりなどなど改良してみてください。
この記事が面白いと思った方はぜひ、いいね、お願いします。
モチベーションにつながります。