diff --git a/config.json b/config.json index 4d4625f..52140ac 100644 --- a/config.json +++ b/config.json @@ -414,6 +414,14 @@ "practices": [], "prerequisites": [], "difficulty": 1 + }, + { + "slug": "minesweeper", + "name": "Minesweeper", + "uuid": "58338fa3-b558-4de7-af05-04d5c9289000", + "practices": [], + "prerequisites": [], + "difficulty": 7 } ] }, diff --git a/exercises/practice/minesweeper/.docs/instructions.md b/exercises/practice/minesweeper/.docs/instructions.md new file mode 100644 index 0000000..7c1df2e --- /dev/null +++ b/exercises/practice/minesweeper/.docs/instructions.md @@ -0,0 +1,26 @@ +# Instructions + +Your task is to add the mine counts to empty squares in a completed Minesweeper board. +The board itself is a rectangle composed of squares that are either empty (`' '`) or a mine (`'*'`). + +For each empty square, count the number of mines adjacent to it (horizontally, vertically, diagonally). +If the empty square has no adjacent mines, leave it empty. +Otherwise replace it with the adjacent mines count. + +For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen): + +```text +·*·*· +··*·· +··*·· +····· +``` + +Which your code should transform into this: + +```text +1*3*1 +13*31 +·2*2· +·111· +``` diff --git a/exercises/practice/minesweeper/.docs/introduction.md b/exercises/practice/minesweeper/.docs/introduction.md new file mode 100644 index 0000000..5f74a74 --- /dev/null +++ b/exercises/practice/minesweeper/.docs/introduction.md @@ -0,0 +1,5 @@ +# Introduction + +[Minesweeper][wikipedia] is a popular game where the user has to find the mines using numeric hints that indicate how many mines are directly adjacent (horizontally, vertically, diagonally) to a square. + +[wikipedia]: https://en.wikipedia.org/wiki/Minesweeper_(video_game) diff --git a/exercises/practice/minesweeper/.meta/config.json b/exercises/practice/minesweeper/.meta/config.json new file mode 100644 index 0000000..b0d94e5 --- /dev/null +++ b/exercises/practice/minesweeper/.meta/config.json @@ -0,0 +1,17 @@ +{ + "authors": [ + "SimaDovakin" + ], + "files": { + "solution": [ + "minesweeper.u" + ], + "test": [ + "minesweeper.test.u" + ], + "example": [ + ".meta/examples/minesweeper.example.u" + ] + }, + "blurb": "Add the numbers to a minesweeper board." +} diff --git a/exercises/practice/minesweeper/.meta/examples/minesweeper.example.u b/exercises/practice/minesweeper/.meta/examples/minesweeper.example.u new file mode 100644 index 0000000..5cde03b --- /dev/null +++ b/exercises/practice/minesweeper/.meta/examples/minesweeper.example.u @@ -0,0 +1,87 @@ +minesweeper.annotate : [Text] -> [Text] +minesweeper.annotate board = + boardHeight = size board |> toInt + boardWidth = List.head board |> Optional.getOrElse "" |> Text.size |> toInt + neighbours = + board + |> findBoardMines + |> map (coordinates -> getNeighbours coordinates boardWidth boardHeight) + |> join + coordinatesMap = createCoordinatesMap neighbours + annotateBoard board coordinatesMap + +minesweeper.directions : [(Int, Int)] +minesweeper.directions = + use base.Int range + range -1 +2 + |> map (n -> zip (fill 3 n) (range -1 +2)) + |> join + |> filter (p -> (at1 p, at2 p) !== (+0, +0)) + +minesweeper.natToDigit : Nat -> Char +minesweeper.natToDigit = cases + n | (n >= 0) && (n <= 9) -> Char.fromNat (48 + n) |> Optional.getOrElse ?\s + _ -> ?\s + +minesweeper.getNeighbours : (Int, Int) -> Int -> Int -> [(Int, Int)] +minesweeper.getNeighbours coordinates width height = + addCoordinates coords1 coords2 = + use Int + + (r1, c1) = coords1 + (r2, c2) = coords2 + (r1 + r2, c1 + c2) + isOnBoard w h coords = + (r, c) = coords + (r >= +0) && (r < h) && (c >= +0) && (c < w) + directions + |> map (addCoordinates coordinates) + |> filter (isOnBoard width height) + +minesweeper.createCoordinatesMap : [(Int, Int)] -> Map (Int, Int) Nat +minesweeper.createCoordinatesMap coordinates = + incrementFrequency = cases + Some old -> Some (old + 1) + _ -> Some 1 + coordinates + |> List.foldLeft (acc coord -> Map.alter incrementFrequency coord acc) Map.empty + +minesweeper.findRowMines : Int -> Text -> [(Int, Int)] +minesweeper.findRowMines rowNumber row = + use base.Int range + isMine = at2 >> (==) ?* + row + |> toCharList + |> zip (range +0 (size row |> toInt)) + |> filter isMine + |> map (p -> (rowNumber, at1 p)) + +minesweeper.findBoardMines : [Text] -> [(Int, Int)] +minesweeper.findBoardMines board = + use base.Int range + board + |> zip (range +0 (size board |> toInt)) + |> map (uncurry findRowMines) + |> join + +minesweeper.annotateRow : Int -> Text -> Map (Int, Int) Nat -> Text +minesweeper.annotateRow rowNumber row coordinatesMap = + use base.Int range + populateTile indexedTile = + (colNumber, value) = indexedTile + match value with + ?* -> value + _ -> match Map.get (rowNumber, colNumber) coordinatesMap with + Some val -> natToDigit val + None -> value + row + |> toCharList + |> zip (range +0 (size row |> toInt)) + |> map populateTile + |> fromCharList + +minesweeper.annotateBoard : [Text] -> Map (Int, Int) Nat -> [Text] +minesweeper.annotateBoard board coordinatesMap = + use base.Int range + board + |> zip (range +0 (size board |> toInt)) + |> map (p -> (uncurry annotateRow p) coordinatesMap) diff --git a/exercises/practice/minesweeper/.meta/testAnnotation.json b/exercises/practice/minesweeper/.meta/testAnnotation.json new file mode 100644 index 0000000..a1ea1c7 --- /dev/null +++ b/exercises/practice/minesweeper/.meta/testAnnotation.json @@ -0,0 +1,50 @@ +[ + { + "name": "minesweeper.annotate.tests.ex1", + "test_code": "expected = []\n input = []\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"no rows\"" + }, + { + "name": "minesweeper.annotate.tests.ex2", + "test_code": "expected = [\"\"]\n input = [\"\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"no columns\"" + }, + { + "name": "minesweeper.annotate.tests.ex3", + "test_code": "expected = [\" \", \" \", \" \"]\n input = [\" \", \" \", \" \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"no mines\"" + }, + { + "name": "minesweeper.annotate.tests.ex4", + "test_code": "expected = [\"***\", \"***\", \"***\"]\n input = [\"***\", \"***\", \"***\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"minefield with only mines\"" + }, + { + "name": "minesweeper.annotate.tests.ex5", + "test_code": "expected = [\"111\", \"1*1\", \"111\"]\n input = [\" \", \" * \", \" \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"mine surrounded by spaces\"" + }, + { + "name": "minesweeper.annotate.tests.ex6", + "test_code": "expected = [\"***\", \"*8*\", \"***\"]\n input = [\"***\", \"* *\", \"***\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"space surrounded by mines\"" + }, + { + "name": "minesweeper.annotate.tests.ex7", + "test_code": "expected = [\"1*2*1\"]\n input = [\" * * \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"horizontal line\"" + }, + { + "name": "minesweeper.annotate.tests.ex8", + "test_code": "expected = [\"*1 1*\"]\n input = [\"* *\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"horizontal line, mines at edges\"" + }, + { + "name": "minesweeper.annotate.tests.ex9", + "test_code": "expected = [\"1\", \"*\", \"2\", \"*\", \"1\"]\n input = [\" \", \"*\", \" \", \"*\", \" \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"vertical line\"" + }, + { + "name": "minesweeper.annotate.tests.ex10", + "test_code": "expected = [\"*\", \"1\", \" \", \"1\", \"*\"]\n input = [\"*\", \" \", \" \", \" \", \"*\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"vertical line, mines at edges\"" + }, + { + "name": "minesweeper.annotate.tests.ex11", + "test_code": "expected = [\" 2*2 \", \"25*52\", \"*****\", \"25*52\", \" 2*2 \"]\n input = [\" * \", \" * \", \"*****\", \" * \", \" * \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"cross\"" + }, + { + "name": "minesweeper.annotate.tests.ex12", + "test_code": "expected = [\"1*22*1\", \"12*322\", \" 123*2\", \"112*4*\", \"1*22*2\", \"111111\"]\n input = [\" * * \", \" * \", \" * \", \" * *\", \" * * \", \" \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"large minefield\"" + } +] \ No newline at end of file diff --git a/exercises/practice/minesweeper/.meta/testLoader.md b/exercises/practice/minesweeper/.meta/testLoader.md new file mode 100644 index 0000000..0894346 --- /dev/null +++ b/exercises/practice/minesweeper/.meta/testLoader.md @@ -0,0 +1,9 @@ +# Testing transcript for minesweeper exercise + +```ucm +.> load ./minesweeper.u +.> add +.> load ./minesweeper.test.u +.> add +.> move.term minesweeper.tests tests +``` diff --git a/exercises/practice/minesweeper/.meta/tests.toml b/exercises/practice/minesweeper/.meta/tests.toml new file mode 100644 index 0000000..2a14222 --- /dev/null +++ b/exercises/practice/minesweeper/.meta/tests.toml @@ -0,0 +1,46 @@ +# 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. + +[0c5ec4bd-dea7-4138-8651-1203e1cb9f44] +description = "no rows" + +[650ac4c0-ad6b-4b41-acde-e4ea5852c3b8] +description = "no columns" + +[6fbf8f6d-a03b-42c9-9a58-b489e9235478] +description = "no mines" + +[61aff1c4-fb31-4078-acad-cd5f1e635655] +description = "minefield with only mines" + +[84167147-c504-4896-85d7-246b01dea7c5] +description = "mine surrounded by spaces" + +[cb878f35-43e3-4c9d-93d9-139012cccc4a] +description = "space surrounded by mines" + +[7037f483-ddb4-4b35-b005-0d0f4ef4606f] +description = "horizontal line" + +[e359820f-bb8b-4eda-8762-47b64dba30a6] +description = "horizontal line, mines at edges" + +[c5198b50-804f-47e9-ae02-c3b42f7ce3ab] +description = "vertical line" + +[0c79a64d-703d-4660-9e90-5adfa5408939] +description = "vertical line, mines at edges" + +[4b098563-b7f3-401c-97c6-79dd1b708f34] +description = "cross" + +[04a260f1-b40a-4e89-839e-8dd8525abe0e] +description = "large minefield" diff --git a/exercises/practice/minesweeper/minesweeper.test.u b/exercises/practice/minesweeper/minesweeper.test.u new file mode 100644 index 0000000..358f5fe --- /dev/null +++ b/exercises/practice/minesweeper/minesweeper.test.u @@ -0,0 +1,98 @@ +minesweeper.annotate.tests.ex1 = + expected = [] + input = [] + expected === minesweeper.annotate input + |> expect + |> Test.label "no rows" + +minesweeper.annotate.tests.ex2 = + expected = [""] + input = [""] + expected === minesweeper.annotate input + |> expect + |> Test.label "no columns" + +minesweeper.annotate.tests.ex3 = + expected = [" ", " ", " "] + input = [" ", " ", " "] + expected === minesweeper.annotate input + |> expect + |> Test.label "no mines" + +minesweeper.annotate.tests.ex4 = + expected = ["***", "***", "***"] + input = ["***", "***", "***"] + expected === minesweeper.annotate input + |> expect + |> Test.label "minefield with only mines" + +minesweeper.annotate.tests.ex5 = + expected = ["111", "1*1", "111"] + input = [" ", " * ", " "] + expected === minesweeper.annotate input + |> expect + |> Test.label "mine surrounded by spaces" + +minesweeper.annotate.tests.ex6 = + expected = ["***", "*8*", "***"] + input = ["***", "* *", "***"] + expected === minesweeper.annotate input + |> expect + |> Test.label "space surrounded by mines" + +minesweeper.annotate.tests.ex7 = + expected = ["1*2*1"] + input = [" * * "] + expected === minesweeper.annotate input + |> expect + |> Test.label "horizontal line" + +minesweeper.annotate.tests.ex8 = + expected = ["*1 1*"] + input = ["* *"] + expected === minesweeper.annotate input + |> expect + |> Test.label "horizontal line, mines at edges" + +minesweeper.annotate.tests.ex9 = + expected = ["1", "*", "2", "*", "1"] + input = [" ", "*", " ", "*", " "] + expected === minesweeper.annotate input + |> expect + |> Test.label "vertical line" + +minesweeper.annotate.tests.ex10 = + expected = ["*", "1", " ", "1", "*"] + input = ["*", " ", " ", " ", "*"] + expected === minesweeper.annotate input + |> expect + |> Test.label "vertical line, mines at edges" + +minesweeper.annotate.tests.ex11 = + expected = [" 2*2 ", "25*52", "*****", "25*52", " 2*2 "] + input = [" * ", " * ", "*****", " * ", " * "] + expected === minesweeper.annotate input + |> expect + |> Test.label "cross" + +minesweeper.annotate.tests.ex12 = + expected = ["1*22*1", "12*322", " 123*2", "112*4*", "1*22*2", "111111"] + input = [" * * ", " * ", " * ", " * *", " * * ", " "] + expected === minesweeper.annotate input + |> expect + |> Test.label "large minefield" + +test> minesweeper.tests = runAll [ + minesweeper.annotate.tests.ex1, + minesweeper.annotate.tests.ex2, + minesweeper.annotate.tests.ex3, + minesweeper.annotate.tests.ex4, + minesweeper.annotate.tests.ex5, + minesweeper.annotate.tests.ex6, + minesweeper.annotate.tests.ex7, + minesweeper.annotate.tests.ex8, + minesweeper.annotate.tests.ex9, + minesweeper.annotate.tests.ex10, + minesweeper.annotate.tests.ex11, + minesweeper.annotate.tests.ex12 +] diff --git a/exercises/practice/minesweeper/minesweeper.u b/exercises/practice/minesweeper/minesweeper.u new file mode 100644 index 0000000..790b2da --- /dev/null +++ b/exercises/practice/minesweeper/minesweeper.u @@ -0,0 +1,2 @@ +minesweeper.annotate : [Text] -> [Text] +minesweeper.annotate = todo "implement annotate"