-
Notifications
You must be signed in to change notification settings - Fork 0
3.タイピングゲームの機能をElixir風に改良する
お題をElixirの関数にします。
- Typing.Editor.GameEditor構造体のchar_listに以下の表の要素を持ったリストを割り当ててください。
お題 |
---|
"Enum.map([1, 2, 3], fn a -> a * 2 end)" |
"Enum.shuffle([1, 2, 3])" |
"Enum.reverse([1, 2, 3])" |
"Map.put(%{a: "a", b: "b", c: "c"}, :d, "b")" |
"Enum.map([1, 2, 3], fn a -> a * 2 end) |
ヒント
- 現在スペースも入力を受け付けないようにしているのでガード句の条件からスペースを外します。
- editor/game_editor.ex
def construct() do
char_list =
[
"Enum.map([1, 2, 3], fn a -> a * 2 end)",
"Enum.shuffle([1, 2, 3])",
"Enum.reverse([1, 2, 3])",
"Map.put(%{a: \"a\", b: \"b\", c: \"c\"}, :d, \"b\")",
"Enum.map([1, 2, 3], fn a -> a * 2 end) |> Enum.shuffle()"
]
display_char = hd(char_list)
%__MODULE__{
display_char: display_char,
char_count: String.length(display_char),
game_status: 1,
char_list: char_list
}
end
ガード句の条件からスペースを外します。
- eidtor/game_editor.ex
@exclusion_key ~w(
Tab
Control
Shift
CapsLock
Alt
Meta
Eisu
KanjiMode
Backspace
Enter
Escape
ArrowLeft
ArrowRight
ArrowUp
ArrowDown
)
入力した関数を実行させて結果を表示させるように以下を実装してください。
※今回はお題の関数を.exsに書き込みそのファイルを実行させます。
- priv/staticディレクトリにresult.exsを作成してください。
- update/3 で記述している next_char(editor, key) はコメントアウトまたは削除してください。
- Typing.Editor.GameEditor構造体に以下の表のキーを追加してください。
- 全ての入力し終わったらresult.exsファイルに関数を書き込み、実行させてください。
- main.html.heex にresultの値を表示させてください。(<h2>タグで囲みます)
キー | 値の型 |
---|---|
result | any |
ヒント
Code.eval_file/2
File.write/3
ビュートテンプレート
- File.write/3を使用してお題の関数をresult.exsに書き込みます。
- Code.eval_file/2の第1引数にresult.exsのパスを渡します。
- Code.eval_file/2で実行した値をTyping.Editor.GameEditor構造体のresultに割り当てます。
- テンプレートでリストなどの値を表示させるにはinspectを使用します。
Typing.Editor.GameEditor構造体にresultを追加します。
- editor/game_editor.ex
defstruct input_char: "",
display_char: "",
char_count: 0,
now_char_count: 0,
failure_count: 0,
game_status: 0,
char_list: [],
clear_count: 0,
result: nil
お題の関数をファイルに書き込み実行させるモジュールと関数を作成します。
utilsディレクトリにexecution.exを作成します。
- utils/execution.ex
defmodule Typing.Utils.Execution do
@file_path("priv/static/result.exs")
def execution(expr) do
File.write(@file_path, expr)
Code.eval_file(@file_path)
end
end
Typing.Editor.GameEditorの関数でTyping.Utils.Executionモジュールを使用できるようにエイリアスを指定します。
- editor/game_editor.ex
defmodule Typing.Editor.GameEditor do
import Typing.Utils.KeysDecision
alias Typing.Utils.Execution
game_editorにdisplay_charの値をTyping.Utils.Execution.execution/1に渡す関数を作成します。
- editor/game_editor.ex
defp display_result(editor, key) do
{result, _} = Execution.execution(editor.display_char)
%{
editor
| result: result,
input_char: editor.input_char <> key,
clear_count: editor.clear_count + 1
}
end
update/3 で display_result/2 を呼び出すように記述します。
- editor/game_editor.ex
def update(%__MODULE__{display_char: char, now_char_count: count} = editor, "input_key", %{"key" => key})
when key not in @exclusion_key and key_check(char, count, key) and editor.game_status == 1 do
cond do
editor.now_char_count == editor.char_count - 1 ->
display_result(editor, key)
true ->
%{editor | input_char: editor.input_char <> key, now_char_count: editor.now_char_count + 1}
end
end
テンプレートでresultを表示させるようにします。
- game_editor/main.html.heex
<h2>
<%= if @editor.game_status == 1 do %>
<span style="color: blue;"><%= @editor.input_char %></span><%= trem_display_char(@editor) %>
<% else %>
<%= @editor.display_char %>
<% end %>
</h2>
<p>クリアした回数:<%= @editor.clear_count %> 回</p>
<p>ミスした回数:<%= @editor.failure_count %> 回</p>
<h2>実行結果</h2>
<h2><%= if @editor.result, do: inspect(@editor.result), else: "" %></h2>
<div
phx-window-blur="page-inacive"
phx-window-focus="page-active"
phx-window-keyup="toggle_input_key">
</div>
入力して実行結果を表示させた時に Enter を押して、次のお題に進めるように実装します。
- game_status の状態に2(Enter入力待ち)を追加することにします。
- テンプレートでEnterを押してくださいを表示させてください。(<h1>タグで囲みます)
ヒント
- update/3 に Enter と game_statusが2の状態を受け付けられるようにガード句付きで追加します。
Enterキーとgame_statusが2の状態を受け付けるように update/3 を追加します。
- editor/game_editor.ex
def update(%__MODULE__{} = editor, "input_key", %{"key" => key})
when key == "Enter" and editor.game_status == 2 do
next_char(editor, key)
end
game_statusに状態が追加されたのでnext_char/2とdisplay_result/2を変更します。
- editor/game_editor.ex
defp next_char(editor, key) do
char_list = List.delete(editor.char_list, editor.display_char)
case length(char_list) do
0 ->
%{
editor
| char_list: char_list,
display_char: "クリア",
input_char: editor.input_char <> key,
game_status: 0,
result: nil
}
_num ->
display_char = hd(char_list)
%{
editor
| char_list: char_list,
display_char: display_char,
input_char: "",
char_count: String.length(display_char),
now_char_count: 0,
game_status: 1,
result: nil
}
end
end
defp display_result(editor, key) do
{result, _} = Execution.execution(editor.display_char)
%{
editor
| result: result,
game_status: 2,
input_char: editor.input_char <> key,
clear_count: editor.clear_count + 1
}
end
テプンレートにEnterを押してくださいと表示させるようにします。
- editor/main.html.heex
<h2>
<%= if @editor.game_status == 1 do %>
<span style="color: blue;"><%= @editor.input_char %></span><%= trem_display_char(@editor) %>
<% else %>
<%= @editor.display_char %>
<% end %>
</h2>
<p>クリアした回数:<%= @editor.clear_count %> 回</p>
<p>ミスした回数:<%= @editor.failure_count %> 回</p>
<h2>実行結果</h2>
<h2><%= if @editor.result, do: inspect(@editor.result), else: "" %></h2>
<%= if @editor.game_status == 2 do %>
<h1>Enterを押してください</h1>
<% end %>
<div
phx-window-blur="page-inacive"
phx-window-focus="page-active"
phx-window-keyup="toggle_input_key">
</div>
現在以下のようにエラーになる関数を実行しようとするとエラーが発生して先に再マウントされます。
[error] GenServer #PID<0.621.0> terminating
** (UndefinedFunctionError) function Enum.map/1 is undefined or private
(elixir 1.13.3) Enum.map([1, 2, 3])
(stdlib 3.17.1) erl_eval.erl:685: :erl_eval.do_apply/6
(elixir 1.13.3) src/elixir.erl:296: :elixir.recur_eval/3
(elixir 1.13.3) src/elixir.erl:274: :elixir.eval_forms/3
(elixir 1.13.3) lib/code.ex:404: Code.validated_eval_string/3
(typing 0.1.0) lib/typing/editor/game_editor.ex:120: Typing.Editor.GameEditor.display_result/2
(phoenix_live_view 0.17.9) lib/phoenix_live_view.ex:887: Phoenix.LiveView.update/3
(typing 0.1.0) lib/typing_web/live/game_editor_live.ex:19: TypingWeb.GameEditorLive.handle_event/3
(phoenix_live_view 0.17.9) lib/phoenix_live_view/channel.ex:382: anonymous fn/3 in Phoenix.LiveView.Channel.view_handle_event/3
(telemetry 1.1.0) /apps/typing/deps/telemetry/src/telemetry.erl:320: :telemetry.span/3
(phoenix_live_view 0.17.9) lib/phoenix_live_view/channel.ex:215: Phoenix.LiveView.Channel.handle_info/2
(stdlib 3.17.1) gen_server.erl:695: :gen_server.try_dispatch/4
(stdlib 3.17.1) gen_server.erl:771: :gen_server.handle_msg/6
(stdlib 3.17.1) proc_lib.erl:226: :proc_lib.init_p_do_apply/3
Last message: %Phoenix.Socket.Message{event: "event", join_ref: "4", payload: %{"event" => "toggle_input_key", "type" => "keyup", "value" => %{"key" => ")"}}, ref: "26", topic: "lv:phx-FvDAnHdZpI3YlQSi"}
State: %{components: {%{}, %{}, 1}, join_ref: "4", serializer: Phoenix.Socket.V2.JSONSerializer, socket: #Phoenix.LiveView.Socket<assigns: %{__changed__: %{}, editor: %Typing.Editor.GameEditor{char_count: 19, char_list: ["Enum.map([1, 2, 3])", "Enum.map([1, 2, 3], fn a -> a * 2 end)", "Enum.shuffle([1, 2, 3])", "Enum.reverse([1, 2, 3])", "Map.put(%{a: \"a\", b: \"b\", c: \"c\"}, :d, \"b\")", "Enum.map([1, 2, 3], fn a -> a * 2 end) |> Enum.shuffle()"], clear_count: 0, display_char: "Enum.map([1, 2, 3])", failure_count: 0, game_status: 1, input_char: "Enum.map([1, 2, 3]", now_char_count: 18, result: nil}, flash: %{}, live_action: :index, page_title: "タイピングゲーム", template: "main.html"}, endpoint: TypingWeb.Endpoint, id: "phx-FvDAnHdZpI3YlQSi", parent_pid: nil, root_pid: #PID<0.621.0>, router: TypingWeb.Router, transport_pid: #PID<0.615.0>, view: TypingWeb.GameEditorLive, ...>, topic: "lv:phx-FvDAnHdZpI3YlQSi", upload_names: %{}, upload_pids: %{}}
このようなエラーになる関数を入力した際に画面にエラーの種類を表示させるように実装してください。
- Typing.Editor.GameEditor構造体のchar_list に割り当てる値(リストの先頭)に以下を追加してください。
- 入力した値を実行してエラーが出た際にそのエラーの種類を出力できるよにしてください。
char_listのリストに追加する値
"Enum.map([1, 2, 3])",
"Enum.map(1, fn a -> a end)",
"String.split(a, \" \")",
ヒント
try/1
- try/1 を使用して発生したエラーをスローします。
- スローしたエラーをパターンマッチを使ってエラーのモジュールを取得しその値を返します。
- エラーが発生しなかった場合はそのまま、その値を返します。
- case を使いパターンマッチをさせて値を取得します。
エラーになる関数を渡された際にエラーの種類を渡せるようにします。
- utils/execution.ex
def execution(expr) do
File.write(@file_path, expr)
try do
throw(Code.eval_file(@file_path))
rescue
x ->
%module{} = x
module = String.replace("#{module}", "Elixir.", "")
"#{module}"
catch
x -> x
end
end
GameEditorで戻ってくる値の形が違うのでcase のパターンマッチを使って値を取得します。
- editor/game_editor.ex
defp display_result(editor, key) do
result =
case Execution.execution(editor.display_char) do
{r, _} -> r
error -> error
end
%{
editor
| result: result,
game_status: 2,
input_char: editor.input_char <> key,
clear_count: editor.clear_count + 1
}
end