10
0

More than 1 year has passed since last update.

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

Last updated at Posted at 2021-12-14

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

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

東京だけど fukuoka.ex の YOSUKENAKAO.me です。
The Waggleという会社でScrumとElixirと研修講師をやってます。

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

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

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

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

後半は、「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で環境構築はこちら(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

それでは、これをベースに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を使う為のモジュールの宣言をしていきます。

defmodule TypingGameWeb.PageLive do
    use TypingGameWeb, :live_view

end

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

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

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関数
について既に知っている方は飛ばして、タイピング実装まで進んでください。

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にルートを追加していきます。

  scope "/", TypingGameWeb do
    pipe_through :browser

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

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

スクリーンショット 2021-11-25 14.37.43.png

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

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

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


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が更新されるだけなので、カウントアップ
するように記述を変えていきます。

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

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

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

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

続いて、render関数で実行していたものを移植していきます。
移植したら、render関数は削除しましょう。

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

<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

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

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

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

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

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を置くだけです。これで、実装は完了です。あとは、キーを叩いて、
叩いたキーが表示されることを確認しましょう。

<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イベントも実装しておきます。

<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に再束縛する処理を追加しました。
これで、同じ文字なら表示されている文字が削れていく表現が実現できました。

    @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

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

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

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

10
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
0