じゃんけんをシミュレートしてみる。練習問題だ。
人間を五人ほど作り、グー/チョキ/パーのなにを出させるかをそれぞれに決めさせ、勝負する。引き分けを考慮し、勝者が最後の一人になるまで繰り返す。
せっかくElixir (Erlang) でつくるのだから、各人間をプロセスにしてみる。
まずPersonモジュールをstructにする。Elixirはstructが入って大変便利になった。それまではrecoedを使ってゐたもので、プロセスの関数を含むモジュールとrecordのモジュールは別にしなければならなかった。Ruby人間としてstructは助かる。
spawnしてPersonを五人作る。PersonのプロセスIDが得られるので、これで話しかけられる。
さて、五人に手を出してもらう。そして五人が何の手を出したか確認せねばならない。おお。五人全員の手が揃わなければならないのだ。四人で勝ちを決めても五人目の手で引き分けになるかもしれない、當たり前だ。すなはち五人全員が手を出し終はるのを待たねばならない。五人それぞれに手を出すよう話しかけ、いつかわからぬ帰ってくる手を待って、どの手を出したのは誰かをわからねばならず、負けた人には死んでもらわねばならない。使へる道具は原始的な再帰だけだ。これは分散DBである。
defmodule Person do defstruct pid: nil, name: 1, hand: :gu def make main, name do me = choose_hand %Person{pid: self(), name: name} loop main, me end def choose_hand me do hands = [:gu, :choki, :pa] hand = Enum.at hands, rem(:crypto.bytes_to_integer(:crypto.rand_bytes 1), Enum.count hands) %{me | hand: hand} end def loop main, me do receive do :choose_hand -> me = choose_hand me send main, {:ok_choose_hand, me} loop main, me :die -> nil end end end defmodule Jyanken do def prepare_persons pids do Enum.each pids, &(send &1, :choose_hand) prepare_persons pids, [] end def prepare_persons pids, persons do receive do {:ok_choose_hand, person} -> persons = [person | persons] if Enum.count(persons) == Enum.count(pids) do persons else prepare_persons pids, persons end end end def fighting persons, when_fight, when_end do persons = prepare_persons Enum.map persons, &(&1.pid) {winners, loosers} = fight persons draw? = Enum.count(loosers) == 0 when_fight.(persons, winners, draw?) Enum.each loosers, &(send &1.pid, :die) if Enum.count(winners) == 1 do when_end.(List.first winners) else fighting winners, when_fight, when_end end end defp fight persons do hands = Enum.uniq Enum.map persons, &(&1.hand) if Enum.count(hands) != 2 do {persons, []} else winners_hand = case Enum.sort hands do [:choki, :gu] -> :gu [:choki, :pa] -> :choki [:gu , :pa] -> :pa end Enum.partition persons, &(&1.hand == winners_hand) end end end hand_names = [ gu: "グー ", choki: "チョキ", pa: "パー ", ] main = self() pids = for n <- 1..5, do: spawn(Person, :make, [main, n]) persons = Jyanken.prepare_persons pids Jyanken.fighting persons, fn persons, winners, draw? -> Enum.each persons, fn person -> IO.write "#{person.name}: #{hand_names[person.hand]}\t" end IO.puts "" if draw? do IO.puts "\t引き分け" else IO.puts "\t勝ち: #{hand_names[List.first(winners).hand]}" end end, fn winner -> IO.puts "\n勝者: #{winner.name}" end
手をランダムに決定するには、Erlangのcryptoモジュールを使った。
cf. ElixirにてListのランダムな要素を取得する http://c4se.hatenablog.com/entry/2015/04/20/101926
Haskellは見栄えが気に入らず手を出しかねてゐる。
OTPを学べば再帰を振り囘さずもっと簡潔に書けるのかもしれない。学びたいものだ。
Ruby版
同じことをするRuby版を對照として載せておく。ただし非同期にはまったくしてゐないから、ずっと簡単だ。
#!/usr/bin/env ruby #coding=utf-8 class Person attr_reader :name attr_accessor :hand def initialize name @name = name end def choose_hand @hand = [:gu, :choki, :pa].sample end end class Turn attr_reader :winners_hand # @param [Person[]] persons def initialize persons @persons = persons fight end def draw? @is_draw || false end def winners @persons.select {|p| p.hand == @winners_hand } end private def fight hands = @persons.map(&:hand).uniq unless hands.length == 2 @is_draw = true else @winners_hand = case ([:gu, :choki, :pa] - hands)[0] when :gu then :choki when :choki then :pa when :pa then :gu else nil end end end end module Message HAND_NAMES = { gu: "グー ", choki: "チョキ", pa: "パー ", } # @param [Person[]] persons def self.start_turn persons puts persons.inject('') {|str, p| str + "#{p.name}: #{HAND_NAMES[p.hand]}\t" } end # @param [Turn] turn def self.fight turn if turn.draw? puts "\t引き分け" else puts "\t勝ち: #{HAND_NAMES[turn.winners_hand]}" end end # @param [Person] winner def self.end_game winner puts "\n勝者: #{winner.name}" end end persons = 5.times.map {|i| Person.new i } while persons.length > 1 persons.each &:choose_hand Message::start_turn persons turn = Turn.new persons persons = turn.winners unless turn.draw? Message::fight turn end Message::end_game persons[0]