c4se記:さっちゃんですよ☆

.。oO(さっちゃんですよヾ(〃l _ l)ノ゙☆)

.。oO(此のblogは、主に音樂考察Programming に分類されますよ。ヾ(〃l _ l)ノ゙♬♪♡)

音樂は SoundCloud に公開中です。

考察は現在は主に Scrapbox で公表中です。

Programming は GitHub で開發中です。

Elixirで分散じゃんけんをシミュレートする

じゃんけんをシミュレートしてみる。練習問題だ。
人間を五人ほど作り、グー/チョキ/パーのなにを出させるかをそれぞれに決めさせ、勝負する。引き分けを考慮し、勝者が最後の一人になるまで繰り返す。
せっかく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]