Skip to content

Commit 68cf9f7

Browse files
authored
minesweeper-exercise: Added new exercise. (#130)
1 parent a27178a commit 68cf9f7

10 files changed

+348
-0
lines changed

config.json

+8
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,14 @@
414414
"practices": [],
415415
"prerequisites": [],
416416
"difficulty": 1
417+
},
418+
{
419+
"slug": "minesweeper",
420+
"name": "Minesweeper",
421+
"uuid": "58338fa3-b558-4de7-af05-04d5c9289000",
422+
"practices": [],
423+
"prerequisites": [],
424+
"difficulty": 7
417425
}
418426
]
419427
},
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Instructions
2+
3+
Your task is to add the mine counts to empty squares in a completed Minesweeper board.
4+
The board itself is a rectangle composed of squares that are either empty (`' '`) or a mine (`'*'`).
5+
6+
For each empty square, count the number of mines adjacent to it (horizontally, vertically, diagonally).
7+
If the empty square has no adjacent mines, leave it empty.
8+
Otherwise replace it with the adjacent mines count.
9+
10+
For example, you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen):
11+
12+
```text
13+
·*·*·
14+
··*··
15+
··*··
16+
·····
17+
```
18+
19+
Which your code should transform into this:
20+
21+
```text
22+
1*3*1
23+
13*31
24+
·2*2·
25+
·111·
26+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Introduction
2+
3+
[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.
4+
5+
[wikipedia]: https://en.wikipedia.org/wiki/Minesweeper_(video_game)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"authors": [
3+
"SimaDovakin"
4+
],
5+
"files": {
6+
"solution": [
7+
"minesweeper.u"
8+
],
9+
"test": [
10+
"minesweeper.test.u"
11+
],
12+
"example": [
13+
".meta/examples/minesweeper.example.u"
14+
]
15+
},
16+
"blurb": "Add the numbers to a minesweeper board."
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
minesweeper.annotate : [Text] -> [Text]
2+
minesweeper.annotate board =
3+
boardHeight = size board |> toInt
4+
boardWidth = List.head board |> Optional.getOrElse "" |> Text.size |> toInt
5+
neighbours =
6+
board
7+
|> findBoardMines
8+
|> map (coordinates -> getNeighbours coordinates boardWidth boardHeight)
9+
|> join
10+
coordinatesMap = createCoordinatesMap neighbours
11+
annotateBoard board coordinatesMap
12+
13+
minesweeper.directions : [(Int, Int)]
14+
minesweeper.directions =
15+
use base.Int range
16+
range -1 +2
17+
|> map (n -> zip (fill 3 n) (range -1 +2))
18+
|> join
19+
|> filter (p -> (at1 p, at2 p) !== (+0, +0))
20+
21+
minesweeper.natToDigit : Nat -> Char
22+
minesweeper.natToDigit = cases
23+
n | (n >= 0) && (n <= 9) -> Char.fromNat (48 + n) |> Optional.getOrElse ?\s
24+
_ -> ?\s
25+
26+
minesweeper.getNeighbours : (Int, Int) -> Int -> Int -> [(Int, Int)]
27+
minesweeper.getNeighbours coordinates width height =
28+
addCoordinates coords1 coords2 =
29+
use Int +
30+
(r1, c1) = coords1
31+
(r2, c2) = coords2
32+
(r1 + r2, c1 + c2)
33+
isOnBoard w h coords =
34+
(r, c) = coords
35+
(r >= +0) && (r < h) && (c >= +0) && (c < w)
36+
directions
37+
|> map (addCoordinates coordinates)
38+
|> filter (isOnBoard width height)
39+
40+
minesweeper.createCoordinatesMap : [(Int, Int)] -> Map (Int, Int) Nat
41+
minesweeper.createCoordinatesMap coordinates =
42+
incrementFrequency = cases
43+
Some old -> Some (old + 1)
44+
_ -> Some 1
45+
coordinates
46+
|> List.foldLeft (acc coord -> Map.alter incrementFrequency coord acc) Map.empty
47+
48+
minesweeper.findRowMines : Int -> Text -> [(Int, Int)]
49+
minesweeper.findRowMines rowNumber row =
50+
use base.Int range
51+
isMine = at2 >> (==) ?*
52+
row
53+
|> toCharList
54+
|> zip (range +0 (size row |> toInt))
55+
|> filter isMine
56+
|> map (p -> (rowNumber, at1 p))
57+
58+
minesweeper.findBoardMines : [Text] -> [(Int, Int)]
59+
minesweeper.findBoardMines board =
60+
use base.Int range
61+
board
62+
|> zip (range +0 (size board |> toInt))
63+
|> map (uncurry findRowMines)
64+
|> join
65+
66+
minesweeper.annotateRow : Int -> Text -> Map (Int, Int) Nat -> Text
67+
minesweeper.annotateRow rowNumber row coordinatesMap =
68+
use base.Int range
69+
populateTile indexedTile =
70+
(colNumber, value) = indexedTile
71+
match value with
72+
?* -> value
73+
_ -> match Map.get (rowNumber, colNumber) coordinatesMap with
74+
Some val -> natToDigit val
75+
None -> value
76+
row
77+
|> toCharList
78+
|> zip (range +0 (size row |> toInt))
79+
|> map populateTile
80+
|> fromCharList
81+
82+
minesweeper.annotateBoard : [Text] -> Map (Int, Int) Nat -> [Text]
83+
minesweeper.annotateBoard board coordinatesMap =
84+
use base.Int range
85+
board
86+
|> zip (range +0 (size board |> toInt))
87+
|> map (p -> (uncurry annotateRow p) coordinatesMap)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
[
2+
{
3+
"name": "minesweeper.annotate.tests.ex1",
4+
"test_code": "expected = []\n input = []\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"no rows\""
5+
},
6+
{
7+
"name": "minesweeper.annotate.tests.ex2",
8+
"test_code": "expected = [\"\"]\n input = [\"\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"no columns\""
9+
},
10+
{
11+
"name": "minesweeper.annotate.tests.ex3",
12+
"test_code": "expected = [\" \", \" \", \" \"]\n input = [\" \", \" \", \" \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"no mines\""
13+
},
14+
{
15+
"name": "minesweeper.annotate.tests.ex4",
16+
"test_code": "expected = [\"***\", \"***\", \"***\"]\n input = [\"***\", \"***\", \"***\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"minefield with only mines\""
17+
},
18+
{
19+
"name": "minesweeper.annotate.tests.ex5",
20+
"test_code": "expected = [\"111\", \"1*1\", \"111\"]\n input = [\" \", \" * \", \" \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"mine surrounded by spaces\""
21+
},
22+
{
23+
"name": "minesweeper.annotate.tests.ex6",
24+
"test_code": "expected = [\"***\", \"*8*\", \"***\"]\n input = [\"***\", \"* *\", \"***\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"space surrounded by mines\""
25+
},
26+
{
27+
"name": "minesweeper.annotate.tests.ex7",
28+
"test_code": "expected = [\"1*2*1\"]\n input = [\" * * \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"horizontal line\""
29+
},
30+
{
31+
"name": "minesweeper.annotate.tests.ex8",
32+
"test_code": "expected = [\"*1 1*\"]\n input = [\"* *\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"horizontal line, mines at edges\""
33+
},
34+
{
35+
"name": "minesweeper.annotate.tests.ex9",
36+
"test_code": "expected = [\"1\", \"*\", \"2\", \"*\", \"1\"]\n input = [\" \", \"*\", \" \", \"*\", \" \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"vertical line\""
37+
},
38+
{
39+
"name": "minesweeper.annotate.tests.ex10",
40+
"test_code": "expected = [\"*\", \"1\", \" \", \"1\", \"*\"]\n input = [\"*\", \" \", \" \", \" \", \"*\"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"vertical line, mines at edges\""
41+
},
42+
{
43+
"name": "minesweeper.annotate.tests.ex11",
44+
"test_code": "expected = [\" 2*2 \", \"25*52\", \"*****\", \"25*52\", \" 2*2 \"]\n input = [\" * \", \" * \", \"*****\", \" * \", \" * \"]\n expected === minesweeper.annotate input\n |> expect\n |> Test.label \"cross\""
45+
},
46+
{
47+
"name": "minesweeper.annotate.tests.ex12",
48+
"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\""
49+
}
50+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# Testing transcript for minesweeper exercise
2+
3+
```ucm
4+
.> load ./minesweeper.u
5+
.> add
6+
.> load ./minesweeper.test.u
7+
.> add
8+
.> move.term minesweeper.tests tests
9+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# This is an auto-generated file.
2+
#
3+
# Regenerating this file via `configlet sync` will:
4+
# - Recreate every `description` key/value pair
5+
# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
6+
# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
7+
# - Preserve any other key/value pair
8+
#
9+
# As user-added comments (using the # character) will be removed when this file
10+
# is regenerated, comments can be added via a `comment` key.
11+
12+
[0c5ec4bd-dea7-4138-8651-1203e1cb9f44]
13+
description = "no rows"
14+
15+
[650ac4c0-ad6b-4b41-acde-e4ea5852c3b8]
16+
description = "no columns"
17+
18+
[6fbf8f6d-a03b-42c9-9a58-b489e9235478]
19+
description = "no mines"
20+
21+
[61aff1c4-fb31-4078-acad-cd5f1e635655]
22+
description = "minefield with only mines"
23+
24+
[84167147-c504-4896-85d7-246b01dea7c5]
25+
description = "mine surrounded by spaces"
26+
27+
[cb878f35-43e3-4c9d-93d9-139012cccc4a]
28+
description = "space surrounded by mines"
29+
30+
[7037f483-ddb4-4b35-b005-0d0f4ef4606f]
31+
description = "horizontal line"
32+
33+
[e359820f-bb8b-4eda-8762-47b64dba30a6]
34+
description = "horizontal line, mines at edges"
35+
36+
[c5198b50-804f-47e9-ae02-c3b42f7ce3ab]
37+
description = "vertical line"
38+
39+
[0c79a64d-703d-4660-9e90-5adfa5408939]
40+
description = "vertical line, mines at edges"
41+
42+
[4b098563-b7f3-401c-97c6-79dd1b708f34]
43+
description = "cross"
44+
45+
[04a260f1-b40a-4e89-839e-8dd8525abe0e]
46+
description = "large minefield"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
minesweeper.annotate.tests.ex1 =
2+
expected = []
3+
input = []
4+
expected === minesweeper.annotate input
5+
|> expect
6+
|> Test.label "no rows"
7+
8+
minesweeper.annotate.tests.ex2 =
9+
expected = [""]
10+
input = [""]
11+
expected === minesweeper.annotate input
12+
|> expect
13+
|> Test.label "no columns"
14+
15+
minesweeper.annotate.tests.ex3 =
16+
expected = [" ", " ", " "]
17+
input = [" ", " ", " "]
18+
expected === minesweeper.annotate input
19+
|> expect
20+
|> Test.label "no mines"
21+
22+
minesweeper.annotate.tests.ex4 =
23+
expected = ["***", "***", "***"]
24+
input = ["***", "***", "***"]
25+
expected === minesweeper.annotate input
26+
|> expect
27+
|> Test.label "minefield with only mines"
28+
29+
minesweeper.annotate.tests.ex5 =
30+
expected = ["111", "1*1", "111"]
31+
input = [" ", " * ", " "]
32+
expected === minesweeper.annotate input
33+
|> expect
34+
|> Test.label "mine surrounded by spaces"
35+
36+
minesweeper.annotate.tests.ex6 =
37+
expected = ["***", "*8*", "***"]
38+
input = ["***", "* *", "***"]
39+
expected === minesweeper.annotate input
40+
|> expect
41+
|> Test.label "space surrounded by mines"
42+
43+
minesweeper.annotate.tests.ex7 =
44+
expected = ["1*2*1"]
45+
input = [" * * "]
46+
expected === minesweeper.annotate input
47+
|> expect
48+
|> Test.label "horizontal line"
49+
50+
minesweeper.annotate.tests.ex8 =
51+
expected = ["*1 1*"]
52+
input = ["* *"]
53+
expected === minesweeper.annotate input
54+
|> expect
55+
|> Test.label "horizontal line, mines at edges"
56+
57+
minesweeper.annotate.tests.ex9 =
58+
expected = ["1", "*", "2", "*", "1"]
59+
input = [" ", "*", " ", "*", " "]
60+
expected === minesweeper.annotate input
61+
|> expect
62+
|> Test.label "vertical line"
63+
64+
minesweeper.annotate.tests.ex10 =
65+
expected = ["*", "1", " ", "1", "*"]
66+
input = ["*", " ", " ", " ", "*"]
67+
expected === minesweeper.annotate input
68+
|> expect
69+
|> Test.label "vertical line, mines at edges"
70+
71+
minesweeper.annotate.tests.ex11 =
72+
expected = [" 2*2 ", "25*52", "*****", "25*52", " 2*2 "]
73+
input = [" * ", " * ", "*****", " * ", " * "]
74+
expected === minesweeper.annotate input
75+
|> expect
76+
|> Test.label "cross"
77+
78+
minesweeper.annotate.tests.ex12 =
79+
expected = ["1*22*1", "12*322", " 123*2", "112*4*", "1*22*2", "111111"]
80+
input = [" * * ", " * ", " * ", " * *", " * * ", " "]
81+
expected === minesweeper.annotate input
82+
|> expect
83+
|> Test.label "large minefield"
84+
85+
test> minesweeper.tests = runAll [
86+
minesweeper.annotate.tests.ex1,
87+
minesweeper.annotate.tests.ex2,
88+
minesweeper.annotate.tests.ex3,
89+
minesweeper.annotate.tests.ex4,
90+
minesweeper.annotate.tests.ex5,
91+
minesweeper.annotate.tests.ex6,
92+
minesweeper.annotate.tests.ex7,
93+
minesweeper.annotate.tests.ex8,
94+
minesweeper.annotate.tests.ex9,
95+
minesweeper.annotate.tests.ex10,
96+
minesweeper.annotate.tests.ex11,
97+
minesweeper.annotate.tests.ex12
98+
]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
minesweeper.annotate : [Text] -> [Text]
2+
minesweeper.annotate = todo "implement annotate"

0 commit comments

Comments
 (0)