はじめに
Qiita の記事には「じゃんけん」の実装が結構多いようです
色々な言語で実装されています
というわけで、 Elixir で実装してみます
せっかくなので Livebook を使います
実装したノートブックはこちら
じゃんけんとは
じゃんけんは手の形で勝敗を決する遊びです
手の形には以下の3種類があります
- ✊グー
- ✌️チョキ
- ✋パー
参加者は「じゃんけんぽん」の掛け声に合わせていずれかの手の形を提示します
グーはチョキに勝ち、チョキはパーに勝ち、パーはグーに勝ちます
同じ手の形の場合は「あいこ」になります
セットアップ
入出力に便利な Kino をインストールしておきます
Mix.install([
{:kino, "~> 0.8"}
])
手と結果の定義
じゃんけんをコード化するため、以下のように定義します
手の形
- 0 = グー
- 1 = チョキ
- 2 = パー
hands = [
{0, "グー"},
{1, "チョキ"},
{2, "パー"}
]
Kino.Input.select
に渡すために Tuple の List にしています
勝敗結果
- 0 = あいこ
- 1 = 勝ち
- 2 = 負け
results = ["あいこ", "勝ち", "負け"]
この並びには意味があります
手の選択
自分の手を選択できるようにします
my_hand_input = Kino.Input.select("あなたの手", hands)
my_hand = Kino.Input.read(my_hand_input)
相手の手はランダムにします
# ランダムに決定
opponents_hand = 0..2 |> Enum.random
手の名称を表示する関数を用意します
get_hand_name = fn hand ->
hands |> Enum.at(hand) |> elem(1)
end
IO.puts("あなたの手: #{get_hand_name.(my_hand)}")
IO.puts("あいての手: #{get_hand_name.(opponents_hand)}")
勝敗判定
では肝となる勝敗判定を実装します
まず「あいこ」は単純で、自分と相手の数字が同じ場合です
つまり組み合わせでは
- 0:0 (グー:グー)
- 1:1 (チョキ:チョキ)
- 2:2 (パー:パー)
続いて勝ちのパターンを見てみましょう
- 0:1 (グー:チョキ)
- 1:2 (チョキ:パー)
- 2:0 (パー:グー)
最後に負けのパターンです
- 0:2 (グー:パー)
- 1:0 (チョキ:グー)
- 2:1 (パー:チョキ)
勝敗判定を関数とみなすと、以下のように考えられます
- f(0,0) = 0
- f(0,1) = 1
- f(0,2) = 2
- f(1,0) = 2
- f(1,1) = 0
- f(1,2) = 1
- f(2,0) = 1
- f(2,1) = 2
- f(2,2) = 0
これを満たす関数を定義すれば勝敗判定できそうです
勘のいい人はもう理解したと思います
(同じ解法はおそらくすでに世にあるものと思います)
相手の手 - 自分の手は以下のようになります
- d(0,0) = 0
- d(0,1) = 1
- d(0,2) = 2
- d(1,0) = -1
- d(1,1) = 0
- d(1,2) = 1
- d(2,0) = -2
- d(2,1) = -1
- d(2,2) = 0
これに 3 を足してみます
- d'(0,0) = 3
- d'(0,1) = 4
- d'(0,2) = 5
- d'(1,0) = 2
- d'(1,1) = 3
- d'(1,2) = 4
- d'(2,0) = 1
- d'(2,1) = 2
- d'(2,2) = 0
3 で割った余りを計算します
- d''(0,0) = 0
- d''(0,1) = 1
- d''(0,2) = 2
- d''(1,0) = 2
- d''(1,1) = 0
- d''(1,2) = 1
- d''(2,0) = 1
- d''(2,1) = 2
- d''(2,2) = 0
この d''() 関数は f() 関数と同じですね
というわけで、じゃんけんの勝敗判定は以下の式で表せます
fn x_hand, y_hand ->
rem(y_hand - x_hand, 3)
end
手の名称表示、結果の名称表示も入れて、以下のような関数を定義しましょう
judge = fn x_hand, y_hand ->
IO.puts("あなたの手: #{get_hand_name.(x_hand)}")
IO.puts("あいての手: #{get_hand_name.(y_hand)}")
result = rem(y_hand - x_hand, 3)
result_name = Enum.at(results, result)
IO.puts("結果: #{result_name}")
result_name
end
呼び出してみます
judge.(my_hand, opponents_hand)
全ての組み合わせでテスト
では全ての組み合わせを作ってテストします
%{
"あいての手\\あなたの手" => Enum.map(hands, &elem(&1, 1))
}
|> Map.merge(
for {x_hand, x_hand_name} <- hands, into: %{} do
{
x_hand_name,
Enum.map(hands, fn {y_hand, _} ->
judge.(x_hand, y_hand)
end)
}
end
)
|> Kino.DataTable.new()
どの組み合わせでも正しい結果になっていますね
まとめ
こういうのは気分転換できていいですね