<html><head></head><body style="word-wrap: break-word; -webkit-nbsp-mode: space; -webkit-line-break: after-white-space; "><div><br></div><div>Consider a random testing approach. Throw the random tester at the main function, Think of 'streams' of input that the main function can absorb, and generate random streams .</div><div><br></div><br><div><div>On Jun 3, 2013, at 5:14 PM, Sean Kanaley wrote:</div><br class="Apple-interchange-newline"><blockquote type="cite"><div dir="ltr">Just kidding there are more parse bugs, but I'm doing my best to fix them. The consolation is that the other implementations are either far longer, incorrect, or both. Still... I shall post it shortly and hope for the best.<br>
</div><div class="gmail_extra"><br><br><div class="gmail_quote">On Mon, Jun 3, 2013 at 4:26 PM, Sean Kanaley <span dir="ltr"><<a href="mailto:skanaley@gmail.com" target="_blank">skanaley@gmail.com</a>></span> wrote:<br>
<blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><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))<div class="im"><br>(define (on-board? b row col)<br>
</div> (and (<= 0 row (sub1 (board-rows b)))<br>
(<= 0 col (sub1 (board-cols b)))))<br>(define (board->lists b) (array->list* b))<div class="im"><br>;run on adjacent board positions<br></div>(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])<div class="im"><br> (let ([r (+ row i)]<br> [c (+ col j)])<br> (when (on-board? b r c)<br></div> body ...))))]))<div class="im"><br>;mark is either hidden, assume-mine, or clear<br>
;n is int equal to # adj mines or -1 for mine<br></div>(struct pos ([mark #:mutable] n) #:transparent)<div class="im"><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>
</div>
;to them are cleared recursively<div class="im"><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></div><div class="im">(define (init-board rows cols)<br> (let ([chance (+ (/ (random) 10) 0.1)]<br>
;empty board<br></div> [b (array->mutable-array (build-array (vector rows cols)<br> (λ (x) (pos 'hidden 0))))])<div class="im"><br> ;loop whole board<br>
(for* ([row (range 0 rows)]<br>
[col (range 0 cols)])<br> (when (< (random) chance)<br> ;put a mine<br></div> (array-set! b (vector row col) (pos 'hidden -1))<div class="im"><br> ;increment adjacent mine counts unless that adjacent position is a mine<br>
</div>
(for-adj b (r row) (c col) #t<div class="im"><br> (let ([p (board-ref b r c)])<br> (unless (mine? p)<br></div> (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]))<div class="im"><br><br>;the following player move functions return boolean where #f = lose, #t = still going<br></div>;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)<div class="im"><br> (let ([p (board-ref b row col)])<br></div> (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))]))))<div class="im"><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></div> ;clear the chosen position first, only continuing if it's a 0<br> (when (try-clear! p)<div class="im"><br> (let clear-adj ([row row] [col col])<br>
</div> (for-adj b (r row) (c col) #f<div class="im"><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></div> (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)]<div class="im"><br>
[col (string->number col)])<br></div> (if (on-board? b row col)<br> (case type<br> [("?") (toggle-assume! b row col)]<div class="im"><br> [("!") (clear! b row col)]<br>
</div> [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"))<div class="im"><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></div>
</blockquote></div><br></div>
____________________<br> Racket Users list:<br> <a href="http://lists.racket-lang.org/users">http://lists.racket-lang.org/users</a><br></blockquote></div><br></body></html>