74868da09e63fe754a3bb5938080eead4d11e432
[cellular-automata.git] / 003 / src / life.erl
1 -module(life).
2
3 -export([bang/1]).
4
5
6 -define(CHAR_DEAD, 32). % " "
7 -define(CHAR_ALIVE, 111). % "o"
8 -define(CHAR_BAR, 45). % "-"
9
10 -define(INTERVAL, 100).
11
12
13 -record(state, {x :: non_neg_integer()
14 ,y :: non_neg_integer()
15 ,n :: pos_integer()
16 ,bar :: nonempty_string()
17 ,board :: array()
18 ,gen_count :: pos_integer()
19 ,gen_duration :: non_neg_integer()
20 ,print_time :: non_neg_integer()
21 }).
22
23
24 %% ============================================================================
25 %% API
26 %% ============================================================================
27
28 bang(Args) ->
29 [X, Y] = [atom_to_integer(A) || A <- Args],
30 {Time, Board} = timer:tc(fun() -> init_board(X, Y) end),
31 State = #state{x = X
32 ,y = Y
33 ,n = X * Y
34 ,bar = [?CHAR_BAR || _ <- lists:seq(1, X)]
35 ,board = Board
36 ,gen_count = 1 % Consider inital state to be generation 1
37 ,gen_duration = Time
38 ,print_time = 0 % There was no print time yet
39 },
40 life_loop(State).
41
42
43 %% ============================================================================
44 %% Internal
45 %% ============================================================================
46
47 life_loop(
48 #state{x = X
49 ,y = Y
50 ,n = N
51 ,bar = Bar
52 ,board = Board
53 ,gen_count = GenCount
54 ,gen_duration = Time
55 ,print_time = LastPrintTime
56 }=State) ->
57
58 {PrintTime, ok} = timer:tc(
59 fun() ->
60 do_print_screen(Board, Bar, X, Y, N, GenCount, Time, LastPrintTime)
61 end
62 ),
63
64 {NewTime, NewBoard} = timer:tc(fun() -> next_generation(X, Y, Board) end),
65 NewState = State#state{board = NewBoard
66 ,gen_count = GenCount + 1
67 ,gen_duration = NewTime
68 ,print_time = PrintTime
69 },
70
71 timer:sleep(?INTERVAL),
72 life_loop(NewState).
73
74
75 do_print_screen(Board, Bar, X, Y, N, GenCount, Time, PrintTime) ->
76 ok = do_print_status(Bar, X, Y, N, GenCount, Time, PrintTime),
77 ok = do_print_board(Board).
78
79
80 do_print_status(Bar, X, Y, N, GenCount, TimeMic, PrintTimeMic) ->
81 TimeSec = TimeMic / 1000000,
82 PrintTimeSec = PrintTimeMic / 1000000,
83 ok = io:format("~s~n", [Bar]),
84 ok = io:format(
85 "X: ~b Y: ~b CELLS: ~b GENERATION: ~b DURATION: ~f PRINT TIME: ~f~n",
86 [X, Y, N, GenCount, TimeSec, PrintTimeSec]
87 ),
88 ok = io:format("~s~n", [Bar]).
89
90
91 do_print_board(Board) ->
92 RowStrings = array:to_list(
93 array:map(
94 fun(_, Row) ->
95 array:to_list(
96 array:map(
97 fun(_, State) ->
98 state_to_char(State)
99 end,
100 Row
101 )
102 )
103 end,
104 Board
105 )
106 ),
107
108 ok = lists:foreach(
109 fun(RowString) ->
110 ok = io:format("~s~n", [RowString])
111 end,
112 RowStrings
113 ).
114
115
116 state_to_char(0) -> ?CHAR_DEAD;
117 state_to_char(1) -> ?CHAR_ALIVE.
118
119
120 next_generation(W, H, Board) ->
121 array:map(
122 fun(Y, Row) ->
123 array:map(
124 fun(X, State) ->
125 Neighbors = filter_offsides(H, W, neighbors(X, Y)),
126 States = neighbor_states(Board, Neighbors),
127 LiveNeighbors = lists:sum(States),
128 new_state(State, LiveNeighbors)
129 end,
130 Row
131 )
132 end,
133 Board
134 ).
135
136
137 new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0;
138 new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1;
139 new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0;
140 new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1;
141 new_state(State, _LiveNeighbors) -> State.
142
143
144 neighbor_states(Board, Neighbors) ->
145 [array:get(X, array:get(Y, Board)) || {X, Y} <- Neighbors].
146
147
148 filter_offsides(H, W, Coordinates) ->
149 [{X, Y} || {X, Y} <- Coordinates, is_onside(X, Y, H, W)].
150
151
152 is_onside(X, Y, H, W) when (X >= 0) and (Y >= 0) and (X < W) and (Y < H) -> true;
153 is_onside(_, _, _, _) -> false.
154
155
156 neighbors(X, Y) ->
157 [{X + OffX, Y + OffY} || {OffX, OffY} <- offsets()].
158
159
160 offsets() ->
161 [offset(D) || D <- directions()].
162
163
164 offset('N') -> { 0, -1};
165 offset('NE') -> { 1, -1};
166 offset('E') -> { 1, 0};
167 offset('SE') -> { 1, 1};
168 offset('S') -> { 0, 1};
169 offset('SW') -> {-1, 1};
170 offset('W') -> {-1, 0};
171 offset('NW') -> {-1, -1}.
172
173
174 directions() ->
175 ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'].
176
177
178 init_board(X, Y) ->
179 array:map(fun(_, _) -> init_row(X) end, array:new(Y)).
180
181
182 init_row(X) ->
183 array:map(fun(_, _) -> init_cell_state() end, array:new(X)).
184
185
186 init_cell_state() ->
187 crypto:rand_uniform(0, 2).
188
189
190 atom_to_integer(Atom) ->
191 list_to_integer(atom_to_list(Atom)).
This page took 0.044792 seconds and 3 git commands to generate.