Skip to content

Instantly share code, notes, and snippets.

@tkshill
Created April 19, 2021 00:48
Show Gist options
  • Save tkshill/01e1551369a1a5ce6081eb5001fd792a to your computer and use it in GitHub Desktop.
Save tkshill/01e1551369a1a5ce6081eb5001fd792a to your computer and use it in GitHub Desktop.
Solving the exercism Minesweeper problem with Haskell

Minesweeper

Add the mine counts to a completed Minesweeper board.

Minesweeper 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.

In this exercise you have to create some code that counts the number of mines adjacent to a given empty square and replaces that square with the count.

The board is a rectangle composed of blank space (' ') characters. A mine is represented by an asterisk ('*') character.

If a given space has no adjacent mines at all, leave that square blank.

Examples

For example you may receive a 5 x 4 board like this (empty spaces are represented here with the '·' character for display on screen):

·*·*·
··*··
··*··
·····

And your code will transform it into this:

1*3*1
13*31
·2*2·
·111·
module Minesweeper (annotate) where
import Data.Char (intToDigit)
import Data.List.Split (chunksOf)
annotate :: [String] -> [String]
annotate [] = []
annotate [[]] = [[]]
annotate board =
toBoard $ zipWith toOutput flatboard $ bombCounts board
where
flatboard = concat board
toBoard = chunksOf (length (head board))
-- convert board (list of strings) to list of bomb counts around each character
bombCounts :: [String] -> [Int]
bombCounts board =
map (countBombsAround board) $ getCoords board
-- given a list of strings, convert to the appropriate array of characters
getCoords :: Foldable t => [t a] -> [(Int, Int)]
getCoords xs =
[(x, y) | x <- [0 .. len -1], y <- [0 .. width -1]]
where
len = length xs
width = length $ head xs
-- given a character and the number of bombs around it, convert to output character
toOutput :: Char -> Int -> Char
toOutput c n
| c == '*' = c
| n == 0 = ' '
| otherwise = intToDigit n
-- given a grid, and a cell index, count the bombs around that cell
countBombsAround :: [String] -> (Int, Int) -> Int
countBombsAround grid index =
foldr addBombsCount 0 neighbours
where
neighbours = getNeighbours len width index
len = length grid
width = length $ head grid
addBombsCount (x, y) n =
if (grid !! x) !! y == '*' then n + 1 else n
-- given the dimensions of a grid, and a point, calculate the valid cell indexes that surround that point
getNeighbours :: Int -> Int -> (Int, Int) -> [(Int, Int)]
getNeighbours l w (x, y) =
filter isValidCoord potentialNeighbours
where
isValidCoord (rowi, coli) =
rowi >= 0 && coli >= 0 && rowi < l && coli < w
potentialNeighbours =
[(i, j) | i <- [x -1 .. x + 1], j <- [y -1 .. y + 1]]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment