From 0310e6a84faab73646db8b637dd5837b2a5e5f2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20B=20Nagy?= <20251272+BNAndras@users.noreply.github.com> Date: Sat, 17 May 2025 21:21:46 -0700 Subject: [PATCH] Add `game-of-life` --- config.json | 8 + .../game-of-life/.docs/instructions.md | 11 ++ .../game-of-life/.docs/introduction.md | 9 ++ .../practice/game-of-life/.meta/config.json | 19 +++ .../practice/game-of-life/.meta/example.erl | 50 +++++++ .../practice/game-of-life/.meta/tests.toml | 34 +++++ exercises/practice/game-of-life/rebar.config | 30 ++++ .../game-of-life/src/game_of_life.app.src | 9 ++ .../game-of-life/src/game_of_life.erl | 6 + .../game-of-life/test/game_of_life_tests.erl | 138 ++++++++++++++++++ 10 files changed, 314 insertions(+) create mode 100644 exercises/practice/game-of-life/.docs/instructions.md create mode 100644 exercises/practice/game-of-life/.docs/introduction.md create mode 100644 exercises/practice/game-of-life/.meta/config.json create mode 100644 exercises/practice/game-of-life/.meta/example.erl create mode 100644 exercises/practice/game-of-life/.meta/tests.toml create mode 100644 exercises/practice/game-of-life/rebar.config create mode 100644 exercises/practice/game-of-life/src/game_of_life.app.src create mode 100644 exercises/practice/game-of-life/src/game_of_life.erl create mode 100644 exercises/practice/game-of-life/test/game_of_life_tests.erl diff --git a/config.json b/config.json index 3175db29..f6fa0c00 100644 --- a/config.json +++ b/config.json @@ -636,6 +636,14 @@ "dates" ] }, + { + "slug": "game-of-life", + "name": "Conway's Game of Life", + "uuid": "14c7124a-7dbc-4f7c-a852-84413f195053", + "practices": [], + "prerequisites": [], + "difficulty": 5 + }, { "slug": "grade-school", "name": "Grade School", diff --git a/exercises/practice/game-of-life/.docs/instructions.md b/exercises/practice/game-of-life/.docs/instructions.md new file mode 100644 index 00000000..49531406 --- /dev/null +++ b/exercises/practice/game-of-life/.docs/instructions.md @@ -0,0 +1,11 @@ +# Instructions + +After each generation, the cells interact with their eight neighbors, which are cells adjacent horizontally, vertically, or diagonally. + +The following rules are applied to each cell: + +- Any live cell with two or three live neighbors lives on. +- Any dead cell with exactly three live neighbors becomes a live cell. +- All other cells die or stay dead. + +Given a matrix of 1s and 0s (corresponding to live and dead cells), apply the rules to each cell, and return the next generation. diff --git a/exercises/practice/game-of-life/.docs/introduction.md b/exercises/practice/game-of-life/.docs/introduction.md new file mode 100644 index 00000000..2347b936 --- /dev/null +++ b/exercises/practice/game-of-life/.docs/introduction.md @@ -0,0 +1,9 @@ +# Introduction + +[Conway's Game of Life][game-of-life] is a fascinating cellular automaton created by the British mathematician John Horton Conway in 1970. + +The game consists of a two-dimensional grid of cells that can either be "alive" or "dead." + +After each generation, the cells interact with their eight neighbors via a set of rules, which define the new generation. + +[game-of-life]: https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life diff --git a/exercises/practice/game-of-life/.meta/config.json b/exercises/practice/game-of-life/.meta/config.json new file mode 100644 index 00000000..a3104ee3 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "BNAndras" + ], + "files": { + "solution": [ + "src/game_of_life.erl" + ], + "test": [ + "test/game_of_life_tests.erl" + ], + "example": [ + ".meta/example.erl" + ] + }, + "blurb": "Implement Conway's Game of Life.", + "source": "Wikipedia", + "source_url": "https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life" +} diff --git a/exercises/practice/game-of-life/.meta/example.erl b/exercises/practice/game-of-life/.meta/example.erl new file mode 100644 index 00000000..7ffbe3a5 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/example.erl @@ -0,0 +1,50 @@ +-module(example). + +-export([tick/1]). + +tick([]) -> []; +tick(Matrix) -> + Rows = length(Matrix), + Cols = length(hd(Matrix)), + RowIndices = lists:seq(0, Rows - 1), + ColIndices = lists:seq(0, Cols - 1), + lists:map( + fun(R) -> + lists:map( + fun(C) -> next_tick(Matrix, R, C, Rows, Cols) end, + ColIndices + ) + end, + RowIndices + ). + +next_tick(Matrix, Row, Col, Rows, Cols) -> + CurrentCell = get_cell(Matrix, Row, Col), + LiveNeighbors = count_live_neighbors(Matrix, Row, Col, Rows, Cols), + update_cell(CurrentCell, LiveNeighbors). + +get_cell(Matrix, Row, Col) -> + lists:nth(Col + 1, lists:nth(Row + 1, Matrix)). + +update_cell(1, LiveNeighbors) when LiveNeighbors =:= 2 orelse LiveNeighbors =:= 3 -> 1; +update_cell(0, 3) -> 1; +update_cell(_, _) -> 0. + +count_live_neighbors(Matrix, Row, Col, Rows, Cols) -> + Neighbors = [ + {Row - 1, Col - 1}, {Row - 1, Col}, {Row - 1, Col + 1}, + {Row, Col - 1}, {Row, Col + 1}, + {Row + 1, Col - 1}, {Row + 1, Col}, {Row + 1, Col + 1} + ], + lists:foldl( + fun({NRow, NCol}, Acc) -> + case NRow >= 0 andalso NRow < Rows andalso NCol >= 0 andalso NCol < Cols of + true -> + Acc + get_cell(Matrix, NRow, NCol); + false -> + Acc + end + end, + 0, + Neighbors + ). diff --git a/exercises/practice/game-of-life/.meta/tests.toml b/exercises/practice/game-of-life/.meta/tests.toml new file mode 100644 index 00000000..398cd454 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/tests.toml @@ -0,0 +1,34 @@ +# This is an auto-generated file. +# +# Regenerating this file via `configlet sync` will: +# - Recreate every `description` key/value pair +# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications +# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion) +# - Preserve any other key/value pair +# +# As user-added comments (using the # character) will be removed when this file +# is regenerated, comments can be added via a `comment` key. + +[ae86ea7d-bd07-4357-90b3-ac7d256bd5c5] +description = "empty matrix" + +[4ea5ccb7-7b73-4281-954a-bed1b0f139a5] +description = "live cells with zero live neighbors die" + +[df245adc-14ff-4f9c-b2ae-f465ef5321b2] +description = "live cells with only one live neighbor die" + +[2a713b56-283c-48c8-adae-1d21306c80ae] +description = "live cells with two live neighbors stay alive" + +[86d5c5a5-ab7b-41a1-8907-c9b3fc5e9dae] +description = "live cells with three live neighbors stay alive" + +[015f60ac-39d8-4c6c-8328-57f334fc9f89] +description = "dead cells with three live neighbors become alive" + +[2ee69c00-9d41-4b8b-89da-5832e735ccf1] +description = "live cells with four or more neighbors die" + +[a79b42be-ed6c-4e27-9206-43da08697ef6] +description = "bigger matrix" diff --git a/exercises/practice/game-of-life/rebar.config b/exercises/practice/game-of-life/rebar.config new file mode 100644 index 00000000..db5d9076 --- /dev/null +++ b/exercises/practice/game-of-life/rebar.config @@ -0,0 +1,30 @@ +%% Erlang compiler options +{erl_opts, [debug_info, warnings_as_errors]}. + +{deps, [{erl_exercism, "0.1.2"}]}. + +{dialyzer, [ + {warnings, [underspecs, no_return]}, + {get_warnings, true}, + {plt_apps, top_level_deps}, % top_level_deps | all_deps + {plt_extra_apps, []}, + {plt_location, local}, % local | "/my/file/name" + {plt_prefix, "rebar3"}, + {base_plt_apps, [stdlib, kernel, crypto]}, + {base_plt_location, global}, % global | "/my/file/name" + {base_plt_prefix, "rebar3"} +]}. + +%% eunit:test(Tests) +{eunit_tests, []}. +%% Options for eunit:test(Tests, Opts) +{eunit_opts, [verbose]}. + +%% == xref == + +{xref_warnings, true}. + +%% xref checks to run +{xref_checks, [undefined_function_calls, undefined_functions, + locals_not_used, exports_not_used, + deprecated_function_calls, deprecated_functions]}. diff --git a/exercises/practice/game-of-life/src/game_of_life.app.src b/exercises/practice/game-of-life/src/game_of_life.app.src new file mode 100644 index 00000000..94397f7b --- /dev/null +++ b/exercises/practice/game-of-life/src/game_of_life.app.src @@ -0,0 +1,9 @@ +{application, game_of_life, + [{description, "exercism.org - game-of-life"}, + {vsn, "0.0.1"}, + {modules, []}, + {registered, []}, + {applications, [kernel, + stdlib]}, + {env, []} + ]}. diff --git a/exercises/practice/game-of-life/src/game_of_life.erl b/exercises/practice/game-of-life/src/game_of_life.erl new file mode 100644 index 00000000..5bd42de1 --- /dev/null +++ b/exercises/practice/game-of-life/src/game_of_life.erl @@ -0,0 +1,6 @@ +-module(game_of_life). + +-export([tick/1]). + + +tick(_Matrix) -> undefined. diff --git a/exercises/practice/game-of-life/test/game_of_life_tests.erl b/exercises/practice/game-of-life/test/game_of_life_tests.erl new file mode 100644 index 00000000..2dfec630 --- /dev/null +++ b/exercises/practice/game-of-life/test/game_of_life_tests.erl @@ -0,0 +1,138 @@ +-module(game_of_life_tests). + +-include_lib("erl_exercism/include/exercism.hrl"). +-include_lib("eunit/include/eunit.hrl"). + + + + +'1_empty_matrix_test_'() -> + {"empty matrix", + fun() -> + Matrix = [], + Expected = [], + ?assertEqual(Expected, game_of_life:tick(Matrix)) + end}. + +'2_live_cells_with_zero_live_neighbors_die_test_'() -> + {"live cells with zero live neighbors die", + fun() -> + Matrix = [ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0] + ], + Expected = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ], + ?assertEqual(Expected, game_of_life:tick(Matrix)) + end}. + +'3_live_cells_with_only_one_live_neighbor_die_test_'() -> + {"live cells with only one live neighbor die", + fun() -> + Matrix = [ + [0, 0, 0], + [0, 1, 0], + [0, 1, 0] + ], + Expected = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ], + ?assertEqual(Expected, game_of_life:tick(Matrix)) + end}. + +'4_live_cells_with_two_live_neighbors_stay_alive_test_'() -> + {"live cells with two live neighbors stay alive", + fun() -> + Matrix = [ + [1, 0, 1], + [1, 0, 1], + [1, 0, 1] + ], + Expected = [ + [0, 0, 0], + [1, 0, 1], + [0, 0, 0] + ], + ?assertEqual(Expected, game_of_life:tick(Matrix)) + end}. + +'5_live_cells_with_three_live_neighbors_stay_alive_test_'() -> + {"live cells with three live neighbors stay alive", + fun() -> + Matrix = [ + [0, 1, 0], + [1, 0, 0], + [1, 1, 0] + ], + Expected = [ + [0, 0, 0], + [1, 0, 0], + [1, 1, 0] + ], + ?assertEqual(Expected, game_of_life:tick(Matrix)) + end}. + +'6_dead_cells_with_three_live_neighbors_become_alive_test_'() -> + {"dead cells with three live neighbors become alive", + fun() -> + Matrix = [ + [1, 1, 0], + [0, 0, 0], + [1, 0, 0] + ], + Expected = [ + [0, 0, 0], + [1, 1, 0], + [0, 0, 0] + ], + ?assertEqual(Expected, game_of_life:tick(Matrix)) + end}. + +'7_live_cells_with_four_or_more_neighbors_die_test_'() -> + {"live cells with four or more neighbors die", + fun() -> + Matrix = [ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1] + ], + Expected = [ + [1, 0, 1], + [0, 0, 0], + [1, 0, 1] + ], + ?assertEqual(Expected, game_of_life:tick(Matrix)) + end}. + +'8_bigger_matrix_test_'() -> + {"bigger matrix", + fun() -> + Matrix = [ + [1, 1, 0, 1, 1, 0, 0, 0], + [1, 0, 1, 1, 0, 0, 0, 0], + [1, 1, 1, 0, 0, 1, 1, 1], + [0, 0, 0, 0, 0, 1, 1, 0], + [1, 0, 0, 0, 1, 1, 0, 0], + [1, 1, 0, 0, 0, 1, 1, 1], + [0, 0, 1, 0, 1, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 1, 1] +], + Expected = [ + [1, 1, 0, 1, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 1, 1, 0], + [1, 0, 1, 1, 1, 1, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 1], + [1, 1, 0, 0, 1, 0, 0, 1], + [1, 1, 0, 1, 0, 0, 0, 1], + [1, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 1, 1] + ], + ?assertEqual(Expected, + game_of_life:tick(Matrix)) + end}.