1#lang racket 2 3 (require "board.rkt") 4 5 ;; a move is either: 6 ;; - (make-enter-piece pawn) 7 ;; - (make-move-piece-main pawn start distance) 8 ;; - (make-move-piece-home pawn start distance) 9 (define-struct move () #:inspector (make-inspector)) 10 (define-struct (enter-piece move) (pawn) #:inspector (make-inspector)) 11 (define-struct (move-piece-main move) (pawn start distance) #:inspector (make-inspector)) 12 (define-struct (move-piece-home move) (pawn start distance) #:inspector (make-inspector)) 13 14 (provide/contract 15 (struct enter-piece ((pawn pawn?))) 16 (struct move-piece-main ([pawn pawn?] [start number?] [distance number?])) 17 (struct move-piece-home ([pawn pawn?] [start number?] [distance number?]))) 18 19 (provide take-turn 20 bad-move 21 make-moves 22 move? 23 24 board-enter-piece 25 board-move-piece-main 26 board-move-piece-home 27 28 blockade-moved? 29 find-end-spot 30 board-doubles-penalty 31 32 make-one-move 33 34 get-move-id 35 get-move-color 36 37 has-entering-roll? 38 entering-blockade? 39 exn:bad-move? 40 exn:bad-move-with-info? 41 exn:bad-move-with-info-color 42 exn:bad-move-with-info-board 43 exn:bad-move-with-info-dice 44 exn:bad-move-with-info-moves 45 46 board-all-in? 47 <=/m 48 possible-to-move) 49 50 (define bop-bonus 20) 51 (define home-bonus 10) 52 53 ;; moves-dice : moves -> (listof number) 54 ;; does not return the die moves that correspond to entering pawns 55 (define (moves-dice moves) 56 (let loop ([moves moves] 57 [dice null]) 58 (cond 59 [(null? moves) dice] 60 [else 61 (let ([move (car moves)]) 62 (cond 63 [(move-piece-main? move) 64 (loop (cdr moves) (cons (move-piece-main-distance move) dice))] 65 [(move-piece-home? move) 66 (loop (cdr moves) (cons (move-piece-home-distance move) dice))] 67 [else (loop (cdr moves) dice)]))]))) 68 69 ;; board-doubles-penalty : board color -> board 70 (define (board-doubles-penalty board color) 71 (let home-row-loop ([i board-home-row-size]) 72 (cond 73 [(zero? i) 74 (let main-board-loop ([i (get-enter-pos color)] 75 [first-time? #t]) 76 (cond 77 [(and (not first-time?) (= i (get-enter-pos color))) 78 board] 79 [else 80 (let* ([next-i (modulo (- i 1) board-main-size)] 81 [ent (board-main-i board next-i)]) 82 (if (and (pair? ent) 83 (eq? (pawn-color (car ent)) color)) 84 (move-piece board (car ent) 'start) 85 (main-board-loop next-i #f)))]))] 86 [else 87 (let ([ent (board-home-row-i board color (- i 1))]) 88 (if (null? ent) 89 (home-row-loop (- i 1)) 90 (move-piece board (car ent) 'start)))]))) 91 92 ;; take-turn : color board (listof number) (listof move) -> board 93 ;; raises an exception if the turn is illegal 94 (define (take-turn color original-board original-dice original-moves) 95 (parameterize ([current-color/board/dice/moves 96 (list color original-board original-dice original-moves)]) 97 (unless (andmap (lambda (x) (eq? color (get-move-color x))) original-moves) 98 (bad-move "attempted to move two different colors")) 99 (let loop ([moves original-moves] 100 [board original-board] 101 [dice original-dice]) 102 (cond 103 [(null? moves) 104 105 (when (and (has-entering-roll? dice) 106 (memf (lambda (pawn) (eq? (pawn-color pawn) color)) 107 (board-start board)) ;; has pieces in start 108 (not (entering-blockade? board color))) 109 (bad-move "can still enter a pawn")) 110 111 (let ([used-dice (moves-dice original-moves)]) 112 (for-each (lambda (die) 113 (let ([potential-board (possible-to-move color board die)]) 114 (when potential-board 115 (unless (blockade-moved? original-board potential-board color) 116 (bad-move "die roll ~a can still be used" die))))) 117 dice) 118 119 (when (blockade-moved? original-board board color) 120 (bad-move "cannot move blockade together"))) 121 122 board] 123 [else 124 (let ([move (car moves)]) 125 (let-values ([(new-board bonus new-dice) 126 (make-move/dice board move dice)]) 127 (let ([new-new-dice (if bonus 128 (cons bonus new-dice) 129 new-dice)]) 130 (loop (cdr moves) 131 new-board 132 new-new-dice))))])))) 133 134 ;; get-move-color : move -> symbol 135 ;; extracts the moved color from the move 136 (define (get-move-color move) (pawn-color (get-move-pawn move))) 137 138 ;; get-move-id : move -> number 139 (define (get-move-id move) (pawn-id (get-move-pawn move))) 140 141 (define (get-move-pawn move) 142 (cond 143 [(enter-piece? move) (enter-piece-pawn move)] 144 [(move-piece-main? move) (move-piece-main-pawn move)] 145 [(move-piece-home? move) (move-piece-home-pawn move)])) 146 147 ;; blocakde-moved? : board board color -> boolean 148 (define (blockade-moved? original-board new-board color) 149 (let ([original-blockades (find-blockades/color original-board color)] 150 [new-blockades (find-blockades/color new-board color)]) 151 (ormap (lambda (new-blockade) (memf (same-blockade-different-place? new-blockade) original-blockades)) 152 new-blockades))) 153 154 (define ((same-blockade-different-place? b1) b2) 155 (and (equal? (blockade-p1 b1) (blockade-p1 b2)) 156 (equal? (blockade-p2 b1) (blockade-p2 b2)) 157 (not (equal? (blockade-loc b1) (blockade-loc b2))))) 158 159 ;; make-move/dice : board move (listof number) number -> (values board bonus (listof number)) 160 ;; makes the given move, removing the used dice from the dice list. 161 ;; raises an error if the move isn't legal. 162 ;; check for: using a five to move when there are pieces to come in 163 ;; moving without the matching roll 164 (define (make-move/dice board move dice) 165 (cond 166 [(enter-piece? move) 167 (let ([new-dice (cond 168 [(memq 5 dice) (remq 5 dice)] 169 [(and (memq 1 dice) (memq 4 dice)) 170 (remq 1 (remq 4 dice))] 171 [(and (memq 2 dice) (memq 3 dice)) 172 (remq 2 (remq 3 dice))] 173 [else (bad-move "entered without having a 5")])]) 174 (let-values ([(board bonus) (board-enter-piece board (enter-piece-pawn move))]) 175 (values board bonus new-dice)))] 176 [(move-piece-main? move) 177 (do-move/dice/moving board dice 178 (move-piece-main-distance move) 179 (move-piece-main-pawn move) 180 (move-piece-main-start move) 181 board-move-piece-main)] 182 [(move-piece-home? move) 183 (do-move/dice/moving board dice 184 (move-piece-home-distance move) 185 (move-piece-home-pawn move) 186 (move-piece-home-start move) 187 board-move-piece-home)])) 188 189 ;; helper function to collapse last two cases of make-move/dice 190 (define (do-move/dice/moving board dice die pawn start board-move-piece) 191 (let ([new-dice (remq die dice)]) 192 (unless (memq die dice) 193 (bad-move "tried to move ~a squares but dice read ~a" die dice)) 194 (let-values ([(new-board bonus) 195 (board-move-piece board pawn start die)]) 196 (values new-board bonus new-dice)))) 197 198 ;; entering-blocade? : board symbol -> boolean 199 (define (entering-blockade? board color) 200 (let ([ent (board-main-i board (get-enter-pos color))]) 201 (and (pair? ent) (pair? (cdr ent))))) 202 203 (define (no-blockades board start end) 204 (let ([ind (find-blockade/between board start end)]) 205 (cond 206 [(not ind) (void)] 207 [(number? ind) 208 (bad-move "there is a blockade at ~a in the main ring" ind)] 209 [(home-row-loc? ind) 210 (bad-move "there is a blockade at ~a in the ~a home row" 211 (home-row-loc-num ind) 212 (home-row-loc-color ind))] 213 [else (bad-move "blockade in the way")]))) 214 215 ;; has-entering-roll? : (listof number) -> boolean 216 (define (has-entering-roll? dice) 217 (or (memq 5 dice) 218 (and (memq 1 dice) (memq 4 dice)) 219 (and (memq 2 dice) (memq 3 dice)))) 220 221 ;; possible-to-move : symbol board number -> (union #f board) 222 ;; indicates if there are any moves that could happen with the 223 ;; given die, for the given color in the given board. 224 ;; doesn't consider entering moves 225 (define (possible-to-move color board die) 226 (let/ec k 227 (for-each-pawn/loc 228 board 229 (lambda (pawn loc) 230 (when (and (eq? color (pawn-color pawn)) 231 (not (symbol? loc))) 232 (with-handlers ([exn:bad-move? (lambda (x) #f)]) 233 (cond 234 [(number? loc) 235 (let-values ([(board bonus) (board-move-piece-main board pawn loc die)]) 236 (k board))] 237 [(home-row-loc? loc) 238 (let-values ([(board bonus) (board-move-piece-home board pawn (home-row-loc-num loc) die)]) 239 (k board))]))))) 240 #f)) 241 242 ;; make-moves : board (listof move) -> board (listof number) 243 ;; only checks that each move, in isloation, would be possible 244 (define (make-moves board moves) 245 (let loop ([board board] 246 [bonus '()] 247 [moves moves]) 248 (cond 249 [(null? moves) (values board bonus)] 250 [else 251 (let-values ([(new-board new-bonus) (make-one-move board (car moves))]) 252 (loop new-board 253 (if new-bonus (cons new-bonus bonus) bonus) 254 (cdr moves)))]))) 255 256 ;; make-one-move : board move -> board 257 (define (make-one-move board move) 258 (cond 259 [(enter-piece? move) (board-enter-piece board (enter-piece-pawn move))] 260 [(move-piece-main? move) (board-move-piece-main board 261 (move-piece-main-pawn move) 262 (move-piece-main-start move) 263 (move-piece-main-distance move))] 264 [(move-piece-home? move) (board-move-piece-home board 265 (move-piece-home-pawn move) 266 (move-piece-home-start move) 267 (move-piece-home-distance move))])) 268 269 270 271 (define (board-all-in? board color) 272 (not (memf (lambda (pawn) (eq? (pawn-color pawn) color)) 273 (board-start board)))) 274 275 ;; enter-piece : board pawn -> (values board (union #f number)) 276 (define (board-enter-piece orig-board pawn) 277 (unless (member pawn (board-start orig-board)) 278 (bad-move "~a's pawn ~a is already on the board" (pawn-color pawn) (pawn-id pawn))) 279 ;; move the color out of the starting area 280 (let* ([pos (get-enter-pos (pawn-color pawn))] 281 [old-ent (board-main-i orig-board pos)]) 282 (when (= 2 (length old-ent)) 283 (bad-move "cannot move out into a blockade")) 284 (cond 285 ;; no bop 286 [(or (null? old-ent) 287 (eq? (pawn-color (car old-ent)) (pawn-color pawn))) 288 (values (move-piece orig-board pawn pos) 289 #f)] 290 ;; bop 291 [else 292 (values (move-piece2 293 orig-board 294 pawn 295 pos 296 (car old-ent) 297 'start) 298 bop-bonus)]))) 299 300 ;; board-move-piece-home : board pawn number number -> (values board (union #f number)) 301 ;; result of #f indicates no bop; result of a color indicates who got bopped 302 (define (board-move-piece-home board pawn start distance) 303 (let* ([color (pawn-color pawn)] 304 [old (board-home-row-i board color start)]) 305 (unless (member pawn old) 306 (bad-move "color ~a is not in the home row on ~a" (pawn-color pawn) start)) 307 (unless (and (<= 0 start) (< start board-home-row-size)) 308 (error 'boad-move-piece-home "bad start argument ~e" start)) 309 (unless (<= 0 start (+ start distance) (+ board-home-row-size 1)) 310 (bad-move "moved too far, off the end of the board")) 311 312 (let ([finish (+ start distance)]) 313 (cond 314 [(= finish board-home-row-size) 315 (when (< start (- finish 1)) 316 ;; if only moving one square, then we don't need to check blockades 317 ;; this lets us satisfy the inputs to no-blockades 318 (no-blockades board 319 (make-home-row-loc (+ start 1) color) 320 (make-home-row-loc (- finish 1) color))) 321 (values (move-piece board pawn 'home) 322 home-bonus)] 323 [(< finish board-home-row-size) 324 (no-blockades board 325 (make-home-row-loc (+ start 1) color) 326 (make-home-row-loc finish color)) 327 328 (let ([old-ent (board-home-row-i board color finish)]) 329 (cond 330 [(or (null? old-ent) 331 (null? (cdr old-ent))) 332 (values (move-piece board 333 pawn 334 (make-home-row-loc finish color)) 335 #f)] 336 [else 337 (bad-move "moved onto a blockade in the home row")]))] 338 [else 339 (bad-move "moved off of the end of the board")])))) 340 341 ;; board-move-piece-main : board pawn number number -> (values board (union #f number)) 342 ;; result of #f indicates no bop; result of a color indicates who got bopped 343 (define (board-move-piece-main board pawn start distance) 344 (unless (member pawn (board-main-i board start)) 345 (bad-move "color ~a (piece #~a) is not on square ~a" 346 (pawn-color pawn) 347 (pawn-id pawn) 348 start)) 349 (let* ([color (pawn-color pawn)] 350 [landed (find-end-spot color start distance)] 351 [exit (get-exit-pos color)]) 352 (cond 353 [(eq? landed 'too-far) (bad-move "moved off of the board")] 354 [(eq? landed 'home) 355 (no-blockades board 356 (modulo (+ start 1) board-main-size) 357 (make-home-row-loc (- board-home-row-size 1) color)) 358 (values (move-piece board 359 pawn 360 'home) 361 10)] 362 [(eq? (car landed) 'home-row) 363 ;; turned onto the exit ramp 364 365 (let* ([final-spot (cdr landed)]) 366 (no-blockades board 367 (next-pos color start) 368 (make-home-row-loc final-spot color)) 369 370 (let ([old (board-home-row-i board color final-spot)]) 371 (when (and (pair? old) 372 (pair? (cdr old))) 373 (bad-move "cannot move onto a blockade")) 374 (values (move-piece board pawn (make-home-row-loc final-spot color)) 375 #f)))] 376 [else 377 ;; stayed on the main board 378 (let ([end (cdr landed)]) 379 (let ([start+1 (modulo (+ start 1) board-main-size)]) 380 (unless (= start+1 end) 381 (no-blockades board start+1 end))) 382 (let ([old-contents (board-main-i board end)]) 383 384 (cond 385 ;; no one there 386 [(null? old-contents) 387 (values (move-piece board pawn end) 388 #f)] 389 390 [(and (pair? old-contents) 391 (pair? (cdr old-contents))) 392 (bad-move "cannot move directly onto a blockade")] 393 394 ;; already one of the same color on that spot 395 [(eq? (pawn-color (car old-contents)) color) 396 (values (move-piece board 397 pawn 398 end) 399 #f)] 400 401 ;; attempt to bop on a safety -- illegal 402 [(safety? end) 403 (bad-move "cannot move onto a safety if someone else is already there")] 404 405 ;; successful bop 406 [else 407 (values 408 (move-piece2 board 409 pawn 410 end 411 (car old-contents) 412 'start) 413 bop-bonus)])))]))) 414 415 ;; next-pos : color number -> (union number home-row-loc) 416 ;; given a position on the main ring, it finds the next position 417 ;; for the given color on the board. 418 (define (next-pos color pos) 419 (cond 420 [(= pos (get-exit-pos color)) 421 (make-home-row-loc 0 color)] 422 [else 423 (modulo (+ pos 1) board-main-size)])) 424 425 ;; find-end-spot : color number number -> (union 'too-far 'home (cons 'home-row number) (cons 'main number))) 426 (define (find-end-spot color start distance) 427 (let ([exit (get-exit-pos color)] 428 [end (modulo (+ start distance) board-main-size)]) 429 (cond 430 [(and (<=/m start exit end) 431 (not (= exit end))) 432 (let* ([distance-to-exit (modulo (- exit start) board-main-size)] 433 [final-spot (- distance distance-to-exit 1)]) 434 (cond 435 [(final-spot . = . board-home-row-size) 436 'home] 437 [(final-spot . < . board-home-row-size) 438 (cons 'home-row final-spot)] 439 [else 440 'too-far]))] 441 [else 442 (cons 'main end)]))) 443 444 445 (define (<=/m one two three) 446 (or (<= one two three) 447 (<= two three one) 448 (<= three one two))) 449 450 (define-struct (exn:bad-move exn) ()) 451 (define-struct (exn:bad-move-with-info exn:bad-move) (color board dice moves)) 452 453 (define current-color/board/dice/moves (make-parameter #f)) 454 455 (define (bad-move _str . args) 456 (define str (if (null? args) _str (apply format _str args))) 457 (raise 458 (cond 459 [(current-color/board/dice/moves) 460 (define-values (color board dice moves) (apply values (current-color/board/dice/moves))) 461 (make-exn:bad-move-with-info str (current-continuation-marks) color board dice moves)] 462 [else 463 (make-exn:bad-move str (current-continuation-marks))]))) 464