Last active
August 29, 2015 14:08
-
-
Save tallakt/c7bfa6766f3731d2f35f to your computer and use it in GitHub Desktop.
Conways Game of Life in Elixir
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env elixir | |
# https://elixirquiz.github.io/2014-11-01-game-of-life.html | |
defmodule Conway do | |
defstruct lookup: HashDict.new, rows: 0, cols: 0 | |
def run_file(filename) do | |
case load_file(filename) do | |
{:ok, initial_state} -> | |
run initial_state | |
{:error, reason} -> | |
IO.puts reason | |
end | |
end | |
defp load_file(filename) do | |
lines = File.stream!(filename) |> Enum.take(41) | |
case check_number_of_input_lines(lines) do | |
{:ok, row_count} -> | |
stripped = Enum.map(lines, fn s -> String.replace s, ~r/\r?\n$/, "" end) | |
case check_all_lines_good_length(stripped) do | |
{:ok, column_count} -> | |
case check_line_contents(stripped) do | |
:ok -> | |
lookup = build_lookup_table_from_strings stripped | |
{:ok, %Conway{lookup: lookup, rows: row_count, cols: column_count}} | |
err -> | |
err | |
end | |
err -> | |
err | |
end | |
err -> | |
err | |
end | |
end | |
defp build_lookup_table_from_strings(lines) do | |
lines | |
|> Enum.with_index | |
|> Enum.reduce(HashDict.new, fn {row, row_number}, acc -> | |
row | |
|> String.codepoints | |
|> Enum.with_index | |
|> Enum.reduce(acc, fn {char, column_number}, inner_acc -> | |
if char == "#" do | |
Dict.put inner_acc, {row_number, column_number}, :alive | |
else | |
inner_acc | |
end | |
end) | |
end) | |
end | |
defp check_number_of_input_lines(lines) do | |
case Enum.count(lines) do | |
n when n in 5..40 -> | |
{:ok, n} | |
_ -> | |
{:error, "Number of lines in input must be between 5 and 40"} | |
end | |
end | |
defp check_all_lines_good_length(lines) do | |
length = lines |> Enum.at(0) |> String.length | |
cond do | |
Enum.any?(lines, &(String.length(&1) != length)) -> | |
{:error, "The lines in the file are not all same length" } | |
length in 5..40 -> | |
{:ok, length} | |
true -> | |
{:error, "Lines must be of length 4 to 40"} | |
end | |
end | |
defp check_line_contents(lines) do | |
if lines |> Enum.all?(fn s -> String.match? s, ~r/^[ #]+$/ end) do | |
:ok | |
else | |
{:error, "Lines must consist of spaces and #"} | |
end | |
end | |
def run(initial_state) do | |
states = Stream.unfold initial_state, fn acc -> {acc, next_step(acc)} end | |
timer = Stream.timer(500) |> Stream.cycle | |
Stream.zip(states, timer) | |
|> Enum.each(fn {s, _} -> print(s) end) | |
end | |
def next_step(conway) do | |
new_lookup = | |
conway | |
|> grid | |
|> Enum.reduce(HashDict.new, fn {row, col}, acc -> | |
update_single_grid_cell(conway, row, col, acc) | |
end) | |
%{conway | lookup: new_lookup} | |
end | |
defp update_single_grid_cell(conway, row, col, acc) do | |
live_neighbors = | |
neighbors_for(row, col) | |
|> Enum.map(fn neighbor -> Dict.get(conway.lookup, neighbor) end) | |
|> Enum.count(fn x -> x end) | |
#require IEx | |
#if Dict.get(conway.lookup, {row, col}), do: IEx.pry | |
case live_neighbors do | |
x when x in 0..1 -> # too few, dies | |
acc | |
x when x == 2 -> # continue living | |
Dict.put acc, {row, col}, Dict.get(conway.lookup, {row, col}) | |
x when x == 3 -> # reproduction / continue living | |
Dict.put acc, {row, col}, :alive | |
_ -> # overpopulation | |
acc | |
end | |
end | |
defp neighbors_for(row, col) do | |
rr = row - 1 .. row + 1 | |
cc = col - 1 .. col + 1 | |
for a <- rr, b <- cc, {a, b} != {row, col}, do: {a, b} | |
end | |
defp grid(conway) do | |
for r <- 1..conway.rows, c <- 1..conway.cols, do: {r, c} | |
end | |
def print(conway) do | |
IO.ANSI.clear |> IO.puts | |
IO.ANSI.home |> IO.puts | |
(1..conway.rows) |> Enum.each(fn row -> print_line conway, row end) | |
end | |
defp print_line(conway, row) do | |
(1..conway.cols) | |
|> Enum.map(fn col -> if Dict.get(conway.lookup, {row, col}), do: "#", else: " " end) | |
|> Enum.join | |
|> IO.puts | |
end | |
end | |
Conway.run_file hd(System.argv) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment