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