Commit | Line | Data |
---|---|---|
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 |
23 | bang([X, Y]) -> |
24 | bang(atom_to_integer(X), atom_to_integer(Y)). | |
25 | ||
26 | ||
3a74f872 SK |
27 | bang(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 | ||
65 | tick(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 | ||
73 | tock(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 | ||
91 | tock(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 | |
105 | cell(MyID, MyParent, MyNeighbors) -> | |
106 | MyState = crypto:rand_uniform(0, 2), | |
107 | cell(MyID, MyParent, MyNeighbors, MyState). | |
108 | ||
109 | ||
110 | cell(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 | |
119 | cell(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 | |
126 | cell(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 | ||
143 | new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0; | |
144 | new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1; | |
145 | new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0; | |
146 | new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1; | |
147 | new_state(State, _LiveNeighbors) -> State. | |
148 | ||
149 | ||
156f5e4a SK |
150 | neighbor_id(Direction, X, ID) -> |
151 | ID + offset(Direction, X). | |
3a74f872 SK |
152 | |
153 | ||
156f5e4a SK |
154 | offset('N' , X) -> ensure_negative(X); |
155 | offset('NE', X) -> ensure_negative(X - 1); | |
156 | offset('E' , _) -> 1; | |
157 | offset('SE', X) -> X + 1; | |
158 | offset('S' , X) -> X; | |
159 | offset('SW', X) -> X - 1; | |
160 | offset('W' , _) -> ensure_negative( 1); | |
161 | offset('NW', X) -> ensure_negative(X + 1). | |
3a74f872 SK |
162 | |
163 | ||
164 | filter_offsides(N, IDs) -> | |
165 | [ID || ID <- IDs, is_onside(N, atom_to_integer(ID))]. | |
166 | ||
167 | ||
168 | is_onside(_, ID) when ID < 1 -> false; | |
169 | is_onside(N, ID) when ID > N -> false; | |
170 | is_onside(_, _) -> true. | |
171 | ||
172 | ||
173 | %% ============================================================================ | |
174 | %% Plumbing | |
175 | %% ============================================================================ | |
176 | ||
156f5e4a SK |
177 | ensure_negative(N) when N < 0 -> N; |
178 | ensure_negative(N) -> -(N). | |
179 | ||
180 | ||
3a74f872 SK |
181 | atom_to_integer(Atom) -> |
182 | list_to_integer(atom_to_list(Atom)). | |
183 | ||
184 | ||
185 | integer_to_atom(Integer) -> | |
186 | list_to_atom(integer_to_list(Integer)). | |
187 | ||
188 | ||
189 | send_all([], _) -> ok; | |
190 | send_all([PID | PIDs], Msg) -> | |
191 | PID ! Msg, | |
192 | send_all(PIDs, Msg). | |
193 | ||
194 | ||
195 | do_print_states(_, []) -> ok; | |
196 | do_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 | 202 | do_print_bar(X) -> |
156f5e4a | 203 | io:format("~s~n", [[?CHAR_BAR || _ <- lists:seq(1, X - 1)]]). |
a3eea437 SK |
204 | |
205 | ||
3a74f872 SK |
206 | state_to_char(0) -> ?CHAR_DEAD; |
207 | state_to_char(1) -> ?CHAR_ALIVE. |