Code: Select all
..*.
A...
..B*
C*..[code]
Get the number of mines (*) surrounding each letter.<ul><li>Start a new module named MineSweeper.
[code]module MineSweeper where[code]
</li><li>Import a few useful modules.
[code]import List
import IO
-- I realized this one isn't really necessary:
-- import Char
import Maybe[code]
</li><li>Create a new data type called Square. Square can be represented by a Mine, a Safe spot, or a Letter representing some other character. Derive Show and Eq so they can be stringified for printing, and so that squares can be compared for equality.
[code]data Square = Mine | Safe | Letter Char deriving (Show, Eq)[code]
</li><li>Create type Row and type Grid, which are basically just type synonyms for lists of Squares and lists of lists of Squares.
[code]type Row = [Square]
type Grid = [Row][code]
</li><li>Get a grid given a FilePath (a String).
[code]getGrid :: FilePath -> IO Grid[code]
To do this, read the file's contents, then use the "lines" function to split that content into lines. Let the first line be called "first" and the rest be called "others." Use parseGridDimensions to parse the first line and get the height and width of the grid. Use createGrid to process the other lines and turn characters into Mine, Safe, or Letter values. Then use trimGrid to remove anything outside the bounds of the dimensions parsed earlier.
[code]getGrid fileName = do
contents <- readFile fileName
let (first:others) = lines contents
let dimensions = parseGridDimensions first
return $ trimGrid dimensions $ createGrid others[code]
</li><li>Generic parseGridDimensions function. Take a String and return two readable values. In this case Ints.
[code]parseGridDimensions :: Read a => String -> (a, a)[code]
The reads function parses the desired Int from the input String. We parse once, then parse the String remaining after the first parse.
[code]parseGridDimensions input = (n, m)
where
[(n, input')] = reads input
[(m, _) ] = reads input'[code]
</li><li>Pretty simple. Takes a Char and converts it to a Square.
[code]charToSquare :: Char -> Square[code]
If ch is '.' return Safe. If ch is '*' then we get Mine. Otherwise we get a Letter value storing the input Char.
[code]charToSquare ch =
case ch of
'.' -> Safe
'*' -> Mine
n -> Letter n[code]
</li><li>Process a list of Strings into a Grid.
[code]createGrid :: [String] -> Grid[code]
Do this by mapping "map toSquare" to each String. "map toSquare" applies toSquare to each Char in a String and collects the result.
The result of running this on "D.*." would be [Letter 'D', Safe, Mine, Safe].
[code]createGrid = map (map charToSquare)[code]
</li><li>Trim unnecessary rows and columns from the Grid.
[code]trimGrid :: (Int, Int) -> Grid -> Grid[code]
Do this by first "take n", which grabs the first n rows from the Grid. Then, for each of those Rows, "take m", which grabs the first m elements in each Row.
[code]trimGrid (n, m) = map (take m) . take n[code]
</li><li>Find all of the letters in the grid and the coordinates at which they're located, starting from some initial set of coordinates (0, 0).
[code]findLetters :: (Int, Int) -> Grid -> [(Char, (Int, Int))][code]
If the grid to search is empty, then we don't care about the coordinates. Clearly there can be no Letters within.
[code]findLetters (_,_) [] = [][code]
If the first Row in the Grid is empty, continue searching with the remaining Rows. Increment n by 1 and set m to zero to indicates starting again one row down at .
[code]findLetters (n, m) ([]:r) = findLetters (n + 1, 0) r[code]
If the first element in the current Row is a Letter, then append the Char it contains and the current coordinates to the result of finding the latters in the remaining Rows. Increment the column (m) by 1.
[code]findLetters coords@(n, m) (((Letter ch):xs):r) =
(ch, coords) : findLetters (n, m + 1) (xs:r)[code]
If the first element in the current grid is anything other than a letter, find the Letters in the remaining part of the Grid, incrementing the column count by 1.
[code]findLetters (n, m) ((_:xs):r) =
findLetters (n, m + 1) (xs:r)[code]
</li><li>Find the Letters present in the Grid.
[code]lettersPresent :: Grid -> [Char][code]
To do this, first use the above findLetters function to find all of the letters and their coordinates. Then unzip that list, meaning you get two lists: one containing the Letters,and the other containing the coordinates. Use fst to just get the first list.
[code]lettersPresent = fst . unzip . findLetters (0, 0)[code]
</li><li>Find all coordinates adjacent to the input coordinates based on input dimensions for the grid.
[code]adjacentCoords :: (Int, Int) -> (Int, Int) -> [(Int, Int)][code]
Let n and m be the bounds of the Grid. Let n' and m' be the coordinates of the target. n'' and m'' are each coordinate between n' - 1 and n' + 1 and m' - 1 and m' + 1. Coordinates are only accepted if they do not match the target exactly, and are within the bounds of the Grid.
[code]adjacentCoords s@(n, m) s'@(n', m') =
[(n'', m'') | n'' <- [n' - 1 .. n' + 1],
m'' <- [m' - 1 .. m' + 1],
(n'', m'') /= s' && inBounds s (n'', m'')][code]
</li><li>Get a Grid's dimensions.
[code]getGridDimensions :: Grid -> (Int, Int)[code]
Get the height and width of a Grid by calculating its length and the length of it's first Row.
[code]getGridDimensions grid@(x:_) = (length grid, length x)[code]
</li><li>Collect all adjacent Squares to a set of target coordinates.
[code]adjacentSquares :: (Int, Int) -> Grid -> [Square][code]
First collect all adjacentCoords. Then, for each of those collect the Square located at them.
[code]adjacentSquares s@(n, m) grid =
map (`squareAt` grid) $ adjacentCoords (getGridDimensions grid) s[code]
</li><li>Determine if a set of coordinates is within a set of bounds.
[code]inBounds :: (Int, Int) -> (Int, Int) -> Bool[code]
Let n and m be the bounds of the Grid. Let n' and m' be the coordinates being tested. Pretty easy to understand.
[code]inBounds (n, m) (n', m') =
n' >= 0 && n' < n && m' >= 0 && m' < m[code]
</li><li>Retrieve a Square at a particular set of coordinates within a Grid.
[code]squareAt :: (Int, Int) -> Grid -> Square[code]
Use the !! operator to do this. Consider "grid !! n !! m" similar to "grid[n][m]" in some other languages.
[code]squareAt (n, m) grid = grid !! n !! m[code]
</li><li>Count adjacent Squares which are Mines.
[code]countAdjacentMines :: (Int, Int) -> Grid -> Int[code]
Find all adjacentSquares, filter it down to just those that are Mines, then count the resulting list.
[code]countAdjacentMines coords =
length . filter (Mine ==) . adjacentSquares coords[code]
</li><li>Bundle all of it up together to read a file, get the Grid it represents, find the letters within it, then count the mines adjacent to those letters.
[code]getAdjacentMineCounts :: FilePath -> IO [(Char, Int)]
getAdjacentMineCounts fileName = do
grid <- getGrid fileName
let letterMap = findLetters (0, 0) grid
let letters = lettersPresent grid
return [(l, countAdjacentMines (fromJust $ lookup l letterMap) grid) | l <- letters][code]</li></ul>
[color=#888888][size=85]Archived topic from Iceteks, old topic ID:3180, old post ID:25942[/size][/color]