Commit | Line | Data |
---|---|---|
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 | ||
28 | bang(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 |
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 | |
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 |
83 | at_least_zero(Integer) when Integer >= 0 -> Integer; |
84 | at_least_zero(_) -> 0. | |
85 | ||
86 | ||
6fc17e92 SK |
87 | do_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 | ||
92 | do_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 | ||
103 | do_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 | ||
131 | state_to_char(0) -> ?CHAR_DEAD; | |
132 | state_to_char(1) -> ?CHAR_ALIVE. | |
133 | ||
134 | ||
68194920 | 135 | next_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 | ||
152 | new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0; | |
153 | new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1; | |
154 | new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0; | |
155 | new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1; | |
156 | new_state(State, _LiveNeighbors) -> State. | |
157 | ||
158 | ||
159 | neighbor_states(Board, Neighbors) -> | |
160 | [array:get(X, array:get(Y, Board)) || {X, Y} <- Neighbors]. | |
161 | ||
162 | ||
163 | filter_offsides(H, W, Coordinates) -> | |
164 | [{X, Y} || {X, Y} <- Coordinates, is_onside(X, Y, H, W)]. | |
165 | ||
166 | ||
167 | is_onside(X, Y, H, W) when (X >= 0) and (Y >= 0) and (X < W) and (Y < H) -> true; | |
168 | is_onside(_, _, _, _) -> false. | |
169 | ||
170 | ||
171 | neighbors(X, Y) -> | |
172 | [{X + OffX, Y + OffY} || {OffX, OffY} <- offsets()]. | |
173 | ||
174 | ||
175 | offsets() -> | |
176 | [offset(D) || D <- directions()]. | |
177 | ||
178 | ||
179 | offset('N') -> { 0, -1}; | |
180 | offset('NE') -> { 1, -1}; | |
181 | offset('E') -> { 1, 0}; | |
182 | offset('SE') -> { 1, 1}; | |
183 | offset('S') -> { 0, 1}; | |
184 | offset('SW') -> {-1, 1}; | |
185 | offset('W') -> {-1, 0}; | |
186 | offset('NW') -> {-1, -1}. | |
187 | ||
188 | ||
189 | directions() -> | |
190 | ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']. | |
191 | ||
192 | ||
193 | init_board(X, Y) -> | |
194 | array:map(fun(_, _) -> init_row(X) end, array:new(Y)). | |
195 | ||
196 | ||
197 | init_row(X) -> | |
198 | array:map(fun(_, _) -> init_cell_state() end, array:new(X)). | |
199 | ||
200 | ||
201 | init_cell_state() -> | |
202 | crypto:rand_uniform(0, 2). | |
203 | ||
204 | ||
205 | atom_to_integer(Atom) -> | |
206 | list_to_integer(atom_to_list(Atom)). |