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