LiveViewを使って簡単にステートフルなタイピングゲームアプリを作ろう！前編

# LiveViewを使って簡単にステートフルなタイピングゲームアプリを作ろう！前編

この記事は、「Elixir Advent Calendar 2021」の15日目になります。
昨日は、@takamizawa46さんの[「多分、UNIXの本読んで出てきた「小さく作る」がElixirでの開発だったこと」]()でした。

東京だけど fukuoka.ex の YOSUKENAKAO.me です。
[The Waggle](https://www.thewaggle-training.com/)という会社でScrumとElixirと研修講師をやってます。

Scrum開発で学ぶElixir研修を2020年にテストケースとしてローンチし、９名の方がわずか１ヶ月で
Elixirでモジュール開発から、簡単な地図アプリケーションをデプロイするまで成長しました。
企業向けの研修ですが、個人でも受けたいという方がいらしたらご連絡お待ちしてます。
もちろん、企業様のお問い合わせもお待ちしてます。

ご要望が多ければ個人向けも提供したいと考えております。

なお、研修講師になりたい人も 絶賛募集中です。
こちらは、Elixirだけでなく、JavaやPHP、C、C#, Python、機械学習、統計などの分野でも募集しています。自身のスキルの棚卸しや、コーチングやファシリテーションのスキルなども身につきますので、自身のスキルアップとしてチャレンジしたい方もいましたら[こちらのページの下記にあるお問い合わせフォームまで](https://www.thewaggle-training.com/)ご連絡ください

この記事では、LiveViewを使って簡単にタイピングゲーム擬きを作成していきます。
Phoenixfremorkを既に何回か使ったことある方は本編から読み進めてください。
こちらは２部構成で今回は簡単にタイピングゲームの基礎となるキー入力を受け付けて
消すところまでを実装します。

後半は、「fukuoka.ex Elixir／Phoenix Advent Calendar 2021」の18日目で掲載予定です。

記者の環境とツール
この記事を作成する際に利用している環境はインテルCPUのmacOS Big Sur
利用しているコードは、Visual Studio Code.
バージョン管理ツール asdf
Elixir 1.12.3
OTP-24
Phoenixframework 1.6

## 環境構築がまだの方
環境構築がまだの方はasdfで環境構築は[こちら](https://qiita.com/Yoosuke/items/7fc0dfe100d4076dccee)（macOS)
を参考にasdfの環境を作ります。
バージョンは、Elixir1.12.3 OTP-24をインストール終えたら、

```
mix local.hex
mix archive.install hex phx_new
```
をインストールして、PhoenixFrameworkを利用できるようにしてください。
postgresqlもインストールください。

## Phoenixframework初心者の方はこちらから
```
mix phx.new typing_game --live
```

コマンドを実行すると以下のように次やるコマンドリストが表示されます。
上から順に進めていきます。

```
Fetch and install dependencies? [Yn] Y
* running mix deps.get

We are almost there! The following steps are missing:

    $ cd typing_game

Then configure your database in config/dev.exs and run:

    $ mix ecto.create

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server

```

```mix phx.server```を実行し、ブラウザで```localhost:4000```を確認すると、以下のような画面が表示されています。
![スクリーンショット 2021-11-25 11.19.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72962/f8bbdefc-d5d4-0d63-32a4-2044bce2d66b.png)

それでは、これをベースにLiveViewでタイピングしたキー入力を取得してそれに応じてアクションを起こすLiveViewを実装していきましょう。

## 本編はここから（LiveViewでタイピングゲーム作り）

まずは、libフォルダまで移動しましょう。

```
lib.
├── typing_game
├── typing_game.ex
├── typing_game_web
└── typing_game_web.ex
```

libフォルダの中の```typing_game_web```の中にliveView用のページを作成するフォルダを追加していきます。
そして、liveフォルダの中に```page_live.ex```を追加します。

```
typing_game_web.
├── controllers
│   └── page_controller.ex
├── endpoint.ex
├── gettext.ex
├── live.            <- 追加
│   └── page_live.ex <- 追加
├── router.ex
├── telemetry.ex
├── templates
│   ├── layout
│   │   ├── app.html.heex
│   │   ├── live.html.heex
│   │   └── root.html.heex
│   └── page
│       └── index.html.heex
└── views
    ├── error_helpers.ex
    ├── error_view.ex
    ├── layout_view.ex
    └── page_view.ex
```

```page_live.ex``` の中には、live_viewを使う為のモジュールの宣言をしていきます。

```elixir
defmodule TypingGameWeb.PageLive do
    use TypingGameWeb, :live_view
    
end
```

続いて、mountを追加します。
mount関数には、paramsやsessionを受け取り口がありますが、
今回はsocket以外は使わないので_をつけています。

socketで受け取る引数に resultsとvalの2つを準備しておきます。
valには、まずliveViweの特徴を理解する為の簡単なレクチャー用なので、
一旦気にせずに準備してください。

```elixir
defmodule TypingGameWeb.PageLive do
    use TypingGameWeb, :live_view
   
    def mount(_params, _session, socket) do
         {:ok, assign(socket, results: %{}, val: 0 )}
    end

end
```

それでは、liveViewの理解を進めるために一旦、一時的にrender関数を用いて、
ページ表示を行う機能を追加していきたいと思います。liveViewのrender関数
について既に知っている方は飛ばして、タイピング実装まで進んでください。


```elixir
defmodule TypingGameWeb.PageLive do
    use TypingGameWeb, :live_view
   
    def mount(_params, _session, socket) do
         {:ok, assign(socket, results: %{}, val: 0 )}
    end

    def render(assigns) do
        ~L"""
        <div>
          <h1>The count is: <%= @val %></h1>
          <button phx-click="dec">-</button>
          <button phx-click="inc">+</button>
        </div>
        """
      end

end

```

~LはLiveViewのシジルです。
さて、これで、LiveViewのページをレンダリングする関数を追加したので、
router.exのscopeにルートを追加していきます。

```elixir
  scope "/", TypingGameWeb do
    pipe_through :browser

    get "/", PageController, :index
    live "/count", PageLive, :index. #<- 追加
  end
```

サーバを起動して、```localhost:4000/count```にアクセスしてみましょう。

![スクリーンショット 2021-11-25 14.37.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72962/abb06eb6-dc27-690c-c477-894c899f63ad.png)


それでは、続いて、redner関数で作成したページのボタンをクリックして、カウントを更新する処理をLiveViewで実装していきます。

render関数内に書かれているbuttonタグの中に```phx-click```があります。ここに”dec”や”inc”というキーが渡されているので、このボタンが押された時に反応するeventを受け付ける関数を実装します。

できたら、ボタンを押して変化する事を確認します。


```elixir

defmodule TypingGameWeb.PageLive do
    use TypingGameWeb, :live_view
   
    def mount(_params, _session, socket) do
         {:ok, assign(socket, results: %{}, val: 0 )}
    end

    def render(assigns) do
        ~L"""
        <div>
          <h1>The count is: <%= @val %></h1>
          <button phx-click="dec">-</button>
          <button phx-click="inc">+</button>
        </div>
        """
      end

    def handle_event("inc", _, socket) do
        {:noreply, assign(socket, val: 1)}
    end
     
end

```

これで、```+```ボタンをクリックすると、カウントが１に更新されます。
しかし、このままだと何度押しても、1が更新されるだけなので、カウントアップ
するように記述を変えていきます。

&演算子を利用して、+1する関数を作ります。

またこの時、update関数にて実装します。

```elixir
    def handle_event("inc", _, socket) do
        {:noreply, update(socket, :val, &(&1 + 1))}
     end
```

これを真似して、```dec```の方も更新する処理を書いてみてください。

続いて、render関数で実行していたものを移植していきます。
移植したら、<font color="red">render関数は削除</font>しましょう。

```live/page_live.html.heex``` を作成し、render関数に記述した内容を移植していきます。
なお、ちょっとオシャレにしたいので、```<section class="phx-hero">```タグを追加してみました。

```eex
<section class="phx-hero">
  <h1>The count is: <%= @val %></h1>
  <button phx-click="dec">-</button>
  <button phx-click="inc">+</button>
</section>
```

```localhost:4000/count``` にアクセスすると以下のように表示され、カウントも追加できます。


![スクリーンショット 2021-11-25 15.49.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/72962/886e10a7-9e56-c641-d2ef-3116e6944aa3.png)

それでは、準備ができたところで、前編のクライマックスとして、キー入力を取得して表示するところまで実装していきます。

## LiveViewでキーを受け付けて表示する

まずは、```page_live.ex```の中身から書き換えていきます。今回は、mount関数で、初期表示するデータを扱う```word``` を用意しておきます。

次に、```handle_event```を作成します。この関数では、```typing``` で取得した ```key```を受け付けて、```word```の値に受け付けた```key```を渡して更新します。

```elixir
defmodule TypingGameWeb.PageLive do
    use TypingGameWeb, :live_view
   
    def mount(_params, _session, socket) do
         {:ok, assign(socket, word: "test case" )}
    end

    @impl true
    def handle_event("typing", %{"key" => key}, socket) do
      {:noreply, assign(socket, word: key) }
    end
     
end
```

続いて、```page_live.html.heex``` の中を整えていきます。
keyイベントを受け付ける為のコンテナを追加し、typingというキーワードを準備します。
そして、```@word```を置くだけです。これで、実装は完了です。あとは、キーを叩いて、
叩いたキーが表示されることを確認しましょう。

```elixir
<section class="phx-hero">
  <div class="container"
    phx-window-keyup="typing"
  >
  <p><%= @word %></p>
  </div>
</section>
```

さて、準備が整ったところで、タイピングゲームのように表示されているワードから
入力したキーを判定するところまでを作成していきましょう。

まずは、word の先頭文字と押したkeyがあっているかの判定をしたいので、wordの頭文字を
keyupした際にsocketに値として渡すようにしたいと思います。

また、wordも先頭の文字がヒットした時に　文字を削りたいので、更新して行けるように値を返すことを想定して、keyupの際にsocketに引き渡します。この時、keyはどこで入力しても、受け渡したい値は```<div class="container">``` のDOMに対して実施してほしいので、focusとblurイベントも実装しておきます。

```elixir
<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) }
  >
  <p><%= @word %></p>
  </div>
</section>
```

```handle_event``` では、keyとcharとwordを受け取り、keyとcharが同じなら、wordの頭文字を削ってwordに再束縛する処理を追加しました。
これで、同じ文字なら表示されている文字が削れていく表現が実現できました。

```elixir
    @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)
        end

        {:noreply, assign(socket, word: word) }
    end
```

以上で、タイピングゲームを作成する為の最低限の土台ができたかと思います。

後半では、タイピングの文字が複数リストから出題など追加して、よりゲームっぽい表現を実装していきたいと思います。

この記事が面白いと思った方はぜひ、いいね、お願いします。
モチベーションにつながります。