Skip to content

Instantly share code, notes, and snippets.

@simon-brooke
Last active August 29, 2015 14:02
Show Gist options
  • Save simon-brooke/d0833e7319924ce6e62c to your computer and use it in GitHub Desktop.
Save simon-brooke/d0833e7319924ce6e62c to your computer and use it in GitHub Desktop.
An attempt to solve the minesweeper kata in Clojure
(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