[racket] Rosetta Code Minesweeper Implementation

From: Sean Kanaley (skanaley at gmail.com)
Date: Mon Jun 3 11:03:16 EDT 2013

Hello all,

Rosetta Code does not have a minesweeper implementation in Racket.  I have
created the one shown below.  Before I post it to Rosetta, I was looking
for possible feedback to shorten, make clearer, make "safer" in some way,
make more use of library functions, etc.  I would hate to give Racket a bad
name in the official show-what-your-language-can-do repository.

I believe it's not an unreasonable implementation, and I understand this
request may not sound exhilarating, so I do not expect any replies!  After
however long I feel I will simply post this solution!  With comments and a
bit of utility function groundwork it's 131 lines, 82 with that removed.

To test, run "(run)" and enter "! <row> <col>" to clear @ (row,col) or "?
<row> <col>" to assume a mine @ (row,col).

#lang racket
;turns list into list of lists each of size n
(define (group-n n l)
  (let group-n ([l l] [acc '()])
    (if (null? l)
        (reverse acc)
        (let-values ([(takes drops) (split-at l n)])
          (group-n drops (cons takes acc))))))

;small 2d vector library
;uses built-in vector with minor size stored at index 0
(define (build-vector2 maj min [f (const 0)])
  (let ([v (build-vector (add1 (* maj min))
                         (λ (n) (let-values ([(i j) (quotient/remainder
(sub1 n) min)])
                                  (f i j))))])
    (vector-set! v 0 min)
(define (vector2-maj v) (quotient (sub1 (vector-length v)) (vector2-min v)))
(define (vector2-min v) (vector-ref v 0))
(define (vector2-in-range? v i j)
  (and (<= 0 i (sub1 (vector2-maj v)))
       (<= 0 j (sub1 (vector2-min v)))))
(define (vector2-index v i j) (+ 1 j (* i (vector2-min v))))
(define (vector2-ref v i j) (vector-ref v (vector2-index v i j)))
(define (vector2-set! v i j x) (vector-set! v (vector2-index v i j) x))
(define (vector2->immutable-vector2 v) (vector->immutable-vector v))
(define (vector2->lists v) (group-n (vector2-min v) (cdr (vector->list v))))

;board uses vector2's directly, but maintaining an abstraction is nice
(define (board-ref b row col) (vector2-ref b row col))
(define (board-rows b) (vector2-maj b))
(define (board-cols b) (vector2-min b))
(define (on-board? b row col) (vector2-in-range? b row col))
(define (board->lists b) (vector2->lists b))
;run on adjacent board positions
(define-syntax-rule (for-adj b (r row) (c col) body ...)
  (for ([i '(0 0 1 1 1 -1 -1 -1)] [j '(1 -1 0 -1 1 0 -1 1)])
    (let ([r (+ row i)]
          [c (+ col j)])
      (when (on-board? b r c)
        body ...))))
;mark is either hidden, assume-mine, or clear
;n is int equal to # adj mines or -1 for mine
(struct pos ([mark #:mutable] n))
(define (mine? p) (= (pos-n p) -1))
;hidden0? is needed because only spaces with no mines in them and no mines
adjacent to them are cleared
(define (hidden0? p)
  (and (symbol=? (pos-mark p) 'hidden)
       (zero? (pos-n p))))
(define (show-pos p)
  (match-let ([(pos m n) p])
    (case m
      [(hidden) "."]
      [(assume-mine) "?"]
      [(clear) (if (zero? n) " " (number->string n))]
      [else (error "illegal mark" m)])))
;put "|" around positions
(define (show-board b)
  (for ([row (board->lists b)])
    (displayln (format "|~a|" (string-join (map show-pos row) "|")))))

;winning = every position is either cleared or a hidden mine
(define (win? b)
  (for*/and ([r (range 0 (board-rows b))]
             [c (range 0 (board-cols b))])
    (let ([p (board-ref b r c)])
      (or (symbol=? (pos-mark p) 'clear)
          (mine? p)))))

;the board is immutable even though its individual positions can mutate
their mark field
(define (init-board rows cols)
  (let ([chance (+ (/ (random) 10) 0.1)]
        ;empty board
        [b (build-vector2 rows cols (λ (r c) (pos 'hidden 0)))])
    ;loop whole board
    (for* ([row (range 0 rows)]
           [col (range 0 cols)])
      (when (< (random) chance)
        ;put a mine
        (vector2-set! b row col (pos 'hidden -1))
        ;increment adjacent mine counts unless that adjacent position is a
        (for-adj b (r row) (c col)
                 (let ([p (board-ref b r c)])
                   (unless (mine? p)
                     (vector2-set! b r c (pos 'hidden (add1 (pos-n
    (vector2->immutable-vector2 b)))

;only clear position if its hidden and isn't adjacent to a mine
(define (try-clear! p)
  (when (hidden0? p)
    (set-pos-mark! p 'clear)))

;the following player move functions return boolean where #f = lose, #t =
still going
;assuming can never directly lose ((void) == #t)
(define (assume! b row col) (set-pos-mark! (board-ref b row col)

;clearing loses when the chosen position is a mine
;void = #t as far as if works, so no need to return #t
(define (clear! b row col)
  (let ([p (board-ref b row col)])
    (and (not (mine? p))
         ;not a mine, so recursively check adjacents, and maintain list of
visited positions
         ;to avoid infinite loops
         (let ([seen '()])
           ;clear the chosen position first
           (set-pos-mark! p 'clear)
           (let clear-adj ([row row] [col col])
             (for-adj b (r row) (c col)
                      ;make sure its not seen
                      (when (and (not (member (list r c) seen))
                                 (try-clear! (board-ref b r c)))
                        ;it was cleared, so loop after saving this position
as being seen
                        (set! seen (cons (list r c) seen))
                        (clear-adj r c))))))))

(define (parse-and-do-move! b s)
  (match-let* ([(list type row col) (string-split s)]
               [row (string->number row)]
               [col (string->number col)])
    (case type
      [("?") (assume! b row col)]
      [("!") (clear! b row col)]
      [else (error "invalid move command" type)])))
(define (run)
  (let ([b (init-board 4 6)])
    (let run ()
      (show-board b)
      (display "enter move: ")
      (if (parse-and-do-move! b (read-line))
          (if (win? b) (displayln "CLEAR!") (run))
          (displayln "BOOM!")))))
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.racket-lang.org/users/archive/attachments/20130603/cd942fed/attachment.html>

Posted on the users mailing list.