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