Start script. Sets size of the board to match terminal.
[cellular-automata.git] / 001 / life.erl
CommitLineData
3a74f872
SK
1-module(life).
2
156f5e4a 3-export([bang/1]).
3a74f872
SK
4
5
6-define(DIRECTIONS, ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']).
a3eea437 7
3a74f872 8-define(INTERVAL, 0). % In milliseconds
a3eea437 9
156f5e4a
SK
10-define(CHAR_DEAD, 32). % Space
11-define(CHAR_ALIVE, 111). % o
12-define(CHAR_BAR, 61). % =
3a74f872
SK
13
14
15%% ============================================================================
16%% Life processes
17%% ============================================================================
18
19%% ----------------------------------------------------------------------------
20%% Big bang
21%% ----------------------------------------------------------------------------
22
156f5e4a
SK
23bang([X, Y]) ->
24 bang(atom_to_integer(X), atom_to_integer(Y)).
25
26
3a74f872
SK
27bang(X, Y) ->
28 N = X * Y,
29 CellIDs = lists:seq(1, N),
30
31 Graph =
32 lists:foldl(
33 fun(ID, Pairs) ->
34 Neighbors = [
35 integer_to_atom(neighbor_id(D, X, ID))
36 || D <- ?DIRECTIONS
37 ],
38 [{integer_to_atom(ID), Neighbors} | Pairs]
39 end,
40 [],
41 CellIDs
42 ),
43
44 Parent = self(),
45
46 lists:foreach(
47 fun({ID, Neighbors}) ->
48 register(
49 ID,
50 spawn(fun() -> cell(ID, Parent, Neighbors) end)
51 )
52 end,
53 [{ID, filter_offsides(N, Neighbors)} || {ID, Neighbors} <- Graph]
54 ),
55
56 CellNames = [integer_to_atom(ID) || ID <- CellIDs],
57
58 tick(X, CellNames).
59
60
61%% ----------------------------------------------------------------------------
62%% Tick / tock
63%% ----------------------------------------------------------------------------
64
65tick(X, Cells) ->
66 ok = send_all(Cells, {tick, self()}),
67 All = Cells,
68 Pending = Cells,
69 StatePairs = [],
70 tock(X, All, Pending, StatePairs).
71
72
73tock(X, All, [], StatePairs) ->
74 States =
75 lists:foldl(
76 fun({_ID, State}, States) -> [State | States] end,
77 [],
156f5e4a
SK
78 lists:sort(
79 fun({A, _}, {B, _}) ->
80 atom_to_integer(A) < atom_to_integer(B)
81 end,
82 StatePairs
83 )
3a74f872 84 ),
a3eea437 85 ok = do_print_bar(X),
3a74f872 86 ok = do_print_states(X, States),
a3eea437 87 ok = do_print_bar(X),
3a74f872
SK
88 ok = timer:sleep(?INTERVAL),
89 tick(X, All);
90
91tock(X, All, Pending, StatePairs) ->
92 receive
93 {tock, {ID, State}} ->
94 NewPending = lists:delete(ID, Pending),
95 NewStatePairs = [{ID, State} | StatePairs],
96 tock(X, All, NewPending, NewStatePairs)
97 end.
98
99
100%% ----------------------------------------------------------------------------
101%% Cell
102%% ----------------------------------------------------------------------------
103
104% Init
105cell(MyID, MyParent, MyNeighbors) ->
106 MyState = crypto:rand_uniform(0, 2),
107 cell(MyID, MyParent, MyNeighbors, MyState).
108
109
110cell(MyID, MyParent, MyNeighbors, MyState) ->
111 receive
112 {tick, MyParent} ->
113 ok = send_all(MyNeighbors, {request_state, MyID}),
114 cell(MyID, MyParent, MyNeighbors, MyState, {MyNeighbors, []})
115 end.
116
117
118% All neighbors replied
119cell(MyID, MyParent, MyNeighbors, MyState, {[], States}) ->
120 LiveNeighbors = lists:sum(States),
121 MyNewState = new_state(MyState, LiveNeighbors),
0ede9127 122 MyParent ! {tock, {MyID, MyState}},
3a74f872
SK
123 cell(MyID, MyParent, MyNeighbors, MyNewState);
124
125% Awaiting requests and replies
126cell(MyID, MyParent, MyNeighbors, MyState, {Pending, States}) ->
127 receive
128 {request_state, ID} ->
129 ID ! {response_state, MyID, MyState},
130 cell(MyID, MyParent, MyNeighbors, MyState, {Pending, States});
131
132 {response_state, ID, State} ->
133 NewPending = lists:delete(ID, Pending),
134 NewStates = [State | States],
135 cell(MyID, MyParent, MyNeighbors, MyState, {NewPending, NewStates})
136 end.
137
138
139%% ============================================================================
140%% Rules
141%% ============================================================================
142
143new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0;
144new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1;
145new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0;
146new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1;
147new_state(State, _LiveNeighbors) -> State.
148
149
156f5e4a
SK
150neighbor_id(Direction, X, ID) ->
151 ID + offset(Direction, X).
3a74f872
SK
152
153
156f5e4a
SK
154offset('N' , X) -> ensure_negative(X);
155offset('NE', X) -> ensure_negative(X - 1);
156offset('E' , _) -> 1;
157offset('SE', X) -> X + 1;
158offset('S' , X) -> X;
159offset('SW', X) -> X - 1;
160offset('W' , _) -> ensure_negative( 1);
161offset('NW', X) -> ensure_negative(X + 1).
3a74f872
SK
162
163
164filter_offsides(N, IDs) ->
165 [ID || ID <- IDs, is_onside(N, atom_to_integer(ID))].
166
167
168is_onside(_, ID) when ID < 1 -> false;
169is_onside(N, ID) when ID > N -> false;
170is_onside(_, _) -> true.
171
172
173%% ============================================================================
174%% Plumbing
175%% ============================================================================
176
156f5e4a
SK
177ensure_negative(N) when N < 0 -> N;
178ensure_negative(N) -> -(N).
179
180
3a74f872
SK
181atom_to_integer(Atom) ->
182 list_to_integer(atom_to_list(Atom)).
183
184
185integer_to_atom(Integer) ->
186 list_to_atom(integer_to_list(Integer)).
187
188
189send_all([], _) -> ok;
190send_all([PID | PIDs], Msg) ->
191 PID ! Msg,
192 send_all(PIDs, Msg).
193
194
195do_print_states(_, []) -> ok;
196do_print_states(X, States) ->
197 {XStates, RestStates} = lists:split(X, States),
198 ok = io:format([state_to_char(S) || S <- XStates] ++ "\n"),
199 do_print_states(X, RestStates).
200
201
a3eea437 202do_print_bar(X) ->
156f5e4a 203 io:format("~s~n", [[?CHAR_BAR || _ <- lists:seq(1, X - 1)]]).
a3eea437
SK
204
205
3a74f872
SK
206state_to_char(0) -> ?CHAR_DEAD;
207state_to_char(1) -> ?CHAR_ALIVE.
This page took 0.044153 seconds and 4 git commands to generate.