On 06/03/2013 02:26 PM, Sean Kanaley wrote:
It's surprising how difficult a seemingly easy task like cloning
minesweeper can be.  Ultimately this took several hours for what I
thought would be 30-60 minutes.

Heh. :)

Also I couldn't find a way to make a mutable-array immutable with
anything resembling mutable-array->array so I left the board as mutable.

You're right - this isn't obvious. It didn't occur to me that anyone would want such a function, because a (Mutable-Array A) can be used anywhere an (Array A) is required. But sometimes you need to protect yourself from others...

Anyway, here's a short-ish implementation in Typed Racket:

(: array->immutable-array (All (A) ((Array A) -> (Array A))))
(define (array->immutable-array arr)
  (build-array (array-shape arr)
               (λ: ([js : Indexes]) (array-ref arr js))))

There are faster implementations, but they use undocumented, unsafe functions (albeit safely).

Neil ⊥

#lang racket
(require math)
;board uses arrays directly, but maintaining an abstraction is nice
(define (board-ref b row col) (array-ref b (vector row col)))
(define (board-rows b) (vector-ref (array-shape b) 0))
(define (board-cols b) (vector-ref (array-shape b) 1))
(define (on-board? b row col)
   (and (<= 0 row (sub1 (board-rows b)))
        (<= 0 col (sub1 (board-cols b)))))
(define (board->lists b) (array->list* b))
;run on adjacent board positions
(define-syntax (for-adj stx)
   (syntax-case stx ()
     [(_ b (r row) (c col) diag? body ...)
      (with-syntax ([is (if (syntax->datum #'diag?) #''(0 0 1 1 1 -1 -1
-1) #''(0 0 1 -1))]
                    [js (if (syntax->datum #'diag?) #''(1 -1 0 -1 1 0 -1
1) #''(1 -1 0 0))])
        #'(for ([i is] [j js])
            (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) #:transparent)
(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 recursively
(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)))))

(define (init-board rows cols)
   (let ([chance (+ (/ (random) 10) 0.1)]
         ;empty board
         [b (array->mutable-array (build-array (vector rows cols)
                                               (λ (x) (pos 'hidden 0))))])
     ;loop whole board
     (for* ([row (range 0 rows)]
            [col (range 0 cols)])
       (when (< (random) chance)
         ;put a mine
         (array-set! b (vector row col) (pos 'hidden -1))
         ;increment adjacent mine counts unless that adjacent position
is a mine
         (for-adj b (r row) (c col) #t
                  (let ([p (board-ref b r c)])
                    (unless (mine? p)
                      (array-set! b (vector r c) (pos 'hidden (add1
(pos-n p)))))))))
     b))

;only clear position if it's not a mine
;only continue recursing when it's a hidden0?
(define (try-clear! p)
   (cond [(mine? p) #f]
         [(hidden0? p) (set-pos-mark! p 'clear) #t]
         [else (set-pos-mark! p 'clear) #f]))

;the following player move functions return boolean where #f = lose, #t
= still going
;assuming can never directly lose ((void) == #t from the set!)
;make sure to not allow overwriting an already cleared position
(define (toggle-assume! b row col)
   (let ([p (board-ref b row col)])
     (set-pos-mark! p (case (pos-mark p)
                        [(assume-mine) 'hidden]
                        [(hidden) 'assume-mine]
                        [(clear) 'clear]
                        [else (error "invalid mark" (pos-mark p))]))))

;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, only continuing if it's a 0
            (when (try-clear! p)
              (let clear-adj ([row row] [col col])
                (for-adj b (r row) (c col) #f
                         ;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 (string-split s)
     [(list type row col)
      (let ([row (string->number row)]
            [col (string->number col)])
        (if (on-board? b row col)
            (case type
              [("?") (toggle-assume! b row col)]
              [("!") (clear! b row col)]
              [else (parse-and-do-move! b (read-line))])
            (parse-and-do-move! b (read-line))))]
     [else (parse-and-do-move! b (read-line))]))
(define (run)
   (displayln (string-append "--- Enter one of:\n"
                             "--- \"! <row> <col>\" to clear at
(row,col), or\n"
                             "--- \"? <row> <col>\" to flag a possible
mine at (row,col).\n"))
   (let ([b (init-board 8 8)])
     (let run ()
       (show-board b)
       (display "enter move: ")
       (if (parse-and-do-move! b (read-line))
           (if (win? b) (displayln "CLEAR!") (run))
           (displayln "BOOM!")))))


____________________
   Racket Users list:
   http://lists.racket-lang.org/users


____________________
 Racket Users list:
 http://lists.racket-lang.org/users

Reply via email to