Moved Life implementations into 'life' directory.
[cellular-automata.git] / life / 003 / src / life.erl
diff --git a/life/003/src/life.erl b/life/003/src/life.erl
new file mode 100644 (file)
index 0000000..605fa95
--- /dev/null
@@ -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)).
This page took 0.03959 seconds and 4 git commands to generate.