| 1 | -module(life). |
| 2 | |
| 3 | -export([bang/1]). |
| 4 | |
| 5 | |
| 6 | -define(DIRECTIONS, ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']). |
| 7 | |
| 8 | -define(INTERVAL, 0). % In milliseconds |
| 9 | |
| 10 | -define(CHAR_DEAD, 32). % Space |
| 11 | -define(CHAR_ALIVE, 111). % o |
| 12 | -define(CHAR_BAR, 61). % = |
| 13 | |
| 14 | |
| 15 | %% ============================================================================ |
| 16 | %% Life processes |
| 17 | %% ============================================================================ |
| 18 | |
| 19 | %% ---------------------------------------------------------------------------- |
| 20 | %% Big bang |
| 21 | %% ---------------------------------------------------------------------------- |
| 22 | |
| 23 | bang([X, Y]) -> |
| 24 | bang(atom_to_integer(X), atom_to_integer(Y)). |
| 25 | |
| 26 | |
| 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 | [], |
| 78 | lists:sort( |
| 79 | fun({A, _}, {B, _}) -> |
| 80 | atom_to_integer(A) < atom_to_integer(B) |
| 81 | end, |
| 82 | StatePairs |
| 83 | ) |
| 84 | ), |
| 85 | ok = do_print_bar(X), |
| 86 | ok = do_print_states(X, States), |
| 87 | ok = do_print_bar(X), |
| 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), |
| 122 | MyParent ! {tock, {MyID, MyState}}, |
| 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 | |
| 150 | neighbor_id(Direction, X, ID) -> |
| 151 | ID + offset(Direction, X). |
| 152 | |
| 153 | |
| 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). |
| 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 | |
| 177 | ensure_negative(N) when N < 0 -> N; |
| 178 | ensure_negative(N) -> -(N). |
| 179 | |
| 180 | |
| 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 | |
| 202 | do_print_bar(X) -> |
| 203 | io:format("~s~n", [[?CHAR_BAR || _ <- lists:seq(1, X - 1)]]). |
| 204 | |
| 205 | |
| 206 | state_to_char(0) -> ?CHAR_DEAD; |
| 207 | state_to_char(1) -> ?CHAR_ALIVE. |