RosettaCodeData/Task/Wireworld/Elixir/wireworld.ex

85 lines
2.3 KiB
Elixir

defmodule Wireworld do
@empty " "
@head "H"
@tail "t"
@conductor "."
@neighbours (for x<- -1..1, y <- -1..1, do: {x,y}) -- [{0,0}]
def set_up(string) do
lines = String.split(string, "\n", trim: true)
grid = Enum.with_index(lines)
|> Enum.flat_map(fn {line,i} ->
String.codepoints(line)
|> Enum.with_index
|> Enum.map(fn {char,j} -> {{i, j}, char} end)
end)
|> Enum.into(Map.new)
width = Enum.map(lines, fn line -> String.length(line) end) |> Enum.max
height = length(lines)
{grid, width, height}
end
# to string
defp to_s(grid, width, height) do
Enum.map_join(0..height-1, fn i ->
Enum.map_join(0..width-1, fn j -> Map.get(grid, {i,j}, @empty) end) <> "\n"
end)
end
# transition all cells simultaneously
defp transition(grid) do
Enum.into(grid, Map.new, fn {{x, y}, state} ->
{{x, y}, transition_cell(grid, state, x, y)}
end)
end
# how to transition a single cell
defp transition_cell(grid, current, x, y) do
case current do
@empty -> @empty
@head -> @tail
@tail -> @conductor
_ -> if neighbours_with_state(grid, x, y) in 1..2, do: @head, else: @conductor
end
end
# given a position in the grid, find the neighbour cells with a particular state
def neighbours_with_state(grid, x, y) do
Enum.count(@neighbours, fn {dx,dy} -> Map.get(grid, {x+dx, y+dy}) == @head end)
end
# run a simulation up to a limit of transitions, or until a recurring
# pattern is found
# This will print text to the console
def run(string, iterations\\25) do
{grid, width, height} = set_up(string)
Enum.reduce(0..iterations, {grid, %{}}, fn count,{grd, seen} ->
IO.puts "Generation : #{count}"
IO.puts to_s(grd, width, height)
if seen[grd] do
IO.puts "I've seen this grid before... after #{count} iterations"
exit(:normal)
else
{transition(grd), Map.put(seen, grd, count)}
end
end)
IO.puts "ran through #{iterations} iterations"
end
end
# this is the "2 Clock generators and an XOR gate" example from the wikipedia page
text = """
......tH
. ......
...Ht... .
....
. .....
....
tH...... .
. ......
...Ht...
"""
Wireworld.run(text)