From: Siraaj Khandkar Date: Mon, 23 Jul 2012 08:13:23 +0000 (-0400) Subject: Converted to OTP app. X-Git-Url: https://git.xandkar.net/?a=commitdiff_plain;h=d2a0e2f9319260ac266296451915776f3399fb70;p=cellular-automata.git Converted to OTP app. --- diff --git a/001/.gitignore b/001/.gitignore new file mode 100644 index 0000000..cfb2329 --- /dev/null +++ b/001/.gitignore @@ -0,0 +1 @@ +ebin diff --git a/001/life b/001/bin/life similarity index 55% rename from 001/life rename to 001/bin/life index d653da0..87edae8 100755 --- a/001/life +++ b/001/bin/life @@ -5,5 +5,8 @@ Y=`expr \`stty size | awk '{print $1}'\` - 3` 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 diff --git a/001/life.erl b/001/life.erl deleted file mode 100644 index c44674d..0000000 --- a/001/life.erl +++ /dev/null @@ -1,207 +0,0 @@ --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. diff --git a/001/src/cell.erl b/001/src/cell.erl new file mode 100644 index 0000000..6090fa0 --- /dev/null +++ b/001/src/cell.erl @@ -0,0 +1,136 @@ +-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. diff --git a/001/src/god.erl b/001/src/god.erl new file mode 100644 index 0000000..3922680 --- /dev/null +++ b/001/src/god.erl @@ -0,0 +1,47 @@ +-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]}. + + + + diff --git a/001/src/life.app.src b/001/src/life.app.src new file mode 100644 index 0000000..7514f82 --- /dev/null +++ b/001/src/life.app.src @@ -0,0 +1,12 @@ +{application, life, + [ + {description, ""}, + {vsn, "1"}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, { life, []}}, + {env, []} + ]}. diff --git a/001/src/life.erl b/001/src/life.erl new file mode 100644 index 0000000..c59b98b --- /dev/null +++ b/001/src/life.erl @@ -0,0 +1,86 @@ +-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)). diff --git a/001/src/time.erl b/001/src/time.erl new file mode 100644 index 0000000..d710926 --- /dev/null +++ b/001/src/time.erl @@ -0,0 +1,136 @@ +-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)]]).