Last active
August 29, 2015 14:02
-
-
Save simon-brooke/d0833e7319924ce6e62c to your computer and use it in GitHub Desktop.
An attempt to solve the minesweeper kata in Clojure
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(ns minesweeper.core) | |
;; # minesweeper | |
;; An attempt to solve the Minesweeker kata from http://codingdojo.org/ in Clojure. | |
;; ## Limitations | |
;; The parser cannot handle new lines, as I couldn't think of a regular expression | |
;; to divide the input string on which would produce the new line character as a | |
;; token. Consequently, you need to use the pipe ('|') character to separate lines | |
;; in the input map. Also, the heigh/width elements of the input seemed to me | |
;; redundent - we can count the number of columns and rows from the data supplied | |
;; (this also allows us to cope with uneven-length input lines). | |
;; The output is a string, with embedded new lines. Because the minefield is | |
;; treated as a sparse array, the output covers a rectangle from one cell above | |
;; the topmost mine to one cell below the bottommost, and one cell to the left | |
;; of the leftmost to one cell to the right of the rightmost, whatever the size of | |
;; the input map. | |
;; ## Usage | |
;; (print (output-danger-map (parse-mines "*...|.*..|..*.|...*"))) | |
(defn parse-mines | |
"Consider minestring as a string in which '.' indicates a safe location, | |
'*' represents a mine, '|' represents the end of a row. Returns a sparse | |
array represented as a sequence of tuples [row col]" | |
([minestring] | |
(parse-mines (re-seq #"." minestring) 0 0)) | |
([sequence row col] | |
"Consider sequence as a sequence of single-character strings with | |
interpretations as discussed above; row and col as position coordinates | |
within the minefield." | |
(cond | |
(empty? sequence) nil | |
true | |
(let [chr (first sequence)] | |
(cond | |
(= chr "|") (parse-mines (rest sequence) (+ row 1) 0) | |
(= chr ".") (parse-mines (rest sequence) row (+ 1 col)) | |
(= chr "*") (cons [row col] (parse-mines (rest sequence) row (+ 1 col)))))))) | |
(defn mine-at | |
"Return 1 if there is a mine at row, col in the minefield, else 0" | |
[minefield row col] | |
(if | |
(>= (.indexOf minefield [row col]) 0) 1 | |
0)) | |
(defn adjacent-mines | |
"Produce a list containing a 1 for each adjacent location containing a mine, | |
0 for each not containing a mine" | |
[minefield row col] | |
(let [minx (- row 1) | |
maxx (+ row 2) ;; now there's a gotcha: (range 0 2) returns (0 1) not (0 1 2) | |
miny (- col 1) | |
maxy (+ col 2)] | |
(for | |
[x (range minx maxx) | |
y (range miny maxy)] | |
(mine-at minefield x y)))) | |
(defn risk-at | |
"Count the number of mines adjacent to the cell at (row, col) in this | |
minefield" | |
[minefield row col] | |
(apply + (adjacent-mines minefield row col))) | |
(defn mine-or-risk-at | |
"If there is a mine at (row, col) in this minefield, return a '*' else | |
return the 'risk' (number of adjacent mines) of this location" | |
[minefield row col] | |
(if | |
(= (mine-at minefield row col) 1) "*" | |
(risk-at minefield row col))) | |
(defn output-danger-map | |
"Output, as a string, a map of the dangerous locations in this minefield" | |
[minefield] | |
(if (empty? minefield) "No mines found" | |
(let [minx (- (apply min (map #(get % 1) minefield)) 1) | |
maxx (+ (apply max (map #(get % 1) minefield)) 2) | |
miny (- (apply min (map #(get % 0) minefield)) 1) | |
maxy (+ (apply max (map #(get % 0) minefield)) 2)] | |
(apply str | |
(flatten | |
(for | |
[x (range minx maxx)] | |
(concat | |
(for | |
[y (range miny maxy) ] | |
(mine-or-risk-at minefield x y)) ["\n"]))))))) | |
(print (output-danger-map (parse-mines "*...|....|.*..|...."))) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment