X-Git-Url: https://git.xandkar.net/?a=blobdiff_plain;f=life%2F003%2Fsrc%2Flife.erl;fp=life%2F003%2Fsrc%2Flife.erl;h=605fa95ce873533b2b4d2da920001db536cb531c;hb=8d06c463b83b79e046250fbe60e5429f182e3a7a;hp=0000000000000000000000000000000000000000;hpb=4eb3be32ce405e69f39573b5e38171764cd0d789;p=cellular-automata.git diff --git a/life/003/src/life.erl b/life/003/src/life.erl new file mode 100644 index 0000000..605fa95 --- /dev/null +++ b/life/003/src/life.erl @@ -0,0 +1,206 @@ +-module(life). + +-export([bang/1]). + + +-define(CHAR_DEAD, 32). % " " +-define(CHAR_ALIVE, 111). % "o" +-define(CHAR_BAR, 45). % "-" + +-define(GEN_INTERVAL, 100). + + +-record(state, {x :: non_neg_integer() + ,y :: non_neg_integer() + ,n :: pos_integer() + ,bar :: nonempty_string() + ,board :: array() + ,gen_count :: pos_integer() + ,gen_duration :: non_neg_integer() + ,print_time :: non_neg_integer() + }). + + +%% ============================================================================ +%% API +%% ============================================================================ + +bang(Args) -> + [X, Y] = [atom_to_integer(A) || A <- Args], + {Time, Board} = timer:tc(fun() -> init_board(X, Y) end), + State = #state{x = X + ,y = Y + ,n = X * Y + ,bar = [?CHAR_BAR || _ <- lists:seq(1, X)] + ,board = Board + ,gen_count = 1 % Consider inital state to be generation 1 + ,gen_duration = Time + ,print_time = 0 % There was no print time yet + }, + life_loop(State). + + +%% ============================================================================ +%% Internal +%% ============================================================================ + +life_loop( + #state{x = X + ,y = Y + ,n = N + ,bar = Bar + ,board = Board + ,gen_count = GenCount + ,gen_duration = Time + ,print_time = LastPrintTime + }=State) -> + + {PrintTime, ok} = timer:tc( + fun() -> + do_print_screen(Board, Bar, X, Y, N, GenCount, Time, LastPrintTime) + end + ), + + {NewTime, NewBoard} = timer:tc( + fun() -> + next_generation(X, Y, Board) + end + ), + + NewState = State#state{board = NewBoard + ,gen_count = GenCount + 1 + ,gen_duration = NewTime + ,print_time = PrintTime + }, + + NewTimeMil = NewTime / 1000, + NextGenDelay = at_least_zero(round(?GEN_INTERVAL - NewTimeMil)), + timer:sleep(NextGenDelay), + + life_loop(NewState). + + +at_least_zero(Integer) when Integer >= 0 -> Integer; +at_least_zero(_) -> 0. + + +do_print_screen(Board, Bar, X, Y, N, GenCount, Time, PrintTime) -> + ok = do_print_status(Bar, X, Y, N, GenCount, Time, PrintTime), + ok = do_print_board(Board). + + +do_print_status(Bar, X, Y, N, GenCount, TimeMic, PrintTimeMic) -> + TimeSec = TimeMic / 1000000, + PrintTimeSec = PrintTimeMic / 1000000, + ok = io:format("~s~n", [Bar]), + ok = io:format( + "X: ~b Y: ~b CELLS: ~b GENERATION: ~b DURATION: ~f PRINT TIME: ~f~n", + [X, Y, N, GenCount, TimeSec, PrintTimeSec] + ), + ok = io:format("~s~n", [Bar]). + + +do_print_board(Board) -> + % It seems that just doing a fold should be faster than map + to_list + % combo, but, after measuring several times, map + to_list has been + % consistently (nearly twice) faster than either foldl or foldr. + RowStrings = array:to_list( + array:map( + fun(_, Row) -> + array:to_list( + array:map( + fun(_, State) -> + state_to_char(State) + end, + Row + ) + ) + end, + Board + ) + ), + + ok = lists:foreach( + fun(RowString) -> + ok = io:format("~s~n", [RowString]) + end, + RowStrings + ). + + +state_to_char(0) -> ?CHAR_DEAD; +state_to_char(1) -> ?CHAR_ALIVE. + + +next_generation(W, H, Board) -> + array:map( + fun(Y, Row) -> + array:map( + fun(X, State) -> + Neighbors = filter_offsides(H, W, neighbors(X, Y)), + States = neighbor_states(Board, Neighbors), + LiveNeighbors = lists:sum(States), + new_state(State, LiveNeighbors) + end, + Row + ) + end, + Board + ). + + +new_state(1, LiveNeighbors) when LiveNeighbors < 2 -> 0; +new_state(1, LiveNeighbors) when LiveNeighbors < 4 -> 1; +new_state(1, LiveNeighbors) when LiveNeighbors > 3 -> 0; +new_state(0, LiveNeighbors) when LiveNeighbors =:= 3 -> 1; +new_state(State, _LiveNeighbors) -> State. + + +neighbor_states(Board, Neighbors) -> + [array:get(X, array:get(Y, Board)) || {X, Y} <- Neighbors]. + + +filter_offsides(H, W, Coordinates) -> + [{X, Y} || {X, Y} <- Coordinates, is_onside(X, Y, H, W)]. + + +is_onside(X, Y, H, W) when (X >= 0) and (Y >= 0) and (X < W) and (Y < H) -> true; +is_onside(_, _, _, _) -> false. + + +neighbors(X, Y) -> + [{X + OffX, Y + OffY} || {OffX, OffY} <- offsets()]. + + +offsets() -> + [offset(D) || D <- directions()]. + + +offset('N') -> { 0, -1}; +offset('NE') -> { 1, -1}; +offset('E') -> { 1, 0}; +offset('SE') -> { 1, 1}; +offset('S') -> { 0, 1}; +offset('SW') -> {-1, 1}; +offset('W') -> {-1, 0}; +offset('NW') -> {-1, -1}. + + +directions() -> + ['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW']. + + +init_board(X, Y) -> + array:map(fun(_, _) -> init_row(X) end, array:new(Y)). + + +init_row(X) -> + array:map(fun(_, _) -> init_cell_state() end, array:new(X)). + + +init_cell_state() -> + crypto:rand_uniform(0, 2). + + +atom_to_integer(Atom) -> + list_to_integer(atom_to_list(Atom)).