From 4ca3d0b9b3d35b94f107509a6a0e7613d3d017c7 Mon Sep 17 00:00:00 2001 From: SimaDovakin Date: Tue, 30 Sep 2025 18:49:48 +0300 Subject: [PATCH 1/2] Added Game of Life exercise. --- 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 +++ .../.meta/examples/gameOfLife.example.u | 62 ++++++++ .../game-of-life/.meta/testAnnotation.json | 34 +++++ .../practice/game-of-life/.meta/testLoader.md | 9 ++ .../practice/game-of-life/.meta/tests.toml | 34 +++++ .../practice/game-of-life/gameOfLife.test.u | 140 ++++++++++++++++++ exercises/practice/game-of-life/gameOfLife.u | 2 + 10 files changed, 328 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/examples/gameOfLife.example.u create mode 100644 exercises/practice/game-of-life/.meta/testAnnotation.json create mode 100644 exercises/practice/game-of-life/.meta/testLoader.md create mode 100644 exercises/practice/game-of-life/.meta/tests.toml create mode 100644 exercises/practice/game-of-life/gameOfLife.test.u create mode 100644 exercises/practice/game-of-life/gameOfLife.u diff --git a/config.json b/config.json index bba7d85..5d36f1e 100644 --- a/config.json +++ b/config.json @@ -479,6 +479,14 @@ "practices": [], "prerequisites": [], "difficulty": 7 + }, + { + "slug": "game-of-life", + "name": "Conway's Game of Life", + "uuid": "d22b3d88-b1ca-41b1-b00a-0d1ed45de2de", + "practices": [], + "prerequisites": [], + "difficulty": 7 } ] }, 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 0000000..4953140 --- /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 0000000..2347b93 --- /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 0000000..ed6d694 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "SimaDovakin" + ], + "files": { + "solution": [ + "gameOfLife.u" + ], + "test": [ + "gameOfLife.test.u" + ], + "example": [ + ".meta/examples/gameOfLife.example.u" + ] + }, + "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/examples/gameOfLife.example.u b/exercises/practice/game-of-life/.meta/examples/gameOfLife.example.u new file mode 100644 index 0000000..e5722a7 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/examples/gameOfLife.example.u @@ -0,0 +1,62 @@ +gameOfLife.directions : [(Int, Int)] +gameOfLife.directions = + use base.Int range + + Each.toList do + row = range -1 +2 |> each + col = range -1 +2 |> each + (row, col) !== (+0, +0) |> guard + (row, col) + +gameOfLife.countAliveNeighbours : Nat -> Nat -> [[Nat]] -> Nat +gameOfLife.countAliveNeighbours row col matrix = + rows = List.size matrix |> Nat.toInt + cols = + List.head matrix + |> Optional.getOrElse [] + |> List.size + |> Nat.toInt + accumulateCells acc position = + (r, c) = position + if Int.inRange +0 rows r && Int.inRange +0 cols c then + matrix + |> List.at (Int.abs r) + |> Optional.getOrElse [] + |> List.at (Int.abs c) + |> Optional.getOrElse 0 + |> (+) acc + else + acc + positions = + List.map + (cases (dr, dc) -> (Nat.toInt row + dr, Nat.toInt col + dc)) + directions + foldLeft + accumulateCells + 0 + positions + +gameOfLife.tick : [[Nat]] -> [[Nat]] +gameOfLife.tick matrix = + Each.toList do + rows = List.size matrix + cols = + matrix + |> List.head + |> getOrElse [] + |> List.size + row = Each.range 0 rows + Each.toList do + col = Each.range 0 cols + + cell = + matrix + |> List.at row + |> getOrElse [] + |> List.at col + |> getOrElse 0 + aliveNeighboursCount = countAliveNeighbours row col matrix + match (cell, aliveNeighboursCount) with + (1, aliveNeighbours) | aliveNeighbours === 2 || aliveNeighbours === 3 -> 1 + (0, 3) -> 1 + _ -> 0 diff --git a/exercises/practice/game-of-life/.meta/testAnnotation.json b/exercises/practice/game-of-life/.meta/testAnnotation.json new file mode 100644 index 0000000..aa71c8d --- /dev/null +++ b/exercises/practice/game-of-life/.meta/testAnnotation.json @@ -0,0 +1,34 @@ +[ + { + "name": "empty matrix", + "test_code": "verify do\n labeled \"empty matrix\" do\n expected = []\n actual = tick []\n label \"expected\" expected\n label \"actual\" actual\n ensureEqual expected actual" + }, + { + "name": "live cells with zero live neighbors die", + "test_code": "verify do\n labeled \"live cells with zero live neighbors die\" do\n expected = [\n [0, 0, 0],\n [0, 0, 0],\n [0, 0, 0]\n ]\n actual = tick [\n [0, 0, 0],\n [0, 1, 0],\n [0, 0, 0]\n ]\n label \"expected\" expected\n label \"actual\" actual\n ensureEqual expected actual" + }, + { + "name": "live cells with only one live neighbor die", + "test_code": "verify do\n labeled \"live cells with only one live neighbor die\" do\n expected = [\n [0, 0, 0],\n [0, 0, 0],\n [0, 0, 0]\n ]\n actual = tick [\n [0, 0, 0],\n [0, 1, 0],\n [0, 1, 0]\n ]\n label \"expected\" expected\n label \"actual\" actual\n ensureEqual expected actual" + }, + { + "name": "live cells with two live neighbors stay alive", + "test_code": "verify do\n labeled \"live cells with two live neighbors stay alive\" do\n expected = [\n [0, 0, 0],\n [1, 0, 1],\n [0, 0, 0]\n ]\n actual = tick [\n [1, 0, 1],\n [1, 0, 1],\n [1, 0, 1]\n ]\n label \"expected\" expected\n label \"actual\" actual\n ensureEqual expected actual" + }, + { + "name": "live cells with three live neighbors stay alive", + "test_code": "verify do\n labeled \"live cells with three live neighbors stay alive\" do\n expected = [\n [0, 0, 0],\n [1, 0, 0],\n [1, 1, 0]\n ]\n actual = tick [\n [0, 1, 0],\n [1, 0, 0],\n [1, 1, 0]\n ]\n label \"expected\" expected\n label \"actual\" actual\n ensureEqual expected actual" + }, + { + "name": "dead cells with three live neighbors become alive", + "test_code": "verify do\n labeled \"dead cells with three live neighbors become alive\" do\n expected = [\n [0, 0, 0],\n [1, 1, 0],\n [0, 0, 0]\n ]\n actual = tick [\n [1, 1, 0],\n [0, 0, 0],\n [1, 0, 0]\n ]\n label \"expected\" expected\n label \"actual\" actual\n ensureEqual expected actual" + }, + { + "name": "live cells with four or more neighbors die", + "test_code": "verify do\n labeled \"live cells with four or more neighbors die\" do\n expected = [\n [1, 0, 1],\n [0, 0, 0],\n [1, 0, 1]\n ]\n actual = tick [\n [1, 1, 1],\n [1, 1, 1],\n [1, 1, 1]\n ]\n label \"expected\" expected\n label \"actual\" actual\n ensureEqual expected actual" + }, + { + "name": "bigger matrix", + "test_code": "verify do\n labeled \"bigger matrix\" do\n expected = [\n [1, 1, 0, 1, 1, 0, 0, 0],\n [0, 0, 0, 0, 0, 1, 1, 0],\n [1, 0, 1, 1, 1, 1, 0, 1],\n [1, 0, 0, 0, 0, 0, 0, 1],\n [1, 1, 0, 0, 1, 0, 0, 1],\n [1, 1, 0, 1, 0, 0, 0, 1],\n [1, 0, 0, 0, 0, 0, 0, 0],\n [0, 0, 0, 0, 0, 0, 1, 1]\n ]\n actual = tick [\n [1, 1, 0, 1, 1, 0, 0, 0],\n [1, 0, 1, 1, 0, 0, 0, 0],\n [1, 1, 1, 0, 0, 1, 1, 1],\n [0, 0, 0, 0, 0, 1, 1, 0],\n [1, 0, 0, 0, 1, 1, 0, 0],\n [1, 1, 0, 0, 0, 1, 1, 1],\n [0, 0, 1, 0, 1, 0, 0, 1],\n [1, 0, 0, 0, 0, 0, 1, 1]\n ]\n label \"expected\" expected\n label \"actual\" actual\n ensureEqual expected actual" + } +] diff --git a/exercises/practice/game-of-life/.meta/testLoader.md b/exercises/practice/game-of-life/.meta/testLoader.md new file mode 100644 index 0000000..4e28341 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/testLoader.md @@ -0,0 +1,9 @@ +# Testing transcript + +```ucm +scratch/main> load ./gameOfLife.u +scratch/main> add +scratch/main> load ./gameOfLife.test.u +scratch/main> add +scratch/main> move.term gameOfLife.tests tests +``` 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 0000000..398cd45 --- /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/gameOfLife.test.u b/exercises/practice/game-of-life/gameOfLife.test.u new file mode 100644 index 0000000..fc11762 --- /dev/null +++ b/exercises/practice/game-of-life/gameOfLife.test.u @@ -0,0 +1,140 @@ +gameOfLife.tick.tests.ex1 = verify do + labeled "empty matrix" do + expected = [] + actual = tick [] + label "expected" expected + label "actual" actual + ensureEqual expected actual + +gameOfLife.tick.tests.ex2 = verify do + labeled "live cells with zero live neighbors die" do + expected = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ] + actual = tick [ + [0, 0, 0], + [0, 1, 0], + [0, 0, 0] + ] + label "expected" expected + label "actual" actual + ensureEqual expected actual + +gameOfLife.tick.tests.ex3 = verify do + labeled "live cells with only one live neighbor die" do + expected = [ + [0, 0, 0], + [0, 0, 0], + [0, 0, 0] + ] + actual = tick [ + [0, 0, 0], + [0, 1, 0], + [0, 1, 0] + ] + label "expected" expected + label "actual" actual + ensureEqual expected actual + +gameOfLife.tick.tests.ex4 = verify do + labeled "live cells with two live neighbors stay alive" do + expected = [ + [0, 0, 0], + [1, 0, 1], + [0, 0, 0] + ] + actual = tick [ + [1, 0, 1], + [1, 0, 1], + [1, 0, 1] + ] + label "expected" expected + label "actual" actual + ensureEqual expected actual + +gameOfLife.tick.tests.ex5 = verify do + labeled "live cells with three live neighbors stay alive" do + expected = [ + [0, 0, 0], + [1, 0, 0], + [1, 1, 0] + ] + actual = tick [ + [0, 1, 0], + [1, 0, 0], + [1, 1, 0] + ] + label "expected" expected + label "actual" actual + ensureEqual expected actual + +gameOfLife.tick.tests.ex6 = verify do + labeled "dead cells with three live neighbors become alive" do + expected = [ + [0, 0, 0], + [1, 1, 0], + [0, 0, 0] + ] + actual = tick [ + [1, 1, 0], + [0, 0, 0], + [1, 0, 0] + ] + label "expected" expected + label "actual" actual + ensureEqual expected actual + +gameOfLife.tick.tests.ex7 = verify do + labeled "live cells with four or more neighbors die" do + expected = [ + [1, 0, 1], + [0, 0, 0], + [1, 0, 1] + ] + actual = tick [ + [1, 1, 1], + [1, 1, 1], + [1, 1, 1] + ] + label "expected" expected + label "actual" actual + ensureEqual expected actual + +gameOfLife.tick.tests.ex8 = verify do + labeled "bigger matrix" do + 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] + ] + actual = tick [ + [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] + ] + label "expected" expected + label "actual" actual + ensureEqual expected actual + +test> gameOfLife.tests = join [ + gameOfLife.tick.tests.ex1, + gameOfLife.tick.tests.ex2, + gameOfLife.tick.tests.ex3, + gameOfLife.tick.tests.ex4, + gameOfLife.tick.tests.ex5, + gameOfLife.tick.tests.ex6, + gameOfLife.tick.tests.ex7, + gameOfLife.tick.tests.ex8, +] diff --git a/exercises/practice/game-of-life/gameOfLife.u b/exercises/practice/game-of-life/gameOfLife.u new file mode 100644 index 0000000..23edbe7 --- /dev/null +++ b/exercises/practice/game-of-life/gameOfLife.u @@ -0,0 +1,2 @@ +gameOfLife.tick : [[Nat]] -> [[Nat]] +gameOfLife.tick matrix = todo "implement tick" From ab07fee93078262203da0dc35d64131ca2a12f20 Mon Sep 17 00:00:00 2001 From: SimaDovakin Date: Tue, 30 Sep 2025 19:02:19 +0300 Subject: [PATCH 2/2] Removed use statement to fix reference to range. --- .../practice/game-of-life/.meta/examples/gameOfLife.example.u | 2 -- 1 file changed, 2 deletions(-) diff --git a/exercises/practice/game-of-life/.meta/examples/gameOfLife.example.u b/exercises/practice/game-of-life/.meta/examples/gameOfLife.example.u index e5722a7..12ed95a 100644 --- a/exercises/practice/game-of-life/.meta/examples/gameOfLife.example.u +++ b/exercises/practice/game-of-life/.meta/examples/gameOfLife.example.u @@ -1,7 +1,5 @@ gameOfLife.directions : [(Int, Int)] gameOfLife.directions = - use base.Int range - Each.toList do row = range -1 +2 |> each col = range -1 +2 |> each