From ee5b277eeaafddc4fc9885b1343b5f223675466b Mon Sep 17 00:00:00 2001 From: Caleb Miller Date: Sat, 6 Apr 2024 11:26:05 -0600 Subject: [PATCH] Add Game of Life exercise --- config.json | 11 ++ .../game-of-life/.config/dotnet-tools.json | 12 ++ .../game-of-life/.docs/instructions.md | 11 ++ .../game-of-life/.docs/introduction.md | 9 ++ .../practice/game-of-life/.meta/Example.fs | 27 +++++ .../practice/game-of-life/.meta/config.json | 22 ++++ .../practice/game-of-life/.meta/tests.toml | 34 ++++++ exercises/practice/game-of-life/GameOfLife.fs | 3 + .../practice/game-of-life/GameOfLife.fsproj | 21 ++++ .../practice/game-of-life/GameOfLifeTests.fs | 107 ++++++++++++++++++ generators/Generators.fs | 15 +++ 11 files changed, 272 insertions(+) create mode 100644 exercises/practice/game-of-life/.config/dotnet-tools.json 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/Example.fs create mode 100644 exercises/practice/game-of-life/.meta/config.json create mode 100644 exercises/practice/game-of-life/.meta/tests.toml create mode 100644 exercises/practice/game-of-life/GameOfLife.fs create mode 100644 exercises/practice/game-of-life/GameOfLife.fsproj create mode 100644 exercises/practice/game-of-life/GameOfLifeTests.fs diff --git a/config.json b/config.json index 5defaf665..948a68419 100644 --- a/config.json +++ b/config.json @@ -2090,6 +2090,17 @@ "numbers" ], "difficulty": 5 + }, + { + "slug": "game-of-life", + "name": "Conway's Game of Life", + "uuid": "c6ad9a2d-4ca2-4013-93df-5f6934203870", + "practices": [], + "prerequisites": [ + "if-then-else-expressions", + "lists" + ], + "difficulty": 7 } ], "foregone": [ diff --git a/exercises/practice/game-of-life/.config/dotnet-tools.json b/exercises/practice/game-of-life/.config/dotnet-tools.json new file mode 100644 index 000000000..3a9dbd470 --- /dev/null +++ b/exercises/practice/game-of-life/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "fantomas-tool": { + "version": "3.2.0", + "commands": [ + "fantomas" + ] + } + } +} \ No newline at end of file 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 000000000..495314064 --- /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 000000000..2347b936e --- /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/Example.fs b/exercises/practice/game-of-life/.meta/Example.fs new file mode 100644 index 000000000..3baa823a5 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/Example.fs @@ -0,0 +1,27 @@ +module GameOfLife + +let tick (input: int list list) = + input + |> List.mapi (fun row cells -> + cells + |> List.mapi (fun col cell -> + match + [ (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) ] + |> List.filter (fun (x, y) -> + x >= 0 + && x < List.length input + && y >= 0 + && y < List.length cells + && input.[x].[y] = 1) + |> List.length + with + | 2 when cell = 1 -> 1 + | 3 -> 1 + | _ -> 0)) 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 000000000..2ff2fe624 --- /dev/null +++ b/exercises/practice/game-of-life/.meta/config.json @@ -0,0 +1,22 @@ +{ + "authors": [ + "Steffan153" + ], + "files": { + "solution": [ + "GameOfLife.fs" + ], + "test": [ + "GameOfLifeTests.fs" + ], + "example": [ + ".meta/Example.fs" + ], + "invalidator": [ + "GameOfLife.fsproj" + ] + }, + "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/tests.toml b/exercises/practice/game-of-life/.meta/tests.toml new file mode 100644 index 000000000..398cd4546 --- /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.fs b/exercises/practice/game-of-life/GameOfLife.fs new file mode 100644 index 000000000..6b9fc3b14 --- /dev/null +++ b/exercises/practice/game-of-life/GameOfLife.fs @@ -0,0 +1,3 @@ +module GameOfLife + +let tick input = failwith "You need to implement this function." \ No newline at end of file diff --git a/exercises/practice/game-of-life/GameOfLife.fsproj b/exercises/practice/game-of-life/GameOfLife.fsproj new file mode 100644 index 000000000..4e384c989 --- /dev/null +++ b/exercises/practice/game-of-life/GameOfLife.fsproj @@ -0,0 +1,21 @@ + + + + net7.0 + + false + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/exercises/practice/game-of-life/GameOfLifeTests.fs b/exercises/practice/game-of-life/GameOfLifeTests.fs new file mode 100644 index 000000000..67164e18b --- /dev/null +++ b/exercises/practice/game-of-life/GameOfLifeTests.fs @@ -0,0 +1,107 @@ +module GameOfLifeTests + +open FsUnit.Xunit +open Xunit + +open GameOfLife + +[] +let ``Empty matrix`` () = + let matrix: int list list = [] + let expected: int list list = [] + tick matrix |> should equal expected + +[] +let ``Live cells with zero live neighbors die`` () = + let matrix = + [ [0; 0; 0]; + [0; 1; 0]; + [0; 0; 0] ] + let expected = + [ [0; 0; 0]; + [0; 0; 0]; + [0; 0; 0] ] + tick matrix |> should equal expected + +[] +let ``Live cells with only one live neighbor die`` () = + let matrix = + [ [0; 0; 0]; + [0; 1; 0]; + [0; 1; 0] ] + let expected = + [ [0; 0; 0]; + [0; 0; 0]; + [0; 0; 0] ] + tick matrix |> should equal expected + +[] +let ``Live cells with two live neighbors stay alive`` () = + let matrix = + [ [1; 0; 1]; + [1; 0; 1]; + [1; 0; 1] ] + let expected = + [ [0; 0; 0]; + [1; 0; 1]; + [0; 0; 0] ] + tick matrix |> should equal expected + +[] +let ``Live cells with three live neighbors stay alive`` () = + let matrix = + [ [0; 1; 0]; + [1; 0; 0]; + [1; 1; 0] ] + let expected = + [ [0; 0; 0]; + [1; 0; 0]; + [1; 1; 0] ] + tick matrix |> should equal expected + +[] +let ``Dead cells with three live neighbors become alive`` () = + let matrix = + [ [1; 1; 0]; + [0; 0; 0]; + [1; 0; 0] ] + let expected = + [ [0; 0; 0]; + [1; 1; 0]; + [0; 0; 0] ] + tick matrix |> should equal expected + +[] +let ``Live cells with four or more neighbors die`` () = + let matrix = + [ [1; 1; 1]; + [1; 1; 1]; + [1; 1; 1] ] + let expected = + [ [1; 0; 1]; + [0; 0; 0]; + [1; 0; 1] ] + tick matrix |> should equal expected + +[] +let ``Bigger matrix`` () = + let 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] ] + let 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] ] + tick matrix |> should equal expected + diff --git a/generators/Generators.fs b/generators/Generators.fs index f84ceea9a..38883f5b8 100644 --- a/generators/Generators.fs +++ b/generators/Generators.fs @@ -2013,3 +2013,18 @@ type AffineCipher() = "System.ArgumentException" else base.RenderExpected(testCase, key, value) + + +type GameOfLife() = + inherit ExerciseGenerator() + + override _.RenderInput(_, _, value) = List.renderMultiLine value + + override _.RenderExpected(_, _, value) = List.renderMultiLine value + + override this.PropertiesWithIdentifier testCase = this.Properties testCase + + override _.IdentifierTypeAnnotation(_, _, value) = + match Seq.isEmpty value with + | true -> Some "int list list" + | false -> None