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