RosettaCodeData/Task/Minesweeper-game/Clojure/minesweeper-game.clj
2023-07-01 13:44:08 -04:00

78 lines
2.2 KiB
Clojure

(defn take-random [n coll]
(->> (repeatedly #(rand-nth coll))
distinct
(take n ,)))
(defn postwalk-fs
"Depth first post-order traversal of form, apply successive fs at each level.
(f1 (map f2 [..]))"
[[f & fs] form]
(f
(if (and (seq fs) (coll? form))
(into (empty form) (map (partial postwalk-fs fs) form))
form)))
(defn neighbors [x y n m pred]
(for [dx (range (Math/max 0 (dec x)) (Math/min n (+ 2 x)))
dy (range (Math/max 0 (dec y)) (Math/min m (+ 2 y)))
:when (pred dx dy)]
[dx dy]))
(defn new-game [n m density]
(let [mines (set (take-random (Math/floor (* n m density)) (range (* n m))))]
(->> (for [y (range m)
x (range n)
:let [neighbor-mines (count (neighbors x y n m #(mines (+ %1 (* %2 n)))))]]
(#(if (mines (+ (* y n) x)) (assoc % :mine true) %) {:value neighbor-mines}))
(partition n ,)
(postwalk-fs [vec vec] ,))))
(defn display [board]
(postwalk-fs [identity println #(condp % nil
:marked \?
:opened (:value %)
\.)] board))
(defn boom [{board :board}]
(postwalk-fs [identity println #(if (:mine %) \* (:value %))] board)
true)
(defn open* [board [[x y] & rest]]
(if-let [value (get-in board [y x :value])] ; if nil? value -> nil? x -> nil? queue
(recur
(assoc-in board [y x :opened] true)
(if (pos? value)
rest
(concat rest
(neighbors x y (count (first board)) (count board)
#(not (get-in board [%2 %1 :opened]))))))
board))
(defn open [board x y]
(let [x (dec x), y (dec y)]
(condp (get-in board [y x]) nil
:mine {:boom true :board board}
:opened board
(open* board [[x y]]))))
(defn mark [board x y]
(let [x (dec x), y (dec y)]
(assoc-in board [y x :marked] (not (get-in board [y x :marked])))))
(defn done? [board]
(if (:boom board)
(boom board)
(do (display board)
(->> (flatten board)
(remove :mine ,)
(every? :opened ,)))))
(defn play [n m density]
(let [board (new-game n m density)]
(println [:mines (count (filter :mine (flatten board)))])
(loop [board board]
(when-not (done? board)
(print ">")
(let [[cmd & xy] (.split #" " (read-line))
[x y] (map #(Integer. %) xy)]
(recur ((if (= cmd "mark") mark open) board x y)))))))