👾

Elixirでライフゲームを作ってみた

2022/08/17に公開

Elixirでライフゲームを作ったことがそういえばなかった気がしたので作ってみました。

https://ja.wikipedia.org/wiki/ライフゲーム

作ったもの

lifame.exs
defmodule LifeGame do
  @x_size 10
  @y_size 10

  def start() do
    board = for y <- 1..@y_size, x <- 1..@x_size, into: %{}, do: {{x, y}, :rand.uniform(2) - 1}
    tick(board)
  end

  def tick(board) do
    board = update(board)
    print(board)

    :timer.sleep(1000)
    tick(board)
  end

  defp update(board) do
    (for y <- 1..@y_size, x <- 1..@x_size, do: {x, y})
    |> Enum.map(fn pos ->
        case {Map.get(board, pos), count_lives(board, pos)} do
          {0, 3} ->
            {pos, 1}
          {1, lives} when lives <= 1 or lives >= 4 ->
            {pos, 0}
          {cell, _} = _other -> 
            {pos, cell}
        end
    end)
    |> Map.new()
  end

  defp count_lives(board, {x, y}) do
    for dx <- -1..1, dy <- -1..1, {dx, dy} != {0, 0} do
      Map.get(board, {x + dx, y + dy}, 0)
    end
    |> Enum.sum()
  end

  defp print(board) when is_map(board) do
    IO.puts(IO.ANSI.clear())
    for y <- 1..@y_size do
      (for x <- 1..@x_size, do: Map.get(board, {x, y}))
      |> Enum.map(fn
        0 -> "□"
        1 -> "■"
      end)
      |> Enum.join("")
    end
    |> Enum.join("\n")
    |> IO.puts()
  end
end

LifeGame.start()

https://replit.com/@tamanugi/ElixirLifeGame?v=1

ライフゲームを実装する際はボードを二次元配列にする場合が多いと思いますが、Elixirの場合はMapのキーにタプルを指定できるので比較的シンプルにすることができます✨
(パフォーマンス的にはそこまでよくないはず?)

隣接8方向を見るところは以下のように書いています

    for dx <- -1..1, dy <- -1..1, {dx, dy} != {0, 0} do
      Map.get(board, {x + dx, y + dy}, 0)
    end

Elixirのforは条件文を書くことができるのが地味に便利です。
またタプルを使うことで条件もわかりやすく書くことができました。

ボードを表示する関数 print/1 の最初に以下のようなコードを入れています
IO.puts(IO.ANSI.clear())

Elixirは意外にも?標準出力をおしゃれにするためのモジュールIO.ANSIがあり、標準出力に色をつけたりすることも可能です。
IO.ANSI.clear() はCSIのErase in Display \e[2J を返してくれます。
これをIO.puts() で出力することによってコンソールをクリアすることができます。

おわり

今回はLiveBookを使ってコーディングしてみました。
さくっとElixirコードを書けるのでとても便利でした✨

また気が向いたら今度はLiveViewでGUIありで作成してみたいと思います 💪

Discussion