| 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. |