X=`stty size | awk '{print $2}'`
-erlc life.erl
-erl -noshell -s life bang $X $Y
+erl \
+ -noshell \
+ -pa ebin \
+ -life x $X y $Y \
+ -s life bang
+++ /dev/null
--module(life).
-
--export([bang/1]).
-
-
--define(DIRECTIONS, ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']).
-
--define(INTERVAL, 0). % In milliseconds
-
--define(CHAR_DEAD, 32). % Space
--define(CHAR_ALIVE, 111). % o
--define(CHAR_BAR, 61). % =
-
-
-%% ============================================================================
-%% Life processes
-%% ============================================================================
-
-%% ----------------------------------------------------------------------------
-%% Big bang
-%% ----------------------------------------------------------------------------
-
-bang([X, Y]) ->
- bang(atom_to_integer(X), atom_to_integer(Y)).
-
-
-bang(X, Y) ->
- N = X * Y,
- CellIDs = lists:seq(1, N),
-
- Graph =
- lists:foldl(
- fun(ID, Pairs) ->
- Neighbors = [
- integer_to_atom(neighbor_id(D, X, ID))
- || D <- ?DIRECTIONS
- ],
- [{integer_to_atom(ID), Neighbors} | Pairs]
- end,
- [],
- CellIDs
- ),
-
- Parent = self(),
-
- lists:foreach(
- fun({ID, Neighbors}) ->
- register(
- ID,
- spawn(fun() -> cell(ID, Parent, Neighbors) end)
- )
- end,
- [{ID, filter_offsides(N, Neighbors)} || {ID, Neighbors} <- Graph]
- ),
-
- CellNames = [integer_to_atom(ID) || ID <- CellIDs],
-
- tick(X, CellNames).
-
-
-%% ----------------------------------------------------------------------------
-%% Tick / tock
-%% ----------------------------------------------------------------------------
-
-tick(X, Cells) ->
- ok = send_all(Cells, {tick, self()}),
- All = Cells,
- Pending = Cells,
- StatePairs = [],
- tock(X, All, Pending, StatePairs).
-
-
-tock(X, All, [], StatePairs) ->
- States =
- lists:foldl(
- fun({_ID, State}, States) -> [State | States] end,
- [],
- lists:sort(
- fun({A, _}, {B, _}) ->
- atom_to_integer(A) < atom_to_integer(B)
- end,
- StatePairs
- )
- ),
- ok = do_print_bar(X),
- ok = do_print_states(X, States),
- ok = do_print_bar(X),
- ok = timer:sleep(?INTERVAL),
- tick(X, All);
-
-tock(X, All, Pending, StatePairs) ->
- receive
- {tock, {ID, State}} ->
- NewPending = lists:delete(ID, Pending),
- NewStatePairs = [{ID, State} | StatePairs],
- tock(X, All, NewPending, NewStatePairs)
- end.
-
-
-%% ----------------------------------------------------------------------------
-%% Cell
-%% ----------------------------------------------------------------------------
-
-% Init
-cell(MyID, MyParent, MyNeighbors) ->
- MyState = crypto:rand_uniform(0, 2),
- cell(MyID, MyParent, MyNeighbors, MyState).
-
-
-cell(MyID, MyParent, MyNeighbors, MyState) ->
- receive
- {tick, MyParent} ->
- ok = send_all(MyNeighbors, {request_state, MyID}),
- cell(MyID, MyParent, MyNeighbors, MyState, {MyNeighbors, []})
- end.
-
-
-% All neighbors replied
-cell(MyID, MyParent, MyNeighbors, MyState, {[], States}) ->
- LiveNeighbors = lists:sum(States),
- MyNewState = new_state(MyState, LiveNeighbors),
- MyParent ! {tock, {MyID, MyState}},
- cell(MyID, MyParent, MyNeighbors, MyNewState);
-
-% Awaiting requests and replies
-cell(MyID, MyParent, MyNeighbors, MyState, {Pending, States}) ->
- receive
- {request_state, ID} ->
- ID ! {response_state, MyID, MyState},
- cell(MyID, MyParent, MyNeighbors, MyState, {Pending, States});
-
- {response_state, ID, State} ->
- NewPending = lists:delete(ID, Pending),
- NewStates = [State | States],
- cell(MyID, MyParent, MyNeighbors, MyState, {NewPending, NewStates})
- end.
-
-
-%% ============================================================================
-%% Rules
-%% ============================================================================
-
-new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0;
-new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1;
-new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0;
-new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1;
-new_state(State, _LiveNeighbors) -> State.
-
-
-neighbor_id(Direction, X, ID) ->
- ID + offset(Direction, X).
-
-
-offset('N' , X) -> ensure_negative(X);
-offset('NE', X) -> ensure_negative(X - 1);
-offset('E' , _) -> 1;
-offset('SE', X) -> X + 1;
-offset('S' , X) -> X;
-offset('SW', X) -> X - 1;
-offset('W' , _) -> ensure_negative( 1);
-offset('NW', X) -> ensure_negative(X + 1).
-
-
-filter_offsides(N, IDs) ->
- [ID || ID <- IDs, is_onside(N, atom_to_integer(ID))].
-
-
-is_onside(_, ID) when ID < 1 -> false;
-is_onside(N, ID) when ID > N -> false;
-is_onside(_, _) -> true.
-
-
-%% ============================================================================
-%% Plumbing
-%% ============================================================================
-
-ensure_negative(N) when N < 0 -> N;
-ensure_negative(N) -> -(N).
-
-
-atom_to_integer(Atom) ->
- list_to_integer(atom_to_list(Atom)).
-
-
-integer_to_atom(Integer) ->
- list_to_atom(integer_to_list(Integer)).
-
-
-send_all([], _) -> ok;
-send_all([PID | PIDs], Msg) ->
- PID ! Msg,
- send_all(PIDs, Msg).
-
-
-do_print_states(_, []) -> ok;
-do_print_states(X, States) ->
- {XStates, RestStates} = lists:split(X, States),
- ok = io:format([state_to_char(S) || S <- XStates] ++ "\n"),
- do_print_states(X, RestStates).
-
-
-do_print_bar(X) ->
- io:format("~s~n", [[?CHAR_BAR || _ <- lists:seq(1, X - 1)]]).
-
-
-state_to_char(0) -> ?CHAR_DEAD;
-state_to_char(1) -> ?CHAR_ALIVE.
--- /dev/null
+-module(cell).
+-behaviour(gen_server).
+
+
+%% API
+-export([start_link/1]).
+
+%% Callbacks
+-export([init/1
+ ,handle_call/3
+ ,handle_cast/2
+ ,handle_info/2
+ ,terminate/2
+ ,code_change/3
+ ]).
+
+
+-record(state, {id :: integer()
+ ,name :: string()
+ ,cell_state :: 0 | 1
+ ,neighbors :: list(atom())
+ ,live_neighbors :: integer()
+ ,num_neighbors :: integer()
+ ,replies_pending :: integer()
+ }).
+
+
+%% ============================================================================
+%% API
+%% ============================================================================
+
+start_link({_ID, Name, _NeighborNames}=Datum) ->
+ ServerName = {local, Name},
+ Args = [Datum],
+ Opts = [],
+ gen_server:start_link(ServerName, ?MODULE, Args, Opts).
+
+
+%% ============================================================================
+%% Callbacks
+%% ============================================================================
+
+init([{ID, Name, NeighborNames}]) ->
+ State = #state{id=ID
+ ,name=Name
+ ,cell_state=crypto:rand_uniform(0, 2)
+ ,neighbors=NeighborNames
+ ,num_neighbors=length(NeighborNames)
+ ,live_neighbors=0
+ ,replies_pending=0
+ },
+ {ok, State}.
+
+
+terminate(_Reason, State) ->
+ {ok, State}.
+
+
+code_change(_Old, State, _Other) ->
+ {ok, State}.
+
+
+handle_call(_Msg, _From, State) ->
+ {reply, ok, State}.
+
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+handle_info(tick,
+ #state{name=Name
+ ,neighbors=Neighbors
+ ,num_neighbors=NumNeighbors
+ }=State) ->
+
+ ok = send_all(Neighbors, {request_state, Name}),
+ {noreply, State#state{replies_pending=NumNeighbors}};
+
+
+handle_info({request_state, Requester}, State) ->
+ Requester ! {response_state, State#state.cell_state},
+ {noreply, State};
+
+
+handle_info({response_state, NeighborState},
+ #state{id=ID
+ ,replies_pending=Pending
+ ,cell_state=CellState
+ ,live_neighbors=LiveNeighbors
+ }=State) ->
+
+ NewPending = Pending - 1,
+ NewLiveNeighbors = LiveNeighbors + NeighborState,
+
+ NewState = State#state{replies_pending=NewPending
+ ,live_neighbors=NewLiveNeighbors
+ },
+
+ case NewPending of
+ 0 ->
+ NewCellState = new_state(CellState, NewLiveNeighbors),
+ ok = time:cast({tock, {ID, NewCellState}}),
+
+ {noreply, NewState#state{live_neighbors=0
+ ,cell_state=NewCellState
+ }};
+
+ _N ->
+ {noreply, NewState}
+ end;
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+
+handle_info(_Msg, State) ->
+ {noreply, State}.
+
+
+%% ============================================================================
+%% Internal
+%% ============================================================================
+
+send_all([], _) -> ok;
+send_all([PID | PIDs], Msg) ->
+ PID ! Msg,
+ send_all(PIDs, Msg).
+
+
+new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0;
+new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1;
+new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0;
+new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1;
+new_state(State, _LiveNeighbors) -> State.
--- /dev/null
+-module(god).
+-behaviour(supervisor).
+
+
+%% API
+-export([start_link/2]).
+
+%% Callbacks
+-export([init/1]).
+
+
+%% Helper macro for declaring children of supervisor
+-define(CHILD(Type, I, Args), {I, {I, start_link, Args}, permanent, 5000, Type, [I]}).
+
+
+%% ============================================================================
+%% API
+%% ============================================================================
+
+start_link(X, CellData) ->
+ supervisor:start_link({local, ?MODULE}, ?MODULE, [X, CellData]).
+
+
+%% ============================================================================
+%% Callbacks
+%% ============================================================================
+
+init([X, CellData]) ->
+ CellNames = [Name || {_, Name, _} <- CellData],
+ RestartStrategy = {one_for_one, 5, 10},
+ Cells = [spec_cell(Datum) || Datum <- CellData],
+ Time = ?CHILD(worker, time, [X, CellNames]),
+ Children = Cells ++ [Time],
+ {ok, {RestartStrategy, Children}}.
+
+
+spec_cell({_, Name, _}=Datum) ->
+ M = cell,
+ F = start_link,
+ A = [Datum],
+ {Name, {M, F, A}, permanent, 5000, worker, [M]}.
+
+ %{ID, {ID, start_link, Args}, permanent, 5000, Type, [ID]}.
+
+
+
+
--- /dev/null
+{application, life,
+ [
+ {description, ""},
+ {vsn, "1"},
+ {registered, []},
+ {applications, [
+ kernel,
+ stdlib
+ ]},
+ {mod, { life, []}},
+ {env, []}
+ ]}.
--- /dev/null
+-module(life).
+-behaviour(application).
+
+
+%% API
+-export([bang/0]).
+
+%% Callbacks
+-export([start/2, stop/1]).
+
+
+-define(DIRECTIONS, ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']).
+
+
+%% ============================================================================
+%% API
+%% ============================================================================
+
+bang() ->
+ application:start(?MODULE).
+
+
+%% ============================================================================
+%% Callbacks
+%% ============================================================================
+
+start(_StartType, _StartArgs) ->
+ {ok, X} = application:get_env(?MODULE, x),
+ {ok, Y} = application:get_env(?MODULE, y),
+ CellData = cell_data(X, Y),
+ god:start_link(X, CellData).
+
+
+stop(_State) ->
+ ok.
+
+
+%% ============================================================================
+%% Internal
+%% ============================================================================
+
+cell_data(X, Y) ->
+ N = X * Y,
+ [cell_datum(X, N, ID) || ID <- lists:seq(1, N)].
+
+
+cell_datum(X, N, ID) ->
+ Name = integer_to_atom(ID),
+ NeighborNames = filter_offsides(N,
+ [integer_to_atom(neighbor_id(Dir, X, ID)) || Dir <- ?DIRECTIONS]
+ ),
+ {ID, Name, NeighborNames}.
+
+
+neighbor_id(Direction, X, ID) -> ID + offset(Direction, X).
+
+
+offset('N' , X) -> ensure_negative(X);
+offset('NE', X) -> ensure_negative(X - 1);
+offset('E' , _) -> 1;
+offset('SE', X) -> X + 1;
+offset('S' , X) -> X;
+offset('SW', X) -> X - 1;
+offset('W' , _) -> ensure_negative( 1);
+offset('NW', X) -> ensure_negative(X + 1).
+
+
+ensure_negative(N) when N < 0 -> N;
+ensure_negative(N) -> -(N).
+
+
+filter_offsides(N, IDs) ->
+ [ID || ID <- IDs, is_onside(N, atom_to_integer(ID))].
+
+
+is_onside(_, ID) when ID < 1 -> false;
+is_onside(N, ID) when ID > N -> false;
+is_onside(_, _) -> true.
+
+
+atom_to_integer(Atom) ->
+ list_to_integer(atom_to_list(Atom)).
+
+
+integer_to_atom(Integer) ->
+ list_to_atom(integer_to_list(Integer)).
--- /dev/null
+-module(time).
+-behaviour(gen_server).
+
+
+%% API
+-export([start_link/2
+ ,cast/1
+ ]).
+
+%% Callbacks
+-export([init/1
+ ,handle_call/3
+ ,handle_cast/2
+ ,handle_info/2
+ ,terminate/2
+ ,code_change/3
+ ]).
+
+
+-define(INTERVAL, 0). % In milliseconds
+
+-define(CHAR_DEAD, 32). % Space
+-define(CHAR_ALIVE, 111). % o
+-define(CHAR_BAR, 61). % =
+
+
+-record(state, {x :: integer()
+ ,cells :: list(atom())
+ ,num_cells :: integer()
+ ,state_pairs :: list(tuple(integer(), integer())) | []
+ ,replies_pending :: integer()
+ }).
+
+
+%% ============================================================================
+%% API
+%% ============================================================================
+
+start_link(X, Cells) ->
+ ServerName = {local, ?MODULE},
+ Args = [X, Cells],
+ Opts = [],
+ gen_server:start_link(ServerName, ?MODULE, Args, Opts).
+
+
+cast(Msg) ->
+ gen_server:cast(?MODULE, Msg).
+
+
+%% ============================================================================
+%% Callbacks
+%% ============================================================================
+
+init([X, Cells]) ->
+ State = #state{x=X
+ ,cells=Cells
+ ,num_cells=length(Cells)
+ ,state_pairs=[]
+ ,replies_pending=0
+ },
+ cast(next_tick),
+ {ok, State}.
+
+
+terminate(_Reason, State) ->
+ {ok, State}.
+
+
+code_change(_Old, State, _Other) ->
+ {ok, State}.
+
+
+handle_call(_Msg, _From, State) ->
+ {reply, ok, State}.
+
+
+handle_cast(next_tick, #state{cells=Cells, num_cells=NumCells, state_pairs=[]}=State) ->
+ ok = send_all(Cells, tick),
+ {noreply, State#state{replies_pending=NumCells}};
+
+handle_cast({tock, {ID, CellState}},
+ #state{x=X
+ ,state_pairs=StatePairs
+ ,replies_pending=RepliesPending
+ }=State) ->
+
+ NewStatePairs = [{ID, CellState} | StatePairs],
+ NewRepliesPending = RepliesPending - 1,
+ NewState = State#state{replies_pending=NewRepliesPending},
+
+ case NewRepliesPending of
+ 0 ->
+ SortedStatePairs = lists:sort(NewStatePairs),
+ StateChars = [state_to_char(S) || {_, S} <- SortedStatePairs],
+ ok = do_print_bar(X),
+ ok = do_print_state_chars(X, StateChars),
+ ok = do_print_bar(X),
+ ok = timer:sleep(?INTERVAL),
+ cast(next_tick),
+ {noreply, NewState#state{state_pairs=[]}};
+
+ _N ->
+ {noreply, NewState#state{state_pairs=NewStatePairs}}
+ end;
+
+handle_cast(_Msg, State) ->
+ {noreply, State}.
+
+
+handle_info(_Msg, State) ->
+ {noreply, State}.
+
+
+%% ============================================================================
+%% Internal
+%% ============================================================================
+
+send_all([], _) -> ok;
+send_all([PID | PIDs], Msg) ->
+ PID ! Msg,
+ send_all(PIDs, Msg).
+
+
+state_to_char(0) -> ?CHAR_DEAD;
+state_to_char(1) -> ?CHAR_ALIVE.
+
+
+do_print_state_chars(_, []) -> ok;
+do_print_state_chars(X, Chars) ->
+ {XChars, RestChars} = lists:split(X, Chars),
+ ok = io:format([XChars, $\n]),
+ do_print_state_chars(X, RestChars).
+
+
+do_print_bar(X) ->
+ io:format("~s~n", [[?CHAR_BAR || _ <- lists:seq(1, X - 1)]]).