<div dir="ltr"><div>Thank you both for the replies. I have incorporated all of your suggestions. The thin board wrapper over vector2 proved useful to quickly switch everything over to arrays. The program should no longer crash due to user input aside from any kind of internal buffer issues that may or may not be possible, which now exceeds the spec of allowing malformed input (though bounds checking was still lacking).<br>
<br></div><div>I also found a few important bugs in the process, so forgive me but I shall repost the code in its entirety. The critical changes are the macro to process adjacent positions, since clearing recursively seems to require not using diagonals but summing the number of adjacent mines obviously does; try-clear! wasn't returning the proper values to stop recursion; assume! should be toggleable (not actually in the spec though); and some other minor things.<br>
<br>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.<br></div><div class="gmail_extra"><br></div><div class="gmail_extra">
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.<br></div><div class="gmail_extra"><br>#lang racket<br>(require math)<br>;board uses arrays directly, but maintaining an abstraction is nice<br>
(define (board-ref b row col) (array-ref b (vector row col)))<br>(define (board-rows b) (vector-ref (array-shape b) 0))<br>(define (board-cols b) (vector-ref (array-shape b) 1))<br>(define (on-board? b row col)<br> (and (<= 0 row (sub1 (board-rows b)))<br>
(<= 0 col (sub1 (board-cols b)))))<br>(define (board->lists b) (array->list* b))<br>;run on adjacent board positions<br>(define-syntax (for-adj stx)<br> (syntax-case stx ()<br> [(_ b (r row) (c col) diag? body ...)<br>
(with-syntax ([is (if (syntax->datum #'diag?) #''(0 0 1 1 1 -1 -1 -1) #''(0 0 1 -1))]<br> [js (if (syntax->datum #'diag?) #''(1 -1 0 -1 1 0 -1 1) #''(1 -1 0 0))])<br>
#'(for ([i is] [j js])<br> (let ([r (+ row i)]<br> [c (+ col j)])<br> (when (on-board? b r c)<br> body ...))))]))<br>;mark is either hidden, assume-mine, or clear<br>
;n is int equal to # adj mines or -1 for mine<br>(struct pos ([mark #:mutable] n) #:transparent)<br>(define (mine? p) (= (pos-n p) -1))<br>;hidden0? is needed because only spaces with no mines in them and no mines adjacent<br>
;to them are cleared recursively<br>(define (hidden0? p)<br> (and (symbol=? (pos-mark p) 'hidden)<br> (zero? (pos-n p))))<br>(define (show-pos p)<br> (match-let ([(pos m n) p])<br> (case m<br> [(hidden) "."]<br>
[(assume-mine) "?"]<br> [(clear) (if (zero? n) " " (number->string n))]<br> [else (error "illegal mark" m)])))<br>;put "|" around positions<br>(define (show-board b)<br>
(for ([row (board->lists b)])<br> (displayln (format "|~a|" (string-join (map show-pos row) "|")))))<br><br>;winning = every position is either cleared or a hidden mine<br>(define (win? b)<br> (for*/and ([r (range 0 (board-rows b))]<br>
[c (range 0 (board-cols b))])<br> (let ([p (board-ref b r c)])<br> (or (symbol=? (pos-mark p) 'clear)<br> (mine? p)))))<br><br>(define (init-board rows cols)<br> (let ([chance (+ (/ (random) 10) 0.1)]<br>
;empty board<br> [b (array->mutable-array (build-array (vector rows cols)<br> (ë (x) (pos 'hidden 0))))])<br> ;loop whole board<br> (for* ([row (range 0 rows)]<br>
[col (range 0 cols)])<br> (when (< (random) chance)<br> ;put a mine<br> (array-set! b (vector row col) (pos 'hidden -1))<br> ;increment adjacent mine counts unless that adjacent position is a mine<br>
(for-adj b (r row) (c col) #t<br> (let ([p (board-ref b r c)])<br> (unless (mine? p)<br> (array-set! b (vector r c) (pos 'hidden (add1 (pos-n p)))))))))<br>
b))<br><br>;only clear position if it's not a mine<br>;only continue recursing when it's a hidden0?<br>(define (try-clear! p)<br> (cond [(mine? p) #f]<br> [(hidden0? p) (set-pos-mark! p 'clear) #t]<br>
[else (set-pos-mark! p 'clear) #f]))<br><br>;the following player move functions return boolean where #f = lose, #t = still going<br>;assuming can never directly lose ((void) == #t from the set!)<br>;make sure to not allow overwriting an already cleared position<br>
(define (toggle-assume! b row col)<br> (let ([p (board-ref b row col)])<br> (set-pos-mark! p (case (pos-mark p)<br> [(assume-mine) 'hidden]<br> [(hidden) 'assume-mine]<br>
[(clear) 'clear]<br> [else (error "invalid mark" (pos-mark p))]))))<br><br>;clearing loses when the chosen position is a mine<br>;void = #t as far as if works, so no need to return #t<br>
(define (clear! b row col)<br> (let ([p (board-ref b row col)])<br> (and (not (mine? p))<br> ;not a mine, so recursively check adjacents, and maintain list of visited positions<br> ;to avoid infinite loops<br>
(let ([seen '()])<br> ;clear the chosen position first, only continuing if it's a 0<br> (when (try-clear! p)<br> (let clear-adj ([row row] [col col])<br> (for-adj b (r row) (c col) #f<br>
;make sure its not seen<br> (when (and (not (member (list r c) seen))<br> (try-clear! (board-ref b r c)))<br> ;it was cleared, so loop after saving this position as being seen<br>
(set! seen (cons (list r c) seen))<br> (clear-adj r c)))))))))<br><br>(define (parse-and-do-move! b s)<br> (match (string-split s)<br> [(list type row col)<br> (let ([row (string->number row)]<br>
[col (string->number col)])<br> (if (on-board? b row col)<br> (case type<br> [("?") (toggle-assume! b row col)]<br> [("!") (clear! b row col)]<br> [else (parse-and-do-move! b (read-line))])<br>
(parse-and-do-move! b (read-line))))]<br> [else (parse-and-do-move! b (read-line))]))<br>(define (run)<br> (displayln (string-append "--- Enter one of:\n"<br> "--- \"! <row> <col>\" to clear at (row,col), or\n"<br>
"--- \"? <row> <col>\" to flag a possible mine at (row,col).\n"))<br> (let ([b (init-board 8 8)])<br> (let run ()<br> (show-board b)<br> (display "enter move: ")<br>
(if (parse-and-do-move! b (read-line))<br> (if (win? b) (displayln "CLEAR!") (run))<br> (displayln "BOOM!")))))<br></div></div>