svn: r8625
This commit is contained in:
parent
88290b46be
commit
6bcaca2f09
|
@ -5,367 +5,329 @@ possible to remap single click (instead of double click)?
|
||||||
|
|
||||||
|#
|
|#
|
||||||
|
|
||||||
(module aces mzscheme
|
#lang mzscheme
|
||||||
|
|
||||||
(require (lib "cards.ss" "games" "cards")
|
|
||||||
(lib "class.ss")
|
|
||||||
(lib "unit.ss")
|
|
||||||
(lib "mred.ss" "mred")
|
|
||||||
(lib "list.ss")
|
|
||||||
(lib "string-constant.ss" "string-constants")
|
|
||||||
"../show-help.ss")
|
|
||||||
|
|
||||||
(provide game@)
|
|
||||||
|
|
||||||
(define game@
|
|
||||||
(unit
|
|
||||||
(import)
|
|
||||||
(export)
|
|
||||||
|
|
||||||
(define table (make-table "Aces" 6 5))
|
|
||||||
|
|
||||||
(make-object button% (string-constant help-menu-label) table
|
(require (lib "cards.ss" "games" "cards")
|
||||||
(let ([show-help (show-help (list "games" "aces") "Aces Help")])
|
(lib "class.ss")
|
||||||
(lambda x
|
(lib "mred.ss" "mred")
|
||||||
(show-help))))
|
(lib "list.ss")
|
||||||
|
(lib "unit.ss")
|
||||||
(define draw-pile null)
|
(lib "string-constant.ss" "string-constants")
|
||||||
|
"../show-help.ss")
|
||||||
(define card-height (send (car (make-deck)) card-height))
|
|
||||||
(define card-width (send (car (make-deck)) card-width))
|
|
||||||
(define region-height (send table table-height))
|
|
||||||
|
|
||||||
;; space between cards in the 4 stacks
|
(provide game@)
|
||||||
(define card-space 30)
|
(define game@ (unit (import) (export)
|
||||||
|
|
||||||
(define-struct stack (x y cards))
|
|
||||||
|
|
||||||
(define (get-x-offset n)
|
|
||||||
(let* ([table-width (send table table-width)]
|
|
||||||
[stack-spacing 7]
|
|
||||||
[num-stacks 5]
|
|
||||||
[all-stacks-width
|
|
||||||
(+ (* num-stacks card-width)
|
|
||||||
(* (- num-stacks 1) stack-spacing))])
|
|
||||||
(+ (- (/ table-width 2) (/ all-stacks-width 2))
|
|
||||||
(* n (+ card-width stack-spacing)))))
|
|
||||||
|
|
||||||
(define draw-pile-region
|
|
||||||
(make-button-region
|
|
||||||
(get-x-offset 0)
|
|
||||||
0
|
|
||||||
card-width
|
|
||||||
region-height ; card-height
|
|
||||||
#f
|
|
||||||
#f))
|
|
||||||
|
|
||||||
(define stacks
|
|
||||||
(list
|
|
||||||
(make-stack
|
|
||||||
(get-x-offset 1)
|
|
||||||
0
|
|
||||||
null)
|
|
||||||
(make-stack
|
|
||||||
(get-x-offset 2)
|
|
||||||
0
|
|
||||||
null)
|
|
||||||
(make-stack
|
|
||||||
(get-x-offset 3)
|
|
||||||
0
|
|
||||||
null)
|
|
||||||
(make-stack
|
|
||||||
(get-x-offset 4)
|
|
||||||
0
|
|
||||||
null)))
|
|
||||||
|
|
||||||
;; type state = (make-state (listof cards) (listof[4] (listof cards)))
|
(define table (make-table "Aces" 6 5))
|
||||||
(define-struct state (draw-pile stacks))
|
|
||||||
|
|
||||||
;; extract-current-state : -> state
|
(make-object button% (string-constant help-menu-label) table
|
||||||
(define (extract-current-state)
|
(let ([show-help (show-help (list "games" "aces") "Aces Help")])
|
||||||
(make-state
|
(lambda x (show-help))))
|
||||||
(copy-list draw-pile)
|
|
||||||
(map (lambda (x) (copy-list (stack-cards x))) stacks)))
|
|
||||||
|
|
||||||
(define (copy-list l) (map (lambda (x) x) l))
|
|
||||||
|
|
||||||
;; install-state : -> void
|
|
||||||
(define (install-state state)
|
|
||||||
(send table begin-card-sequence)
|
|
||||||
|
|
||||||
;; erase all old snips
|
(define draw-pile null)
|
||||||
(send table remove-cards draw-pile)
|
|
||||||
(for-each (lambda (stack)
|
|
||||||
(send table remove-cards (stack-cards stack)))
|
|
||||||
stacks)
|
|
||||||
|
|
||||||
;; restore old state
|
|
||||||
(set! draw-pile (state-draw-pile state))
|
|
||||||
(for-each (lambda (stack cards) (set-stack-cards! stack cards))
|
|
||||||
stacks
|
|
||||||
(state-stacks state))
|
|
||||||
|
|
||||||
;; restore GUI
|
|
||||||
(for-each (lambda (draw-pile-card)
|
|
||||||
(send table add-card draw-pile-card 0 0))
|
|
||||||
draw-pile)
|
|
||||||
(send table move-cards-to-region draw-pile draw-pile-region)
|
|
||||||
(for-each (lambda (draw-pile-card)
|
|
||||||
(send table card-face-down draw-pile-card)
|
|
||||||
(send table card-to-front draw-pile-card))
|
|
||||||
(reverse draw-pile))
|
|
||||||
|
|
||||||
(for-each
|
|
||||||
(lambda (stack)
|
|
||||||
(let ([num-cards (length (stack-cards stack))])
|
|
||||||
(send table add-cards (stack-cards stack) 0 0)
|
|
||||||
(send table move-cards (stack-cards stack)
|
|
||||||
(stack-x stack)
|
|
||||||
(stack-y stack)
|
|
||||||
(lambda (i)
|
|
||||||
(values 0
|
|
||||||
(* (- num-cards i 1) card-space)))))
|
|
||||||
(send table cards-face-up (stack-cards stack)))
|
|
||||||
stacks)
|
|
||||||
(send table end-card-sequence))
|
|
||||||
|
|
||||||
;; undo-stack : (listof state)
|
(define card-height (send (car (make-deck)) card-height))
|
||||||
(define undo-stack null)
|
(define card-width (send (car (make-deck)) card-width))
|
||||||
|
(define region-height (send table table-height))
|
||||||
|
|
||||||
;; redo-stack : (listof state)
|
;; space between cards in the 4 stacks
|
||||||
(define redo-stack null)
|
(define card-space 30)
|
||||||
|
|
||||||
;; save-undo : -> void
|
|
||||||
;; saves the current state in the undo stack
|
|
||||||
(define (save-undo)
|
|
||||||
(set! undo-stack (cons (extract-current-state) undo-stack))
|
|
||||||
(set! redo-stack null))
|
|
||||||
|
|
||||||
;; do-undo : -> void
|
(define-struct stack (x y cards))
|
||||||
;; pre: (not (null? undo-stack))
|
|
||||||
(define (do-undo)
|
|
||||||
(let ([to-install (car undo-stack)])
|
|
||||||
(set! redo-stack (cons (extract-current-state) redo-stack))
|
|
||||||
(set! undo-stack (cdr undo-stack))
|
|
||||||
(install-state to-install)))
|
|
||||||
|
|
||||||
;; do-redo : -> void
|
|
||||||
;; pre: (not (null? redo-stack))
|
|
||||||
(define (do-redo)
|
|
||||||
(let ([to-install (car redo-stack)])
|
|
||||||
(set! undo-stack (cons (extract-current-state) undo-stack))
|
|
||||||
(set! redo-stack (cdr redo-stack))
|
|
||||||
(install-state to-install)))
|
|
||||||
|
|
||||||
(define (position-cards stack)
|
|
||||||
(let ([m (length (stack-cards stack))])
|
|
||||||
(lambda (i)
|
|
||||||
(values 0
|
|
||||||
(if (= m 0)
|
|
||||||
0
|
|
||||||
(* (- m i 1) card-space))))))
|
|
||||||
|
|
||||||
(define (reset-game)
|
|
||||||
(send table remove-cards draw-pile)
|
|
||||||
(for-each
|
|
||||||
(lambda (stack) (send table remove-cards (stack-cards stack)))
|
|
||||||
stacks)
|
|
||||||
|
|
||||||
(set! undo-stack null)
|
|
||||||
(set! redo-stack null)
|
|
||||||
|
|
||||||
(let* ([deck (shuffle-list (make-deck) 7)]
|
|
||||||
[set-stack
|
|
||||||
(lambda (which)
|
|
||||||
(set-stack-cards! (which stacks) (list (which deck))))])
|
|
||||||
(for-each (lambda (card)
|
|
||||||
(send card user-can-move #f)
|
|
||||||
(send card user-can-flip #f))
|
|
||||||
deck)
|
|
||||||
(set! draw-pile (cddddr deck))
|
|
||||||
(set-stack car)
|
|
||||||
(set-stack cadr)
|
|
||||||
(set-stack caddr)
|
|
||||||
(set-stack cadddr))
|
|
||||||
|
|
||||||
(for-each
|
|
||||||
(lambda (stack)
|
|
||||||
(send table add-cards
|
|
||||||
(stack-cards stack)
|
|
||||||
(stack-x stack)
|
|
||||||
(stack-y stack)
|
|
||||||
(position-cards stack))
|
|
||||||
(for-each
|
|
||||||
(lambda (card) (send card flip))
|
|
||||||
(stack-cards stack)))
|
|
||||||
stacks)
|
|
||||||
|
|
||||||
(send table add-cards-to-region draw-pile draw-pile-region))
|
|
||||||
|
|
||||||
(define (move-from-deck)
|
|
||||||
(save-undo)
|
|
||||||
(unless (null? draw-pile)
|
|
||||||
(let ([move-one
|
|
||||||
(lambda (select)
|
|
||||||
(let ([stack (select stacks)]
|
|
||||||
[card (select draw-pile)])
|
|
||||||
(set-stack-cards! stack
|
|
||||||
(cons card (stack-cards stack)))
|
|
||||||
(send table card-to-front card)
|
|
||||||
(send table flip-card card)))])
|
|
||||||
|
|
||||||
(send table begin-card-sequence)
|
|
||||||
(move-one car)
|
|
||||||
(move-one cadr)
|
|
||||||
(move-one caddr)
|
|
||||||
(move-one cadddr)
|
|
||||||
(send table end-card-sequence)
|
|
||||||
|
|
||||||
(let ([cards-to-move (list (car draw-pile)
|
(define (get-x-offset n)
|
||||||
(cadr draw-pile)
|
(let* ([table-width (send table table-width)]
|
||||||
(caddr draw-pile)
|
[stack-spacing 7]
|
||||||
(cadddr draw-pile))])
|
[num-stacks 5]
|
||||||
(send table move-cards cards-to-move
|
[all-stacks-width (+ (* num-stacks card-width)
|
||||||
0 0
|
(* (- num-stacks 1) stack-spacing))])
|
||||||
(lambda (i)
|
(+ (- (/ table-width 2) (/ all-stacks-width 2))
|
||||||
(let ([stack (list-ref stacks i)])
|
(* n (+ card-width stack-spacing)))))
|
||||||
(let-values ([(dx dy) ((position-cards stack) 0)])
|
|
||||||
(values (+ dx (stack-x stack))
|
(define draw-pile-region
|
||||||
(+ dy (stack-y stack))))))))
|
(make-button-region
|
||||||
|
(get-x-offset 0)
|
||||||
(set! draw-pile (cddddr draw-pile))
|
0
|
||||||
|
card-width
|
||||||
(send table move-cards-to-region draw-pile draw-pile-region))))
|
region-height ; card-height
|
||||||
|
#f
|
||||||
(define (move-to-empty-spot card stack)
|
#f))
|
||||||
(save-undo)
|
|
||||||
(send table move-cards
|
(define stacks
|
||||||
(list card)
|
(list (make-stack (get-x-offset 1) 0 null)
|
||||||
(stack-x stack)
|
(make-stack (get-x-offset 2) 0 null)
|
||||||
(stack-y stack)
|
(make-stack (get-x-offset 3) 0 null)
|
||||||
(position-cards stack))
|
(make-stack (get-x-offset 4) 0 null)))
|
||||||
(remove-card-from-stacks card)
|
|
||||||
(set-stack-cards!
|
;; type state = (make-state (listof cards) (listof[4] (listof cards)))
|
||||||
stack
|
(define-struct state (draw-pile stacks))
|
||||||
(cons card (stack-cards stack))))
|
|
||||||
|
;; extract-current-state : -> state
|
||||||
(define (remove-card card)
|
(define (extract-current-state)
|
||||||
(save-undo)
|
(make-state (copy-list draw-pile)
|
||||||
(send table remove-card card)
|
(map (lambda (x) (copy-list (stack-cards x))) stacks)))
|
||||||
(remove-card-from-stacks card))
|
|
||||||
|
(define (copy-list l) (map (lambda (x) x) l))
|
||||||
(define (remove-card-from-stacks card)
|
|
||||||
(let ([old-cards (map stack-cards stacks)])
|
;; install-state : -> void
|
||||||
(for-each
|
(define (install-state state)
|
||||||
(lambda (stack)
|
(send table begin-card-sequence)
|
||||||
(set-stack-cards! stack (remq card (stack-cards stack))))
|
|
||||||
stacks)
|
;; erase all old snips
|
||||||
(for-each (lambda (stack old-cards)
|
(send table remove-cards draw-pile)
|
||||||
(unless (equal? (stack-cards stack) old-cards)
|
(for-each (lambda (stack)
|
||||||
(send table move-cards
|
(send table remove-cards (stack-cards stack)))
|
||||||
(stack-cards stack)
|
stacks)
|
||||||
(stack-x stack)
|
|
||||||
(stack-y stack)
|
;; restore old state
|
||||||
(position-cards stack))))
|
(set! draw-pile (state-draw-pile state))
|
||||||
stacks
|
(for-each (lambda (stack cards) (set-stack-cards! stack cards))
|
||||||
old-cards)))
|
stacks
|
||||||
|
(state-stacks state))
|
||||||
(send table set-single-click-action
|
|
||||||
(lambda (card)
|
;; restore GUI
|
||||||
(cond
|
(for-each (lambda (draw-pile-card)
|
||||||
[(send card face-down?) (move-from-deck)]
|
(send table add-card draw-pile-card 0 0))
|
||||||
[else
|
draw-pile)
|
||||||
(let ([bottom-four
|
(send table move-cards-to-region draw-pile draw-pile-region)
|
||||||
(let loop ([l stacks])
|
(for-each (lambda (draw-pile-card)
|
||||||
(cond
|
(send table card-face-down draw-pile-card)
|
||||||
[(null? l) null]
|
(send table card-to-front draw-pile-card))
|
||||||
[else (let ([stack (car l)])
|
(reverse draw-pile))
|
||||||
(if (null? (stack-cards stack))
|
|
||||||
(loop (cdr l))
|
(for-each (lambda (stack)
|
||||||
(cons (car (stack-cards stack))
|
(let ([num-cards (length (stack-cards stack))])
|
||||||
(loop (cdr l)))))]))])
|
(send table add-cards (stack-cards stack) 0 0)
|
||||||
(when (memq card bottom-four)
|
(send table move-cards (stack-cards stack)
|
||||||
(cond
|
(stack-x stack)
|
||||||
[(ormap (lambda (bottom-card)
|
(stack-y stack)
|
||||||
(and (eq? (send card get-suit)
|
(lambda (i)
|
||||||
(send bottom-card get-suit))
|
(values 0 (* (- num-cards i 1) card-space)))))
|
||||||
(or
|
(send table cards-face-up (stack-cards stack)))
|
||||||
(and (not (= 1 (send card get-value)))
|
stacks)
|
||||||
(= 1 (send bottom-card get-value)))
|
(send table end-card-sequence))
|
||||||
(and (not (= 1 (send card get-value)))
|
|
||||||
(< (send card get-value)
|
;; undo-stack : (listof state)
|
||||||
(send bottom-card get-value))))))
|
(define undo-stack null)
|
||||||
bottom-four)
|
|
||||||
(remove-card card)]
|
;; redo-stack : (listof state)
|
||||||
[else (let loop ([stacks stacks])
|
(define redo-stack null)
|
||||||
(cond
|
|
||||||
[(null? stacks) (void)]
|
;; save-undo : -> void
|
||||||
[else (let ([stack (car stacks)])
|
;; saves the current state in the undo stack
|
||||||
(if (null? (stack-cards stack))
|
(define (save-undo)
|
||||||
(move-to-empty-spot card stack)
|
(set! undo-stack (cons (extract-current-state) undo-stack))
|
||||||
(loop (cdr stacks))))]))])))])
|
(set! redo-stack null))
|
||||||
|
|
||||||
|
;; do-undo : -> void
|
||||||
|
;; pre: (not (null? undo-stack))
|
||||||
|
(define (do-undo)
|
||||||
|
(let ([to-install (car undo-stack)])
|
||||||
|
(set! redo-stack (cons (extract-current-state) redo-stack))
|
||||||
|
(set! undo-stack (cdr undo-stack))
|
||||||
|
(install-state to-install)))
|
||||||
|
|
||||||
|
;; do-redo : -> void
|
||||||
|
;; pre: (not (null? redo-stack))
|
||||||
|
(define (do-redo)
|
||||||
|
(let ([to-install (car redo-stack)])
|
||||||
|
(set! undo-stack (cons (extract-current-state) undo-stack))
|
||||||
|
(set! redo-stack (cdr redo-stack))
|
||||||
|
(install-state to-install)))
|
||||||
|
|
||||||
|
(define (position-cards stack)
|
||||||
|
(let ([m (length (stack-cards stack))])
|
||||||
|
(lambda (i)
|
||||||
|
(values 0 (if (= m 0) 0 (* (- m i 1) card-space))))))
|
||||||
|
|
||||||
|
(define (reset-game)
|
||||||
|
(send table remove-cards draw-pile)
|
||||||
|
(for-each (lambda (stack) (send table remove-cards (stack-cards stack)))
|
||||||
|
stacks)
|
||||||
|
|
||||||
|
(set! undo-stack null)
|
||||||
|
(set! redo-stack null)
|
||||||
|
|
||||||
|
(let* ([deck (shuffle-list (make-deck) 7)]
|
||||||
|
[set-stack
|
||||||
|
(lambda (which)
|
||||||
|
(set-stack-cards! (which stacks) (list (which deck))))])
|
||||||
|
(for-each (lambda (card)
|
||||||
|
(send card user-can-move #f)
|
||||||
|
(send card user-can-flip #f))
|
||||||
|
deck)
|
||||||
|
(set! draw-pile (cddddr deck))
|
||||||
|
(set-stack car)
|
||||||
|
(set-stack cadr)
|
||||||
|
(set-stack caddr)
|
||||||
|
(set-stack cadddr))
|
||||||
|
|
||||||
|
(for-each (lambda (stack)
|
||||||
|
(send table add-cards
|
||||||
|
(stack-cards stack)
|
||||||
|
(stack-x stack)
|
||||||
|
(stack-y stack)
|
||||||
|
(position-cards stack))
|
||||||
|
(for-each (lambda (card) (send card flip))
|
||||||
|
(stack-cards stack)))
|
||||||
|
stacks)
|
||||||
|
|
||||||
|
(send table add-cards-to-region draw-pile draw-pile-region))
|
||||||
|
|
||||||
|
(define (move-from-deck)
|
||||||
|
(save-undo)
|
||||||
|
(unless (null? draw-pile)
|
||||||
|
(let ([move-one
|
||||||
|
(lambda (select)
|
||||||
|
(let ([stack (select stacks)]
|
||||||
|
[card (select draw-pile)])
|
||||||
|
(set-stack-cards! stack (cons card (stack-cards stack)))
|
||||||
|
(send table card-to-front card)
|
||||||
|
(send table flip-card card)))])
|
||||||
|
|
||||||
|
(send table begin-card-sequence)
|
||||||
|
(move-one car)
|
||||||
|
(move-one cadr)
|
||||||
|
(move-one caddr)
|
||||||
|
(move-one cadddr)
|
||||||
|
(send table end-card-sequence)
|
||||||
|
|
||||||
|
(let ([cards-to-move (list (car draw-pile)
|
||||||
|
(cadr draw-pile)
|
||||||
|
(caddr draw-pile)
|
||||||
|
(cadddr draw-pile))])
|
||||||
|
(send table move-cards cards-to-move
|
||||||
|
0 0
|
||||||
|
(lambda (i)
|
||||||
|
(let ([stack (list-ref stacks i)])
|
||||||
|
(let-values ([(dx dy) ((position-cards stack) 0)])
|
||||||
|
(values (+ dx (stack-x stack))
|
||||||
|
(+ dy (stack-y stack))))))))
|
||||||
|
|
||||||
|
(set! draw-pile (cddddr draw-pile))
|
||||||
|
|
||||||
|
(send table move-cards-to-region draw-pile draw-pile-region))))
|
||||||
|
|
||||||
|
(define (move-to-empty-spot card stack)
|
||||||
|
(save-undo)
|
||||||
|
(send table move-cards
|
||||||
|
(list card)
|
||||||
|
(stack-x stack)
|
||||||
|
(stack-y stack)
|
||||||
|
(position-cards stack))
|
||||||
|
(remove-card-from-stacks card)
|
||||||
|
(set-stack-cards! stack (cons card (stack-cards stack))))
|
||||||
|
|
||||||
|
(define (remove-card card)
|
||||||
|
(save-undo)
|
||||||
|
(send table remove-card card)
|
||||||
|
(remove-card-from-stacks card))
|
||||||
|
|
||||||
|
(define (remove-card-from-stacks card)
|
||||||
|
(let ([old-cards (map stack-cards stacks)])
|
||||||
|
(for-each (lambda (stack)
|
||||||
|
(set-stack-cards! stack (remq card (stack-cards stack))))
|
||||||
|
stacks)
|
||||||
|
(for-each (lambda (stack old-cards)
|
||||||
|
(unless (equal? (stack-cards stack) old-cards)
|
||||||
|
(send table move-cards
|
||||||
|
(stack-cards stack)
|
||||||
|
(stack-x stack)
|
||||||
|
(stack-y stack)
|
||||||
|
(position-cards stack))))
|
||||||
|
stacks
|
||||||
|
old-cards)))
|
||||||
|
|
||||||
|
(send table set-single-click-action
|
||||||
|
(lambda (card)
|
||||||
|
(if (send card face-down?)
|
||||||
|
(move-from-deck)
|
||||||
|
(let ([bottom-four
|
||||||
|
(let loop ([l stacks])
|
||||||
|
(if (null? l)
|
||||||
|
null
|
||||||
|
(let ([stack (car l)])
|
||||||
|
(if (null? (stack-cards stack))
|
||||||
|
(loop (cdr l))
|
||||||
|
(cons (car (stack-cards stack)) (loop (cdr l)))))))])
|
||||||
|
(when (memq card bottom-four)
|
||||||
|
(if (ormap (lambda (bottom-card)
|
||||||
|
(and (eq? (send card get-suit)
|
||||||
|
(send bottom-card get-suit))
|
||||||
|
(or (and (not (= 1 (send card get-value)))
|
||||||
|
(= 1 (send bottom-card get-value)))
|
||||||
|
(and (not (= 1 (send card get-value)))
|
||||||
|
(< (send card get-value)
|
||||||
|
(send bottom-card get-value))))))
|
||||||
|
bottom-four)
|
||||||
|
(remove-card card)
|
||||||
|
(let loop ([stacks stacks])
|
||||||
|
(if (null? stacks)
|
||||||
|
(void)
|
||||||
|
(let ([stack (car stacks)])
|
||||||
|
(if (null? (stack-cards stack))
|
||||||
|
(move-to-empty-spot card stack)
|
||||||
|
(loop (cdr stacks))))))))))
|
||||||
(check-game-over)))
|
(check-game-over)))
|
||||||
|
|
||||||
(define (game-over?)
|
|
||||||
(and (null? draw-pile)
|
|
||||||
(let ([suits/false
|
|
||||||
(map (lambda (x)
|
|
||||||
(let ([stack-cards (stack-cards x)])
|
|
||||||
(if (null? stack-cards)
|
|
||||||
#f
|
|
||||||
(send (car stack-cards) get-suit))))
|
|
||||||
stacks)])
|
|
||||||
|
|
||||||
(if (member #f suits/false)
|
|
||||||
#f
|
|
||||||
(and (memq 'clubs suits/false)
|
|
||||||
(memq 'diamonds suits/false)
|
|
||||||
(memq 'hearts suits/false)
|
|
||||||
(memq 'spades suits/false))))))
|
|
||||||
|
|
||||||
(define (won?)
|
|
||||||
(and (game-over?)
|
|
||||||
(andmap (lambda (x)
|
|
||||||
(let ([cards (stack-cards x)])
|
|
||||||
(and (not (null? cards))
|
|
||||||
(null? (cdr cards))
|
|
||||||
(= 1 (send (car cards) get-value)))))
|
|
||||||
stacks)))
|
|
||||||
|
|
||||||
(define (check-game-over)
|
|
||||||
(when (game-over?)
|
|
||||||
(case (message-box "Aces"
|
|
||||||
(if (won?)
|
|
||||||
"Congratulations! You win! Play again?"
|
|
||||||
"Game Over. Play again?")
|
|
||||||
table
|
|
||||||
'(yes-no))
|
|
||||||
[(yes) (reset-game)]
|
|
||||||
[(no) (send table show #f)])))
|
|
||||||
|
|
||||||
(send table add-region draw-pile-region)
|
(define (game-over?)
|
||||||
(reset-game)
|
(and (null? draw-pile)
|
||||||
|
(let ([suits/false
|
||||||
(define mb (or (send table get-menu-bar)
|
(map (lambda (x)
|
||||||
(make-object menu-bar% table)))
|
(let ([stack-cards (stack-cards x)])
|
||||||
(define edit-menu (instantiate menu% ()
|
(if (null? stack-cards)
|
||||||
(parent mb)
|
#f
|
||||||
(label (string-constant edit-menu))))
|
(send (car stack-cards) get-suit))))
|
||||||
(instantiate menu-item% ()
|
stacks)])
|
||||||
(label (string-constant undo-menu-item))
|
|
||||||
(parent edit-menu)
|
(if (member #f suits/false)
|
||||||
(callback (lambda (x y) (do-undo)))
|
#f
|
||||||
(shortcut #\z)
|
(and (memq 'clubs suits/false)
|
||||||
(demand-callback
|
(memq 'diamonds suits/false)
|
||||||
(lambda (item)
|
(memq 'hearts suits/false)
|
||||||
(send item enable (not (null? undo-stack))))))
|
(memq 'spades suits/false))))))
|
||||||
(instantiate menu-item% ()
|
|
||||||
(label (string-constant redo-menu-item))
|
(define (won?)
|
||||||
(parent edit-menu)
|
(and (game-over?)
|
||||||
(callback (lambda (x y) (do-redo)))
|
(andmap (lambda (x)
|
||||||
(shortcut #\y)
|
(let ([cards (stack-cards x)])
|
||||||
(demand-callback
|
(and (not (null? cards))
|
||||||
(lambda (item)
|
(null? (cdr cards))
|
||||||
(send item enable (not (null? redo-stack))))))
|
(= 1 (send (car cards) get-value)))))
|
||||||
|
stacks)))
|
||||||
(send table show #t))))
|
|
||||||
|
(define (check-game-over)
|
||||||
|
(when (game-over?)
|
||||||
|
(case (message-box "Aces"
|
||||||
|
(if (won?)
|
||||||
|
"Congratulations! You win! Play again?"
|
||||||
|
"Game Over. Play again?")
|
||||||
|
table
|
||||||
|
'(yes-no))
|
||||||
|
[(yes) (reset-game)]
|
||||||
|
[(no) (send table show #f)])))
|
||||||
|
|
||||||
|
(send table add-region draw-pile-region)
|
||||||
|
(reset-game)
|
||||||
|
|
||||||
|
(define mb (or (send table get-menu-bar)
|
||||||
|
(make-object menu-bar% table)))
|
||||||
|
(define edit-menu (new menu% [parent mb] [label (string-constant edit-menu)]))
|
||||||
|
(new menu-item%
|
||||||
|
[label (string-constant undo-menu-item)]
|
||||||
|
[parent edit-menu]
|
||||||
|
[callback (lambda (x y) (do-undo))]
|
||||||
|
[shortcut #\z]
|
||||||
|
[demand-callback
|
||||||
|
(lambda (item) (send item enable (not (null? undo-stack))))])
|
||||||
|
(new menu-item%
|
||||||
|
[label (string-constant redo-menu-item)]
|
||||||
|
[parent edit-menu]
|
||||||
|
[callback (lambda (x y) (do-redo))]
|
||||||
|
[shortcut #\y]
|
||||||
|
[demand-callback
|
||||||
|
(lambda (item) (send item enable (not (null? redo-stack))))])
|
||||||
|
|
||||||
|
(send table show #t)
|
||||||
|
|
||||||
|
))
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
** To play _Aces_, run the "Games" application. **
|
** To play Aces, run the "PLT Games" application.
|
||||||
|
|
||||||
Aces is a solitaire card game. The object is to remove all of the cards
|
Aces is a solitaire card game. The object is to remove all of the
|
||||||
from the board, except the four Aces.
|
cards from the board, except the four Aces.
|
||||||
|
|
||||||
Remove a card by clicking it. You may remove a card when two
|
Remove a card by clicking it. You may remove a card when two
|
||||||
conditions are true. First, it must be at the bottom of one of the
|
conditions are true. First, it must be at the bottom of one of the
|
||||||
four stacks of cards. Second, either the ace of the same suit, or a
|
four stacks of cards. Second, either the ace of the same suit, or a
|
||||||
higher card of the same suit must also be at the bottom of one of the
|
higher card of the same suit must also be at the bottom of one of the
|
||||||
four stacks of cards.
|
four stacks of cards.
|
||||||
|
|
||||||
You may also move any card from the bottom of one of the stacks to an
|
You may also move any card from the bottom of one of the stacks to an
|
||||||
empty stack by clicking it. If there are still cards in the deck on the
|
empty stack by clicking it. If there are still cards in the deck on
|
||||||
right, you may click the deck to deal four new cards, one onto the
|
the right, you may click the deck to deal four new cards, one onto the
|
||||||
bottom of each stack.
|
bottom of each stack.
|
||||||
|
|
||||||
Good Luck!
|
Good Luck!
|
||||||
|
|
|
@ -1,450 +1,440 @@
|
||||||
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
;;
|
;;
|
||||||
;; Blackjack
|
;; Blackjack
|
||||||
;;
|
;;
|
||||||
;; The standard rules apply. Specifics:
|
;; The standard rules apply. Specifics:
|
||||||
;;
|
;;
|
||||||
;; 1 player (not counting the dealer)
|
;; 1 player (not counting the dealer)
|
||||||
;;
|
;;
|
||||||
;; 4 decks, reshuffled after 3/4 of the cards are used
|
;; 4 decks, reshuffled after 3/4 of the cards are used
|
||||||
;;
|
;;
|
||||||
;; Dealer stands on soft 17s
|
;; Dealer stands on soft 17s
|
||||||
;;
|
;;
|
||||||
;; Splitting allowed only on the first two cards, and only if they
|
;; Splitting allowed only on the first two cards, and only if they
|
||||||
;; are equal; 10 and the face cards are all considered equal for
|
;; are equal; 10 and the face cards are all considered equal for
|
||||||
;; splitting
|
;; splitting
|
||||||
;;
|
;;
|
||||||
;; Doubling allowed on all unsplit hands, not on split hands
|
;; Doubling allowed on all unsplit hands, not on split hands
|
||||||
;;
|
;;
|
||||||
;; No blackjacks after splitting
|
;; No blackjacks after splitting
|
||||||
;;
|
;;
|
||||||
;; No surrender
|
;; No surrender
|
||||||
;;
|
;;
|
||||||
;; No insurance
|
;; No insurance
|
||||||
;;
|
;;
|
||||||
;; No maximum under-21 hand size
|
;; No maximum under-21 hand size
|
||||||
;;
|
;;
|
||||||
;; Dealer's second card is not revealed if the player busts (or
|
;; Dealer's second card is not revealed if the player busts (or
|
||||||
;; both halves of a split hand bust)
|
;; both halves of a split hand bust)
|
||||||
;;
|
;;
|
||||||
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(module blackjack mzscheme
|
#lang mzscheme
|
||||||
(require (lib "cards.ss" "games" "cards")
|
|
||||||
(lib "mred.ss" "mred")
|
(require (lib "cards.ss" "games" "cards")
|
||||||
(lib "class.ss")
|
(lib "mred.ss" "mred")
|
||||||
(lib "unit.ss")
|
(lib "class.ss")
|
||||||
(lib "list.ss"))
|
(lib "list.ss")
|
||||||
|
(lib "unit.ss"))
|
||||||
(provide game@)
|
|
||||||
|
(provide game@)
|
||||||
(define game@
|
(define game@ (unit (import) (export)
|
||||||
(unit
|
|
||||||
(import)
|
;; Number of decks to use
|
||||||
(export)
|
(define DECK-COUNT 4)
|
||||||
|
|
||||||
;; Number of decks to use
|
;; Region layout constants
|
||||||
(define DECK-COUNT 4)
|
(define MARGIN 10)
|
||||||
|
(define SUBMARGIN 10)
|
||||||
;; Region layout constants
|
(define LABEL-H 15)
|
||||||
(define MARGIN 10)
|
|
||||||
(define SUBMARGIN 10)
|
;; Randomize
|
||||||
(define LABEL-H 15)
|
(random-seed (modulo (current-milliseconds) 10000))
|
||||||
|
|
||||||
;; Randomize
|
;; Reshuffle when 3/4 of the deck is used
|
||||||
(random-seed (modulo (current-milliseconds) 10000))
|
(define min-deck-size (/ (* DECK-COUNT 52) 4))
|
||||||
|
|
||||||
;; Reshuffle when 3/4 of the deck is used
|
;; Set up the table
|
||||||
(define min-deck-size (/ (* DECK-COUNT 52) 4))
|
(define t (make-table "Blackjack" 6 3))
|
||||||
|
(define status-pane (send t create-status-pane))
|
||||||
;; Set up the table
|
(send t add-help-button status-pane '("games" "blackjack") "Blackjack Help" #f)
|
||||||
(define t (make-table "Blackjack" 6 3))
|
(send t show #t)
|
||||||
(define status-pane (send t create-status-pane))
|
(send t set-double-click-action #f)
|
||||||
(send t add-help-button status-pane '("games" "blackjack") "Blackjack Help" #f)
|
(send t set-button-action 'left 'drag/one)
|
||||||
(send t show #t)
|
(send t set-button-action 'middle 'drag/one)
|
||||||
(send t set-double-click-action #f)
|
(send t set-button-action 'right 'drag/one)
|
||||||
(send t set-button-action 'left 'drag/one)
|
|
||||||
(send t set-button-action 'middle 'drag/one)
|
;; Get table width & height
|
||||||
(send t set-button-action 'right 'drag/one)
|
(define w (send t table-width))
|
||||||
|
(define h (send t table-height))
|
||||||
;; Get table width & height
|
|
||||||
(define w (send t table-width))
|
;; Build the deck
|
||||||
(define h (send t table-height))
|
(define deck
|
||||||
|
(let loop ([n DECK-COUNT])
|
||||||
;; Build the deck
|
(if (zero? n)
|
||||||
(define deck
|
null
|
||||||
(let loop ([n DECK-COUNT])
|
(append (make-deck) (loop (sub1 n))))))
|
||||||
(if (zero? n)
|
|
||||||
null
|
;; Card width & height
|
||||||
(append (make-deck) (loop (sub1 n))))))
|
(define cw (send (car deck) card-width))
|
||||||
|
(define ch (send (car deck) card-height))
|
||||||
;; Card width & height
|
|
||||||
(define cw (send (car deck) card-width))
|
;; Size of buttons
|
||||||
(define ch (send (car deck) card-height))
|
(define BUTTON-HEIGHT 16)
|
||||||
|
(define BUTTON-WIDTH cw)
|
||||||
;; Size of buttons
|
|
||||||
(define BUTTON-HEIGHT 16)
|
;; Cards are not movable
|
||||||
(define BUTTON-WIDTH cw)
|
(for-each (lambda (card) (send* card (user-can-move #f) (user-can-flip #f)))
|
||||||
|
deck)
|
||||||
;; Cards are not movable
|
|
||||||
(for-each
|
;; Set up card regions
|
||||||
(lambda (card)
|
(define deck-region
|
||||||
(send card user-can-move #f)
|
(make-region MARGIN MARGIN cw ch #f #f))
|
||||||
(send card user-can-flip #f))
|
|
||||||
deck)
|
(define discard-region
|
||||||
|
(make-region (- w cw MARGIN) MARGIN cw ch #f #f))
|
||||||
;; Set up card regions
|
|
||||||
(define deck-region
|
(define dealer-region
|
||||||
(make-region MARGIN MARGIN
|
(make-region (+ cw (* 2 MARGIN)) MARGIN
|
||||||
cw ch #f #f))
|
(- w (* 2 cw) (* 4 MARGIN)) ch
|
||||||
|
#f #f))
|
||||||
(define discard-region
|
|
||||||
(make-region (- w cw MARGIN) MARGIN
|
(define player-region
|
||||||
cw ch #f #f))
|
(make-region (+ cw (* 2 MARGIN)) (- h (* 2 MARGIN) ch BUTTON-HEIGHT)
|
||||||
|
(- w (* 2 cw) (* 4 MARGIN)) ch
|
||||||
(define dealer-region
|
#f #f))
|
||||||
(make-region (+ cw (* 2 MARGIN)) MARGIN
|
|
||||||
(- w (* 2 cw) (* 4 MARGIN)) ch
|
;; In case of split, we need more regions
|
||||||
#f #f))
|
(define ww (* 3/2 cw))
|
||||||
|
(define player-2-region
|
||||||
(define player-region
|
(make-region MARGIN (region-y player-region)
|
||||||
(make-region (+ cw (* 2 MARGIN)) (- h (* 2 MARGIN) ch BUTTON-HEIGHT)
|
(- w ww (* 3 MARGIN)) (region-h player-region)
|
||||||
(- w (* 2 cw) (* 4 MARGIN)) ch
|
#f #f))
|
||||||
#f #f))
|
(define player-2-wait-region
|
||||||
|
(make-region (region-x player-2-region) (region-y player-2-region)
|
||||||
;; In case of split, we need more regions
|
ww (region-h player-2-region)
|
||||||
(define ww (* 3/2 cw))
|
#f #f))
|
||||||
(define player-2-region
|
(define player-1-region
|
||||||
(make-region MARGIN (region-y player-region)
|
(make-region (- w MARGIN (region-w player-2-region)) (region-y player-2-region)
|
||||||
(- w ww (* 3 MARGIN)) (region-h player-region)
|
(region-w player-2-region) (region-h player-2-region)
|
||||||
#f #f))
|
#f #f))
|
||||||
(define player-2-wait-region
|
(define player-1-wait-region
|
||||||
(make-region (region-x player-2-region) (region-y player-2-region)
|
(make-region (- (+ (region-x player-1-region) (region-w player-1-region)) ww)
|
||||||
ww (region-h player-2-region)
|
(region-y player-1-region)
|
||||||
#f #f))
|
ww (region-h player-1-region)
|
||||||
(define player-1-region
|
#f #f))
|
||||||
(make-region (- w MARGIN (region-w player-2-region)) (region-y player-2-region)
|
(define (make-border-region r)
|
||||||
(region-w player-2-region) (region-h player-2-region)
|
(define hm (/ MARGIN 2))
|
||||||
#f #f))
|
(make-region (- (region-x r) hm) (- (region-y r) hm)
|
||||||
(define player-1-wait-region
|
(+ (region-w r) MARGIN) (+ (region-h r) MARGIN)
|
||||||
(make-region (- (+ (region-x player-1-region) (region-w player-1-region)) ww)
|
"" #f))
|
||||||
(region-y player-1-region)
|
(define player-1-border (make-border-region player-1-region))
|
||||||
ww (region-h player-1-region)
|
(define player-2-border (make-border-region player-2-region))
|
||||||
#f #f))
|
|
||||||
(define (make-border-region r)
|
;; Player buttons
|
||||||
(define hm (/ MARGIN 2))
|
(define (make-button title pos)
|
||||||
(make-region (- (region-x r) hm) (- (region-y r) hm)
|
(make-button-region (+ (/ (- w (* 4 BUTTON-WIDTH) (* 3 MARGIN)) 2)
|
||||||
(+ (region-w r) MARGIN) (+ (region-h r) MARGIN)
|
(* pos (+ BUTTON-WIDTH MARGIN)))
|
||||||
"" #f))
|
(- h MARGIN BUTTON-HEIGHT)
|
||||||
(define player-1-border (make-border-region player-1-region))
|
BUTTON-WIDTH BUTTON-HEIGHT
|
||||||
(define player-2-border (make-border-region player-2-region))
|
title void))
|
||||||
|
(define hit-button (make-button "Hit" 1))
|
||||||
;; Player buttons
|
(define stand-button (make-button "Stand" 2))
|
||||||
(define (make-button title pos)
|
(define split-button (make-button "Split" 0))
|
||||||
(make-button-region (+ (/ (- w (* 4 BUTTON-WIDTH) (* 3 MARGIN)) 2)
|
(define double-button (make-button "Double" 3))
|
||||||
(* pos (+ BUTTON-WIDTH MARGIN)))
|
|
||||||
(- h MARGIN BUTTON-HEIGHT)
|
;; Put the cards on the table
|
||||||
BUTTON-WIDTH BUTTON-HEIGHT
|
(send t add-cards-to-region deck deck-region)
|
||||||
title void))
|
|
||||||
(define hit-button (make-button "Hit" 1))
|
;; Function to compute the normal or minimum value of a card
|
||||||
(define stand-button (make-button "Stand" 2))
|
(define (min-card-value c)
|
||||||
(define split-button (make-button "Split" 0))
|
(let ([v (send c get-value)]) (if (> v 10) 10 v)))
|
||||||
(define double-button (make-button "Double" 3))
|
|
||||||
|
;; Function to compute the value of a hand, counting aces as 1 or 11
|
||||||
;; Put the cards on the table
|
;; to get the highest total possible under 21
|
||||||
(send t add-cards-to-region deck deck-region)
|
(define (best-total l)
|
||||||
|
(let* ([ace? (lambda (is?) (lambda (c) (eq? is? (= (send c get-value) 1))))]
|
||||||
;; Function to compute the normal or minimum value of a card
|
[aces (filter (ace? #t) l)]
|
||||||
(define (min-card-value c)
|
[others (filter (ace? #f) l)]
|
||||||
(let ([v (send c get-value)])
|
[base (apply + (map min-card-value others))])
|
||||||
(if (> v 10)
|
(let loop ([l aces][base base])
|
||||||
10
|
(cond [(null? l) base]
|
||||||
v)))
|
[(<= (+ base (* (length aces) 11)) 21)
|
||||||
|
(+ base (* (length aces) 11))]
|
||||||
;; Function to compute the value of a hand, counting aces as 1 or 11
|
[else (loop (cdr l) (add1 base))]))))
|
||||||
;; to get the highest total possible under 21
|
|
||||||
(define (best-total l)
|
;; Function to test whether a hand is a bust
|
||||||
(let* ([ace? (lambda (is?) (lambda (c) (eq? is? (= (send c get-value) 1))))]
|
(define (bust? p)
|
||||||
[aces (filter (ace? #t) l)]
|
(> (best-total p) 21))
|
||||||
[others (filter (ace? #f) l)]
|
|
||||||
[base (apply + (map min-card-value others))])
|
;; Very simple betting...
|
||||||
(let loop ([l aces][base base])
|
(define money 100)
|
||||||
(cond
|
(define (update-money! d)
|
||||||
[(null? l) base]
|
(set! money (+ money d))
|
||||||
[(<= (+ base (* (length aces) 11)) 21)
|
(send t set-status (format "You have $~a. (Each bet is $2.)" money)))
|
||||||
(+ base (* (length aces) 11))]
|
|
||||||
[else (loop (cdr l) (add1 base))]))))
|
;; Let's play!
|
||||||
|
(let shuffle-loop ()
|
||||||
;; Function to test whether a hand is a bust
|
;; Shuffle the cards, none are discarded, yet
|
||||||
(define (bust? p)
|
(let* ([deck (shuffle-list deck 7)]
|
||||||
(> (best-total p) 21))
|
[discard null]
|
||||||
|
[deal (lambda (n)
|
||||||
;; Very simple betting...
|
(let deal ([n n])
|
||||||
(define money 100)
|
(if (zero? n)
|
||||||
(define (update-money! d)
|
null
|
||||||
(set! money (+ money d))
|
(let ([c (car deck)])
|
||||||
(send t set-status (format "You have $~a. (Each bet is $2.)" money)))
|
(set! deck (cdr deck))
|
||||||
|
(cons c (deal (sub1 n)))))))])
|
||||||
;; Let's play!
|
;; Put the shuffled deck in place
|
||||||
(let shuffle-loop ()
|
(send t move-cards-to-region deck deck-region)
|
||||||
;; Shuffle the cards, none are discarded, yet
|
(send t stack-cards deck)
|
||||||
(let* ([deck (shuffle-list deck 7)]
|
;; Loop rounds over while there's enough cards in the deck
|
||||||
[discard null]
|
(let loop ()
|
||||||
[deal (lambda (n)
|
;; All bets are $2
|
||||||
(let deal ([n n])
|
(update-money! -2)
|
||||||
(if (zero? n)
|
;; Deal to player
|
||||||
null
|
(let ([p (deal 2)]
|
||||||
(let ([c (car deck)])
|
[p2 null] ; in case of splitting
|
||||||
(set! deck (cdr deck))
|
[double? #f]) ; in case of doubling (flag is needed to adjust money)
|
||||||
(cons c (deal (sub1 n)))))))])
|
;; Move the player's cards into place and show them
|
||||||
;; Put the shuffled deck in place
|
(send t move-cards-to-region p player-region)
|
||||||
(send t move-cards-to-region deck deck-region)
|
(send t cards-face-up p)
|
||||||
(send t stack-cards deck)
|
;; Deal to dealer
|
||||||
;; Loop rounds over while there's enough cards in the deck
|
(let ([d (deal 2)])
|
||||||
(let loop ()
|
;; Move the dealer's cards into place and show one
|
||||||
;; All bets are $2
|
(send t move-cards-to-region d dealer-region)
|
||||||
(update-money! -2)
|
(send t card-face-up (car d))
|
||||||
;; Deal to player
|
(let* ([continue (make-semaphore)]
|
||||||
(let ([p (deal 2)]
|
;; Make a button in the center to show results
|
||||||
[p2 null] ; in case of splitting
|
[make-status
|
||||||
[double? #f]) ; in case of doubling (flag is needed to adjust money)
|
(lambda (title continue)
|
||||||
;; Move the player's cards into place and show them
|
(let ([r (make-button-region
|
||||||
(send t move-cards-to-region p player-region)
|
(/ (- w (* 2 cw)) 2)
|
||||||
(send t cards-face-up p)
|
(region-y hit-button)
|
||||||
;; Deal to dealer
|
(* 2 cw) BUTTON-HEIGHT
|
||||||
(let ([d (deal 2)])
|
title #f)])
|
||||||
;; Move the dealer's cards into place and show one
|
(set-region-callback! r (lambda ()
|
||||||
(send t move-cards-to-region d dealer-region)
|
(send t remove-region r)
|
||||||
(send t card-face-up (car d))
|
|
||||||
(let* ([continue (make-semaphore)]
|
|
||||||
;; Make a button in the center to show results
|
|
||||||
[make-status
|
|
||||||
(lambda (title continue)
|
|
||||||
(let ([r (make-button-region
|
|
||||||
(/ (- w (* 2 cw)) 2)
|
|
||||||
(region-y hit-button)
|
|
||||||
(* 2 cw) BUTTON-HEIGHT
|
|
||||||
title #f)])
|
|
||||||
(set-region-callback! r (lambda ()
|
|
||||||
(send t remove-region r)
|
|
||||||
(semaphore-post continue)))
|
|
||||||
r))]
|
|
||||||
;; Done with hand:
|
|
||||||
[done
|
|
||||||
(lambda (title continue)
|
|
||||||
(send t remove-region hit-button)
|
|
||||||
(send t remove-region stand-button)
|
|
||||||
(send t add-region (make-status title continue)))]
|
|
||||||
;; Compute winnings (not called for busts by the player)
|
|
||||||
[finish
|
|
||||||
(lambda (p blackjack?)
|
|
||||||
(let ([pt (best-total p)]
|
|
||||||
[dt (best-total d)]
|
|
||||||
[continue (make-semaphore)])
|
|
||||||
(cond
|
|
||||||
[(or (> dt 21) (> pt dt))
|
|
||||||
(update-money! (if blackjack? 5 (if double? 8 4)))
|
|
||||||
(done (if blackjack?
|
|
||||||
"Blackjack"
|
|
||||||
"You Win")
|
|
||||||
continue)]
|
|
||||||
[(> dt pt)
|
|
||||||
(done (if blackjack?
|
|
||||||
"Dealer Blackjack"
|
|
||||||
"You Lose")
|
|
||||||
continue)]
|
|
||||||
[else (update-money! (if double? 4 2))
|
|
||||||
(done "Push" continue)])
|
|
||||||
(yield continue)))]
|
|
||||||
;; Done with the first hand of a split
|
|
||||||
[finish-split
|
|
||||||
(lambda (p player-region player-wait-region player-border)
|
|
||||||
(unless (bust? p)
|
|
||||||
(send t move-cards-to-region p player-region)
|
|
||||||
(send t add-region player-border)
|
|
||||||
(finish p #f)
|
|
||||||
(send t remove-region player-border)
|
|
||||||
(send t move-cards-to-region p player-wait-region)))]
|
|
||||||
;; Player busts
|
|
||||||
[bust (lambda ()
|
|
||||||
(done "Bust" continue))]
|
|
||||||
;; Bust in one hand of a split
|
|
||||||
[local-bust (lambda ()
|
|
||||||
(let ([cont (make-semaphore)])
|
|
||||||
(done "Bust" cont)
|
|
||||||
(yield cont)))]
|
|
||||||
;; Callback for the hit button; the button's callback
|
|
||||||
;; is changed for diferent modes: normal, split part 1, or split part 2
|
|
||||||
[make-hit-callback
|
|
||||||
(lambda (get-p set-p! player-region bust)
|
|
||||||
(lambda ()
|
|
||||||
(send t remove-region double-button)
|
|
||||||
(send t remove-region split-button)
|
|
||||||
(set-p! (append (deal 1) (get-p)))
|
|
||||||
(send t stack-cards (get-p))
|
|
||||||
(send t move-cards-to-region (get-p) player-region)
|
|
||||||
(send t cards-face-up (get-p))
|
|
||||||
;; Check for bust
|
|
||||||
(when (bust? (get-p))
|
|
||||||
(bust))))])
|
|
||||||
;; Blackjack by player or dealer?
|
|
||||||
(if (or (= 21 (best-total p))
|
|
||||||
(= 21 (best-total d)))
|
|
||||||
(begin
|
|
||||||
;; Show the dealers cards...
|
|
||||||
(send t cards-face-up d)
|
|
||||||
;; ... and compute the result
|
|
||||||
(finish p #t))
|
|
||||||
(begin
|
|
||||||
;; Three basic actions are allowed:
|
|
||||||
(send t add-region hit-button)
|
|
||||||
(send t add-region stand-button)
|
|
||||||
(send t add-region double-button)
|
|
||||||
;; Set the callbacks for normal (unsplit) hands
|
|
||||||
(set-region-callback! hit-button
|
|
||||||
(make-hit-callback
|
|
||||||
(lambda () p)
|
|
||||||
(lambda (v) (set! p v))
|
|
||||||
player-region
|
|
||||||
bust))
|
|
||||||
(set-region-callback! stand-button
|
|
||||||
(lambda ()
|
|
||||||
(semaphore-post continue)))
|
(semaphore-post continue)))
|
||||||
(set-region-callback! double-button
|
r))]
|
||||||
(lambda ()
|
;; Done with hand:
|
||||||
;; Note the double for adjusting money on a win
|
[done
|
||||||
(set! double? #t)
|
(lambda (title continue)
|
||||||
;; Double the bet
|
(send t remove-region hit-button)
|
||||||
(update-money! -2)
|
(send t remove-region stand-button)
|
||||||
;; Deal one more card
|
(send t add-region (make-status title continue)))]
|
||||||
((region-callback hit-button))
|
;; Compute winnings (not called for busts by the player)
|
||||||
;; No more cards or actions, but if the player busted, the hit
|
[finish
|
||||||
;; callback has already continued
|
(lambda (p blackjack?)
|
||||||
(unless (bust? p)
|
(let ([pt (best-total p)]
|
||||||
(semaphore-post continue))))
|
[dt (best-total d)]
|
||||||
;; Split allowed?
|
[continue (make-semaphore)])
|
||||||
(when (= (min-card-value (car p)) (min-card-value (cadr p)))
|
(cond
|
||||||
;; Yes, we can split. If the player hits the split button,
|
[(or (> dt 21) (> pt dt))
|
||||||
;; we have to split the cards, deal one more to each split
|
(update-money! (if blackjack? 5 (if double? 8 4)))
|
||||||
;; half and adjust the callbacks for hit and stand.
|
(done (if blackjack?
|
||||||
;; (If aces are split, the round is over.)
|
"Blackjack"
|
||||||
(send t add-region split-button)
|
"You Win")
|
||||||
(set-region-callback! split-button
|
continue)]
|
||||||
(lambda ()
|
[(> dt pt)
|
||||||
;; Double our bet...
|
(done (if blackjack?
|
||||||
(update-money! -2)
|
"Dealer Blackjack"
|
||||||
;; Split the hand
|
"You Lose")
|
||||||
(set! p2 (list (cadr p)))
|
continue)]
|
||||||
(set! p (list (car p)))
|
[else (update-money! (if double? 4 2))
|
||||||
;; Move the split halves to the "waiting" area. The
|
(done "Push" continue)])
|
||||||
;; active area is reserved for hands that are being
|
(yield continue)))]
|
||||||
;; played
|
;; Done with the first hand of a split
|
||||||
(send t move-cards-to-region p player-1-wait-region)
|
[finish-split
|
||||||
(send t move-cards-to-region p2 player-2-wait-region)
|
(lambda (p player-region player-wait-region player-border)
|
||||||
;; Deal one more card to each half and move them into place
|
(unless (bust? p)
|
||||||
(set! p (append (deal 1) p))
|
(send t move-cards-to-region p player-region)
|
||||||
(set! p2 (append (deal 1) p2))
|
(send t add-region player-border)
|
||||||
(send t stack-cards p)
|
(finish p #f)
|
||||||
(send t stack-cards p2)
|
(send t remove-region player-border)
|
||||||
(send t move-cards-to-region p player-1-wait-region)
|
(send t move-cards-to-region p player-wait-region)))]
|
||||||
(send t move-cards-to-region p2 player-2-wait-region)
|
;; Player busts
|
||||||
;; Show the newly dealt cards
|
[bust (lambda ()
|
||||||
(send t flip-cards (list (car p) (car p2)))
|
(done "Bust" continue))]
|
||||||
;; No more splits, no doubling
|
;; Bust in one hand of a split
|
||||||
(send t remove-region split-button)
|
[local-bust (lambda ()
|
||||||
(send t remove-region double-button)
|
(let ([cont (make-semaphore)])
|
||||||
;; Function called when the last split hand is done
|
(done "Bust" cont)
|
||||||
(let* ([close-split
|
(yield cont)))]
|
||||||
(lambda ()
|
;; Callback for the hit button; the button's callback is
|
||||||
;; Unhilite the second hand
|
;; changed for diferent modes: normal, split part 1, or split
|
||||||
(send t remove-region player-2-border)
|
;; part 2
|
||||||
(send t move-cards-to-region p2 player-2-wait-region)
|
[make-hit-callback
|
||||||
;; Let the main loop finish up
|
(lambda (get-p set-p! player-region bust)
|
||||||
(semaphore-post continue))]
|
(lambda ()
|
||||||
;; Callback to swicth from the first split hand to the second
|
(send t remove-region double-button)
|
||||||
[switch
|
(send t remove-region split-button)
|
||||||
(lambda ()
|
(set-p! (append (deal 1) (get-p)))
|
||||||
;; Unhilite the first hand
|
(send t stack-cards (get-p))
|
||||||
(send t remove-region player-1-border)
|
(send t move-cards-to-region (get-p) player-region)
|
||||||
(send t move-cards-to-region p player-1-wait-region)
|
(send t cards-face-up (get-p))
|
||||||
;; Hilite the second hand
|
;; Check for bust
|
||||||
(send t move-cards-to-region p2 player-2-region)
|
(when (bust? (get-p)) (bust))))])
|
||||||
(send t add-region player-2-border)
|
;; Blackjack by player or dealer?
|
||||||
;; Adjust callbacks to operate on the second hand
|
(if (or (= 21 (best-total p))
|
||||||
(set-region-callback!
|
(= 21 (best-total d)))
|
||||||
hit-button
|
(begin
|
||||||
(make-hit-callback (lambda () p2)
|
;; Show the dealers cards...
|
||||||
(lambda (v) (set! p2 v))
|
(send t cards-face-up d)
|
||||||
player-2-region
|
;; ... and compute the result
|
||||||
(lambda ()
|
(finish p #t))
|
||||||
(local-bust)
|
(begin
|
||||||
(close-split))))
|
;; Three basic actions are allowed:
|
||||||
(set-region-callback!
|
(send t add-region hit-button)
|
||||||
stand-button
|
(send t add-region stand-button)
|
||||||
close-split))])
|
(send t add-region double-button)
|
||||||
;; Did we split aces?
|
;; Set the callbacks for normal (unsplit) hands
|
||||||
(if (= 1 (send (cadr p) get-value))
|
(set-region-callback!
|
||||||
;; Split aces; no more cards
|
hit-button
|
||||||
(semaphore-post continue)
|
(make-hit-callback (lambda () p)
|
||||||
(begin
|
(lambda (v) (set! p v))
|
||||||
;; The first of the split hands is ready to go
|
player-region
|
||||||
(send t move-cards-to-region p player-1-region)
|
bust))
|
||||||
;; Hilite the first hand
|
(set-region-callback!
|
||||||
(send t add-region player-1-border)
|
stand-button
|
||||||
;; Adjust callbacks to work on the first of a split hand
|
(lambda () (semaphore-post continue)))
|
||||||
(set-region-callback!
|
(set-region-callback!
|
||||||
hit-button
|
double-button
|
||||||
(make-hit-callback (lambda () p)
|
(lambda ()
|
||||||
(lambda (v) (set! p v))
|
;; Note the double for adjusting money on a win
|
||||||
player-1-region
|
(set! double? #t)
|
||||||
(lambda ()
|
;; Double the bet
|
||||||
(local-bust)
|
(update-money! -2)
|
||||||
(switch)
|
;; Deal one more card
|
||||||
(send t add-region hit-button)
|
((region-callback hit-button))
|
||||||
(send t add-region stand-button))))
|
;; No more cards or actions, but if the player busted, the
|
||||||
(set-region-callback!
|
;; hit callback has already continued
|
||||||
stand-button
|
(unless (bust? p) (semaphore-post continue))))
|
||||||
switch)))))))
|
;; Split allowed?
|
||||||
;; Wait until the player is done
|
(when (= (min-card-value (car p)) (min-card-value (cadr p)))
|
||||||
(yield continue)
|
;; Yes, we can split. If the player hits the split button, we
|
||||||
;; No more player actions; get rid of the buttons
|
;; have to split the cards, deal one more to each split half
|
||||||
(send t remove-region hit-button)
|
;; and adjust the callbacks for hit and stand. (If aces are
|
||||||
(send t remove-region stand-button)
|
;; split, the round is over.)
|
||||||
(send t remove-region double-button)
|
(send t add-region split-button)
|
||||||
(send t remove-region split-button)
|
(set-region-callback!
|
||||||
;; If all the player's hards are bust, the dealer doesn't do anything
|
split-button
|
||||||
(unless (and (bust? p)
|
(lambda ()
|
||||||
(or (null? p2)
|
;; Double our bet...
|
||||||
(bust? p2)))
|
(update-money! -2)
|
||||||
;; Show the dealer's starting hand
|
;; Split the hand
|
||||||
(send t card-face-up (cadr d))
|
(set! p2 (list (cadr p)))
|
||||||
(let loop ()
|
(set! p (list (car p)))
|
||||||
;; Hit on 16 or lower, stand on 17 and higher
|
;; Move the split halves to the "waiting" area. The active
|
||||||
(when (< (best-total d) 17)
|
;; area is reserved for hands that are being played
|
||||||
;; Hit the dealer
|
(send t move-cards-to-region p player-1-wait-region)
|
||||||
(set! d (append (deal 1) d))
|
(send t move-cards-to-region p2 player-2-wait-region)
|
||||||
(send t stack-cards d)
|
;; Deal one more card to each half and move them into
|
||||||
(send t move-cards-to-region d dealer-region)
|
;; place
|
||||||
(send t cards-face-up d)
|
(set! p (append (deal 1) p))
|
||||||
(loop)))
|
(set! p2 (append (deal 1) p2))
|
||||||
(if (null? p2)
|
(send t stack-cards p)
|
||||||
;; Finish normal game (adjusts winnings)
|
(send t stack-cards p2)
|
||||||
(finish p #f)
|
(send t move-cards-to-region p player-1-wait-region)
|
||||||
;; Finish split game (adjusts winnings for each hand)
|
(send t move-cards-to-region p2 player-2-wait-region)
|
||||||
(begin
|
;; Show the newly dealt cards
|
||||||
(finish-split p player-1-region player-1-wait-region player-1-border)
|
(send t flip-cards (list (car p) (car p2)))
|
||||||
(finish-split p2 player-2-region player-2-wait-region player-2-border))))))
|
;; No more splits, no doubling
|
||||||
;; Move all the discarded cards to the back
|
(send t remove-region split-button)
|
||||||
(unless (null? discard)
|
(send t remove-region double-button)
|
||||||
(send t card-to-back (car discard))
|
;; Function called when the last split hand is done
|
||||||
(send t stack-cards discard))
|
(let* ([close-split
|
||||||
;; Discard all the cards we used
|
(lambda ()
|
||||||
(set! discard (append p p2 d discard))
|
;; Unhilite the second hand
|
||||||
(send t cards-face-down discard)
|
(send t remove-region player-2-border)
|
||||||
(send t move-cards-to-region discard discard-region)
|
(send t move-cards-to-region p2 player-2-wait-region)
|
||||||
;; Go again. Check whether we should reshuffle the deck or keep going with this one
|
;; Let the main loop finish up
|
||||||
(if (< (length deck) min-deck-size)
|
(semaphore-post continue))]
|
||||||
(begin
|
;; Callback to swicth from the first split hand to
|
||||||
(send t move-cards-to-region deck discard-region)
|
;; the second
|
||||||
(shuffle-loop))
|
[switch
|
||||||
(loop)))))))))))
|
(lambda ()
|
||||||
|
;; Unhilite the first hand
|
||||||
|
(send t remove-region player-1-border)
|
||||||
|
(send t move-cards-to-region p player-1-wait-region)
|
||||||
|
;; Hilite the second hand
|
||||||
|
(send t move-cards-to-region p2 player-2-region)
|
||||||
|
(send t add-region player-2-border)
|
||||||
|
;; Adjust callbacks to operate on the second hand
|
||||||
|
(set-region-callback!
|
||||||
|
hit-button
|
||||||
|
(make-hit-callback (lambda () p2)
|
||||||
|
(lambda (v) (set! p2 v))
|
||||||
|
player-2-region
|
||||||
|
(lambda ()
|
||||||
|
(local-bust)
|
||||||
|
(close-split))))
|
||||||
|
(set-region-callback!
|
||||||
|
stand-button
|
||||||
|
close-split))])
|
||||||
|
;; Did we split aces?
|
||||||
|
(if (= 1 (send (cadr p) get-value))
|
||||||
|
;; Split aces; no more cards
|
||||||
|
(semaphore-post continue)
|
||||||
|
(begin
|
||||||
|
;; The first of the split hands is ready to go
|
||||||
|
(send t move-cards-to-region p player-1-region)
|
||||||
|
;; Hilite the first hand
|
||||||
|
(send t add-region player-1-border)
|
||||||
|
;; Adjust callbacks to work on the first of a split
|
||||||
|
;; hand
|
||||||
|
(set-region-callback!
|
||||||
|
hit-button
|
||||||
|
(make-hit-callback (lambda () p)
|
||||||
|
(lambda (v) (set! p v))
|
||||||
|
player-1-region
|
||||||
|
(lambda ()
|
||||||
|
(local-bust)
|
||||||
|
(switch)
|
||||||
|
(send t add-region hit-button)
|
||||||
|
(send t add-region stand-button))))
|
||||||
|
(set-region-callback! stand-button switch)))))))
|
||||||
|
;; Wait until the player is done
|
||||||
|
(yield continue)
|
||||||
|
;; No more player actions; get rid of the buttons
|
||||||
|
(send t remove-region hit-button)
|
||||||
|
(send t remove-region stand-button)
|
||||||
|
(send t remove-region double-button)
|
||||||
|
(send t remove-region split-button)
|
||||||
|
;; If all the player's hards are bust, the dealer doesn't do
|
||||||
|
;; anything
|
||||||
|
(unless (and (bust? p) (or (null? p2) (bust? p2)))
|
||||||
|
;; Show the dealer's starting hand
|
||||||
|
(send t card-face-up (cadr d))
|
||||||
|
(let loop ()
|
||||||
|
;; Hit on 16 or lower, stand on 17 and higher
|
||||||
|
(when (< (best-total d) 17)
|
||||||
|
;; Hit the dealer
|
||||||
|
(set! d (append (deal 1) d))
|
||||||
|
(send t stack-cards d)
|
||||||
|
(send t move-cards-to-region d dealer-region)
|
||||||
|
(send t cards-face-up d)
|
||||||
|
(loop)))
|
||||||
|
(if (null? p2)
|
||||||
|
;; Finish normal game (adjusts winnings)
|
||||||
|
(finish p #f)
|
||||||
|
;; Finish split game (adjusts winnings for each hand)
|
||||||
|
(begin
|
||||||
|
(finish-split p player-1-region player-1-wait-region player-1-border)
|
||||||
|
(finish-split p2 player-2-region player-2-wait-region player-2-border))))))
|
||||||
|
;; Move all the discarded cards to the back
|
||||||
|
(unless (null? discard)
|
||||||
|
(send t card-to-back (car discard))
|
||||||
|
(send t stack-cards discard))
|
||||||
|
;; Discard all the cards we used
|
||||||
|
(set! discard (append p p2 d discard))
|
||||||
|
(send t cards-face-down discard)
|
||||||
|
(send t move-cards-to-region discard discard-region)
|
||||||
|
;; Go again. Check whether we should reshuffle the deck or keep
|
||||||
|
;; going with this one
|
||||||
|
(if (< (length deck) min-deck-size)
|
||||||
|
(begin (send t move-cards-to-region deck discard-region)
|
||||||
|
(shuffle-loop))
|
||||||
|
(loop))))))))
|
||||||
|
|
||||||
|
))
|
||||||
|
|
|
@ -1,27 +1,26 @@
|
||||||
** To play _Blackjack_, run the "Games" application. **
|
** To play Blackjack, run the "PLT Games" application.
|
||||||
|
|
||||||
Standard Blackjack rules, plus the following specifics:
|
Standard Blackjack rules, plus the following specifics:
|
||||||
|
|
||||||
* 1 player (not counting the dealer)
|
* 1 player (not counting the dealer)
|
||||||
|
|
||||||
* 4 decks, reshuffled after 3/4 of the cards are used
|
* 4 decks, reshuffled after 3/4 of the cards are used
|
||||||
|
|
||||||
* Dealer stands on soft 17s
|
* Dealer stands on soft 17s
|
||||||
|
|
||||||
* Splitting is allowed only on the first two cards, and only if
|
* Splitting is allowed only on the first two cards, and only if they
|
||||||
they are equal; 10 and the face cards are all considered equal
|
are equal; 10 and the face cards are all considered equal for
|
||||||
for splitting
|
splitting
|
||||||
|
|
||||||
* Doubling is allowed on all unsplit hands, not on split hands
|
* Doubling is allowed on all unsplit hands, not on split hands
|
||||||
|
|
||||||
* No blackjacks after splitting
|
* No blackjacks after splitting
|
||||||
|
|
||||||
* No surrender
|
* No surrender
|
||||||
|
|
||||||
* No insurance
|
* No insurance
|
||||||
|
|
||||||
* No maximum under-21 hand size
|
* No maximum under-21 hand size
|
||||||
|
|
||||||
* Dealer's second card is not revealed if the player busts (or
|
|
||||||
both halves of a split hand bust)
|
|
||||||
|
|
||||||
|
* Dealer's second card is not revealed if the player busts (or both
|
||||||
|
halves of a split hand bust)
|
||||||
|
|
|
@ -1,244 +1,238 @@
|
||||||
(module checkers mzscheme
|
#lang mzscheme
|
||||||
(require (lib "gl-board.ss" "games" "gl-board-game")
|
|
||||||
(lib "class.ss")
|
|
||||||
(lib "math.ss")
|
|
||||||
(lib "mred.ss" "mred")
|
|
||||||
(lib "gl-vectors.ss" "sgl")
|
|
||||||
(prefix gl- (lib "sgl.ss" "sgl"))
|
|
||||||
(lib "gl.ss" "sgl")
|
|
||||||
(lib "array.ss" "srfi" "25")
|
|
||||||
(lib "unit.ss")
|
|
||||||
(lib "include-bitmap.ss" "mrlib")
|
|
||||||
"honu-bitmaps.ss")
|
|
||||||
|
|
||||||
(provide game@)
|
|
||||||
|
|
||||||
(define-struct image (width height rgba))
|
(require (lib "gl-board.ss" "games" "gl-board-game")
|
||||||
|
(lib "class.ss")
|
||||||
(define (argb->rgba argb)
|
(lib "math.ss")
|
||||||
(let* ((length (bytes-length argb))
|
(lib "mred.ss" "mred")
|
||||||
(rgba (make-gl-ubyte-vector length)))
|
(lib "unit.ss")
|
||||||
(let loop ((i 0))
|
(lib "gl-vectors.ss" "sgl")
|
||||||
(when (< i length)
|
(prefix gl- (lib "sgl.ss" "sgl"))
|
||||||
(gl-vector-set! rgba (+ i 0) (bytes-ref argb (+ i 1)))
|
(lib "gl.ss" "sgl")
|
||||||
(gl-vector-set! rgba (+ i 1) (bytes-ref argb (+ i 2)))
|
(lib "array.ss" "srfi" "25")
|
||||||
(gl-vector-set! rgba (+ i 2) (bytes-ref argb (+ i 3)))
|
(lib "include-bitmap.ss" "mrlib")
|
||||||
(gl-vector-set! rgba (+ i 3) (bytes-ref argb (+ i 0)))
|
"honu-bitmaps.ss")
|
||||||
(loop (+ i 4))))
|
(provide game@)
|
||||||
rgba))
|
|
||||||
|
|
||||||
(define (bitmap->argb bmp)
|
|
||||||
(let* ((width (send bmp get-width))
|
|
||||||
(height (send bmp get-height))
|
|
||||||
(argb (make-bytes (* 4 width height) 255))
|
|
||||||
(dc (make-object bitmap-dc% bmp)))
|
|
||||||
(send dc get-argb-pixels 0 0 width height argb #f)
|
|
||||||
(when (send bmp get-loaded-mask)
|
|
||||||
(send dc set-bitmap (send bmp get-loaded-mask))
|
|
||||||
(send dc get-argb-pixels 0 0 width height argb #t))
|
|
||||||
(send dc set-bitmap #f)
|
|
||||||
argb))
|
|
||||||
|
|
||||||
(define (bitmap->image bmp)
|
|
||||||
(make-image (send bmp get-width) (send bmp get-height)
|
|
||||||
(argb->rgba (bitmap->argb bmp))))
|
|
||||||
|
|
||||||
(define light-square-img (bitmap->image (include-bitmap "light.jpg")))
|
(define-struct image (width height rgba))
|
||||||
(define light-square-color (gl-float-vector .7216 .6471 .5176 1))
|
|
||||||
(define dark-square-img (bitmap->image (include-bitmap "dark.jpg")))
|
|
||||||
(define dark-square-color (gl-float-vector .4745 .3569 .2627 1))
|
|
||||||
|
|
||||||
(define (color-name->vector name darken?)
|
|
||||||
(let ([color (send the-color-database find-color name)]
|
|
||||||
[adj (if darken? sqr values)])
|
|
||||||
(unless color
|
|
||||||
(error 'color-name->vector "could not find ~e" name))
|
|
||||||
(gl-float-vector (adj (/ (send color red) 255))
|
|
||||||
(adj (/ (send color green) 255))
|
|
||||||
(adj (/ (send color blue) 255))
|
|
||||||
1.0)))
|
|
||||||
|
|
||||||
(define light-checker-img (bitmap->image honu-down-bitmap))
|
|
||||||
(define dark-checker-img (bitmap->image honu-bitmap))
|
|
||||||
|
|
||||||
(define-struct space-info (x y light?))
|
|
||||||
(define-struct piece-info (x y color king?) (make-inspector))
|
|
||||||
(define-struct moves (list forced-jump?))
|
|
||||||
|
|
||||||
(define-signature model^
|
(define (argb->rgba argb)
|
||||||
(move))
|
(let* ([length (bytes-length argb)]
|
||||||
(define-signature view^
|
[rgba (make-gl-ubyte-vector length)])
|
||||||
(add-space add-piece remove-piece move-piece set-turn show))
|
(let loop ((i 0))
|
||||||
|
(when (< i length)
|
||||||
|
(gl-vector-set! rgba (+ i 0) (bytes-ref argb (+ i 1)))
|
||||||
|
(gl-vector-set! rgba (+ i 1) (bytes-ref argb (+ i 2)))
|
||||||
|
(gl-vector-set! rgba (+ i 2) (bytes-ref argb (+ i 3)))
|
||||||
|
(gl-vector-set! rgba (+ i 3) (bytes-ref argb (+ i 0)))
|
||||||
|
(loop (+ i 4))))
|
||||||
|
rgba))
|
||||||
|
|
||||||
(define-unit view@
|
(define (bitmap->argb bmp)
|
||||||
(import model^)
|
(let* ([width (send bmp get-width)]
|
||||||
(export view^)
|
[height (send bmp get-height)]
|
||||||
|
[argb (make-bytes (* 4 width height) 255)]
|
||||||
|
[dc (make-object bitmap-dc% bmp)])
|
||||||
|
(send dc get-argb-pixels 0 0 width height argb #f)
|
||||||
|
(when (send bmp get-loaded-mask)
|
||||||
|
(send dc set-bitmap (send bmp get-loaded-mask))
|
||||||
|
(send dc get-argb-pixels 0 0 width height argb #t))
|
||||||
|
(send dc set-bitmap #f)
|
||||||
|
argb))
|
||||||
|
|
||||||
(define (get-space-draw-fn space)
|
(define (bitmap->image bmp)
|
||||||
(let* ((list-id (get-square-dl (space-info-light? space)
|
(make-image (send bmp get-width) (send bmp get-height)
|
||||||
(send texture-box get-value)))
|
(argb->rgba (bitmap->argb bmp))))
|
||||||
(sx (space-info-x space))
|
|
||||||
(sy (space-info-y space)))
|
|
||||||
(lambda ()
|
|
||||||
(gl-push-matrix)
|
|
||||||
(gl-translate sx sy 0)
|
|
||||||
(gl-call-list list-id)
|
|
||||||
(gl-pop-matrix))))
|
|
||||||
|
|
||||||
(define (add-space space)
|
|
||||||
(send board add-space (get-space-draw-fn space) space))
|
|
||||||
|
|
||||||
(define (get-piece-draw-fn piece glow?)
|
(define light-square-img (bitmap->image (include-bitmap "light.jpg")))
|
||||||
(let ((list-id (get-checker-dl (eq? 'red (piece-info-color piece))
|
(define light-square-color (gl-float-vector .7216 .6471 .5176 1))
|
||||||
(piece-info-king? piece)
|
(define dark-square-img (bitmap->image (include-bitmap "dark.jpg")))
|
||||||
(send texture-box get-value))))
|
(define dark-square-color (gl-float-vector .4745 .3569 .2627 1))
|
||||||
(if glow?
|
|
||||||
(lambda (for-shadow?)
|
|
||||||
(gl-material-v 'front 'emission (gl-float-vector 0.15 0.15 0.15 1.0))
|
|
||||||
(gl-call-list ((if for-shadow? cdr car) list-id))
|
|
||||||
(gl-material-v 'front 'emission (gl-float-vector 0.0 0.0 0.0 1.0)))
|
|
||||||
(lambda (for-shadow?)
|
|
||||||
(gl-call-list ((if for-shadow? cdr car) list-id))))))
|
|
||||||
|
|
||||||
(define add-piece
|
|
||||||
(case-lambda
|
|
||||||
((piece) (add-piece piece #f))
|
|
||||||
((piece glow?)
|
|
||||||
(send board add-piece (+ .5 (piece-info-x piece)) (+ .5 (piece-info-y piece)) 0.0
|
|
||||||
(get-piece-draw-fn piece glow?)
|
|
||||||
piece))))
|
|
||||||
|
|
||||||
(define (move-piece from to-x to-y)
|
|
||||||
(remove-piece from)
|
|
||||||
(add-piece (make-piece-info to-x to-y (piece-info-color from) (piece-info-king? from))))
|
|
||||||
|
|
||||||
(define (remove-piece p)
|
|
||||||
(send board remove-piece p))
|
|
||||||
|
|
||||||
(define (internal-move old move-to)
|
(define (color-name->vector name darken?)
|
||||||
(when (piece-info? old)
|
(let ([color (send the-color-database find-color name)]
|
||||||
(move old move-to)))
|
[adj (if darken? sqr values)])
|
||||||
|
(unless color
|
||||||
|
(error 'color-name->vector "could not find ~e" name))
|
||||||
|
(gl-float-vector (adj (/ (send color red) 255))
|
||||||
|
(adj (/ (send color green) 255))
|
||||||
|
(adj (/ (send color blue) 255))
|
||||||
|
1.0)))
|
||||||
|
|
||||||
(define (set-turn turn moves)
|
(define light-checker-img (bitmap->image honu-down-bitmap))
|
||||||
(let* ([pieces (send board get-pieces)])
|
(define dark-checker-img (bitmap->image honu-bitmap))
|
||||||
(for-each (lambda (p)
|
|
||||||
(send board set-piece-draw p
|
|
||||||
(get-piece-draw-fn p #f))
|
|
||||||
(send board enable-piece p #f))
|
|
||||||
pieces)
|
|
||||||
(for-each (lambda (p)
|
|
||||||
(send board set-piece-draw p
|
|
||||||
(get-piece-draw-fn p #t))
|
|
||||||
(send board enable-piece p #t))
|
|
||||||
(moves-list moves)))
|
|
||||||
(send msg set-label
|
|
||||||
(if (null? (moves-list moves))
|
|
||||||
(format "~a wins!" (if (eq? turn 'red) "Black" "Red"))
|
|
||||||
(format "~a's turn~a"
|
|
||||||
(if (eq? turn 'red) "Red" "Black")
|
|
||||||
(if (moves-forced-jump? moves)
|
|
||||||
" - must take jump"
|
|
||||||
"")))))
|
|
||||||
|
|
||||||
(define f (new frame% (label "Checkers") (width 800) (height 600)))
|
|
||||||
(define board
|
|
||||||
(new gl-board% (parent f) (who "Checkers")
|
|
||||||
(min-x 0.0) (max-x 8.0) (min-y 0.0) (max-y 8.0)
|
|
||||||
(lift .35)
|
|
||||||
(move internal-move)))
|
|
||||||
(define hp (new horizontal-pane% (parent f) (stretchable-height #f)))
|
|
||||||
(define msg
|
|
||||||
(new message% (label "") (parent hp) (stretchable-width #t)))
|
|
||||||
(define texture-box
|
|
||||||
(new check-box% (label "Textured") (parent hp)
|
|
||||||
(callback
|
|
||||||
(lambda (box _)
|
|
||||||
(for-each
|
|
||||||
(lambda (s)
|
|
||||||
(send board set-space-draw s
|
|
||||||
(get-space-draw-fn s)))
|
|
||||||
(send board get-spaces))
|
|
||||||
(for-each
|
|
||||||
(lambda (p)
|
|
||||||
(send board set-piece-draw p
|
|
||||||
(get-piece-draw-fn p (send board enabled? p))))
|
|
||||||
(send board get-pieces))
|
|
||||||
(send board refresh)))))
|
|
||||||
(new grow-box-spacer-pane% [parent hp])
|
|
||||||
(send texture-box set-value #t)
|
|
||||||
|
|
||||||
(define q
|
|
||||||
(send board with-gl-context
|
|
||||||
(lambda () (gl-new-quadric))))
|
|
||||||
|
|
||||||
(define-values (dark-tex light-tex dark-checker-tex light-checker-tex)
|
(define-struct space-info (x y light?))
|
||||||
(send board with-gl-context
|
(define-struct piece-info (x y color king?) (make-inspector))
|
||||||
|
(define-struct moves (list forced-jump?))
|
||||||
|
|
||||||
|
(define-signature model^
|
||||||
|
(move))
|
||||||
|
(define-signature view^
|
||||||
|
(add-space add-piece remove-piece move-piece set-turn show))
|
||||||
|
|
||||||
|
(define-unit view@
|
||||||
|
(import model^)
|
||||||
|
(export view^)
|
||||||
|
|
||||||
|
(define (get-space-draw-fn space)
|
||||||
|
(let* ([list-id (get-square-dl (space-info-light? space)
|
||||||
|
(send texture-box get-value))]
|
||||||
|
[sx (space-info-x space)]
|
||||||
|
[sy (space-info-y space)])
|
||||||
|
(lambda ()
|
||||||
|
(gl-push-matrix)
|
||||||
|
(gl-translate sx sy 0)
|
||||||
|
(gl-call-list list-id)
|
||||||
|
(gl-pop-matrix))))
|
||||||
|
|
||||||
|
(define (add-space space)
|
||||||
|
(send board add-space (get-space-draw-fn space) space))
|
||||||
|
|
||||||
|
(define (get-piece-draw-fn piece glow?)
|
||||||
|
(let ([list-id (get-checker-dl (eq? 'red (piece-info-color piece))
|
||||||
|
(piece-info-king? piece)
|
||||||
|
(send texture-box get-value))])
|
||||||
|
(if glow?
|
||||||
|
(lambda (for-shadow?)
|
||||||
|
(gl-material-v 'front 'emission (gl-float-vector 0.15 0.15 0.15 1.0))
|
||||||
|
(gl-call-list ((if for-shadow? cdr car) list-id))
|
||||||
|
(gl-material-v 'front 'emission (gl-float-vector 0.0 0.0 0.0 1.0)))
|
||||||
|
(lambda (for-shadow?)
|
||||||
|
(gl-call-list ((if for-shadow? cdr car) list-id))))))
|
||||||
|
|
||||||
|
(define add-piece
|
||||||
|
(case-lambda
|
||||||
|
[(piece) (add-piece piece #f)]
|
||||||
|
[(piece glow?)
|
||||||
|
(send board add-piece
|
||||||
|
(+ .5 (piece-info-x piece)) (+ .5 (piece-info-y piece)) 0.0
|
||||||
|
(get-piece-draw-fn piece glow?)
|
||||||
|
piece)]))
|
||||||
|
|
||||||
|
(define (move-piece from to-x to-y)
|
||||||
|
(remove-piece from)
|
||||||
|
(add-piece (make-piece-info to-x to-y
|
||||||
|
(piece-info-color from)
|
||||||
|
(piece-info-king? from))))
|
||||||
|
|
||||||
|
(define (remove-piece p)
|
||||||
|
(send board remove-piece p))
|
||||||
|
|
||||||
|
(define (internal-move old move-to)
|
||||||
|
(when (piece-info? old) (move old move-to)))
|
||||||
|
|
||||||
|
(define (set-turn turn moves)
|
||||||
|
(let ([pieces (send board get-pieces)])
|
||||||
|
(for-each (lambda (p)
|
||||||
|
(send board set-piece-draw p (get-piece-draw-fn p #f))
|
||||||
|
(send board enable-piece p #f))
|
||||||
|
pieces)
|
||||||
|
(for-each (lambda (p)
|
||||||
|
(send board set-piece-draw p (get-piece-draw-fn p #t))
|
||||||
|
(send board enable-piece p #t))
|
||||||
|
(moves-list moves)))
|
||||||
|
(send msg set-label
|
||||||
|
(if (null? (moves-list moves))
|
||||||
|
(format "~a wins!" (if (eq? turn 'red) "Black" "Red"))
|
||||||
|
(format "~a's turn~a"
|
||||||
|
(if (eq? turn 'red) "Red" "Black")
|
||||||
|
(if (moves-forced-jump? moves) " - must take jump" "")))))
|
||||||
|
|
||||||
|
(define f (new frame% (label "Checkers") (width 800) (height 600)))
|
||||||
|
(define board
|
||||||
|
(new gl-board% (parent f) (who "Checkers")
|
||||||
|
(min-x 0.0) (max-x 8.0) (min-y 0.0) (max-y 8.0)
|
||||||
|
(lift .35)
|
||||||
|
(move internal-move)))
|
||||||
|
(define hp (new horizontal-pane% (parent f) (stretchable-height #f)))
|
||||||
|
(define msg
|
||||||
|
(new message% (label "") (parent hp) (stretchable-width #t)))
|
||||||
|
(define texture-box
|
||||||
|
(new check-box% (label "Textured") (parent hp)
|
||||||
|
(callback
|
||||||
|
(lambda (box _)
|
||||||
|
(for-each
|
||||||
|
(lambda (s)
|
||||||
|
(send board set-space-draw s (get-space-draw-fn s)))
|
||||||
|
(send board get-spaces))
|
||||||
|
(for-each
|
||||||
|
(lambda (p)
|
||||||
|
(send board set-piece-draw p
|
||||||
|
(get-piece-draw-fn p (send board enabled? p))))
|
||||||
|
(send board get-pieces))
|
||||||
|
(send board refresh)))))
|
||||||
|
(new grow-box-spacer-pane% [parent hp])
|
||||||
|
(send texture-box set-value #t)
|
||||||
|
|
||||||
|
(define q
|
||||||
|
(send board with-gl-context (lambda () (gl-new-quadric))))
|
||||||
|
|
||||||
|
(define-values (dark-tex light-tex dark-checker-tex light-checker-tex)
|
||||||
|
(send board with-gl-context
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((x (glGenTextures 4)))
|
(let ((x (glGenTextures 4)))
|
||||||
(values
|
(values (gl-vector-ref x 0)
|
||||||
(gl-vector-ref x 0)
|
(gl-vector-ref x 1)
|
||||||
(gl-vector-ref x 1)
|
(gl-vector-ref x 2)
|
||||||
(gl-vector-ref x 2)
|
(gl-vector-ref x 3))))))
|
||||||
(gl-vector-ref x 3))))))
|
|
||||||
|
|
||||||
(define (init-tex tex img)
|
|
||||||
(send board with-gl-context
|
|
||||||
(lambda ()
|
|
||||||
(glBindTexture GL_TEXTURE_2D tex)
|
|
||||||
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR)
|
|
||||||
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_LINEAR)
|
|
||||||
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_CLAMP)
|
|
||||||
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_CLAMP)
|
|
||||||
(glTexImage2D GL_TEXTURE_2D 0 GL_RGBA (image-width img) (image-height img) 0
|
|
||||||
GL_RGBA GL_UNSIGNED_BYTE (image-rgba img)))))
|
|
||||||
|
|
||||||
(init-tex light-tex light-square-img)
|
|
||||||
(init-tex dark-tex dark-square-img)
|
|
||||||
(init-tex dark-checker-tex dark-checker-img)
|
|
||||||
(init-tex light-checker-tex light-checker-img)
|
|
||||||
|
|
||||||
(define (make-piece-dl color height tex shadow?)
|
|
||||||
(send board with-gl-context
|
|
||||||
(lambda ()
|
|
||||||
(let ((list-id (gl-gen-lists 1)))
|
|
||||||
(gl-quadric-draw-style q 'fill)
|
|
||||||
(gl-quadric-normals q 'smooth)
|
|
||||||
(gl-new-list list-id 'compile)
|
|
||||||
|
|
||||||
(when shadow?
|
|
||||||
(gl-disable 'lighting))
|
|
||||||
(gl-material-v 'front 'specular (gl-float-vector 1.0 1.0 1.0 1.0))
|
|
||||||
(gl-material 'front 'shininess 120.0)
|
|
||||||
|
|
||||||
(gl-material-v 'front 'ambient-and-diffuse color)
|
|
||||||
(gl-cylinder q .35 .35 height 25 1)
|
|
||||||
(gl-push-matrix)
|
|
||||||
(gl-translate 0.0 0.0 height)
|
|
||||||
|
|
||||||
(when (and tex (not shadow?))
|
|
||||||
(gl-enable 'texture-2d)
|
|
||||||
(glBindTexture GL_TEXTURE_2D tex)
|
|
||||||
(glTexEnvf GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE GL_DECAL)
|
|
||||||
(gl-quadric-texture q #t))
|
|
||||||
|
|
||||||
(gl-disk q 0.0 .35 25 1)
|
|
||||||
|
|
||||||
(when (and tex (not shadow?))
|
|
||||||
(gl-quadric-texture q #f)
|
|
||||||
(glTexEnvf GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE GL_MODULATE)
|
|
||||||
(gl-disable 'texture-2d))
|
|
||||||
|
|
||||||
(gl-pop-matrix)
|
|
||||||
|
|
||||||
(when shadow?
|
|
||||||
(gl-enable 'lighting))
|
|
||||||
(gl-end-list)
|
|
||||||
list-id))))
|
|
||||||
|
|
||||||
(define (make-tex-square-dl tex)
|
(define (init-tex tex img)
|
||||||
(send board with-gl-context
|
(send board with-gl-context
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((list-id (gl-gen-lists 1)))
|
(glBindTexture GL_TEXTURE_2D tex)
|
||||||
|
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MIN_FILTER GL_LINEAR)
|
||||||
|
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_MAG_FILTER GL_LINEAR)
|
||||||
|
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_S GL_CLAMP)
|
||||||
|
(glTexParameteri GL_TEXTURE_2D GL_TEXTURE_WRAP_T GL_CLAMP)
|
||||||
|
(glTexImage2D GL_TEXTURE_2D 0 GL_RGBA
|
||||||
|
(image-width img) (image-height img) 0
|
||||||
|
GL_RGBA GL_UNSIGNED_BYTE (image-rgba img)))))
|
||||||
|
|
||||||
|
(init-tex light-tex light-square-img)
|
||||||
|
(init-tex dark-tex dark-square-img)
|
||||||
|
(init-tex dark-checker-tex dark-checker-img)
|
||||||
|
(init-tex light-checker-tex light-checker-img)
|
||||||
|
|
||||||
|
(define (make-piece-dl color height tex shadow?)
|
||||||
|
(send board with-gl-context
|
||||||
|
(lambda ()
|
||||||
|
(let ([list-id (gl-gen-lists 1)])
|
||||||
|
(gl-quadric-draw-style q 'fill)
|
||||||
|
(gl-quadric-normals q 'smooth)
|
||||||
|
(gl-new-list list-id 'compile)
|
||||||
|
|
||||||
|
(when shadow? (gl-disable 'lighting))
|
||||||
|
(gl-material-v 'front 'specular (gl-float-vector 1.0 1.0 1.0 1.0))
|
||||||
|
(gl-material 'front 'shininess 120.0)
|
||||||
|
|
||||||
|
(gl-material-v 'front 'ambient-and-diffuse color)
|
||||||
|
(gl-cylinder q .35 .35 height 25 1)
|
||||||
|
(gl-push-matrix)
|
||||||
|
(gl-translate 0.0 0.0 height)
|
||||||
|
|
||||||
|
(when (and tex (not shadow?))
|
||||||
|
(gl-enable 'texture-2d)
|
||||||
|
(glBindTexture GL_TEXTURE_2D tex)
|
||||||
|
(glTexEnvf GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE GL_DECAL)
|
||||||
|
(gl-quadric-texture q #t))
|
||||||
|
|
||||||
|
(gl-disk q 0.0 .35 25 1)
|
||||||
|
|
||||||
|
(when (and tex (not shadow?))
|
||||||
|
(gl-quadric-texture q #f)
|
||||||
|
(glTexEnvf GL_TEXTURE_ENV GL_TEXTURE_ENV_MODE GL_MODULATE)
|
||||||
|
(gl-disable 'texture-2d))
|
||||||
|
|
||||||
|
(gl-pop-matrix)
|
||||||
|
|
||||||
|
(when shadow? (gl-enable 'lighting))
|
||||||
|
(gl-end-list)
|
||||||
|
list-id))))
|
||||||
|
|
||||||
|
(define (make-tex-square-dl tex)
|
||||||
|
(send board with-gl-context
|
||||||
|
(lambda ()
|
||||||
|
(let ([list-id (gl-gen-lists 1)])
|
||||||
(gl-new-list list-id 'compile)
|
(gl-new-list list-id 'compile)
|
||||||
(gl-enable 'texture-2d)
|
(gl-enable 'texture-2d)
|
||||||
(glBindTexture GL_TEXTURE_2D tex)
|
(glBindTexture GL_TEXTURE_2D tex)
|
||||||
|
@ -258,10 +252,10 @@
|
||||||
(gl-end-list)
|
(gl-end-list)
|
||||||
list-id))))
|
list-id))))
|
||||||
|
|
||||||
(define (make-square-dl color)
|
(define (make-square-dl color)
|
||||||
(send board with-gl-context
|
(send board with-gl-context
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(let ((list-id (gl-gen-lists 1)))
|
(let ([list-id (gl-gen-lists 1)])
|
||||||
(gl-new-list list-id 'compile)
|
(gl-new-list list-id 'compile)
|
||||||
(gl-material-v 'front 'ambient-and-diffuse color)
|
(gl-material-v 'front 'ambient-and-diffuse color)
|
||||||
(gl-begin 'polygon)
|
(gl-begin 'polygon)
|
||||||
|
@ -272,15 +266,16 @@
|
||||||
(gl-end)
|
(gl-end)
|
||||||
(gl-end-list)
|
(gl-end-list)
|
||||||
list-id))))
|
list-id))))
|
||||||
|
|
||||||
(define checkers
|
(define checkers
|
||||||
(map
|
(map (lambda (x)
|
||||||
(lambda (x)
|
(let ([color (if (car x)
|
||||||
(let ((color (if (car x)
|
|
||||||
(color-name->vector "firebrick" #t)
|
(color-name->vector "firebrick" #t)
|
||||||
(gl-float-vector 0.15 0.15 0.15 1.0)))
|
(gl-float-vector 0.15 0.15 0.15 1.0))]
|
||||||
(height (if (cadr x) .4 .2))
|
[height (if (cadr x) .4 .2)]
|
||||||
(tex (if (caddr x) (if (car x) light-checker-tex dark-checker-tex) #f)))
|
[tex (if (caddr x)
|
||||||
|
(if (car x) light-checker-tex dark-checker-tex)
|
||||||
|
#f)])
|
||||||
(cons x (cons (make-piece-dl color height tex #f)
|
(cons x (cons (make-piece-dl color height tex #f)
|
||||||
(make-piece-dl color height tex #t)))))
|
(make-piece-dl color height tex #t)))))
|
||||||
'((#f #f #f)
|
'((#f #f #f)
|
||||||
|
@ -291,155 +286,142 @@
|
||||||
(#t #f #t)
|
(#t #f #t)
|
||||||
(#t #t #f)
|
(#t #t #f)
|
||||||
(#t #t #t))))
|
(#t #t #t))))
|
||||||
(define (get-checker-dl light? king? tex?)
|
(define (get-checker-dl light? king? tex?)
|
||||||
(cdr (assoc (list light? king? tex?) checkers)))
|
(cdr (assoc (list light? king? tex?) checkers)))
|
||||||
|
|
||||||
(define dark-square (cons (make-tex-square-dl dark-tex)
|
|
||||||
(make-square-dl dark-square-color)))
|
|
||||||
(define light-square (cons (make-tex-square-dl light-tex)
|
|
||||||
(make-square-dl light-square-color)))
|
|
||||||
(define (get-square-dl light? tex?)
|
|
||||||
(let ((getter (if tex? car cdr)))
|
|
||||||
(getter (if light? light-square dark-square))))
|
|
||||||
|
|
||||||
(define (show)
|
|
||||||
(send f show #t)))
|
|
||||||
|
|
||||||
(define-unit model@
|
|
||||||
(import view^)
|
|
||||||
(export model^)
|
|
||||||
|
|
||||||
(define turn 'red)
|
(define dark-square (cons (make-tex-square-dl dark-tex)
|
||||||
(define board (make-array (shape 0 8 0 8) #f))
|
(make-square-dl dark-square-color)))
|
||||||
|
(define light-square (cons (make-tex-square-dl light-tex)
|
||||||
(let loop ((i 0)
|
(make-square-dl light-square-color)))
|
||||||
(j 0))
|
(define (get-square-dl light? tex?)
|
||||||
(cond
|
(let ((getter (if tex? car cdr)))
|
||||||
((and (< j 8) (< i 8))
|
(getter (if light? light-square dark-square))))
|
||||||
(cond
|
|
||||||
((even? (+ i j))
|
(define (show) (send f show #t)))
|
||||||
(add-space (make-space-info j i #f))
|
|
||||||
(cond
|
(define-unit model@
|
||||||
((< i 3)
|
(import view^)
|
||||||
|
(export model^)
|
||||||
|
|
||||||
|
(define turn 'red)
|
||||||
|
(define board (make-array (shape 0 8 0 8) #f))
|
||||||
|
|
||||||
|
(let loop ([i 0] [j 0])
|
||||||
|
(cond
|
||||||
|
[(and (< j 8) (< i 8))
|
||||||
|
(cond
|
||||||
|
[(even? (+ i j))
|
||||||
|
(add-space (make-space-info j i #f))
|
||||||
|
(cond [(< i 3)
|
||||||
(array-set! board j i (cons 'red #f))
|
(array-set! board j i (cons 'red #f))
|
||||||
(add-piece (make-piece-info j i 'red #f)))
|
(add-piece (make-piece-info j i 'red #f))]
|
||||||
((> i 4)
|
[(> i 4)
|
||||||
(array-set! board j i (cons 'black #f))
|
(array-set! board j i (cons 'black #f))
|
||||||
(add-piece (make-piece-info j i 'black #f)))))
|
(add-piece (make-piece-info j i 'black #f))])]
|
||||||
(else
|
[else (add-space (make-space-info j i #t))])
|
||||||
(add-space (make-space-info j i #t))))
|
(loop i (add1 j))]
|
||||||
(loop i (add1 j)))
|
[(< i 8) (loop (add1 i) 0)]))
|
||||||
((< i 8) (loop (add1 i) 0))))
|
|
||||||
|
|
||||||
(define (other-color c)
|
(define (other-color c)
|
||||||
(cond
|
(if (eq? c 'red) 'black 'red))
|
||||||
((eq? c 'red) 'black)
|
|
||||||
(else 'red)))
|
|
||||||
|
|
||||||
(define (single-move-ok? direction from-x from-y to-x to-y)
|
|
||||||
(and (= to-y (+ direction from-y))
|
|
||||||
(= 1 (abs (- from-x to-x)))))
|
|
||||||
|
|
||||||
(define (can-move? direction from-x from-y)
|
(define (single-move-ok? direction from-x from-y to-x to-y)
|
||||||
(and (<= 0 (+ from-y direction) 7)
|
(and (= to-y (+ direction from-y))
|
||||||
(or (and (<= 0 (+ from-x 1) 7)
|
(= 1 (abs (- from-x to-x)))))
|
||||||
(not (array-ref board (+ from-x 1) (+ from-y direction))))
|
|
||||||
(and (<= 0 (+ from-x -1) 7)
|
|
||||||
(not (array-ref board (+ from-x -1) (+ from-y direction)))))))
|
|
||||||
|
|
||||||
(define (get-jumped-piece color direction from-x from-y to-x to-y)
|
|
||||||
(and (= to-y (+ direction direction from-y))
|
|
||||||
(= 2 (abs (- from-x to-x)))
|
|
||||||
(let* ((jumped-x (+ from-x (/ (- to-x from-x) 2)))
|
|
||||||
(jumped-y (+ from-y direction))
|
|
||||||
(jumped-piece (array-ref board jumped-x jumped-y)))
|
|
||||||
(and jumped-piece
|
|
||||||
(eq? (other-color color) (car jumped-piece))
|
|
||||||
(make-piece-info jumped-x jumped-y (car jumped-piece) (cdr jumped-piece))))))
|
|
||||||
|
|
||||||
(define (can-jump? direction from-color from-x from-y)
|
|
||||||
(let ((to-y (+ direction direction from-y))
|
|
||||||
(to-x1 (+ from-x 2))
|
|
||||||
(to-x2 (- from-x 2)))
|
|
||||||
(and (<= 0 to-y 7)
|
|
||||||
(or (and (<= 0 to-x1 7)
|
|
||||||
(not (array-ref board to-x1 to-y))
|
|
||||||
(get-jumped-piece from-color direction
|
|
||||||
from-x from-y
|
|
||||||
to-x1 to-y))
|
|
||||||
(and (<= 0 to-x2)
|
|
||||||
(not (array-ref board to-x2 to-y))
|
|
||||||
(get-jumped-piece from-color direction
|
|
||||||
from-x from-y
|
|
||||||
to-x2 to-y))))))
|
|
||||||
|
|
||||||
|
|
||||||
(define (fold-board f v)
|
(define (can-move? direction from-x from-y)
|
||||||
(let iloop ([i 0][v v])
|
(and (<= 0 (+ from-y direction) 7)
|
||||||
(if (= i 8)
|
(or (and (<= 0 (+ from-x 1) 7)
|
||||||
v
|
(not (array-ref board (+ from-x 1) (+ from-y direction))))
|
||||||
(let jloop ([j 0][v v])
|
(and (<= 0 (+ from-x -1) 7)
|
||||||
(if (= j 8)
|
(not (array-ref board (+ from-x -1) (+ from-y direction)))))))
|
||||||
(iloop (add1 i) v)
|
|
||||||
(jloop (add1 j)
|
|
||||||
(if (even? (+ i j))
|
|
||||||
(f i j v)
|
|
||||||
v)))))))
|
|
||||||
|
|
||||||
(define (get-jump-moves)
|
(define (get-jumped-piece color direction from-x from-y to-x to-y)
|
||||||
(let ([direction (if (eq? turn 'red) 1 -1)])
|
(and (= to-y (+ direction direction from-y))
|
||||||
(fold-board
|
(= 2 (abs (- from-x to-x)))
|
||||||
(lambda (i j l)
|
(let* ([jumped-x (+ from-x (/ (- to-x from-x) 2))]
|
||||||
(let ([p (array-ref board i j)])
|
[jumped-y (+ from-y direction)]
|
||||||
(if (and p
|
[jumped-piece (array-ref board jumped-x jumped-y)])
|
||||||
(eq? (car p) turn)
|
(and jumped-piece
|
||||||
(or (can-jump? direction turn i j)
|
(eq? (other-color color) (car jumped-piece))
|
||||||
(and (cdr p)
|
(make-piece-info jumped-x jumped-y
|
||||||
(can-jump? (- direction) turn i j))))
|
(car jumped-piece) (cdr jumped-piece))))))
|
||||||
(cons (make-piece-info i j turn (cdr p)) l)
|
|
||||||
l)))
|
|
||||||
null)))
|
|
||||||
|
|
||||||
(define (get-moves)
|
(define (can-jump? direction from-color from-x from-y)
|
||||||
(let ([jumps (get-jump-moves)])
|
(let ([to-y (+ direction direction from-y)]
|
||||||
(if (pair? jumps)
|
[to-x1 (+ from-x 2)]
|
||||||
(make-moves jumps #t)
|
[to-x2 (- from-x 2)])
|
||||||
(make-moves
|
(and (<= 0 to-y 7)
|
||||||
(let ([direction (if (eq? turn 'red) 1 -1)])
|
(or (and (<= 0 to-x1 7)
|
||||||
(fold-board
|
(not (array-ref board to-x1 to-y))
|
||||||
(lambda (i j l)
|
(get-jumped-piece from-color direction
|
||||||
(let ([p (array-ref board i j)])
|
from-x from-y
|
||||||
(if (and p
|
to-x1 to-y))
|
||||||
(eq? (car p) turn)
|
(and (<= 0 to-x2)
|
||||||
(or (can-move? direction i j)
|
(not (array-ref board to-x2 to-y))
|
||||||
(and (cdr p)
|
(get-jumped-piece from-color direction
|
||||||
(can-move? (- direction) i j))))
|
from-x from-y
|
||||||
(cons (make-piece-info i j turn (cdr p)) l)
|
to-x2 to-y))))))
|
||||||
l)))
|
|
||||||
null))
|
|
||||||
#f))))
|
|
||||||
|
|
||||||
(define (move from to)
|
(define (fold-board f v)
|
||||||
(let* ((to-x (inexact->exact (floor (gl-vector-ref to 0))))
|
(let iloop ([i 0] [v v])
|
||||||
(to-y (inexact->exact (floor (gl-vector-ref to 1))))
|
(if (= i 8)
|
||||||
(from-x (piece-info-x from))
|
v
|
||||||
(from-y (piece-info-y from))
|
(let jloop ([j 0] [v v])
|
||||||
(from-color (piece-info-color from))
|
(if (= j 8)
|
||||||
(from-king? (piece-info-king? from))
|
(iloop (add1 i) v)
|
||||||
(to-king? (or from-king?
|
(jloop (add1 j) (if (even? (+ i j)) (f i j v) v)))))))
|
||||||
(if (eq? 'red from-color)
|
|
||||||
(= to-y 7)
|
(define (get-jump-moves)
|
||||||
(= to-y 0))))
|
(let ([direction (if (eq? turn 'red) 1 -1)])
|
||||||
(direction (if (eq? turn 'red) 1 -1)))
|
(fold-board
|
||||||
(when (and (eq? turn from-color)
|
(lambda (i j l)
|
||||||
(<= 0 to-x 7)
|
(let ([p (array-ref board i j)])
|
||||||
(<= 0 to-y 7)
|
(if (and p
|
||||||
(not (array-ref board to-x to-y)))
|
(eq? (car p) turn)
|
||||||
(cond
|
(or (can-jump? direction turn i j)
|
||||||
((and (null? (get-jump-moves))
|
(and (cdr p)
|
||||||
|
(can-jump? (- direction) turn i j))))
|
||||||
|
(cons (make-piece-info i j turn (cdr p)) l)
|
||||||
|
l)))
|
||||||
|
null)))
|
||||||
|
|
||||||
|
(define (get-moves)
|
||||||
|
(let ([jumps (get-jump-moves)])
|
||||||
|
(if (pair? jumps)
|
||||||
|
(make-moves jumps #t)
|
||||||
|
(make-moves
|
||||||
|
(let ([direction (if (eq? turn 'red) 1 -1)])
|
||||||
|
(fold-board
|
||||||
|
(lambda (i j l)
|
||||||
|
(let ([p (array-ref board i j)])
|
||||||
|
(if (and p
|
||||||
|
(eq? (car p) turn)
|
||||||
|
(or (can-move? direction i j)
|
||||||
|
(and (cdr p) (can-move? (- direction) i j))))
|
||||||
|
(cons (make-piece-info i j turn (cdr p)) l)
|
||||||
|
l)))
|
||||||
|
null))
|
||||||
|
#f))))
|
||||||
|
|
||||||
|
(define (move from to)
|
||||||
|
(let* ([to-x (inexact->exact (floor (gl-vector-ref to 0)))]
|
||||||
|
[to-y (inexact->exact (floor (gl-vector-ref to 1)))]
|
||||||
|
[from-x (piece-info-x from)]
|
||||||
|
[from-y (piece-info-y from)]
|
||||||
|
[from-color (piece-info-color from)]
|
||||||
|
[from-king? (piece-info-king? from)]
|
||||||
|
[to-king? (or from-king? (= to-y (if (eq? 'red from-color) 7 0)))]
|
||||||
|
[direction (if (eq? turn 'red) 1 -1)])
|
||||||
|
(when (and (eq? turn from-color)
|
||||||
|
(<= 0 to-x 7)
|
||||||
|
(<= 0 to-y 7)
|
||||||
|
(not (array-ref board to-x to-y)))
|
||||||
|
(cond [(and (null? (get-jump-moves))
|
||||||
(or (single-move-ok? direction from-x from-y to-x to-y)
|
(or (single-move-ok? direction from-x from-y to-x to-y)
|
||||||
(and from-king?
|
(and from-king?
|
||||||
(single-move-ok? (- direction) from-x from-y to-x to-y))))
|
(single-move-ok? (- direction) from-x from-y
|
||||||
|
to-x to-y))))
|
||||||
(move-piece from to-x to-y)
|
(move-piece from to-x to-y)
|
||||||
(set! turn (other-color from-color))
|
(set! turn (other-color from-color))
|
||||||
(array-set! board to-x to-y (cons from-color to-king?))
|
(array-set! board to-x to-y (cons from-color to-king?))
|
||||||
|
@ -447,8 +429,9 @@
|
||||||
(when (and to-king? (not from-king?))
|
(when (and to-king? (not from-king?))
|
||||||
(remove-piece (make-piece-info to-x to-y from-color from-king?))
|
(remove-piece (make-piece-info to-x to-y from-color from-king?))
|
||||||
(add-piece (make-piece-info to-x to-y from-color to-king?)))
|
(add-piece (make-piece-info to-x to-y from-color to-king?)))
|
||||||
(set-turn turn (get-moves)))
|
(set-turn turn (get-moves))]
|
||||||
((or (get-jumped-piece from-color direction from-x from-y to-x to-y)
|
[(or (get-jumped-piece from-color direction from-x from-y
|
||||||
|
to-x to-y)
|
||||||
(and from-king?
|
(and from-king?
|
||||||
(get-jumped-piece from-color (- direction) from-x from-y to-x to-y)))
|
(get-jumped-piece from-color (- direction) from-x from-y to-x to-y)))
|
||||||
=>
|
=>
|
||||||
|
@ -462,25 +445,24 @@
|
||||||
(remove-piece (make-piece-info to-x to-y from-color from-king?))
|
(remove-piece (make-piece-info to-x to-y from-color from-king?))
|
||||||
(add-piece (make-piece-info to-x to-y from-color to-king?)))
|
(add-piece (make-piece-info to-x to-y from-color to-king?)))
|
||||||
(cond
|
(cond
|
||||||
((or (can-jump? direction from-color to-x to-y)
|
[(or (can-jump? direction from-color to-x to-y)
|
||||||
(and from-king?
|
(and from-king?
|
||||||
(can-jump? (- direction) from-color to-x to-y)))
|
(can-jump? (- direction) from-color to-x to-y)))
|
||||||
(set-turn turn (make-moves (list (make-piece-info to-x to-y from-color to-king?)) #t)))
|
(set-turn turn
|
||||||
(else
|
(make-moves (list (make-piece-info
|
||||||
|
to-x to-y from-color to-king?))
|
||||||
|
#t))]
|
||||||
|
[else
|
||||||
(set! turn (other-color from-color))
|
(set! turn (other-color from-color))
|
||||||
(set-turn turn (get-moves))))))))))
|
(set-turn turn (get-moves))]))]))))
|
||||||
|
|
||||||
(set-turn turn (get-moves))
|
(set-turn turn (get-moves))
|
||||||
)
|
|
||||||
|
|
||||||
(define-unit show@
|
|
||||||
(import view^)
|
|
||||||
(export)
|
|
||||||
(show))
|
|
||||||
|
|
||||||
(define game@
|
|
||||||
(compound-unit/infer
|
|
||||||
(import)
|
|
||||||
(export)
|
|
||||||
(link view@ model@ show@)))
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
(define-unit show@
|
||||||
|
(import view^)
|
||||||
|
(export)
|
||||||
|
(show))
|
||||||
|
|
||||||
|
(define game@
|
||||||
|
(compound-unit/infer (import) (export) (link view@ model@ show@)))
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,24 +1,24 @@
|
||||||
** To play _Crazy 8s_, run the "Games" application. **
|
** To play Crazy 8s, run the "PLT Games" application.
|
||||||
|
|
||||||
Try to get rid of all you cards by matching the value or suit of the
|
Try to get rid of all you cards by matching the value or suit of the
|
||||||
top card in the discard pile. In the default mode, click a card to
|
top card in the discard pile. In the default mode, click a card to
|
||||||
discard it; you can adjust the options so that you discard by dragging
|
discard it; you can adjust the options so that you discard by dragging
|
||||||
a card from your hand to the discard pile.
|
a card from your hand to the discard pile.
|
||||||
|
|
||||||
An 8 can be discarded at any time, and in that case, the player who
|
An 8 can be discarded at any time, and in that case, the player who
|
||||||
discarded the 8 gets to pick any suit for it (hence the craziness of
|
discarded the 8 gets to pick any suit for it (hence the craziness of
|
||||||
8s). When you discard an 8, a panel of buttons appears to the right of
|
8s). When you discard an 8, a panel of buttons appears to the right
|
||||||
the discard pile, so you can pick the suit.
|
of the discard pile, so you can pick the suit.
|
||||||
|
|
||||||
A player can choose to draw a card instead of discarding, as long as
|
A player can choose to draw a card instead of discarding, as long as
|
||||||
cards are left in the draw pile. A player's turn continues after
|
cards are left in the draw pile. A player's turn continues after
|
||||||
drawing, so a player can continue drawing to find something to
|
drawing, so a player can continue drawing to find something to
|
||||||
discard. In the default mode, click the face-down draw pile in the
|
discard. In the default mode, click the face-down draw pile in the
|
||||||
middle of the table; you can adjust the options to that you draw by
|
middle of the table; you can adjust the options to that you draw by
|
||||||
dragging it from the draw pile to your hand.
|
dragging it from the draw pile to your hand.
|
||||||
|
|
||||||
If no cards are left in the deck, a player may pass instead of
|
If no cards are left in the deck, a player may pass instead of
|
||||||
discarding. To pass, click the "Pass" button.
|
discarding. To pass, click the "Pass" button.
|
||||||
|
|
||||||
The status line at the bottom of the window provides instructions as
|
The status line at the bottom of the window provides instructions as
|
||||||
you go.
|
you go.
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
_doors.ss_
|
_doors.ss_
|
||||||
|
|
||||||
The "doors.ss" library builds on "gl-board.ss" to support simple
|
The "doors.ss" library builds on "gl-board.ss" to support simple
|
||||||
|
|
|
@ -1,114 +1,97 @@
|
||||||
(module games mzscheme
|
#lang scheme/gui
|
||||||
(require (lib "mred.ss" "mred")
|
|
||||||
(lib "class.ss")
|
|
||||||
(lib "unit.ss")
|
|
||||||
(lib "list.ss")
|
|
||||||
(lib "getinfo.ss" "setup")
|
|
||||||
(lib "bitmap-label.ss" "mrlib")
|
|
||||||
"show-help.ss")
|
|
||||||
|
|
||||||
(define game-mapping
|
(require setup/getinfo mrlib/bitmap-label "show-help.ss")
|
||||||
(let ([games (let ([d (collection-path "games")])
|
|
||||||
(filter (lambda (f)
|
|
||||||
(let ([p (build-path d f)])
|
|
||||||
(and (directory-exists? p)
|
|
||||||
(with-handlers ([exn:fail? (lambda (x) #f)])
|
|
||||||
((get-info (list (string->path "games") f)) 'game (lambda () #f))))))
|
|
||||||
(directory-list d)))])
|
|
||||||
(map (lambda (g)
|
|
||||||
(let ([info (get-info `(,(string->path "games") ,g))])
|
|
||||||
(list (path->string g)
|
|
||||||
(info 'game (lambda () "wrong.ss"))
|
|
||||||
(info 'name (lambda () g))
|
|
||||||
(info 'game-set (lambda () "Other Games"))
|
|
||||||
(info 'game-icon (lambda () (build-path (collection-path "games" g)
|
|
||||||
(format "~a.png" g)))))))
|
|
||||||
games)))
|
|
||||||
|
|
||||||
(define f (new (class frame%
|
|
||||||
(augment*
|
|
||||||
[on-close (lambda () (exit))])
|
|
||||||
(super-new))
|
|
||||||
[label "PLT Games"]
|
|
||||||
[style '(metal no-resize-border)]))
|
|
||||||
(define hp (make-object horizontal-panel% f))
|
|
||||||
(define main (make-object vertical-panel% hp))
|
|
||||||
(send f set-alignment 'left 'top)
|
|
||||||
(send f stretchable-width #f)
|
|
||||||
(send f stretchable-height #f)
|
|
||||||
|
|
||||||
(define main-horizontal-panel (make-object horizontal-panel% main))
|
(define-struct game (file name set icon))
|
||||||
|
|
||||||
(define (game-button p desc)
|
(define gamedir (collection-path "games"))
|
||||||
(let* ([collect (car desc)]
|
|
||||||
[file (cadr desc)]
|
|
||||||
[name (caddr desc)]
|
|
||||||
[dir (with-handlers ([void (lambda (x) #f)])
|
|
||||||
(collection-path "games" collect))])
|
|
||||||
(when dir
|
|
||||||
(make-object button%
|
|
||||||
((bitmap-label-maker name (list-ref desc 4))
|
|
||||||
p)
|
|
||||||
p
|
|
||||||
(lambda (b e)
|
|
||||||
(let ([game@ (dynamic-wind
|
|
||||||
begin-busy-cursor
|
|
||||||
(lambda () (dynamic-require (build-path dir file) 'game@))
|
|
||||||
end-busy-cursor)])
|
|
||||||
(let ([c (make-custodian)])
|
|
||||||
(parameterize ([current-custodian c])
|
|
||||||
(parameterize ([current-eventspace (make-eventspace)])
|
|
||||||
(queue-callback
|
|
||||||
(lambda ()
|
|
||||||
(exit-handler (lambda (v)
|
|
||||||
(custodian-shutdown-all c)))
|
|
||||||
(invoke-unit game@))))))))))))
|
|
||||||
|
|
||||||
(let ([game-mapping (sort game-mapping
|
|
||||||
(lambda (a b)
|
|
||||||
(string<? (list-ref a 3) (list-ref b 3))))])
|
|
||||||
(let loop ([l game-mapping])
|
|
||||||
(unless (null? l)
|
|
||||||
(let* ([set (list-ref (car l) 3)]
|
|
||||||
[p (new group-box-panel%
|
|
||||||
[label set]
|
|
||||||
[parent main-horizontal-panel])])
|
|
||||||
(let xloop ([here (list (car l))]
|
|
||||||
[l (cdr l)])
|
|
||||||
(if (and (pair? l)
|
|
||||||
(string=? set (list-ref (car l) 3)))
|
|
||||||
(xloop (cons (car l) here) (cdr l))
|
|
||||||
(begin
|
|
||||||
(for-each (lambda (g) (game-button p g)) here)
|
|
||||||
(loop l))))))))
|
|
||||||
|
|
||||||
(for-each (lambda (p)
|
(define (get-game game)
|
||||||
(let ([pred (lambda (x y) (<= (send x min-width) (send y min-width)))])
|
(let* ([game (path-element->string game)]
|
||||||
(send p change-children (lambda (l) (sort l pred)))))
|
[info (with-handlers ([exn:fail? (lambda (x) #f)])
|
||||||
(send main-horizontal-panel get-children))
|
(get-info (list "games" game)))]
|
||||||
|
[main (and info (info 'game (lambda () #f)))]
|
||||||
(send main-horizontal-panel change-children
|
[gamefile (lambda (f) (build-path gamedir game f))])
|
||||||
(lambda (l)
|
(and main
|
||||||
(sort l (lambda (x y)
|
(make-game
|
||||||
(let ([l1 (length (send x get-children))]
|
(gamefile main)
|
||||||
[l2 (length (send y get-children))])
|
(info 'name (lambda () (string-titlecase game)))
|
||||||
(cond [(> l1 l2) #t]
|
(info 'game-set (lambda () "Other Games"))
|
||||||
[(= l1 l2) (string-ci<? (send x get-label)
|
(info 'game-icon (lambda () (gamefile (format "~a.png" game))))))))
|
||||||
(send y get-label))]
|
|
||||||
[else #f]))))))
|
|
||||||
|
|
||||||
(define show-games-help
|
(define (run-game game)
|
||||||
(show-help '("games") "About PLT Games"))
|
(define c (make-custodian))
|
||||||
|
(define run
|
||||||
|
(dynamic-wind
|
||||||
|
begin-busy-cursor
|
||||||
|
(lambda ()
|
||||||
|
(with-handlers ([exn? (lambda (e) (lambda () (raise e)))])
|
||||||
|
(let ([u (dynamic-require (game-file game) 'game@)])
|
||||||
|
(lambda () (invoke-unit u)))))
|
||||||
|
end-busy-cursor))
|
||||||
|
(parameterize* ([current-custodian c]
|
||||||
|
[current-namespace (make-gui-empty-namespace)]
|
||||||
|
[current-eventspace (make-eventspace)])
|
||||||
|
(queue-callback
|
||||||
|
(lambda ()
|
||||||
|
(exit-handler (lambda (v) (custodian-shutdown-all c)))
|
||||||
|
(with-handlers ([exn? (lambda (e)
|
||||||
|
(message-box (format "Error in \"~a\""
|
||||||
|
(game-name game))
|
||||||
|
(exn-message e) f '(ok)))])
|
||||||
|
(run))))))
|
||||||
|
|
||||||
(application-about-handler show-games-help)
|
(define games
|
||||||
(application-preferences-handler (lambda ()
|
(filter values (map get-game (directory-list gamedir))))
|
||||||
(message-box
|
|
||||||
"Oops"
|
|
||||||
"There aren't actually any preferences. This is just a test for Mac OS X"
|
|
||||||
f
|
|
||||||
'(ok))))
|
|
||||||
|
|
||||||
(send f show #t))
|
|
||||||
|
|
||||||
|
(define game-sets
|
||||||
|
(let ([ht (make-hash-table 'equal)])
|
||||||
|
(for ([g games])
|
||||||
|
(let ([set (game-set g)])
|
||||||
|
(hash-table-put! ht set (cons g (hash-table-get ht set '())))))
|
||||||
|
(sort (hash-table-map ht cons)
|
||||||
|
(lambda (x y)
|
||||||
|
(let ([xlen (length x)] [ylen (length y)])
|
||||||
|
(cond [(> xlen ylen) #t]
|
||||||
|
[(< xlen ylen) #f]
|
||||||
|
[else (string<? (car x) (car y))]))))))
|
||||||
|
|
||||||
|
(define f (new (class frame%
|
||||||
|
(augment* [on-close (lambda () (exit))])
|
||||||
|
(super-new))
|
||||||
|
[label "PLT Games"]
|
||||||
|
[style '(metal no-resize-border)]))
|
||||||
|
(define main (make-object horizontal-panel% f))
|
||||||
|
(send f set-alignment 'left 'top)
|
||||||
|
(send f stretchable-width #f)
|
||||||
|
(send f stretchable-height #f)
|
||||||
|
|
||||||
|
(for ([set game-sets])
|
||||||
|
(define set-name (car set))
|
||||||
|
(define games (cdr set))
|
||||||
|
(define panel
|
||||||
|
(new group-box-panel% [label set-name] [parent main]))
|
||||||
|
(define buttons
|
||||||
|
(map (lambda (game)
|
||||||
|
(new button%
|
||||||
|
[label ((bitmap-label-maker (game-name game) (game-icon game))
|
||||||
|
panel)]
|
||||||
|
[parent panel]
|
||||||
|
[callback (lambda _ (run-game game))]))
|
||||||
|
games))
|
||||||
|
(define sorted
|
||||||
|
(sort buttons (lambda (x y) (< (send x min-width) (send y min-width)))))
|
||||||
|
(send panel change-children (lambda (l) sorted)))
|
||||||
|
|
||||||
|
(define show-games-help (show-help '("games") "About PLT Games"))
|
||||||
|
|
||||||
|
(application-about-handler show-games-help)
|
||||||
|
(application-preferences-handler
|
||||||
|
(lambda ()
|
||||||
|
(message-box
|
||||||
|
"Oops"
|
||||||
|
"There aren't actually any preferences. This is just a test for Mac OS X"
|
||||||
|
f
|
||||||
|
'(ok))))
|
||||||
|
|
||||||
|
(send f show #t)
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
_GCalc_ a system for visually demonstrating the Lambda Calculus.
|
GCalc is a system for visually demonstrating the Lambda Calculus.
|
||||||
|
(Not really a game...)
|
||||||
|
|
||||||
See the following for the principles:
|
See the following for the principles:
|
||||||
http://www.grame.fr/Research/GCalcul/Graphic_Calculus.html
|
http://www.grame.fr/Research/GCalcul/Graphic_Calculus.html
|
||||||
|
@ -9,30 +10,30 @@ The window layout
|
||||||
-----------------
|
-----------------
|
||||||
|
|
||||||
The window is divided into three working areas, each made of cells.
|
The window is divided into three working areas, each made of cells.
|
||||||
Cells hold cube objects, which can be dragged between cells (with a few
|
Cells hold cube objects, which can be dragged between cells (with a
|
||||||
exceptions that are listed below). The working areas are:
|
few exceptions that are listed below). The working areas are:
|
||||||
|
|
||||||
1. The right side is the storage area.
|
1. The right side is the storage area. This is used for saving
|
||||||
This is used for saving objects -- drag any cube to/from here. Note
|
objects -- drag any cube to/from here. Note that cubes can be
|
||||||
that cubes can be named for convenience.
|
named for convenience.
|
||||||
|
|
||||||
2. The left side is a panel of basic color cubes.
|
2. The left side is a panel of basic color cubes. These cells always
|
||||||
These cells always contain a set of basic cubes that are used as the
|
contain a set of basic cubes that are used as the primitive
|
||||||
primitive building blocks all other values are made of. They cannot
|
building blocks all other values are made of. They cannot be
|
||||||
be overwritten. (Note that this includes a transparent cell.)
|
overwritten. (Note that this includes a transparent cell.)
|
||||||
|
|
||||||
3. The center part is the working panel.
|
3. The center part is the working panel. This is the main panel where
|
||||||
This is the main panel where new cubes are constructed. The center
|
new cubes are constructed. The center cell is similar to a storage
|
||||||
cell is similar to a storage cell, and the surrounding eight cells
|
cell, and the surrounding eight cells all perform some operation on
|
||||||
all perform some operation on this cell.
|
this cell.
|
||||||
|
|
||||||
|
|
||||||
User Interaction
|
User Interaction
|
||||||
----------------
|
----------------
|
||||||
|
|
||||||
Right-click any cell except for the basic colors on the left panel, or
|
Right-click any cell except for the basic colors on the left panel, or
|
||||||
hit escape or F10 for a menu of operations. The menu also includes the
|
hit escape or F10 for a menu of operations. The menu also includes
|
||||||
keyboard shortcuts for these operations.
|
the keyboard shortcuts for these operations.
|
||||||
|
|
||||||
|
|
||||||
Cube operations
|
Cube operations
|
||||||
|
@ -41,38 +42,38 @@ Cube operations
|
||||||
There are six simple operations that are considered part of the simple
|
There are six simple operations that are considered part of the simple
|
||||||
graphic cube world. The operations correspond to six of the operation
|
graphic cube world. The operations correspond to six of the operation
|
||||||
cells: a left-right composition is built using the left and the right
|
cells: a left-right composition is built using the left and the right
|
||||||
cells, a top-bottom using the top and the bottom, and a front-back using
|
cells, a top-bottom using the top and the bottom, and a front-back
|
||||||
the top-left and bottom-right. Dragging a cube to one of these cells
|
using the top-left and bottom-right. Dragging a cube to one of these
|
||||||
will use the corresponding operator to combine it with the main cell's
|
cells will use the corresponding operator to combine it with the main
|
||||||
cube. Using a right mouse click on one of these cells can be used to
|
cell's cube. Using a right mouse click on one of these cells can be
|
||||||
cancel dragging an object to that cell, this is not really an undo
|
used to cancel dragging an object to that cell, this is not really an
|
||||||
feature: a right-click on the right cell always splits the main cube to
|
undo feature: a right-click on the right cell always splits the main
|
||||||
two halves and throws the right side.
|
cube to two halves and throws the right side.
|
||||||
|
|
||||||
The colored cubes and the six basic operators make this simple domain,
|
The colored cubes and the six basic operators make this simple domain,
|
||||||
which is extended to form a Lambda-Calculus-like language by adding
|
which is extended to form a Lambda-Calculus-like language by adding
|
||||||
abstractions and applications. Right-clicking on a basic cube on the
|
abstractions and applications. Right-clicking on a basic cube on the
|
||||||
left panel creates an abstraction which is actually a lambda expression
|
left panel creates an abstraction which is actually a lambda
|
||||||
except that colors are used instead of syntactic variables. For
|
expression except that colors are used instead of syntactic variables.
|
||||||
example, if the main cell contains `R|G' (red-green on the left and
|
For example, if the main cell contains `R|G' (red-green on the left
|
||||||
right), then right-clicking the green cube on the left panel leaves us
|
and right), then right-clicking the green cube on the left panel
|
||||||
with `lambda G . R|G', which is visualized as `R|G' with a green circle.
|
leaves us with `lambda G . R|G', which is visualized as `R|G' with a
|
||||||
The last two operator cells are used for application of these
|
green circle. The last two operator cells are used for application of
|
||||||
abstractions: drag a function to the top-right to have it applied on the
|
these abstractions: drag a function to the top-right to have it
|
||||||
main cube, or to the bottom-left to have the main cube applied to it.
|
applied on the main cube, or to the bottom-left to have the main cube
|
||||||
As in the Lambda Calculus, all abstractions have exactly one variable,
|
applied to it. As in the Lambda Calculus, all abstractions have
|
||||||
use currying for multiple variables.
|
exactly one variable, use currying for multiple variables.
|
||||||
|
|
||||||
So far the result is a domain of colored cubes that can be used in the
|
So far the result is a domain of colored cubes that can be used in the
|
||||||
same way as the simple Lambda Calculus. There is one last extension
|
same way as the simple Lambda Calculus. There is one last extension
|
||||||
that goes one step further: function cubes can themselves be combined
|
that goes one step further: function cubes can themselves be combined
|
||||||
with other functions using the simple operations. This results in
|
with other functions using the simple operations. This results in a
|
||||||
a form of "spatial functions" that behave differently in different parts
|
form of "spatial functions" that behave differently in different parts
|
||||||
of the cube according to the construction. For example, a left-right
|
of the cube according to the construction. For example, a left-right
|
||||||
construction of two functions `f|g' operates on a given cube by applying
|
construction of two functions `f|g' operates on a given cube by
|
||||||
`f' on its left part and `g' on its right part. You can use the
|
applying `f' on its left part and `g' on its right part. You can use
|
||||||
preferences dialog to change a few aspects of the computation.
|
the preferences dialog to change a few aspects of the computation.
|
||||||
|
|
||||||
Use the "Open Example" menu entry to open a sample file that contains
|
Use the "Open Example" menu entry to open a sample file that contains
|
||||||
lots of useful objects (Church numerals, booleans, lists, Y-combinator,
|
lots of useful objects (Church numerals, booleans, lists,
|
||||||
etc).
|
Y-combinator, etc).
|
||||||
|
|
|
@ -3,24 +3,23 @@
|
||||||
;;; based on http://www.grame.fr/Research/GCalcul/Graphic_Calculus.html
|
;;; based on http://www.grame.fr/Research/GCalcul/Graphic_Calculus.html
|
||||||
;;; implemented by Eli Barzilay: Maze is Life! (eli@barzilay.org)
|
;;; implemented by Eli Barzilay: Maze is Life! (eli@barzilay.org)
|
||||||
|
|
||||||
(module gcalc mzscheme
|
#lang mzscheme
|
||||||
(require (lib "class.ss") (lib "mred.ss" "mred") (lib "etc.ss")
|
|
||||||
"../show-help.ss" (lib "unit.ss"))
|
|
||||||
(provide game@)
|
|
||||||
|
|
||||||
(define customs '())
|
(require (lib "class.ss") (lib "mred.ss" "mred") (lib "etc.ss")
|
||||||
(define (add-custom! name get set type desc)
|
"../show-help.ss" (lib "unit.ss"))
|
||||||
(set! customs
|
(provide game@)
|
||||||
(append customs (list (make-custom name get set type desc)))))
|
d
|
||||||
(define-struct custom (name getter setter type description))
|
(define customs '())
|
||||||
(define-syntax defcustom
|
(define (add-custom! name get set type desc)
|
||||||
(syntax-rules ()
|
(set! customs (append customs (list (make-custom name get set type desc)))))
|
||||||
[(_ var default type description)
|
(define-struct custom (name getter setter type description))
|
||||||
(begin (define var default)
|
(define-syntax defcustom
|
||||||
(add-custom! 'var (lambda () var) (lambda (v) (set! var v))
|
(syntax-rules ()
|
||||||
type description))]))
|
[(_ var default type description)
|
||||||
(define game@
|
(begin (define var default)
|
||||||
(unit (import) (export)
|
(add-custom! 'var (lambda () var) (lambda (v) (set! var v))
|
||||||
|
type description))]))
|
||||||
|
(define game@ (unit (import) (export)
|
||||||
|
|
||||||
;;;============================================================================
|
;;;============================================================================
|
||||||
;;; Customizations etc
|
;;; Customizations etc
|
||||||
|
@ -1025,4 +1024,4 @@
|
||||||
;; start the whole thing
|
;; start the whole thing
|
||||||
(send gcalc-frame show #t)
|
(send gcalc-frame show #t)
|
||||||
|
|
||||||
)))
|
))
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
** To play _Rummy_, run the "Games" application. **
|
** To play Rummy, run the "PLT Games" application.
|
||||||
|
|
||||||
This is a simple variant of Rummy.
|
This is a simple variant of Rummy.
|
||||||
|
|
||||||
Put all cards in your hand into straights (3 or more cards) and 3- or
|
Put all cards in your hand into straights (3 or more cards) and 3- or
|
||||||
4-of-a-kind sets to win. Each card counts for only one set. Aces can
|
4-of-a-kind sets to win. Each card counts for only one set. Aces can
|
||||||
be used in both A-2-3 sequences and Q-K-A sequences.
|
be used in both A-2-3 sequences and Q-K-A sequences.
|
||||||
|
|
||||||
When all of your cards fit into sets (the game detects this
|
When all of your cards fit into sets (the game detects this
|
||||||
|
@ -15,4 +15,4 @@ one of your own cards (by dragging from your hand to the discard
|
||||||
pile).
|
pile).
|
||||||
|
|
||||||
The status line at the bottom of the window provides instructions as
|
The status line at the bottom of the window provides instructions as
|
||||||
you go. The computer player is fairly smart.
|
you go. The computer player is fairly smart.
|
||||||
|
|
|
@ -1,513 +1,485 @@
|
||||||
|
#lang mzscheme
|
||||||
|
(require (lib "cards.ss" "games" "cards")
|
||||||
|
(lib "mred.ss" "mred")
|
||||||
|
(lib "class.ss")
|
||||||
|
(lib "unit.ss")
|
||||||
|
(lib "list.ss"))
|
||||||
|
|
||||||
|
(provide game@)
|
||||||
|
(define game@ (unit (import) (export)
|
||||||
|
|
||||||
|
;; Initial card count
|
||||||
|
(define DEAL-COUNT 10)
|
||||||
|
|
||||||
|
;; Messages
|
||||||
|
(define YOUR-TURN-MESSAGE "Your turn. (Draw a card or pickup a discard.)")
|
||||||
|
(define DISCARD-MESSAGE "Drag a card from your hand to discard.")
|
||||||
|
(define GAME-OVER-MESSAGE "GAME OVER")
|
||||||
|
|
||||||
|
;; Area labels
|
||||||
|
(define YOU-NAME "You")
|
||||||
|
(define MACHINE-NAME "Opponent")
|
||||||
|
|
||||||
|
;; Region layout constants
|
||||||
|
(define MARGIN 5)
|
||||||
|
(define LABEL-H 15)
|
||||||
|
|
||||||
|
;; Randomize
|
||||||
|
(random-seed (modulo (current-milliseconds) 10000))
|
||||||
|
|
||||||
|
;; Set up the table
|
||||||
|
(define t (make-table "Rummy" 8 4.5))
|
||||||
|
(define status-pane (send t create-status-pane))
|
||||||
|
(send t add-help-button status-pane '("games" "ginrummy") "Rummy Help" #f)
|
||||||
|
(send t show #t)
|
||||||
|
(send t set-double-click-action #f)
|
||||||
|
(send t set-button-action 'left 'drag-raise/one)
|
||||||
|
(send t set-button-action 'middle 'drag/one)
|
||||||
|
(send t set-button-action 'right 'drag/above)
|
||||||
|
|
||||||
|
;; Get table width & height
|
||||||
|
(define w (send t table-width))
|
||||||
|
(define h (send t table-height))
|
||||||
|
|
||||||
|
;; Set up the cards
|
||||||
|
(define deck (shuffle-list (make-deck) 7))
|
||||||
|
(for-each (lambda (card)
|
||||||
|
(send card user-can-move #f)
|
||||||
|
(send card user-can-flip #f))
|
||||||
|
deck)
|
||||||
|
|
||||||
|
;; Function for dealing or drawing cards
|
||||||
|
(define (deal n)
|
||||||
|
(let loop ([n n][d deck])
|
||||||
|
(if (zero? n)
|
||||||
|
(begin (set! deck d) null)
|
||||||
|
(cons (car d) (loop (sub1 n) (cdr d))))))
|
||||||
|
|
||||||
|
;; Card width & height
|
||||||
|
(define cw (send (car deck) card-width))
|
||||||
|
(define ch (send (car deck) card-height))
|
||||||
|
|
||||||
|
;; Draw and discard pile locations
|
||||||
|
(define draw-x (/ (- w (* 3 cw)) 2))
|
||||||
|
(define draw-y (/ (- h ch) 2))
|
||||||
|
(define discard-x (+ draw-x (* 2 cw)))
|
||||||
|
(define discard-y draw-y)
|
||||||
|
|
||||||
|
;; Put the cards on the table
|
||||||
|
(send t add-cards deck draw-x draw-y)
|
||||||
|
|
||||||
|
;; Player region size
|
||||||
|
(define pw (- w (* 2 MARGIN)))
|
||||||
|
(define ph (- (* 1.75 ch) (* 4 MARGIN)))
|
||||||
|
|
||||||
|
;; Define the regions
|
||||||
|
(define machine-region
|
||||||
|
(make-region MARGIN MARGIN pw ph MACHINE-NAME #f))
|
||||||
|
(define you-region
|
||||||
|
(make-region MARGIN (- h ph MARGIN) pw ph YOU-NAME void))
|
||||||
|
(define discard-region
|
||||||
|
(make-region (- discard-x MARGIN) (- discard-y MARGIN)
|
||||||
|
(+ cw (* 2 MARGIN)) (+ ch (* 2 MARGIN))
|
||||||
|
"" #f))
|
||||||
|
|
||||||
|
;; Install the visible regions
|
||||||
|
(send t add-region machine-region)
|
||||||
|
(send t add-region you-region)
|
||||||
|
(send t add-region discard-region)
|
||||||
|
|
||||||
|
;; Deal the initial hands
|
||||||
|
(define machine-hand (deal DEAL-COUNT))
|
||||||
|
(define you-hand (deal DEAL-COUNT))
|
||||||
|
|
||||||
(module ginrummy mzscheme
|
|
||||||
(require (lib "cards.ss" "games" "cards")
|
|
||||||
(lib "mred.ss" "mred")
|
|
||||||
(lib "class.ss")
|
|
||||||
(lib "unit.ss")
|
|
||||||
(lib "list.ss"))
|
|
||||||
|
|
||||||
(provide game@)
|
|
||||||
|
|
||||||
(define game@
|
|
||||||
(unit
|
|
||||||
(import)
|
|
||||||
(export)
|
|
||||||
|
|
||||||
;; Initial card count
|
|
||||||
(define DEAL-COUNT 10)
|
|
||||||
|
|
||||||
;; Messages
|
|
||||||
(define YOUR-TURN-MESSAGE "Your turn. (Draw a card or pickup a discard.)")
|
|
||||||
(define DISCARD-MESSAGE "Drag a card from your hand to discard.")
|
|
||||||
(define GAME-OVER-MESSAGE "GAME OVER")
|
|
||||||
|
|
||||||
;; Area labels
|
|
||||||
(define YOU-NAME "You")
|
|
||||||
(define MACHINE-NAME "Opponent")
|
|
||||||
|
|
||||||
;; Region layout constants
|
|
||||||
(define MARGIN 5)
|
|
||||||
(define LABEL-H 15)
|
|
||||||
|
|
||||||
;; Randomize
|
|
||||||
(random-seed (modulo (current-milliseconds) 10000))
|
|
||||||
|
|
||||||
;; Set up the table
|
|
||||||
(define t (make-table "Rummy" 8 4.5))
|
|
||||||
(define status-pane (send t create-status-pane))
|
|
||||||
(send t add-help-button status-pane '("games" "ginrummy") "Rummy Help" #f)
|
|
||||||
(send t show #t)
|
|
||||||
(send t set-double-click-action #f)
|
|
||||||
(send t set-button-action 'left 'drag-raise/one)
|
|
||||||
(send t set-button-action 'middle 'drag/one)
|
|
||||||
(send t set-button-action 'right 'drag/above)
|
|
||||||
|
|
||||||
;; Get table width & height
|
|
||||||
(define w (send t table-width))
|
|
||||||
(define h (send t table-height))
|
|
||||||
|
|
||||||
;; Set up the cards
|
|
||||||
(define deck (shuffle-list (make-deck) 7))
|
|
||||||
(for-each
|
|
||||||
(lambda (card)
|
|
||||||
(send card user-can-move #f)
|
|
||||||
(send card user-can-flip #f))
|
|
||||||
deck)
|
|
||||||
|
|
||||||
;; Function for dealing or drawing cards
|
|
||||||
(define (deal n)
|
|
||||||
(let loop ([n n][d deck])
|
|
||||||
(if (zero? n)
|
|
||||||
(begin
|
|
||||||
(set! deck d)
|
|
||||||
null)
|
|
||||||
(cons (car d) (loop (sub1 n) (cdr d))))))
|
|
||||||
|
|
||||||
;; Card width & height
|
|
||||||
(define cw (send (car deck) card-width))
|
|
||||||
(define ch (send (car deck) card-height))
|
|
||||||
|
|
||||||
;; Draw and discard pile locations
|
|
||||||
(define draw-x (/ (- w (* 3 cw)) 2))
|
|
||||||
(define draw-y (/ (- h ch) 2))
|
|
||||||
(define discard-x (+ draw-x (* 2 cw)))
|
|
||||||
(define discard-y draw-y)
|
|
||||||
|
|
||||||
;; Put the cards on the table
|
|
||||||
(send t add-cards deck draw-x draw-y)
|
|
||||||
|
|
||||||
;; Player region size
|
|
||||||
(define pw (- w (* 2 MARGIN)))
|
|
||||||
(define ph (- (* 1.75 ch) (* 4 MARGIN)))
|
|
||||||
|
|
||||||
;; Define the regions
|
|
||||||
(define machine-region
|
|
||||||
(make-region
|
|
||||||
MARGIN MARGIN pw ph
|
|
||||||
MACHINE-NAME
|
|
||||||
#f))
|
|
||||||
(define you-region
|
|
||||||
(make-region
|
|
||||||
MARGIN (- h ph MARGIN) pw ph
|
|
||||||
YOU-NAME
|
|
||||||
void))
|
|
||||||
(define discard-region
|
|
||||||
(make-region
|
|
||||||
(- discard-x MARGIN) (- discard-y MARGIN)
|
|
||||||
(+ cw (* 2 MARGIN)) (+ ch (* 2 MARGIN))
|
|
||||||
"" #f))
|
|
||||||
|
|
||||||
;; Install the visible regions
|
|
||||||
(send t add-region machine-region)
|
|
||||||
(send t add-region you-region)
|
|
||||||
(send t add-region discard-region)
|
|
||||||
|
|
||||||
;; Deal the initial hands
|
|
||||||
(define machine-hand (deal DEAL-COUNT))
|
|
||||||
(define you-hand (deal DEAL-COUNT))
|
|
||||||
|
|
||||||
;; Function to inset a region
|
;; Function to inset a region
|
||||||
(define (region->display-region r)
|
(define (region->display-region r)
|
||||||
(define m MARGIN)
|
(define m MARGIN)
|
||||||
(make-region
|
(make-region (+ m (region-x r)) (+ m (region-y r))
|
||||||
(+ m (region-x r)) (+ m (region-y r))
|
(- (region-w r) (* 2 m)) (- (region-h r) (* 2 m))
|
||||||
(- (region-w r) (* 2 m)) (- (region-h r) (* 2 m))
|
#f #f))
|
||||||
#f #f))
|
|
||||||
|
|
||||||
;; Place cards nicely
|
|
||||||
(define machine-display-region (region->display-region machine-region))
|
|
||||||
(send t move-cards-to-region machine-hand machine-display-region)
|
|
||||||
(send t move-cards-to-region you-hand (region->display-region you-region))
|
|
||||||
|
|
||||||
;; All cards in your hand are movable, but must stay in your region
|
|
||||||
(for-each
|
|
||||||
(lambda (card)
|
|
||||||
(send card home-region you-region)
|
|
||||||
(send card user-can-move #t))
|
|
||||||
you-hand)
|
|
||||||
|
|
||||||
;; More card setup: Show your cards
|
|
||||||
(send t cards-face-up you-hand)
|
|
||||||
|
|
||||||
;; Start the discard pile
|
|
||||||
(define discards (deal 1))
|
|
||||||
(send t card-face-up (car discards))
|
|
||||||
(send t move-card (car discards) discard-x discard-y)
|
|
||||||
|
|
||||||
;; ;;;;;;;;;;;;; Finding Rummy and The Machine Player Strategy ;;;;;;;;
|
|
||||||
|
|
||||||
;; Check whether a group of (at least 3) cards forms a set (building
|
|
||||||
;; up to gin).
|
|
||||||
(define (set? cards)
|
|
||||||
(let ([values (map (lambda (c) (send c get-value)) cards)]
|
|
||||||
[suits (map (lambda (c) (send c get-suit-id)) cards)])
|
|
||||||
;; All same value? ... or
|
|
||||||
(or (apply = values)
|
|
||||||
;; ... All same suit and a straight?
|
|
||||||
(and (apply = suits)
|
|
||||||
(let ([sorted (sort values <)]
|
|
||||||
[try (lambda (l)
|
|
||||||
(let loop ([l l])
|
|
||||||
(or (null? (cdr l))
|
|
||||||
(and (= (car l) (sub1 (cadr l)))
|
|
||||||
(loop (cdr l))))))])
|
|
||||||
;; Try with Ace at end and at beginning
|
|
||||||
(or (try sorted)
|
|
||||||
(and (= 1 (car sorted))
|
|
||||||
(try (append (cdr sorted) (list 14))))))))))
|
|
||||||
|
|
||||||
;; Check how close a hand comes to winning by returning the maximum
|
|
||||||
;; number of cards that can be arranged into sets. This function is
|
|
||||||
;; used both to detect gin for the end-of-game condition, and also
|
|
||||||
;; as part of the machine player's strategy.
|
|
||||||
(define (gin-size cards)
|
|
||||||
(if (<= (length cards) 2)
|
|
||||||
0
|
|
||||||
(let* ([sort (lambda (get)
|
|
||||||
(sort cards (lambda (a b) (< (get a) (get b)))))]
|
|
||||||
|
|
||||||
;; It's not reasonable to test every combination of 10 cards,
|
|
||||||
;; but we can cut down the search space a lot by starting
|
|
||||||
;; with two different sorts on the card list.
|
|
||||||
|
|
||||||
;; We sort by value, to find 3-of-a-kind sets, and by
|
|
||||||
;; suit-then-value, to find straights. Whatever the
|
|
||||||
;; best allocation of cards to sets, one of the sets
|
|
||||||
;; must show up as three cards together in one of the
|
|
||||||
;; sorted lists. Also, if an extension to that set
|
|
||||||
;; leads to an optimal allocation, the extended set
|
|
||||||
;; corresponds to an extended section of the list.
|
|
||||||
[value-sorted (sort (lambda (c) (send c get-value)))]
|
|
||||||
[suit-sorted (sort (lambda (c) (+ (* 20 (send c get-suit-id)) (send c get-value))))]
|
|
||||||
|
|
||||||
;; Procedure to find a set allocation given one of the sorted
|
|
||||||
;; lists. It picks each group of three consecutive items
|
|
||||||
;; from the list and see how that choice works out.
|
|
||||||
;; (We're still performing a lot of redundant work here,
|
|
||||||
;; but it's fast enough.)
|
|
||||||
[find-set
|
|
||||||
(lambda (l)
|
|
||||||
;; 3loop tries each group of three items
|
|
||||||
(let 3loop ([pre null] ; prefix we've tried already
|
|
||||||
[group (list (car l) (cadr l) (caddr l))] ; the group to try
|
|
||||||
[post (cdddr l)]) ; suffix we haven't tried yet
|
|
||||||
(max (if (set? group)
|
|
||||||
;; We have a start; try to extend or not, and
|
|
||||||
;; make gin with the rest, then try the next 3-set
|
|
||||||
(max (let exloop ([set group][post post])
|
|
||||||
(cond
|
|
||||||
[(null? post)
|
|
||||||
;; No more items? Can't extend the set. Does the
|
|
||||||
;; set we found work out in the long run?
|
|
||||||
(+ (length set)
|
|
||||||
(if (null? pre)
|
|
||||||
0
|
|
||||||
(gin-size pre)))]
|
|
||||||
;; Try to extend the set...
|
|
||||||
[(set? (cons (car post) set))
|
|
||||||
;; The set can be extended.
|
|
||||||
;; Maybe this extension works in the long run...
|
|
||||||
(max (exloop (cons (car post) set) (cdr post))
|
|
||||||
;; or maybe without extension works in the long run...
|
|
||||||
(+ (length set) (gin-size (append pre post))))]
|
|
||||||
;; Can't extend the set, so try without extension
|
|
||||||
[else (+ (length set)
|
|
||||||
(gin-size (append pre post)))])))
|
|
||||||
0)
|
|
||||||
;; Try next three, if possible
|
|
||||||
(if (null? post)
|
|
||||||
0
|
|
||||||
;; Rotate the group, pulling a new last item in from post
|
|
||||||
;; and kicking the first item out to pre.
|
|
||||||
(3loop (cons (car group) pre)
|
|
||||||
(list (cadr group) (caddr group) (car post))
|
|
||||||
(cdr post))))))])
|
|
||||||
;; Try the value-sorted list, the the suit-sorted list, then...
|
|
||||||
(max (find-set value-sorted)
|
|
||||||
(find-set suit-sorted)
|
|
||||||
;; the suit-sorted list with with Aces at the end instead of the beginning
|
|
||||||
(let ace-loop ([pre null][l suit-sorted])
|
|
||||||
(cond
|
|
||||||
[(null? l)
|
|
||||||
;; No more aces to find
|
|
||||||
(find-set (reverse pre))]
|
|
||||||
[(null? (cdr l))
|
|
||||||
;; No more aces to find
|
|
||||||
(find-set (reverse (cons (car l) pre)))]
|
|
||||||
;; Is the front card an ace (before something else of the same suit)?
|
|
||||||
[(and (= 1 (send (car l) get-value))
|
|
||||||
(= (send (car l) get-suit-id) (send (cadr l) get-suit-id)))
|
|
||||||
;; Ace is at beginning; move it to the end
|
|
||||||
(let* ([ace (car l)]
|
|
||||||
[ace-suit (send ace get-suit-id)])
|
|
||||||
(let loop ([pre (cons (cadr l) pre)][l (cddr l)])
|
|
||||||
;; At end of this suit?
|
|
||||||
(if (or (null? l)
|
|
||||||
(> (send (car l) get-suit-id) ace-suit))
|
|
||||||
;; At the end; add Ace here
|
|
||||||
(ace-loop (cons ace pre) l)
|
|
||||||
;; still looking for new spot for Ace
|
|
||||||
(loop (cons (car l) pre) (cdr l)))))]
|
|
||||||
[else
|
|
||||||
;; Didn't find an ace; keep looking
|
|
||||||
(ace-loop (cons (car l) pre) (cdr l))]))))))
|
|
||||||
|
|
||||||
;; A hand wins if the biggest gin configuration includes all the cards
|
|
||||||
(define (gin? cards)
|
|
||||||
(= (gin-size cards) (length cards)))
|
|
||||||
|
|
||||||
;; This procedure is the second part of the machine's strategy. If
|
|
||||||
;; the machine sees two choices that are equally good according to
|
|
||||||
;; gin-size, then it computes a rating based on pairs, i.e., cards
|
|
||||||
;; that might eventually go together in a set.
|
|
||||||
(define (pair-rating cards gone-cards)
|
|
||||||
(let ([suits (map (lambda (card) (send card get-suit-id)) cards)]
|
|
||||||
[values (map (lambda (card) (send card get-value)) cards)])
|
|
||||||
;; Its O(n*n), but n is always 10 or 11
|
|
||||||
(apply +
|
|
||||||
(map (lambda (suit value)
|
|
||||||
(apply +
|
|
||||||
(map (lambda (suit2 value2)
|
|
||||||
(cond
|
|
||||||
[(= value value2)
|
|
||||||
(- 2 (count-gone value gone-cards))]
|
|
||||||
[(= suit suit2)
|
|
||||||
(rate-straight suit value value2 gone-cards)]
|
|
||||||
[else 0]))
|
|
||||||
suits values)))
|
|
||||||
suits values))))
|
|
||||||
|
|
||||||
;; count-gone checks how many of a given value are known
|
;; Place cards nicely
|
||||||
;; to be permanently discarded
|
(define machine-display-region (region->display-region machine-region))
|
||||||
(define (count-gone value gone-cards)
|
(send t move-cards-to-region machine-hand machine-display-region)
|
||||||
(cond
|
(send t move-cards-to-region you-hand (region->display-region you-region))
|
||||||
[(null? gone-cards) 0]
|
|
||||||
[(= value (send (car gone-cards) get-value))
|
|
||||||
(+ 1 (count-gone value (cdr gone-cards)))]
|
|
||||||
[else (count-gone value (cdr gone-cards))]))
|
|
||||||
|
|
||||||
;; count-avail checks whether a given value/suit is
|
;; All cards in your hand are movable, but must stay in your region
|
||||||
;; known to be discarded (returns 0) or not (returns 1)
|
(for-each (lambda (card)
|
||||||
(define (count-avail value suit gone-cards)
|
(send card home-region you-region)
|
||||||
(cond
|
(send card user-can-move #t))
|
||||||
[(null? gone-cards) 1]
|
you-hand)
|
||||||
[(and (= value (send (car gone-cards) get-value))
|
|
||||||
(= suit (send (car gone-cards) get-suit-id)))
|
|
||||||
0]
|
|
||||||
[else (count-avail value suit (cdr gone-cards))]))
|
|
||||||
|
|
||||||
;; rates the possibility for forming a straight given
|
;; More card setup: Show your cards
|
||||||
;; two card values in a particular suit, and taking
|
(send t cards-face-up you-hand)
|
||||||
;; into account cards known to be discarded; the
|
|
||||||
;; rating is the number of non-discarded cards that
|
;; Start the discard pile
|
||||||
;; would form a straight with the given values
|
(define discards (deal 1))
|
||||||
(define (rate-straight suit value value2 gone-cards)
|
(send t card-face-up (car discards))
|
||||||
(let ([v1 (if (= value 1)
|
(send t move-card (car discards) discard-x discard-y)
|
||||||
(if (value2 . > . 6)
|
|
||||||
14
|
;; ;;;;;;;;;;;;; Finding Rummy and The Machine Player Strategy ;;;;;;;;
|
||||||
1)
|
|
||||||
value)]
|
;; Check whether a group of (at least 3) cards forms a set (building
|
||||||
[v2 (if (= value2 1)
|
;; up to gin).
|
||||||
(if (value . > . 6)
|
(define (set? cards)
|
||||||
14
|
(let ([values (map (lambda (c) (send c get-value)) cards)]
|
||||||
1)
|
[suits (map (lambda (c) (send c get-suit-id)) cards)])
|
||||||
value2)])
|
;; All same value? ... or
|
||||||
(let ([delta (abs (- v1 v2))])
|
(or (apply = values)
|
||||||
(cond
|
;; ... All same suit and a straight?
|
||||||
[(= delta 1)
|
(and (apply = suits)
|
||||||
(cond
|
(let ([sorted (sort values <)]
|
||||||
[(or (= v1 1) (= v2 1))
|
[try (lambda (l)
|
||||||
;; Might get the 3?
|
(let loop ([l l])
|
||||||
(count-avail 3 suit gone-cards)]
|
(or (null? (cdr l))
|
||||||
[(or (= v1 14) (= v2 14))
|
(and (= (car l) (sub1 (cadr l)))
|
||||||
;; Might get the queen?
|
(loop (cdr l))))))])
|
||||||
(count-avail 12 suit gone-cards)]
|
;; Try with Ace at end and at beginning
|
||||||
[(or (= v1 13) (= v2 13))
|
(or (try sorted)
|
||||||
;; Might get the jack or ace?
|
(and (= 1 (car sorted))
|
||||||
(+ (count-avail 11 suit gone-cards)
|
(try (append (cdr sorted) (list 14))))))))))
|
||||||
(count-avail 1 suit gone-cards))]
|
|
||||||
[else
|
;; Check how close a hand comes to winning by returning the maximum number of
|
||||||
;; Might get top or bottom?
|
;; cards that can be arranged into sets. This function is used both to detect
|
||||||
(+ (count-avail (sub1 (min v1 v2)) suit gone-cards)
|
;; gin for the end-of-game condition, and also as part of the machine player's
|
||||||
(count-avail (add1 (max v1 v2)) suit gone-cards))])]
|
;; strategy.
|
||||||
[(= delta 2)
|
(define (gin-size cards)
|
||||||
;; Might get the middle one?
|
(if (<= (length cards) 2)
|
||||||
(let ([middle (quotient (+ v1 v2) 2)])
|
0
|
||||||
(count-avail middle suit gone-cards))]
|
(let* ([sort (lambda (get)
|
||||||
[else 0]))))
|
(sort cards (lambda (a b) (< (get a) (get b)))))]
|
||||||
|
|
||||||
;; The procedure implements the machine's card-drawing choice
|
;; It's not reasonable to test every combination of 10 cards, but we
|
||||||
(define (machine-wants-card? machine-hand card gone-cards)
|
;; can cut down the search space a lot by starting with two
|
||||||
;; Simple strategy: the machine wants the card if taking it will
|
;; different sorts on the card list.
|
||||||
;; make the gin-size of its hand increase, or if taking it will not
|
|
||||||
;; make the gin-size decrease but will increase the pair rating.
|
;; We sort by value, to find 3-of-a-kind sets, and by
|
||||||
(let* ([orig-size (gin-size machine-hand)]
|
;; suit-then-value, to find straights. Whatever the best allocation
|
||||||
[new-hand (remq (machine-discard (cons card machine-hand) gone-cards)
|
;; of cards to sets, one of the sets must show up as three cards
|
||||||
(cons card machine-hand))]
|
;; together in one of the sorted lists. Also, if an extension to
|
||||||
[new-size (gin-size new-hand)])
|
;; that set leads to an optimal allocation, the extended set
|
||||||
(or (> new-size orig-size)
|
;; corresponds to an extended section of the list.
|
||||||
(and (= new-size orig-size)
|
[value-sorted (sort (lambda (c) (send c get-value)))]
|
||||||
(> (pair-rating new-hand gone-cards)
|
[suit-sorted (sort (lambda (c) (+ (* 20 (send c get-suit-id)) (send c get-value))))]
|
||||||
(pair-rating machine-hand gone-cards))))))
|
|
||||||
|
;; Procedure to find a set allocation given one of the sorted
|
||||||
;; The procedure implements the machine's discard choice
|
;; lists. It picks each group of three consecutive items from the
|
||||||
(define (machine-discard machine-hand gone-cards)
|
;; list and see how that choice works out. (We're still performing
|
||||||
;; Discard the card that leaves the hand with the largest
|
;; a lot of redundant work here, but it's fast enough.)
|
||||||
;; gin-size. If multiple cards leave the same largest gin size,
|
[find-set
|
||||||
;; pick card leaving the best pair rating.
|
(lambda (l)
|
||||||
(let* ([gin-size-card-pairs
|
;; 3loop tries each group of three items
|
||||||
(map (lambda (card) (cons (gin-size (remq card machine-hand))
|
(let 3loop ([pre null] ; prefix we've tried already
|
||||||
card))
|
[group (list (car l) (cadr l) (caddr l))] ; the group to try
|
||||||
machine-hand)]
|
[post (cdddr l)]) ; suffix we haven't tried yet
|
||||||
[most (apply max (map car gin-size-card-pairs))]
|
(max (if (set? group)
|
||||||
[best (filter (lambda (x) (= most (car x))) gin-size-card-pairs)]
|
;; We have a start; try to extend or not, and
|
||||||
[best-cards (map cdr best)]
|
;; make gin with the rest, then try the next 3-set
|
||||||
[rating-card-pairs
|
(max (let exloop ([set group][post post])
|
||||||
(map (lambda (card) (cons (pair-rating (remq card machine-hand) gone-cards)
|
(cond
|
||||||
card))
|
[(null? post)
|
||||||
best-cards)]
|
;; No more items? Can't extend the set. Does the
|
||||||
[most (apply max (map car rating-card-pairs))]
|
;; set we found work out in the long run?
|
||||||
[best (filter (lambda (x) (= most (car x))) rating-card-pairs)])
|
(+ (length set)
|
||||||
(cdar best)))
|
(if (null? pre) 0 (gin-size pre)))]
|
||||||
|
;; Try to extend the set...
|
||||||
;; ;;;;;; Game Loop ;;;;;;;;
|
[(set? (cons (car post) set))
|
||||||
|
;; The set can be extended. Maybe this
|
||||||
;; This procedure finalizes the display when the game is over
|
;; extension works in the long run...
|
||||||
(define (end-of-game why)
|
(max (exloop (cons (car post) set) (cdr post))
|
||||||
(send t set-status-text
|
;; or maybe without extension works in
|
||||||
(format "~aGame over. ~a."
|
;; the long run...
|
||||||
why
|
(+ (length set) (gin-size (append pre post))))]
|
||||||
(cond
|
;; Can't extend the set, so try without
|
||||||
[(and (gin? you-hand) (gin? machine-hand)) "Tie"] ; only on deal
|
;; extension
|
||||||
[(gin? you-hand) "You win"]
|
[else (+ (length set)
|
||||||
[else "Opponent wins"])))
|
(gin-size (append pre post)))])))
|
||||||
(send t cards-face-up machine-hand))
|
0)
|
||||||
|
;; Try next three, if possible
|
||||||
;; Deck empty? Shuffle the discard pile (preserving the top discard)
|
(if (null? post)
|
||||||
(define (check-empty-deck)
|
0
|
||||||
(when (null? deck)
|
;; Rotate the group, pulling a new last item in from
|
||||||
(set! deck (shuffle-list (cdr discards) 7))
|
;; post and kicking the first item out to pre.
|
||||||
(set! discards (list (car discards)))
|
(3loop (cons (car group) pre)
|
||||||
(send t cards-face-down deck)
|
(list (cadr group) (caddr group) (car post))
|
||||||
(send t stack-cards deck)
|
(cdr post))))))])
|
||||||
(send t move-cards deck draw-x draw-y)))
|
;; Try the value-sorted list, the the suit-sorted list, then...
|
||||||
|
(max (find-set value-sorted)
|
||||||
;; Check for starge start...
|
(find-set suit-sorted)
|
||||||
(if (or (gin? you-hand) (gin? machine-hand))
|
;; the suit-sorted list with with Aces at the end instead of the
|
||||||
;; Someone was delt gin - game over
|
;; beginning
|
||||||
(end-of-game "Dealt gin. ")
|
(let ace-loop ([pre null][l suit-sorted])
|
||||||
|
(cond
|
||||||
;; This is the main game loop
|
[(null? l)
|
||||||
(let loop ()
|
;; No more aces to find
|
||||||
(check-empty-deck)
|
(find-set (reverse pre))]
|
||||||
|
[(null? (cdr l))
|
||||||
;; Your turn; you can select the top card on the deck or on the discard pile
|
;; No more aces to find
|
||||||
(send (car discards) user-can-move #t)
|
(find-set (reverse (cons (car l) pre)))]
|
||||||
(send (car discards) snap-back-after-move #t)
|
;; Is the front card an ace (before something else of the same
|
||||||
(send (car deck) user-can-move #t)
|
;; suit)?
|
||||||
(send (car deck) snap-back-after-move #t)
|
[(and (= 1 (send (car l) get-value))
|
||||||
(send t set-status-text YOUR-TURN-MESSAGE)
|
(= (send (car l) get-suit-id) (send (cadr l) get-suit-id)))
|
||||||
(let ([something-happened (make-semaphore 0)])
|
;; Ace is at beginning; move it to the end
|
||||||
;; Set callback in your region to receive the deck/discard card
|
(let* ([ace (car l)]
|
||||||
(set-region-callback!
|
[ace-suit (send ace get-suit-id)])
|
||||||
you-region
|
(let loop ([pre (cons (cadr l) pre)][l (cddr l)])
|
||||||
(lambda (cards)
|
;; At end of this suit?
|
||||||
(let ([card (car cards)])
|
(if (or (null? l) (> (send (car l) get-suit-id) ace-suit))
|
||||||
;; Adjust the deck, discard pile, and your hand
|
;; At the end; add Ace here
|
||||||
(if (eq? card (car discards))
|
(ace-loop (cons ace pre) l)
|
||||||
(set! discards (cdr discards))
|
;; still looking for new spot for Ace
|
||||||
(set! deck (cdr deck)))
|
(loop (cons (car l) pre) (cdr l)))))]
|
||||||
(set! you-hand (cons card you-hand))
|
[else
|
||||||
(send t card-face-up card))
|
;; Didn't find an ace; keep looking
|
||||||
|
(ace-loop (cons (car l) pre) (cdr l))]))))))
|
||||||
;; Action done - clean up and move on
|
|
||||||
(semaphore-post something-happened)
|
;; A hand wins if the biggest gin configuration includes all the cards
|
||||||
(unless (null? deck)
|
(define (gin? cards)
|
||||||
(send (car deck) user-can-move #f)
|
(= (gin-size cards) (length cards)))
|
||||||
(send (car deck) home-region #f))
|
|
||||||
(unless (null? discards)
|
;; This procedure is the second part of the machine's strategy. If the machine
|
||||||
(send (car discards) user-can-move #f)
|
;; sees two choices that are equally good according to gin-size, then it
|
||||||
(send (car discards) home-region #f))
|
;; computes a rating based on pairs, i.e., cards that might eventually go
|
||||||
(set-region-callback! you-region #f)
|
;; together in a set.
|
||||||
(set-region-interactive-callback! you-region #f)))
|
(define (pair-rating cards gone-cards)
|
||||||
;; Interactive callback: change home of card if region is hilited.
|
(let ([suits (map (lambda (card) (send card get-suit-id)) cards)]
|
||||||
;; As a result, the card snaps to where you put it instead of back
|
[values (map (lambda (card) (send card get-value)) cards)])
|
||||||
;; to its original position.
|
;; Its O(n*n), but n is always 10 or 11
|
||||||
(set-region-interactive-callback!
|
(apply
|
||||||
you-region
|
+ (map (lambda (suit value)
|
||||||
(lambda (on? cards)
|
(apply
|
||||||
(send (car cards) snap-back-after-move (not on?))
|
+ (map (lambda (suit2 value2)
|
||||||
(send (car cards) home-region (and on? you-region))))
|
(cond [(= value value2)
|
||||||
;; Wait for action (the action itself is handled by the callback
|
(- 2 (count-gone value gone-cards))]
|
||||||
;; for you-region)
|
[(= suit suit2)
|
||||||
(yield something-happened))
|
(rate-straight suit value value2 gone-cards)]
|
||||||
|
[else 0]))
|
||||||
;; Time for you to discard something
|
suits values)))
|
||||||
(send t set-status-text DISCARD-MESSAGE)
|
suits values))))
|
||||||
(let ([something-happened (make-semaphore 0)])
|
|
||||||
;; This time, the discard pile is the active region
|
;; count-gone checks how many of a given value are known to be permanently
|
||||||
(set-region-callback!
|
;; discarded
|
||||||
discard-region
|
(define (count-gone value gone-cards)
|
||||||
(lambda (cards)
|
(cond [(null? gone-cards) 0]
|
||||||
(let ([card (car cards)])
|
[(= value (send (car gone-cards) get-value))
|
||||||
;; Adjust the discard pile and your hand
|
(+ 1 (count-gone value (cdr gone-cards)))]
|
||||||
(set! you-hand (remq card you-hand))
|
[else (count-gone value (cdr gone-cards))]))
|
||||||
(set! discards (cons card discards))
|
|
||||||
(send t card-to-front card)
|
;; count-avail checks whether a given value/suit is
|
||||||
(send t move-card card discard-x discard-y)
|
;; known to be discarded (returns 0) or not (returns 1)
|
||||||
|
(define (count-avail value suit gone-cards)
|
||||||
;; Discarded card is now relatively immobile
|
(cond [(null? gone-cards) 1]
|
||||||
(send card user-can-move #t)
|
[(and (= value (send (car gone-cards) get-value))
|
||||||
(send card home-region #f))
|
(= suit (send (car gone-cards) get-suit-id)))
|
||||||
|
0]
|
||||||
;; Action done - clean up and move on
|
[else (count-avail value suit (cdr gone-cards))]))
|
||||||
(semaphore-post something-happened)
|
|
||||||
(set-region-callback! discard-region #f)
|
;; rates the possibility for forming a straight given two card values in a
|
||||||
(set-region-interactive-callback! discard-region #f)))
|
;; particular suit, and taking into account cards known to be discarded; the
|
||||||
;; Interactive callback: change home of card if region is hilited,
|
;; rating is the number of non-discarded cards that would form a straight with
|
||||||
;; so the card you drag snaps to the discard pile.
|
;; the given values
|
||||||
(set-region-interactive-callback!
|
(define (rate-straight suit value value2 gone-cards)
|
||||||
discard-region
|
(let ([v1 (if (= value 1)
|
||||||
(lambda (on? cards)
|
(if (value2 . > . 6) 14 1)
|
||||||
(send (car cards) home-region
|
value)]
|
||||||
(if on? discard-region you-region))))
|
[v2 (if (= value2 1)
|
||||||
;; Wait for action
|
(if (value . > . 6) 14 1)
|
||||||
(yield something-happened))
|
value2)])
|
||||||
|
(let ([delta (abs (- v1 v2))])
|
||||||
(if (gin? you-hand)
|
(cond [(= delta 1)
|
||||||
;; Game over
|
(cond [(or (= v1 1) (= v2 1))
|
||||||
(end-of-game "")
|
;; Might get the 3?
|
||||||
|
(count-avail 3 suit gone-cards)]
|
||||||
;; Keep going; machine's turn
|
[(or (= v1 14) (= v2 14))
|
||||||
(begin
|
;; Might get the queen?
|
||||||
(check-empty-deck)
|
(count-avail 12 suit gone-cards)]
|
||||||
;; Machine picks a card
|
[(or (= v1 13) (= v2 13))
|
||||||
(if (machine-wants-card? machine-hand (car discards) (cdr discards))
|
;; Might get the jack or ace?
|
||||||
(let ([card (car discards)])
|
(+ (count-avail 11 suit gone-cards)
|
||||||
(set! discards (cdr discards))
|
(count-avail 1 suit gone-cards))]
|
||||||
(send t card-face-down card)
|
[else
|
||||||
(send card user-can-move #f)
|
;; Might get top or bottom?
|
||||||
(set! machine-hand (cons card machine-hand)))
|
(+ (count-avail (sub1 (min v1 v2)) suit gone-cards)
|
||||||
(let ([card (car deck)])
|
(count-avail (add1 (max v1 v2)) suit gone-cards))])]
|
||||||
(send t card-to-front card)
|
[(= delta 2)
|
||||||
(set! deck (cdr deck))
|
;; Might get the middle one?
|
||||||
(send card user-can-move #f)
|
(let ([middle (quotient (+ v1 v2) 2)])
|
||||||
(set! machine-hand (cons card machine-hand))))
|
(count-avail middle suit gone-cards))]
|
||||||
(send t move-cards-to-region machine-hand machine-display-region)
|
[else 0]))))
|
||||||
|
|
||||||
;; Machine discards
|
;; The procedure implements the machine's card-drawing choice
|
||||||
(let ([card (machine-discard machine-hand discards)])
|
(define (machine-wants-card? machine-hand card gone-cards)
|
||||||
(send t card-face-up card)
|
;; Simple strategy: the machine wants the card if taking it will make the
|
||||||
(send t card-to-front card)
|
;; gin-size of its hand increase, or if taking it will not make the gin-size
|
||||||
(send t move-card card discard-x discard-y)
|
;; decrease but will increase the pair rating.
|
||||||
(set! discards (cons card discards))
|
(let* ([orig-size (gin-size machine-hand)]
|
||||||
(set! machine-hand (remq card machine-hand))
|
[new-hand (remq (machine-discard (cons card machine-hand) gone-cards)
|
||||||
(send t move-cards-to-region machine-hand machine-display-region))
|
(cons card machine-hand))]
|
||||||
|
[new-size (gin-size new-hand)])
|
||||||
(if (gin? machine-hand)
|
(or (> new-size orig-size)
|
||||||
;; Game over
|
(and (= new-size orig-size)
|
||||||
(end-of-game "")
|
(> (pair-rating new-hand gone-cards)
|
||||||
|
(pair-rating machine-hand gone-cards))))))
|
||||||
;; Next turn
|
|
||||||
(loop)))))))))
|
;; The procedure implements the machine's discard choice
|
||||||
|
(define (machine-discard machine-hand gone-cards)
|
||||||
|
;; Discard the card that leaves the hand with the largest gin-size. If
|
||||||
|
;; multiple cards leave the same largest gin size, pick card leaving the best
|
||||||
|
;; pair rating.
|
||||||
|
(let* ([gin-size-card-pairs
|
||||||
|
(map (lambda (card) (cons (gin-size (remq card machine-hand)) card))
|
||||||
|
machine-hand)]
|
||||||
|
[most (apply max (map car gin-size-card-pairs))]
|
||||||
|
[best (filter (lambda (x) (= most (car x))) gin-size-card-pairs)]
|
||||||
|
[best-cards (map cdr best)]
|
||||||
|
[rating-card-pairs
|
||||||
|
(map (lambda (card)
|
||||||
|
(cons (pair-rating (remq card machine-hand) gone-cards) card))
|
||||||
|
best-cards)]
|
||||||
|
[most (apply max (map car rating-card-pairs))]
|
||||||
|
[best (filter (lambda (x) (= most (car x))) rating-card-pairs)])
|
||||||
|
(cdar best)))
|
||||||
|
|
||||||
|
;; ;;;;;; Game Loop ;;;;;;;;
|
||||||
|
|
||||||
|
;; This procedure finalizes the display when the game is over
|
||||||
|
(define (end-of-game why)
|
||||||
|
(send t set-status-text
|
||||||
|
(format
|
||||||
|
"~aGame over. ~a."
|
||||||
|
why
|
||||||
|
(cond [(and (gin? you-hand) (gin? machine-hand)) "Tie"] ; only on deal
|
||||||
|
[(gin? you-hand) "You win"]
|
||||||
|
[else "Opponent wins"])))
|
||||||
|
(send t cards-face-up machine-hand))
|
||||||
|
|
||||||
|
;; Deck empty? Shuffle the discard pile (preserving the top discard)
|
||||||
|
(define (check-empty-deck)
|
||||||
|
(when (null? deck)
|
||||||
|
(set! deck (shuffle-list (cdr discards) 7))
|
||||||
|
(set! discards (list (car discards)))
|
||||||
|
(send t cards-face-down deck)
|
||||||
|
(send t stack-cards deck)
|
||||||
|
(send t move-cards deck draw-x draw-y)))
|
||||||
|
|
||||||
|
;; Check for starge start...
|
||||||
|
(if (or (gin? you-hand) (gin? machine-hand))
|
||||||
|
;; Someone was delt gin - game over
|
||||||
|
(end-of-game "Dealt gin. ")
|
||||||
|
|
||||||
|
;; This is the main game loop
|
||||||
|
(let loop ()
|
||||||
|
(check-empty-deck)
|
||||||
|
|
||||||
|
;; Your turn; you can select the top card on the deck or on the discard
|
||||||
|
;; pile
|
||||||
|
(send (car discards) user-can-move #t)
|
||||||
|
(send (car discards) snap-back-after-move #t)
|
||||||
|
(send (car deck) user-can-move #t)
|
||||||
|
(send (car deck) snap-back-after-move #t)
|
||||||
|
(send t set-status-text YOUR-TURN-MESSAGE)
|
||||||
|
(let ([something-happened (make-semaphore 0)])
|
||||||
|
;; Set callback in your region to receive the deck/discard card
|
||||||
|
(set-region-callback!
|
||||||
|
you-region
|
||||||
|
(lambda (cards)
|
||||||
|
(let ([card (car cards)])
|
||||||
|
;; Adjust the deck, discard pile, and your hand
|
||||||
|
(if (eq? card (car discards))
|
||||||
|
(set! discards (cdr discards))
|
||||||
|
(set! deck (cdr deck)))
|
||||||
|
(set! you-hand (cons card you-hand))
|
||||||
|
(send t card-face-up card))
|
||||||
|
|
||||||
|
;; Action done - clean up and move on
|
||||||
|
(semaphore-post something-happened)
|
||||||
|
(unless (null? deck)
|
||||||
|
(send (car deck) user-can-move #f)
|
||||||
|
(send (car deck) home-region #f))
|
||||||
|
(unless (null? discards)
|
||||||
|
(send (car discards) user-can-move #f)
|
||||||
|
(send (car discards) home-region #f))
|
||||||
|
(set-region-callback! you-region #f)
|
||||||
|
(set-region-interactive-callback! you-region #f)))
|
||||||
|
;; Interactive callback: change home of card if region is hilited. As a
|
||||||
|
;; result, the card snaps to where you put it instead of back to its
|
||||||
|
;; original position.
|
||||||
|
(set-region-interactive-callback!
|
||||||
|
you-region
|
||||||
|
(lambda (on? cards)
|
||||||
|
(send (car cards) snap-back-after-move (not on?))
|
||||||
|
(send (car cards) home-region (and on? you-region))))
|
||||||
|
;; Wait for action (the action itself is handled by the callback
|
||||||
|
;; for you-region)
|
||||||
|
(yield something-happened))
|
||||||
|
|
||||||
|
;; Time for you to discard something
|
||||||
|
(send t set-status-text DISCARD-MESSAGE)
|
||||||
|
(let ([something-happened (make-semaphore 0)])
|
||||||
|
;; This time, the discard pile is the active region
|
||||||
|
(set-region-callback!
|
||||||
|
discard-region
|
||||||
|
(lambda (cards)
|
||||||
|
(let ([card (car cards)])
|
||||||
|
;; Adjust the discard pile and your hand
|
||||||
|
(set! you-hand (remq card you-hand))
|
||||||
|
(set! discards (cons card discards))
|
||||||
|
(send t card-to-front card)
|
||||||
|
(send t move-card card discard-x discard-y)
|
||||||
|
|
||||||
|
;; Discarded card is now relatively immobile
|
||||||
|
(send card user-can-move #t)
|
||||||
|
(send card home-region #f))
|
||||||
|
|
||||||
|
;; Action done - clean up and move on
|
||||||
|
(semaphore-post something-happened)
|
||||||
|
(set-region-callback! discard-region #f)
|
||||||
|
(set-region-interactive-callback! discard-region #f)))
|
||||||
|
;; Interactive callback: change home of card if region is hilited,
|
||||||
|
;; so the card you drag snaps to the discard pile.
|
||||||
|
(set-region-interactive-callback!
|
||||||
|
discard-region
|
||||||
|
(lambda (on? cards)
|
||||||
|
(send (car cards) home-region (if on? discard-region you-region))))
|
||||||
|
;; Wait for action
|
||||||
|
(yield something-happened))
|
||||||
|
|
||||||
|
(if (gin? you-hand)
|
||||||
|
;; Game over
|
||||||
|
(end-of-game "")
|
||||||
|
|
||||||
|
;; Keep going; machine's turn
|
||||||
|
(begin
|
||||||
|
(check-empty-deck)
|
||||||
|
;; Machine picks a card
|
||||||
|
(if (machine-wants-card? machine-hand (car discards) (cdr discards))
|
||||||
|
(let ([card (car discards)])
|
||||||
|
(set! discards (cdr discards))
|
||||||
|
(send t card-face-down card)
|
||||||
|
(send card user-can-move #f)
|
||||||
|
(set! machine-hand (cons card machine-hand)))
|
||||||
|
(let ([card (car deck)])
|
||||||
|
(send t card-to-front card)
|
||||||
|
(set! deck (cdr deck))
|
||||||
|
(send card user-can-move #f)
|
||||||
|
(set! machine-hand (cons card machine-hand))))
|
||||||
|
(send t move-cards-to-region machine-hand machine-display-region)
|
||||||
|
|
||||||
|
;; Machine discards
|
||||||
|
(let ([card (machine-discard machine-hand discards)])
|
||||||
|
(send t card-face-up card)
|
||||||
|
(send t card-to-front card)
|
||||||
|
(send t move-card card discard-x discard-y)
|
||||||
|
(set! discards (cons card discards))
|
||||||
|
(set! machine-hand (remq card machine-hand))
|
||||||
|
(send t move-cards-to-region machine-hand machine-display-region))
|
||||||
|
|
||||||
|
(if (gin? machine-hand)
|
||||||
|
;; Game over
|
||||||
|
(end-of-game "")
|
||||||
|
|
||||||
|
;; Next turn
|
||||||
|
(loop))))))
|
||||||
|
|
||||||
|
))
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
** To play _Goblet_, run the "Games" application. **
|
** To play Goblet, run the "PLT Games" application.
|
||||||
|
|
||||||
"Gobblet!" is a board game from Blue Orange Games:
|
"Gobblet!" is a board game from Blue Orange Games:
|
||||||
http://www.blueorangegames.com/
|
http://www.blueorangegames.com/
|
||||||
|
@ -16,7 +16,7 @@ Game Rules
|
||||||
The 3x3 game is a generalization of tic-tac-toe:
|
The 3x3 game is a generalization of tic-tac-toe:
|
||||||
|
|
||||||
* The object of the game is to get three in a row of your color,
|
* The object of the game is to get three in a row of your color,
|
||||||
vertically, horizontally, or diagonally. Size doesn't matter for
|
vertically, horizontally, or diagonally. Size doesn't matter for
|
||||||
determining a winner.
|
determining a winner.
|
||||||
|
|
||||||
* Each player (red or yellow) starts with 6 pieces: two large, two
|
* Each player (red or yellow) starts with 6 pieces: two large, two
|
||||||
|
@ -28,14 +28,14 @@ The 3x3 game is a generalization of tic-tac-toe:
|
||||||
|
|
||||||
* A piece can be placed (or moved to) an empty space, or it can be
|
* A piece can be placed (or moved to) an empty space, or it can be
|
||||||
placed/moved on top of a smaller piece already on the board,
|
placed/moved on top of a smaller piece already on the board,
|
||||||
"gobbling" the smaller piece. The smaller piece does not have to be
|
"gobbling" the smaller piece. The smaller piece does not have to
|
||||||
an opponent's piece, and the smaller piece may itself have gobbled
|
be an opponent's piece, and the smaller piece may itself have
|
||||||
another piece previously.
|
gobbled another piece previously.
|
||||||
|
|
||||||
* Only visible pieces can be moved, and only visible pieces count
|
* Only visible pieces can be moved, and only visible pieces count
|
||||||
toward winning. Gobbled pieces stay on the board, however, and when
|
toward winning. Gobbled pieces stay on the board, however, and
|
||||||
a piece is moved, any piece that it gobbled stays put and becomes
|
when a piece is moved, any piece that it gobbled stays put and
|
||||||
visible.
|
becomes visible.
|
||||||
|
|
||||||
* If moving a piece exposes a winning sequence for the opponent, and
|
* If moving a piece exposes a winning sequence for the opponent, and
|
||||||
if the destination for the move does not cover up one of the other
|
if the destination for the move does not cover up one of the other
|
||||||
|
@ -43,10 +43,10 @@ The 3x3 game is a generalization of tic-tac-toe:
|
||||||
makes a winning sequence for the moving player.
|
makes a winning sequence for the moving player.
|
||||||
|
|
||||||
* Technically, if a player touches a piece, then the piece must be
|
* Technically, if a player touches a piece, then the piece must be
|
||||||
moved on that turn. In other words, you're not allowed to peek
|
moved on that turn. In other words, you're not allowed to peek
|
||||||
under a piece to remind yourself whether it gobbled anything. If
|
under a piece to remind yourself whether it gobbled anything. If
|
||||||
the piece can't be moved, the player forfeits. This particular rule
|
the piece can't be moved, the player forfeits. This particular
|
||||||
is not enforced by our version --- in part because our version
|
rule is not enforced by our version --- in part because our version
|
||||||
supports a rewind button, which is also not in the official game.
|
supports a rewind button, which is also not in the official game.
|
||||||
|
|
||||||
The 4x4 game has a few changes:
|
The 4x4 game has a few changes:
|
||||||
|
@ -72,36 +72,36 @@ The 4x4 game has a few changes:
|
||||||
Controls
|
Controls
|
||||||
--------
|
--------
|
||||||
|
|
||||||
Click and drag pieces in the obvious way to take a turn. The shadow
|
Click and drag pieces in the obvious way to take a turn. The shadow
|
||||||
under a piece shows where it will land when you drop it.
|
under a piece shows where it will land when you drop it.
|
||||||
|
|
||||||
Use the arrow keys on your keyboard to rotate the board. Use the "-"
|
Use the arrow keys on your keyboard to rotate the board. Use the "-"
|
||||||
and "=" keys to zoom in and out. Use "_" and "+" to make the game
|
and "=" keys to zoom in and out. Use "_" and "+" to make the game
|
||||||
smaller and larger. (Changing the size adjusts perspective in a
|
smaller and larger. (Changing the size adjusts perspective in a
|
||||||
slightly different way than zooming.) Depending on how keyboard focus
|
slightly different way than zooming.) Depending on how keyboard focus
|
||||||
works on your machine, you may have to click the board area to make
|
works on your machine, you may have to click the board area to make
|
||||||
these controls work.
|
these controls work.
|
||||||
|
|
||||||
The button labeled "<" at the bottom of the window rewinds the game
|
The button labeled "<" at the bottom of the window rewinds the game by
|
||||||
by one turn. The button labeled ">" re-plays one turn in a rewound
|
one turn. The button labeled ">" re-plays one turn in a rewound game.
|
||||||
game. An alternate move can be made at any point in a rewound game,
|
An alternate move can be made at any point in a rewound game,
|
||||||
replacing the old game from that point on.
|
replacing the old game from that point on.
|
||||||
|
|
||||||
Auto-Play
|
Auto-Play
|
||||||
---------
|
---------
|
||||||
|
|
||||||
Turn on a computer player at any time by checking the "Auto-Play Red"
|
Turn on a computer player at any time by checking the "Auto-Play Red"
|
||||||
or "Auto-Play Yellow" checkbox. If you rewind the game, you can choose
|
or "Auto-Play Yellow" checkbox. If you rewind the game, you can
|
||||||
an alternate move for yourself or for the auto-player to find out what
|
choose an alternate move for yourself or for the auto-player to find
|
||||||
would have happened. The auto-player is not always deterministic, so
|
out what would have happened. The auto-player is not always
|
||||||
replying the same move might lead to a different result. You can
|
deterministic, so replying the same move might lead to a different
|
||||||
disable an auto-player at any point by unchecking the corresponding
|
result. You can disable an auto-player at any point by unchecking the
|
||||||
"Auto-Play" checkbox.
|
corresponding "Auto-Play" checkbox.
|
||||||
|
|
||||||
Important: In the 3x3 game, you CANNOT win as yellow against the smart
|
Important: In the 3x3 game, you CANNOT win as yellow against the smart
|
||||||
auto-player (if the auto-player is allowed to play red from the start
|
auto-player (if the auto-player is allowed to play red from the start
|
||||||
of the game). In other words, red has a forced win in the 3x3 game,
|
of the game). In other words, red has a forced win in the 3x3 game,
|
||||||
and the smart auto-player knows the path to victory. You might have a
|
and the smart auto-player knows the path to victory. You might have a
|
||||||
chance to beat the red player in the default mode, though, which is
|
chance to beat the red player in the default mode, though, which is
|
||||||
represented by the "Ok" choice (instead of "Smart") in the "Auto-Play
|
represented by the "Ok" choice (instead of "Smart") in the "Auto-Play
|
||||||
Options" dialog.
|
Options" dialog.
|
||||||
|
|
|
@ -1,62 +1,60 @@
|
||||||
(module gobblet mzscheme
|
#lang mzscheme
|
||||||
(require (lib "unitsig.ss")
|
(require (lib "unitsig.ss")
|
||||||
(only (lib "unit.ss") unit import export)
|
(only (lib "unit.ss") unit import export)
|
||||||
(lib "file.ss")
|
(lib "file.ss")
|
||||||
(lib "mred.ss" "mred")
|
(lib "mred.ss" "mred")
|
||||||
"sig.ss"
|
"sig.ss"
|
||||||
"model.ss"
|
"model.ss"
|
||||||
"gui.ss"
|
"gui.ss"
|
||||||
"heuristics.ss"
|
"heuristics.ss"
|
||||||
"explore.ss"
|
"explore.ss"
|
||||||
"../show-help.ss")
|
"../show-help.ss")
|
||||||
|
|
||||||
(provide game@)
|
(provide game@)
|
||||||
|
|
||||||
(define game@
|
(define game@
|
||||||
(unit
|
(unit (import) (export)
|
||||||
(import)
|
|
||||||
(export)
|
|
||||||
|
|
||||||
(define (make-gobblet-unit size)
|
(define (make-gobblet-unit size)
|
||||||
(compound-unit/sig
|
(compound-unit/sig
|
||||||
(import)
|
(import)
|
||||||
(link [CONFIG : config^ ((unit/sig config^
|
(link [CONFIG : config^
|
||||||
(import)
|
((unit/sig config^ (import)
|
||||||
(define BOARD-SIZE size)))]
|
(define BOARD-SIZE size)))]
|
||||||
[RESTART : restart^ ((unit/sig restart^
|
[RESTART : restart^
|
||||||
(import)
|
((unit/sig restart^ (import)
|
||||||
(define (new-game n)
|
(define (new-game n)
|
||||||
(put-preferences '(gobblet:board-size) (list n) void)
|
(put-preferences '(gobblet:board-size) (list n) void)
|
||||||
(parameterize ([current-eventspace orig-eventspace])
|
(parameterize ([current-eventspace orig-eventspace])
|
||||||
(queue-callback
|
(queue-callback
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(start-gobblet n)))))
|
(start-gobblet n)))))
|
||||||
(define (show-gobblet-help)
|
(define (show-gobblet-help)
|
||||||
(parameterize ([current-eventspace orig-eventspace])
|
(parameterize ([current-eventspace orig-eventspace])
|
||||||
(queue-callback
|
(queue-callback
|
||||||
(lambda ()
|
(lambda ()
|
||||||
(unless help
|
(unless help
|
||||||
(set! help (show-help (list "games" "gobblet")
|
(set! help (show-help (list "games" "gobblet")
|
||||||
"Gobblet Help" #f)))
|
"Gobblet Help" #f)))
|
||||||
(help)))))))]
|
(help)))))))]
|
||||||
[MODEL : model^ (model-unit CONFIG)]
|
[MODEL : model^ (model-unit CONFIG)]
|
||||||
[HEURISTICS : heuristics^ (heuristics-unit CONFIG MODEL EXPLORE)]
|
[HEURISTICS : heuristics^ (heuristics-unit CONFIG MODEL EXPLORE)]
|
||||||
[EXPLORE : explore^ (explore-unit CONFIG MODEL)]
|
[EXPLORE : explore^ (explore-unit CONFIG MODEL)]
|
||||||
[GUI : () (gui-unit CONFIG MODEL RESTART HEURISTICS EXPLORE)])
|
[GUI : () (gui-unit CONFIG MODEL RESTART HEURISTICS EXPLORE)])
|
||||||
(export)))
|
(export)))
|
||||||
|
|
||||||
(define help #f)
|
(define help #f)
|
||||||
|
|
||||||
(define orig-eventspace (current-eventspace))
|
(define orig-eventspace (current-eventspace))
|
||||||
|
|
||||||
(define (start-gobblet board-size)
|
(define (start-gobblet board-size)
|
||||||
;; Start a new game as a child process:
|
;; Start a new game as a child process:
|
||||||
(parameterize ([current-custodian (make-custodian)])
|
(parameterize* ([current-custodian (make-custodian)]
|
||||||
(parameterize ([exit-handler (lambda (v)
|
[exit-handler
|
||||||
(custodian-shutdown-all (current-custodian)))])
|
(lambda (v)
|
||||||
(parameterize ([current-eventspace (make-eventspace)])
|
(custodian-shutdown-all (current-custodian)))]
|
||||||
(queue-callback
|
[current-eventspace (make-eventspace)])
|
||||||
(lambda ()
|
(queue-callback
|
||||||
(invoke-unit/sig (make-gobblet-unit board-size))))))))
|
(lambda () (invoke-unit/sig (make-gobblet-unit board-size))))))
|
||||||
|
|
||||||
(start-gobblet (get-preference 'gobblet:board-size (lambda () 3))))))
|
(start-gobblet (get-preference 'gobblet:board-size (lambda () 3)))))
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
** To play _Go Fish_, run the "Games" application. **
|
** To play Go Fish, run the "PLT Games" application.
|
||||||
|
|
||||||
Go Fish is the children's card game where you try to get rid of all
|
Go Fish is the children's card game where you try to get rid of all
|
||||||
you cards by forming pairs. You play against two computer players.
|
you cards by forming pairs. You play against two computer players.
|
||||||
|
|
||||||
On Each turn, if you have a match in your hand, drag one of the
|
On Each turn, if you have a match in your hand, drag one of the
|
||||||
matching cards to your numbered box, and the match will move into the
|
matching cards to your numbered box, and the match will move into the
|
||||||
|
@ -12,17 +12,17 @@ opponent's area to ask the opponent for a matching card:
|
||||||
|
|
||||||
* If the opponent has a card with the same value as the card that you
|
* If the opponent has a card with the same value as the card that you
|
||||||
drag, the opponent will give you the card, and they'll go into your
|
drag, the opponent will give you the card, and they'll go into your
|
||||||
match area. Drag another card to an opponent.
|
match area. Drag another card to an opponent.
|
||||||
|
|
||||||
* If the opponent has no matching card, the top card on draw pile
|
* If the opponent has no matching card, the top card on draw pile
|
||||||
will move, indicating that you must "Go Fish!". Draw a card by
|
will move, indicating that you must "Go Fish!". Draw a card by
|
||||||
dragging it from the draw pile to your hand. If the drawn card
|
dragging it from the draw pile to your hand. If the drawn card
|
||||||
gives you a match, then the match will automatically move into your
|
gives you a match, then the match will automatically move into your
|
||||||
match area, and it's still your turn (so drag another card to one
|
match area, and it's still your turn (so drag another card to one
|
||||||
of the opponents).
|
of the opponents).
|
||||||
|
|
||||||
The game is over when one player runs out of cards. The winner is the
|
The game is over when one player runs out of cards. The winner is the
|
||||||
one with the most matches.
|
one with the most matches.
|
||||||
|
|
||||||
The status line at the bottom of the window provides instructions as
|
The status line at the bottom of the window provides instructions as
|
||||||
you go. The computer players are not particularly smart.
|
you go. The computer players are not particularly smart.
|
||||||
|
|
|
@ -1,359 +1,329 @@
|
||||||
|
#lang mzscheme
|
||||||
|
(require (lib "cards.ss" "games" "cards")
|
||||||
|
(lib "mred.ss" "mred")
|
||||||
|
(lib "class.ss")
|
||||||
|
(lib "unit.ss")
|
||||||
|
(lib "list.ss"))
|
||||||
|
|
||||||
(module gofish mzscheme
|
(provide game@)
|
||||||
(require (lib "cards.ss" "games" "cards")
|
(define game@ (unit (import) (export)
|
||||||
(lib "mred.ss" "mred")
|
|
||||||
(lib "class.ss")
|
;; Player record
|
||||||
(lib "unit.ss")
|
(define-struct player (r hand-r discard-r count-r ; regions
|
||||||
(lib "list.ss"))
|
hand discarded ; cards
|
||||||
|
tried)) ; memory for simulating players
|
||||||
(provide game@)
|
|
||||||
|
;; Player names
|
||||||
(define game@
|
(define PLAYER-1-NAME "Opponent 1")
|
||||||
(unit
|
(define PLAYER-2-NAME "Opponent 2")
|
||||||
(import)
|
(define YOUR-NAME "You")
|
||||||
(export)
|
|
||||||
|
;; Initial card count
|
||||||
;; Player record
|
(define DEAL-COUNT 7)
|
||||||
(define-struct player (r hand-r discard-r count-r ; regions
|
|
||||||
hand discarded ; cards
|
;; Messages
|
||||||
tried)) ; memory for simulating players
|
(define YOUR-TURN-MESSAGE
|
||||||
|
"Your turn. (Drag a match to your discard box or drag a card to an opponent.)")
|
||||||
;; Player names
|
(define GO-FISH-MESSAGE
|
||||||
(define PLAYER-1-NAME "Opponent 1")
|
"Go Fish! (Drag a card from the center deck to your box.)")
|
||||||
(define PLAYER-2-NAME "Opponent 2")
|
(define MATCH-MESSAGE "Match!")
|
||||||
(define YOUR-NAME "You")
|
(define GAME-OVER-MESSAGE "GAME OVER")
|
||||||
|
|
||||||
;; Initial card count
|
;; Region layout constants
|
||||||
(define DEAL-COUNT 7)
|
(define MARGIN 10)
|
||||||
|
(define SUBMARGIN 10)
|
||||||
;; Messages
|
(define LABEL-H 15)
|
||||||
(define YOUR-TURN-MESSAGE "Your turn. (Drag a match to your discard box or drag a card to an opponent.)")
|
|
||||||
(define GO-FISH-MESSAGE "Go Fish! (Drag a card from the center deck to your box.)")
|
;; Randomize
|
||||||
(define MATCH-MESSAGE "Match!")
|
(random-seed (modulo (current-milliseconds) 10000))
|
||||||
(define GAME-OVER-MESSAGE "GAME OVER")
|
|
||||||
|
;; Set up the table
|
||||||
;; Region layout constants
|
(define t (make-table "Go Fish" 8 4.5))
|
||||||
(define MARGIN 10)
|
(define status-pane (send t create-status-pane))
|
||||||
(define SUBMARGIN 10)
|
(send t add-help-button status-pane '("games" "gofish") "Go Fish Help" #f)
|
||||||
(define LABEL-H 15)
|
(send t show #t)
|
||||||
|
(send t set-double-click-action #f)
|
||||||
;; Randomize
|
(send t set-button-action 'left 'drag-raise/one)
|
||||||
(random-seed (modulo (current-milliseconds) 10000))
|
(send t set-button-action 'middle 'drag/one)
|
||||||
|
(send t set-button-action 'right 'drag/one)
|
||||||
;; Set up the table
|
|
||||||
(define t (make-table "Go Fish" 8 4.5))
|
;; Get table width & height
|
||||||
(define status-pane (send t create-status-pane))
|
(define w (send t table-width))
|
||||||
(send t add-help-button status-pane '("games" "gofish") "Go Fish Help" #f)
|
(define h (send t table-height))
|
||||||
(send t show #t)
|
|
||||||
(send t set-double-click-action #f)
|
;; Set up the cards
|
||||||
(send t set-button-action 'left 'drag-raise/one)
|
(define deck (shuffle-list (make-deck) 7))
|
||||||
(send t set-button-action 'middle 'drag/one)
|
(for-each (lambda (card)
|
||||||
(send t set-button-action 'right 'drag/one)
|
(send card snap-back-after-move #t)
|
||||||
|
(send card user-can-flip #f))
|
||||||
;; Get table width & height
|
deck)
|
||||||
(define w (send t table-width))
|
|
||||||
(define h (send t table-height))
|
;; Function for dealing or drawing cards
|
||||||
|
(define (deal n)
|
||||||
;; Set up the cards
|
(let loop ([n n][d deck])
|
||||||
(define deck (shuffle-list (make-deck) 7))
|
(if (zero? n)
|
||||||
(for-each
|
(begin (set! deck d) null)
|
||||||
(lambda (card)
|
(cons (car d) (loop (sub1 n) (cdr d))))))
|
||||||
(send card snap-back-after-move #t)
|
|
||||||
(send card user-can-flip #f))
|
;; Card width & height
|
||||||
deck)
|
(define cw (send (car deck) card-width))
|
||||||
|
(define ch (send (car deck) card-height))
|
||||||
;; Function for dealing or drawing cards
|
|
||||||
(define (deal n)
|
;; Put the cards on the table
|
||||||
(let loop ([n n][d deck])
|
(send t add-cards deck (/ (- w cw) 2) (- (/ (- h ch) 2) (/ ch 3)))
|
||||||
(if (zero? n)
|
|
||||||
|
;; Player region size
|
||||||
|
(define pw (- (/ (- w cw) 2) (* 2 MARGIN)))
|
||||||
|
(define ph (- (/ (- h (/ ch 3)) 2) (* 2 MARGIN)))
|
||||||
|
|
||||||
|
;; Region-makers
|
||||||
|
(define (make-hand-region r)
|
||||||
|
(define m SUBMARGIN)
|
||||||
|
(make-region (+ m (region-x r)) (+ LABEL-H m (region-y r))
|
||||||
|
(- (region-w r) (* 3 m) cw) (- (region-h r) LABEL-H (* 2 m))
|
||||||
|
#f #f))
|
||||||
|
(define (make-discard-region r)
|
||||||
|
(make-region (- (+ (region-x r) (region-w r)) SUBMARGIN cw)
|
||||||
|
(- (+ (region-y r) (region-h r)) SUBMARGIN ch)
|
||||||
|
cw ch
|
||||||
|
#f #f))
|
||||||
|
(define (make-discard-count-region r c cb)
|
||||||
|
(make-region
|
||||||
|
(- (+ (region-x r) (region-w r)) SUBMARGIN cw (/ SUBMARGIN 2))
|
||||||
|
(- (+ (region-y r) (region-h r)) SUBMARGIN ch LABEL-H (/ SUBMARGIN 2))
|
||||||
|
(+ cw SUBMARGIN) (+ ch LABEL-H SUBMARGIN)
|
||||||
|
(number->string c)
|
||||||
|
cb))
|
||||||
|
|
||||||
|
;; Define the initial regions
|
||||||
|
(define player-1-region
|
||||||
|
(make-region MARGIN MARGIN pw ph PLAYER-1-NAME void))
|
||||||
|
(define player-2-region
|
||||||
|
(make-region (- w MARGIN pw) MARGIN pw ph PLAYER-2-NAME void))
|
||||||
|
(define you-region
|
||||||
|
(make-region MARGIN (- h MARGIN ph) (- w (* 2 MARGIN)) ph YOUR-NAME void))
|
||||||
|
|
||||||
|
;; Player setup
|
||||||
|
(define (create-player r discard-callback)
|
||||||
|
(let ([p (make-player
|
||||||
|
r
|
||||||
|
(make-hand-region r)
|
||||||
|
(make-discard-region r)
|
||||||
|
(make-discard-count-region r 0 discard-callback)
|
||||||
|
(deal DEAL-COUNT)
|
||||||
|
null
|
||||||
|
null)])
|
||||||
|
(send t add-region r)
|
||||||
|
(send t add-region (player-count-r p))
|
||||||
|
(for-each (lambda (card)
|
||||||
|
(send t card-to-front card)) (reverse (player-hand p)))
|
||||||
|
(send t move-cards-to-region (player-hand p) (player-hand-r p))
|
||||||
|
p))
|
||||||
|
|
||||||
|
(define player-1 (create-player player-1-region #f))
|
||||||
|
(define player-2 (create-player player-2-region #f))
|
||||||
|
(define you (create-player you-region
|
||||||
|
;; Dragging to your discard pile checks to see if
|
||||||
|
;; the card makes a match:
|
||||||
|
(lambda (cards)
|
||||||
|
(check-hand you (car cards))
|
||||||
|
(send t set-status YOUR-TURN-MESSAGE))))
|
||||||
|
|
||||||
|
;; More card setup: Opponents's cards and deck initally can't be moved
|
||||||
|
(for-each (lambda (card) (send card user-can-move #f))
|
||||||
|
(append (player-hand player-1) (player-hand player-2) deck))
|
||||||
|
|
||||||
|
;; More card setup: Show your cards
|
||||||
|
(send t flip-cards (player-hand you))
|
||||||
|
|
||||||
|
;; Function to update the display for a player record
|
||||||
|
(define (rearrange-cards p)
|
||||||
|
;; Stack cards in 3D first-to-last
|
||||||
|
(send t stack-cards (player-discarded p))
|
||||||
|
(send t stack-cards (player-hand p))
|
||||||
|
;; Move them to their regions
|
||||||
|
(send t move-cards-to-region (player-discarded p) (player-discard-r p))
|
||||||
|
(send t move-cards-to-region (player-hand p) (player-hand-r p))
|
||||||
|
;; Recreate the counter region to reset the count
|
||||||
|
(send t begin-card-sequence)
|
||||||
|
(send t remove-region (player-count-r p))
|
||||||
|
(set-player-count-r! p (make-discard-count-region
|
||||||
|
(player-r p) (/ (length (player-discarded p)) 2)
|
||||||
|
(region-callback (player-count-r p))))
|
||||||
|
(send t add-region (player-count-r p))
|
||||||
|
(send t end-card-sequence))
|
||||||
|
|
||||||
|
;; Function to search for an equivalent card
|
||||||
|
(define (find-equiv card hand)
|
||||||
|
(ormap (lambda (c)
|
||||||
|
(and (not (eq? c card))
|
||||||
|
(= (send card get-value) (send c get-value))
|
||||||
|
c))
|
||||||
|
hand))
|
||||||
|
|
||||||
|
;; Function to check for a match involving `card' already in the player's hand
|
||||||
|
(define (check-hand player card)
|
||||||
|
(let* ([h (player-hand player)]
|
||||||
|
[found (find-equiv card h)])
|
||||||
|
(if found
|
||||||
|
(begin
|
||||||
|
;; Make sure the matching cards are face-up and pause for the user
|
||||||
|
(send t cards-face-up (list found card))
|
||||||
|
(send t set-status MATCH-MESSAGE)
|
||||||
|
;; The players has a match! Move the card from the player's hand
|
||||||
|
;; to his discard pile
|
||||||
|
(set-player-hand! player (remove* (list card found) h))
|
||||||
|
(set-player-discarded! player
|
||||||
|
(list* found card (player-discarded player)))
|
||||||
|
;; The dicarded cards can no longer be moved
|
||||||
|
(send card user-can-move #f)
|
||||||
|
(send found user-can-move #f)
|
||||||
|
;; Move the cards to their new places
|
||||||
|
(rearrange-cards player)
|
||||||
|
;; Slower
|
||||||
|
#t)
|
||||||
|
#f)))
|
||||||
|
|
||||||
|
;; Function to enable/disable moving your cards
|
||||||
|
(define (enable-your-cards on?)
|
||||||
|
(for-each (lambda (c) (send c user-can-move on?)) (player-hand you)))
|
||||||
|
|
||||||
|
;; Callbacks communicate back to the main loop via these
|
||||||
|
(define something-happened (make-semaphore 1))
|
||||||
|
(define go-fish? #f)
|
||||||
|
|
||||||
|
;; Function for trying to get a card from another player
|
||||||
|
(define (ask-player-for-match getter giver card)
|
||||||
|
(let* ([h (player-hand giver)]
|
||||||
|
[found (find-equiv card h)])
|
||||||
|
(if found
|
||||||
|
(begin
|
||||||
|
;; The giver player has a matching card - give it to the getter
|
||||||
|
(set-player-hand! giver (remq found h))
|
||||||
|
(set-player-hand! getter (cons found (player-hand getter)))
|
||||||
|
;; Make sure the matching cards are face-up and pause for the user
|
||||||
|
(send t cards-face-up (list found card))
|
||||||
|
;; Move the cards around
|
||||||
|
(check-hand getter card)
|
||||||
|
(rearrange-cards giver)
|
||||||
|
#t)
|
||||||
|
;; The giver player doesn't have it - Go Fish!
|
||||||
|
#f)))
|
||||||
|
|
||||||
|
;; Callback for dragging a card to an opponent
|
||||||
|
(define (player-callback player)
|
||||||
|
(lambda (cards)
|
||||||
|
(set! go-fish? (not (ask-player-for-match you player (car cards))))
|
||||||
|
(semaphore-post something-happened)))
|
||||||
|
|
||||||
|
;; Visual info to go fish
|
||||||
|
(define wiggle-top-card
|
||||||
|
(lambda ()
|
||||||
|
(let ([top (car deck)]
|
||||||
|
[x (/ (- w cw) 2)]
|
||||||
|
[y (- (/ (- h ch) 2) (/ ch 3))])
|
||||||
|
(send t move-card top (- x 10) y)
|
||||||
|
(send t move-card top (+ x 10) y)
|
||||||
|
(send t move-card top x y))))
|
||||||
|
|
||||||
|
;; Callback for going fishing
|
||||||
|
(define fishing
|
||||||
|
(lambda (cards)
|
||||||
|
(send t flip-card (car deck))
|
||||||
|
(set-player-hand! you (append (deal 1) (player-hand you)))
|
||||||
|
(rearrange-cards you)
|
||||||
|
(semaphore-post something-happened)))
|
||||||
|
|
||||||
|
;; Function to simulate a player
|
||||||
|
(define (simulate-player player other-player k)
|
||||||
|
;; Try cards in the players hand that haven't been tried
|
||||||
|
(let ([cards-to-try (remq* (player-tried player) (player-hand player))])
|
||||||
|
(if (null? cards-to-try)
|
||||||
|
(begin
|
||||||
|
;; No cards to try. Reset the history and start over
|
||||||
|
(set-player-tried! player null)
|
||||||
|
(simulate-player player other-player k))
|
||||||
|
;; Pick a random card and a random opponent
|
||||||
|
(let ([c (list-ref cards-to-try (random (length cards-to-try)))]
|
||||||
|
[o (list-ref (list you other-player) (random 2))])
|
||||||
|
(set-player-tried! player (cons c (player-tried player)))
|
||||||
|
;; Show you the card-to-ask
|
||||||
|
(send t flip-card c)
|
||||||
|
;; Hilight player-to-ask
|
||||||
|
(send t hilite-region (player-r o))
|
||||||
|
;; Wait a moment
|
||||||
|
(sleep 0.3)
|
||||||
|
;; Unhilight player-to-ask
|
||||||
|
(send t unhilite-region (player-r o))
|
||||||
|
(if (ask-player-for-match player o c)
|
||||||
|
;; Got it - go again
|
||||||
|
(check-done
|
||||||
|
(lambda ()
|
||||||
|
(simulate-player player other-player k)))
|
||||||
|
;; Go fish
|
||||||
|
(begin
|
||||||
|
;; Wait a bit, then turn the asked-for card back over
|
||||||
|
(sleep 0.3)
|
||||||
|
(send t flip-card c)
|
||||||
|
(if (null? deck)
|
||||||
|
;; No more cards; pass
|
||||||
|
(k)
|
||||||
(begin
|
(begin
|
||||||
(set! deck d)
|
;; Draw a card
|
||||||
null)
|
(set-player-hand! player (append (deal 1) (player-hand player)))
|
||||||
(cons (car d) (loop (sub1 n) (cdr d))))))
|
|
||||||
|
|
||||||
;; Card width & height
|
|
||||||
(define cw (send (car deck) card-width))
|
|
||||||
(define ch (send (car deck) card-height))
|
|
||||||
|
|
||||||
;; Put the cards on the table
|
|
||||||
(send t add-cards
|
|
||||||
deck
|
|
||||||
(/ (- w cw) 2)
|
|
||||||
(- (/ (- h ch) 2) (/ ch 3)))
|
|
||||||
|
|
||||||
;; Player region size
|
|
||||||
(define pw (- (/ (- w cw) 2) (* 2 MARGIN)))
|
|
||||||
(define ph (- (/ (- h (/ ch 3)) 2) (* 2 MARGIN)))
|
|
||||||
|
|
||||||
;; Region-makers
|
|
||||||
(define (make-hand-region r)
|
|
||||||
(define m SUBMARGIN)
|
|
||||||
(make-region
|
|
||||||
(+ m (region-x r)) (+ LABEL-H m (region-y r))
|
|
||||||
(- (region-w r) (* 3 m) cw) (- (region-h r) LABEL-H (* 2 m))
|
|
||||||
#f #f))
|
|
||||||
(define (make-discard-region r)
|
|
||||||
(make-region
|
|
||||||
(- (+ (region-x r) (region-w r)) SUBMARGIN cw)
|
|
||||||
(- (+ (region-y r) (region-h r)) SUBMARGIN ch)
|
|
||||||
cw ch
|
|
||||||
#f #f))
|
|
||||||
(define (make-discard-count-region r c cb)
|
|
||||||
(make-region
|
|
||||||
(- (+ (region-x r) (region-w r)) SUBMARGIN cw (/ SUBMARGIN 2))
|
|
||||||
(- (+ (region-y r) (region-h r)) SUBMARGIN ch LABEL-H (/ SUBMARGIN 2))
|
|
||||||
(+ cw SUBMARGIN) (+ ch LABEL-H SUBMARGIN)
|
|
||||||
(number->string c)
|
|
||||||
cb))
|
|
||||||
|
|
||||||
;; Define the initial regions
|
|
||||||
(define player-1-region
|
|
||||||
(make-region
|
|
||||||
MARGIN MARGIN pw ph
|
|
||||||
PLAYER-1-NAME
|
|
||||||
void))
|
|
||||||
(define player-2-region
|
|
||||||
(make-region
|
|
||||||
(- w MARGIN pw) MARGIN pw ph
|
|
||||||
PLAYER-2-NAME
|
|
||||||
void))
|
|
||||||
(define you-region
|
|
||||||
(make-region
|
|
||||||
MARGIN (- h MARGIN ph) (- w (* 2 MARGIN)) ph
|
|
||||||
YOUR-NAME
|
|
||||||
void))
|
|
||||||
|
|
||||||
;; Player setup
|
|
||||||
(define (create-player r discard-callback)
|
|
||||||
(let ([p (make-player
|
|
||||||
r
|
|
||||||
(make-hand-region r)
|
|
||||||
(make-discard-region r)
|
|
||||||
(make-discard-count-region r 0 discard-callback)
|
|
||||||
(deal DEAL-COUNT)
|
|
||||||
null
|
|
||||||
null)])
|
|
||||||
(send t add-region r)
|
|
||||||
(send t add-region (player-count-r p))
|
|
||||||
(for-each (lambda (card) (send t card-to-front card)) (reverse (player-hand p)))
|
|
||||||
(send t move-cards-to-region (player-hand p) (player-hand-r p))
|
|
||||||
p))
|
|
||||||
|
|
||||||
(define player-1 (create-player player-1-region #f))
|
|
||||||
(define player-2 (create-player player-2-region #f))
|
|
||||||
(define you (create-player you-region
|
|
||||||
;; Dragging to your discard pile checks to see if the card
|
|
||||||
;; makes a match:
|
|
||||||
(lambda (cards)
|
|
||||||
(check-hand you (car cards))
|
|
||||||
(send t set-status YOUR-TURN-MESSAGE))))
|
|
||||||
|
|
||||||
;; More card setup: Opponents's cards and deck initally can't be moved
|
|
||||||
(for-each
|
|
||||||
(lambda (card) (send card user-can-move #f))
|
|
||||||
(append
|
|
||||||
(player-hand player-1)
|
|
||||||
(player-hand player-2)
|
|
||||||
deck))
|
|
||||||
|
|
||||||
;; More card setup: Show your cards
|
|
||||||
(send t flip-cards (player-hand you))
|
|
||||||
|
|
||||||
;; Function to update the display for a player record
|
|
||||||
(define (rearrange-cards p)
|
|
||||||
;; Stack cards in 3D first-to-last
|
|
||||||
(send t stack-cards (player-discarded p))
|
|
||||||
(send t stack-cards (player-hand p))
|
|
||||||
;; Move them to their regions
|
|
||||||
(send t move-cards-to-region (player-discarded p) (player-discard-r p))
|
|
||||||
(send t move-cards-to-region (player-hand p) (player-hand-r p))
|
|
||||||
;; Recreate the counter region to reset the count
|
|
||||||
(send t begin-card-sequence)
|
|
||||||
(send t remove-region (player-count-r p))
|
|
||||||
(set-player-count-r! p (make-discard-count-region
|
|
||||||
(player-r p) (/ (length (player-discarded p)) 2)
|
|
||||||
(region-callback (player-count-r p))))
|
|
||||||
(send t add-region (player-count-r p))
|
|
||||||
(send t end-card-sequence))
|
|
||||||
|
|
||||||
;; Function to search for an equivalent card
|
|
||||||
(define (find-equiv card hand)
|
|
||||||
(ormap (lambda (c)
|
|
||||||
(and (not (eq? c card))
|
|
||||||
(= (send card get-value) (send c get-value))
|
|
||||||
c))
|
|
||||||
hand))
|
|
||||||
|
|
||||||
;; Function to check for a match involving `card' already in the player's hand
|
|
||||||
(define (check-hand player card)
|
|
||||||
(let* ([h (player-hand player)]
|
|
||||||
[found (find-equiv card h)])
|
|
||||||
(if found
|
|
||||||
(begin
|
|
||||||
;; Make sure the matching cards are face-up and pause for the user
|
|
||||||
(send t cards-face-up (list found card))
|
|
||||||
(send t set-status MATCH-MESSAGE)
|
|
||||||
;; The players has a match! Move the card from the player's hand
|
|
||||||
;; to his discard pile
|
|
||||||
(set-player-hand! player (remove* (list card found) h))
|
|
||||||
(set-player-discarded! player (cons found
|
|
||||||
(cons card
|
|
||||||
(player-discarded player))))
|
|
||||||
;; The dicarded cards can no longer be moved
|
|
||||||
(send card user-can-move #f)
|
|
||||||
(send found user-can-move #f)
|
|
||||||
;; Move the cards to their new places
|
|
||||||
(rearrange-cards player)
|
(rearrange-cards player)
|
||||||
;; Slower
|
(if (check-hand player (car (player-hand player)))
|
||||||
#t)
|
;; Drew a good card - keep going
|
||||||
#f)))
|
(check-done
|
||||||
|
(lambda ()
|
||||||
;; Function to enable/disable moving your cards
|
(simulate-player player other-player k)))
|
||||||
(define (enable-your-cards on?)
|
;; End of our turn
|
||||||
(for-each (lambda (c) (send c user-can-move on?))
|
(k))))))))))
|
||||||
(player-hand you)))
|
|
||||||
|
;; Function to check for end-of-game
|
||||||
;; Callbacks communicate back to the main loop via these
|
(define (check-done k)
|
||||||
(define something-happened (make-semaphore 1))
|
(if (ormap (lambda (p) (null? (player-hand p))) (list player-1 player-2 you))
|
||||||
(define go-fish? #f)
|
(begin (enable-your-cards #f)
|
||||||
|
(send t set-status GAME-OVER-MESSAGE))
|
||||||
;; Function for trying to get a card from another player
|
(k)))
|
||||||
(define (ask-player-for-match getter giver card)
|
|
||||||
(let* ([h (player-hand giver)]
|
;; Look in opponents' initial hands for matches (Since each player gets 7
|
||||||
[found (find-equiv card h)])
|
;; cards, it's impossible to run out of cards this way)
|
||||||
(if found
|
(define (find-initial-matches player)
|
||||||
(begin
|
(when (ormap (lambda (card) (check-hand player card)) (player-hand player))
|
||||||
;; The giver player has a matching card - give it to the getter
|
;; Found a match in the hand
|
||||||
(set-player-hand! giver (remq found h))
|
(find-initial-matches player)))
|
||||||
(set-player-hand! getter (cons found (player-hand getter)))
|
(find-initial-matches player-1)
|
||||||
;; Make sure the matching cards are face-up and pause for the user
|
(find-initial-matches player-2)
|
||||||
(send t cards-face-up (list found card))
|
|
||||||
;; Move the cards around
|
;; Run the game loop
|
||||||
(check-hand getter card)
|
(let loop ()
|
||||||
(rearrange-cards giver)
|
(set-region-callback! (player-r you) #f)
|
||||||
#t)
|
(set-region-callback! (player-r player-1) (player-callback player-1))
|
||||||
;; The giver player doesn't have it - Go Fish!
|
(set-region-callback! (player-r player-2) (player-callback player-2))
|
||||||
#f)))
|
(send t set-status YOUR-TURN-MESSAGE)
|
||||||
|
(yield something-happened)
|
||||||
;; Callback for dragging a card to an opponent
|
(if go-fish?
|
||||||
(define (player-callback player)
|
(begin
|
||||||
(lambda (cards)
|
(if (if (null? deck)
|
||||||
(set! go-fish? (not (ask-player-for-match you player (car cards))))
|
;; No more cards; pass
|
||||||
(semaphore-post something-happened)))
|
#f
|
||||||
|
;; Draw a card (wait for the user to drag it)
|
||||||
;; Visual info to go fish
|
(begin (send t set-status GO-FISH-MESSAGE)
|
||||||
(define wiggle-top-card
|
(wiggle-top-card)
|
||||||
(lambda ()
|
(enable-your-cards #f)
|
||||||
(let ([top (car deck)]
|
(set-region-callback! (player-r player-1) #f)
|
||||||
[x (/ (- w cw) 2)]
|
(set-region-callback! (player-r player-2) #f)
|
||||||
[y (- (/ (- h ch) 2) (/ ch 3))])
|
(set-region-callback! (player-r you) fishing)
|
||||||
(send t move-card top (- x 10) y)
|
(send (car deck) user-can-move #t)
|
||||||
(send t move-card top (+ x 10) y)
|
(yield something-happened)
|
||||||
(send t move-card top x y))))
|
(enable-your-cards #t)
|
||||||
|
(check-hand you (car (player-hand you)))))
|
||||||
;; Callback for going fishing
|
(check-done loop)
|
||||||
(define fishing
|
(begin (send t set-status PLAYER-1-NAME)
|
||||||
(lambda (cards)
|
(simulate-player
|
||||||
(send t flip-card (car deck))
|
player-1 player-2
|
||||||
(set-player-hand! you (append (deal 1) (player-hand you)))
|
(lambda ()
|
||||||
(rearrange-cards you)
|
(send t set-status PLAYER-2-NAME)
|
||||||
(semaphore-post something-happened)))
|
(simulate-player player-2 player-1 loop))))))
|
||||||
|
(check-done loop)))))
|
||||||
;; Function to simulate a player
|
|
||||||
(define (simulate-player player other-player k)
|
|
||||||
;; Try cards in the players hand that haven't been tried
|
|
||||||
(let ([cards-to-try (remq* (player-tried player) (player-hand player))])
|
|
||||||
(if (null? cards-to-try)
|
|
||||||
(begin
|
|
||||||
;; No cards to try. Reset the history and start over
|
|
||||||
(set-player-tried! player null)
|
|
||||||
(simulate-player player other-player k))
|
|
||||||
;; Pick a random card and a random opponent
|
|
||||||
(let ([c (list-ref cards-to-try (random (length cards-to-try)))]
|
|
||||||
[o (list-ref (list you other-player) (random 2))])
|
|
||||||
(set-player-tried! player (cons c (player-tried player)))
|
|
||||||
;; Show you the card-to-ask
|
|
||||||
(send t flip-card c)
|
|
||||||
;; Hilight player-to-ask
|
|
||||||
(send t hilite-region (player-r o))
|
|
||||||
;; Wait a moment
|
|
||||||
(sleep 0.3)
|
|
||||||
;; Unhilight player-to-ask
|
|
||||||
(send t unhilite-region (player-r o))
|
|
||||||
(if (ask-player-for-match player o c)
|
|
||||||
;; Got it - go again
|
|
||||||
(check-done
|
|
||||||
(lambda ()
|
|
||||||
(simulate-player player other-player k)))
|
|
||||||
;; Go fish
|
|
||||||
(begin
|
|
||||||
;; Wait a bit, then turn the asked-for card back over
|
|
||||||
(sleep 0.3)
|
|
||||||
(send t flip-card c)
|
|
||||||
(if (null? deck)
|
|
||||||
;; No more cards; pass
|
|
||||||
(k)
|
|
||||||
(begin
|
|
||||||
;; Draw a card
|
|
||||||
(set-player-hand! player (append (deal 1) (player-hand player)))
|
|
||||||
(rearrange-cards player)
|
|
||||||
(if (check-hand player (car (player-hand player)))
|
|
||||||
;; Drew a good card - keep going
|
|
||||||
(check-done
|
|
||||||
(lambda ()
|
|
||||||
(simulate-player player other-player k)))
|
|
||||||
;; End of our turn
|
|
||||||
(k))))))))))
|
|
||||||
|
|
||||||
;; Function to check for end-of-game
|
|
||||||
(define (check-done k)
|
|
||||||
(if (ormap (lambda (p) (null? (player-hand p))) (list player-1 player-2 you))
|
|
||||||
(begin
|
|
||||||
(enable-your-cards #f)
|
|
||||||
(send t set-status GAME-OVER-MESSAGE))
|
|
||||||
(k)))
|
|
||||||
|
|
||||||
;; Look in opponents' initial hands for matches
|
|
||||||
;; (Since each player gets 7 cards, it's impossible to run out of cards this way)
|
|
||||||
(define (find-initial-matches player)
|
|
||||||
(when (ormap (lambda (card) (check-hand player card)) (player-hand player))
|
|
||||||
;; Found a match in the hand
|
|
||||||
(find-initial-matches player)))
|
|
||||||
(find-initial-matches player-1)
|
|
||||||
(find-initial-matches player-2)
|
|
||||||
|
|
||||||
;; Run the game loop
|
|
||||||
(let loop ()
|
|
||||||
(set-region-callback! (player-r you) #f)
|
|
||||||
(set-region-callback! (player-r player-1) (player-callback player-1))
|
|
||||||
(set-region-callback! (player-r player-2) (player-callback player-2))
|
|
||||||
(send t set-status YOUR-TURN-MESSAGE)
|
|
||||||
(yield something-happened)
|
|
||||||
(if go-fish?
|
|
||||||
(begin
|
|
||||||
(if (if (null? deck)
|
|
||||||
;; No more cards; pass
|
|
||||||
#f
|
|
||||||
;; Draw a card (wait for the user to drag it)
|
|
||||||
(begin
|
|
||||||
(send t set-status GO-FISH-MESSAGE)
|
|
||||||
(wiggle-top-card)
|
|
||||||
(enable-your-cards #f)
|
|
||||||
(set-region-callback! (player-r player-1) #f)
|
|
||||||
(set-region-callback! (player-r player-2) #f)
|
|
||||||
(set-region-callback! (player-r you) fishing)
|
|
||||||
(send (car deck) user-can-move #t)
|
|
||||||
(yield something-happened)
|
|
||||||
(enable-your-cards #t)
|
|
||||||
(check-hand you (car (player-hand you)))))
|
|
||||||
(check-done loop)
|
|
||||||
(begin
|
|
||||||
(send t set-status PLAYER-1-NAME)
|
|
||||||
(simulate-player
|
|
||||||
player-1 player-2
|
|
||||||
(lambda ()
|
|
||||||
(send t set-status PLAYER-2-NAME)
|
|
||||||
(simulate-player player-2 player-1 loop))))))
|
|
||||||
(check-done loop))))))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,30 +1,29 @@
|
||||||
** To play _Jewel_, run the "Games" application. **
|
** To play Jewel, run the "PLT Games" application.
|
||||||
|
|
||||||
The board is an 8x8 array of jewels of 7 types. You need to get 3 or
|
The board is an 8x8 array of jewels of 7 types. You need to get 3 or
|
||||||
more in a row horizontally or vertically in order to score points. You
|
more in a row horizontally or vertically in order to score points.
|
||||||
can swap any two jewels that are next to each other up and down or
|
You can swap any two jewels that are next to each other up and down or
|
||||||
left and right. The mechanic is to either:
|
left and right. The mechanic is to either:
|
||||||
|
|
||||||
* Click the mouse on the first one, then drag in the direction for
|
* Click the mouse on the first one, then drag in the direction for
|
||||||
the swap.
|
the swap.
|
||||||
|
|
||||||
* Move a bubble using the arrow keys, lock the bubble to a jewel with
|
* Move a bubble using the arrow keys, lock the bubble to a jewel with
|
||||||
the space bar, and the swap the locked jewel with another by using
|
the space bar, and the swap the locked jewel with another by using
|
||||||
the arrow keys. Space unlocks a locked bubble without swapping.
|
the arrow keys. Space unlocks a locked bubble without swapping.
|
||||||
|
|
||||||
Jewels can only be swapped if after the swap there are at least 3 or
|
Jewels can only be swapped if after the swap there are at least 3 or
|
||||||
more same shape or color in a row or column. Otherwise the jewels
|
more same shape or color in a row or column. Otherwise the jewels
|
||||||
return to their original position. There is a clock shown on the
|
return to their original position. There is a clock shown on the
|
||||||
left. When it counts down to 0 the game is over. Getting 3 in a row
|
left. When it counts down to 0 the game is over. Getting 3 in a row
|
||||||
adds time to the clock.
|
adds time to the clock.
|
||||||
|
|
||||||
Hit spacebar to start a new game then select the difficulty number by
|
Hit spacebar to start a new game then select the difficulty number by
|
||||||
pressing '0', '1', '2', '3' or '4'. You can always press 'ESC' to exit.
|
pressing '0', '1', '2', '3' or '4'. You can always press 'ESC' to
|
||||||
During playing press 'p' to pause the game.
|
exit. During playing press 'p' to pause the game.
|
||||||
|
|
||||||
The code is released under the LGPL.
|
The code is released under the LGPL. The code is a conversion of Dave
|
||||||
The code is a conversion of Dave Ashley's C program to Scheme with some
|
Ashley's C program to Scheme with some modifications and enhancements.
|
||||||
modifications and enhancements.
|
|
||||||
|
|
||||||
Enjoy.
|
Enjoy.
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
_Lights Out_
|
** To play Lights Out, run the "PLT Games" application.
|
||||||
|
|
||||||
The object of this game is to turn all of the lights off. Click on a button
|
The object of this game is to turn all of the lights off. Click on a
|
||||||
to turn that light off, but beware it will also toggle the lights above,
|
button to turn that light off, but beware it will also toggle the
|
||||||
below to the left and to the right of that button.
|
lights above, below to the left and to the right of that button.
|
||||||
|
|
||||||
Good luck.
|
Good luck.
|
||||||
|
|
|
@ -1,212 +1,186 @@
|
||||||
(module lights-out mzscheme
|
#lang mzscheme
|
||||||
(require "board.ss"
|
(require "board.ss"
|
||||||
"../show-help.ss"
|
"../show-help.ss"
|
||||||
(lib "mred.ss" "mred")
|
(lib "mred.ss" "mred")
|
||||||
(lib "class.ss")
|
(lib "class.ss")
|
||||||
(lib "unit.ss"))
|
(lib "unit.ss"))
|
||||||
|
|
||||||
(provide game@
|
|
||||||
lights-out^)
|
|
||||||
|
|
||||||
(define-signature lights-out^
|
|
||||||
(init-board))
|
|
||||||
|
|
||||||
(define game@
|
(provide game@ lights-out^)
|
||||||
(unit
|
|
||||||
(import)
|
(define-signature lights-out^ (init-board))
|
||||||
(export lights-out^) ;; : (board -> void) resets the window(s)
|
|
||||||
|
(define game@ (unit (import)
|
||||||
(define frame (make-object frame% "Lights Out"))
|
(export lights-out^) ;; : (board -> void) resets the window(s)
|
||||||
|
|
||||||
(define label-size 30)
|
(define frame (make-object frame% "Lights Out"))
|
||||||
|
|
||||||
(define orange (make-object color% 255 165 0))
|
(define label-size 30)
|
||||||
(define light-orange (make-object color% 255 220 100))
|
|
||||||
|
(define orange (make-object color% 255 165 0))
|
||||||
(define on-pen (send the-pen-list find-or-create-pen orange 1 'solid))
|
(define light-orange (make-object color% 255 220 100))
|
||||||
(define on-brush (send the-brush-list find-or-create-brush orange 'solid))
|
|
||||||
(define off-pen (send the-pen-list find-or-create-pen "BLACK" 1 'solid))
|
(define on-pen (send the-pen-list find-or-create-pen orange 1 'solid))
|
||||||
(define off-brush (send the-brush-list find-or-create-brush "BLACK" 'solid))
|
(define on-brush (send the-brush-list find-or-create-brush orange 'solid))
|
||||||
|
(define off-pen (send the-pen-list find-or-create-pen "BLACK" 1 'solid))
|
||||||
(define dull-on-pen (send the-pen-list find-or-create-pen light-orange 1 'solid))
|
(define off-brush (send the-brush-list find-or-create-brush "BLACK" 'solid))
|
||||||
(define dull-on-brush (send the-brush-list find-or-create-brush light-orange 'solid))
|
|
||||||
(define dull-off-pen (send the-pen-list find-or-create-pen "DARK GRAY" 1 'solid))
|
(define dull-on-pen (send the-pen-list find-or-create-pen light-orange 1 'solid))
|
||||||
(define dull-off-brush (send the-brush-list find-or-create-brush "DARK GRAY" 'solid))
|
(define dull-on-brush (send the-brush-list find-or-create-brush light-orange 'solid))
|
||||||
|
(define dull-off-pen (send the-pen-list find-or-create-pen "DARK GRAY" 1 'solid))
|
||||||
(define (flip-one i j)
|
(define dull-off-brush (send the-brush-list find-or-create-brush "DARK GRAY" 'solid))
|
||||||
|
|
||||||
|
(define (flip-one i j)
|
||||||
|
(when (and (<= 0 i (- (vector-length current-board) 1))
|
||||||
|
(<= 0 j (- (vector-length current-board) 1)))
|
||||||
|
(vector-set!
|
||||||
|
(vector-ref current-board j)
|
||||||
|
i
|
||||||
|
(case (vector-ref (vector-ref current-board j) i)
|
||||||
|
[(x) 'o]
|
||||||
|
[(o) 'x]))))
|
||||||
|
|
||||||
|
(define (flip-surrounding i j)
|
||||||
|
(flip-one i j)
|
||||||
|
(flip-one (- i 1) j)
|
||||||
|
(flip-one i (- j 1))
|
||||||
|
(flip-one (+ i 1) j)
|
||||||
|
(flip-one i (+ j 1)))
|
||||||
|
|
||||||
|
(define current-board #f)
|
||||||
|
(define original-board #f)
|
||||||
|
|
||||||
|
(define board-canvas%
|
||||||
|
(class canvas%
|
||||||
|
(inherit get-dc get-client-size)
|
||||||
|
|
||||||
|
(define/private (get-width) (let-values ([(w h) (get-client-size)]) w))
|
||||||
|
(define/private (get-height) (let-values ([(w h) (get-client-size)]) h))
|
||||||
|
|
||||||
|
[define dull-i 1]
|
||||||
|
[define dull-j 1]
|
||||||
|
[define/private tile->screen
|
||||||
|
(lambda (i j)
|
||||||
|
(let ([x (inexact->exact (floor (* (/ i (vector-length current-board)) (- (get-width) 2))))]
|
||||||
|
[y (inexact->exact (floor (* (/ j (vector-length current-board)) (- (get-height) 2))))]
|
||||||
|
[w (inexact->exact (floor (* (/ (- (get-width) 2) (vector-length current-board)))))]
|
||||||
|
[h (inexact->exact (floor (* (/ (- (get-height) 2) (vector-length current-board)))))])
|
||||||
|
(values (+ x 2)
|
||||||
|
(+ y 2)
|
||||||
|
(max 0 (- w 2))
|
||||||
|
(max 0 (- h 2)))))]
|
||||||
|
[define/private screen->tile
|
||||||
|
(lambda (x y)
|
||||||
|
(values (inexact->exact (floor (* (/ x (get-width)) (vector-length current-board))))
|
||||||
|
(inexact->exact (floor (* (/ y (get-height)) (vector-length current-board))))))]
|
||||||
|
[define/private draw-tile
|
||||||
|
(lambda (dc i j)
|
||||||
(when (and (<= 0 i (- (vector-length current-board) 1))
|
(when (and (<= 0 i (- (vector-length current-board) 1))
|
||||||
(<= 0 j (- (vector-length current-board) 1)))
|
(<= 0 j (- (vector-length current-board) 1)))
|
||||||
(vector-set!
|
(let ([ent (vector-ref (vector-ref current-board j) i)]
|
||||||
(vector-ref current-board j)
|
[dull? (and dull-i
|
||||||
i
|
dull-j
|
||||||
(case (vector-ref (vector-ref current-board j) i)
|
(or (and (= i dull-i) (= j dull-j))
|
||||||
[(x) 'o]
|
(and (= i (- dull-i 1)) (= j dull-j))
|
||||||
[(o) 'x]))))
|
(and (= i (+ dull-i 1)) (= j dull-j))
|
||||||
|
(and (= i dull-i) (= j (- dull-j 1)))
|
||||||
(define (flip-surrounding i j)
|
(and (= i dull-i) (= j (+ dull-j 1)))))])
|
||||||
(flip-one i j)
|
(if dull?
|
||||||
(flip-one (- i 1) j)
|
(if (eq? ent 'x)
|
||||||
(flip-one i (- j 1))
|
(begin (send dc set-pen dull-off-pen)
|
||||||
(flip-one (+ i 1) j)
|
(send dc set-brush dull-off-brush))
|
||||||
(flip-one i (+ j 1)))
|
(begin (send dc set-pen dull-on-pen)
|
||||||
|
(send dc set-brush dull-on-brush)))
|
||||||
(define current-board #f)
|
(if (eq? ent 'x)
|
||||||
(define original-board #f)
|
(begin (send dc set-pen on-pen)
|
||||||
|
(send dc set-brush on-brush))
|
||||||
(define board-canvas%
|
(begin (send dc set-pen off-pen)
|
||||||
(class canvas%
|
(send dc set-brush off-brush)))))
|
||||||
(inherit get-dc get-client-size)
|
(let-values ([(x y w h) (tile->screen i j)])
|
||||||
|
(send dc draw-rectangle x y w h))))]
|
||||||
(define/private (get-width) (let-values ([(w h) (get-client-size)]) w))
|
[define/private get-changed
|
||||||
(define/private (get-height) (let-values ([(w h) (get-client-size)]) h))
|
(lambda (x y)
|
||||||
|
(if (and x y)
|
||||||
[define dull-i 1]
|
(list (cons x y)
|
||||||
[define dull-j 1]
|
(cons (+ x 1) y)
|
||||||
[define/private tile->screen
|
(cons (- x 1) y)
|
||||||
(lambda (i j)
|
(cons x (- y 1))
|
||||||
(let ([x (inexact->exact (floor (* (/ i (vector-length current-board)) (- (get-width) 2))))]
|
(cons x (+ y 1)))
|
||||||
[y (inexact->exact (floor (* (/ j (vector-length current-board)) (- (get-height) 2))))]
|
null))]
|
||||||
[w (inexact->exact (floor (* (/ (- (get-width) 2) (vector-length current-board)))))]
|
[define/public redraw
|
||||||
[h (inexact->exact (floor (* (/ (- (get-height) 2) (vector-length current-board)))))])
|
(lambda ()
|
||||||
(values (+ x 2)
|
(let* ([dc (get-dc)])
|
||||||
(+ y 2)
|
(let loop ([j (vector-length current-board)])
|
||||||
(max 0 (- w 2))
|
(if (zero? j)
|
||||||
(max 0 (- h 2)))))]
|
(void)
|
||||||
[define/private screen->tile
|
(begin (let loop ([i (vector-length current-board)])
|
||||||
(lambda (x y)
|
(if (zero? i)
|
||||||
(values (inexact->exact (floor (* (/ x (get-width)) (vector-length current-board))))
|
(void)
|
||||||
(inexact->exact (floor (* (/ y (get-height)) (vector-length current-board))))))]
|
(begin (draw-tile dc (- i 1) (- j 1))
|
||||||
[define/private draw-tile
|
(loop (- i 1)))))
|
||||||
(lambda (dc i j)
|
(loop (- j 1)))))))]
|
||||||
(when (and (<= 0 i (- (vector-length current-board) 1))
|
|
||||||
(<= 0 j (- (vector-length current-board) 1)))
|
[define/override on-event
|
||||||
(let ([ent (vector-ref (vector-ref current-board j) i)]
|
(lambda (evt)
|
||||||
[dull? (and dull-i
|
(cond
|
||||||
dull-j
|
[(send evt button-up?)
|
||||||
(or (and (= i dull-i)
|
(let-values ([(x y) (screen->tile (send evt get-x) (send evt get-y))])
|
||||||
(= j dull-j))
|
(flip-surrounding x y)
|
||||||
(and (= i (- dull-i 1))
|
(redraw))]
|
||||||
(= j dull-j))
|
[(send evt leaving?)
|
||||||
(and (= i (+ dull-i 1))
|
(let ([changed (get-changed dull-i dull-j)])
|
||||||
(= j dull-j))
|
(set! dull-i #f)
|
||||||
(and (= i dull-i)
|
(set! dull-j #f)
|
||||||
(= j (- dull-j 1)))
|
(for-each (lambda (pair) (draw-tile (get-dc) (car pair) (cdr pair)))
|
||||||
(and (= i dull-i)
|
changed))]
|
||||||
(= j (+ dull-j 1)))))])
|
[(send evt moving?)
|
||||||
(if dull?
|
(let ([changed-one (get-changed dull-i dull-j)])
|
||||||
(if (eq? ent 'x)
|
(let-values ([(x y) (screen->tile (send evt get-x) (send evt get-y))])
|
||||||
(begin
|
(set! dull-i x)
|
||||||
(send dc set-pen dull-off-pen)
|
(set! dull-j y))
|
||||||
(send dc set-brush dull-off-brush))
|
(let ([changed-two (get-changed dull-i dull-j)])
|
||||||
(begin
|
(for-each (lambda (pair) (draw-tile (get-dc) (car pair) (cdr pair)))
|
||||||
(send dc set-pen dull-on-pen)
|
(append changed-one changed-two))))]
|
||||||
(send dc set-brush dull-on-brush)))
|
[else (void)]))]
|
||||||
(if (eq? ent 'x)
|
[define/override on-paint
|
||||||
(begin
|
(lambda ()
|
||||||
(send dc set-pen on-pen)
|
(send (get-dc) clear)
|
||||||
(send dc set-brush on-brush))
|
(redraw))]
|
||||||
(begin
|
(super-instantiate () (parent frame))))
|
||||||
(send dc set-pen off-pen)
|
|
||||||
(send dc set-brush off-brush)))))
|
(define board-canvas (make-object board-canvas%))
|
||||||
(let-values ([(x y w h) (tile->screen i j)])
|
(send board-canvas min-width 100)
|
||||||
(send dc draw-rectangle x y w h))))]
|
(send board-canvas min-height 100)
|
||||||
[define/private get-changed
|
|
||||||
(lambda (x y)
|
(define (copy-board board)
|
||||||
(if (and x y)
|
(list->vector
|
||||||
(list (cons x y)
|
(map (lambda (x) (list->vector (vector->list x)))
|
||||||
(cons (+ x 1) y)
|
(vector->list board))))
|
||||||
(cons (- x 1) y)
|
|
||||||
(cons x (- y 1))
|
(define (init-board new-board)
|
||||||
(cons x (+ y 1)))
|
(set! current-board new-board)
|
||||||
null))]
|
(set! original-board (copy-board new-board))
|
||||||
[define/public redraw
|
(send board-canvas on-paint))
|
||||||
(lambda ()
|
|
||||||
(let* ([dc (get-dc)])
|
(define button-panel (make-object horizontal-panel% frame))
|
||||||
(let loop ([j (vector-length current-board)])
|
|
||||||
(cond
|
(make-object button% "New" button-panel
|
||||||
[(zero? j) (void)]
|
(lambda x
|
||||||
[else
|
(let ([res (new-board)])
|
||||||
(let loop ([i (vector-length current-board)])
|
(when res
|
||||||
(cond
|
(init-board res)))))
|
||||||
[(zero? i) (void)]
|
|
||||||
[else
|
(make-object button% "Reset" button-panel
|
||||||
(draw-tile dc
|
(lambda x
|
||||||
(- i 1)
|
(init-board original-board)))
|
||||||
(- j 1))
|
|
||||||
(loop (- i 1))]))
|
(let ([help (show-help (list "games" "lights-out") "Lights Out Help")])
|
||||||
(loop (- j 1))]))))]
|
(make-object button% "Help" button-panel (lambda x (help))))
|
||||||
|
|
||||||
[define/override on-event
|
(make-object grow-box-spacer-pane% button-panel)
|
||||||
(lambda (evt)
|
(send button-panel stretchable-height #f)
|
||||||
(cond
|
|
||||||
[(send evt button-up?)
|
(init-board (random-board
|
||||||
(let-values ([(x y) (screen->tile (send evt get-x) (send evt get-y))])
|
(+ 3 (random 2) (random 2) (random 2) (random 2) (random 2))))
|
||||||
(flip-surrounding x y)
|
;; (send frame stretchable-width #f)
|
||||||
(redraw))]
|
;; (send frame stretchable-height #f)
|
||||||
[(send evt leaving?)
|
(send frame show #t)))
|
||||||
(let ([changed (get-changed dull-i dull-j)])
|
|
||||||
(set! dull-i #f)
|
|
||||||
(set! dull-j #f)
|
|
||||||
(for-each (lambda (pair) (draw-tile (get-dc) (car pair) (cdr pair)))
|
|
||||||
changed))]
|
|
||||||
[(send evt moving?)
|
|
||||||
(let ([changed-one (get-changed dull-i dull-j)])
|
|
||||||
(let-values ([(x y) (screen->tile (send evt get-x) (send evt get-y))])
|
|
||||||
(set! dull-i x)
|
|
||||||
(set! dull-j y))
|
|
||||||
(let ([changed-two (get-changed dull-i dull-j)])
|
|
||||||
(for-each (lambda (pair) (draw-tile (get-dc) (car pair) (cdr pair)))
|
|
||||||
(append changed-one changed-two))))]
|
|
||||||
[else (void)]))]
|
|
||||||
[define/override on-paint
|
|
||||||
(lambda ()
|
|
||||||
(send (get-dc) clear)
|
|
||||||
(redraw))]
|
|
||||||
(super-instantiate ()
|
|
||||||
(parent frame))))
|
|
||||||
|
|
||||||
(define board-canvas (make-object board-canvas%))
|
|
||||||
(send board-canvas min-width 100)
|
|
||||||
(send board-canvas min-height 100)
|
|
||||||
|
|
||||||
(define (copy-board board)
|
|
||||||
(list->vector
|
|
||||||
(map (lambda (x) (list->vector (vector->list x)))
|
|
||||||
(vector->list board))))
|
|
||||||
|
|
||||||
(define (init-board new-board)
|
|
||||||
(set! current-board new-board)
|
|
||||||
(set! original-board (copy-board new-board))
|
|
||||||
(send board-canvas on-paint))
|
|
||||||
|
|
||||||
(define button-panel (make-object horizontal-panel% frame))
|
|
||||||
|
|
||||||
(make-object button% "New" button-panel
|
|
||||||
(lambda x
|
|
||||||
(let ([res (new-board)])
|
|
||||||
(when res
|
|
||||||
(init-board res)))))
|
|
||||||
|
|
||||||
(make-object button% "Reset" button-panel
|
|
||||||
(lambda x
|
|
||||||
(init-board original-board)))
|
|
||||||
|
|
||||||
(let ([help (show-help
|
|
||||||
(list "games" "lights-out")
|
|
||||||
"Lights Out Help")])
|
|
||||||
(make-object button% "Help" button-panel
|
|
||||||
(lambda x
|
|
||||||
(help))))
|
|
||||||
|
|
||||||
(make-object grow-box-spacer-pane% button-panel)
|
|
||||||
(send button-panel stretchable-height #f)
|
|
||||||
|
|
||||||
(init-board (random-board (+ 3
|
|
||||||
(random 2)
|
|
||||||
(random 2)
|
|
||||||
(random 2)
|
|
||||||
(random 2)
|
|
||||||
(random 2))))
|
|
||||||
;(send frame stretchable-width #f)
|
|
||||||
;(send frame stretchable-height #f)
|
|
||||||
(send frame show #t))))
|
|
||||||
|
|
|
@ -1,176 +1,164 @@
|
||||||
|
#lang mzscheme
|
||||||
|
(require (lib "cards.ss" "games" "cards")
|
||||||
|
(lib "mred.ss" "mred")
|
||||||
|
(lib "class.ss")
|
||||||
|
(lib "unit.ss")
|
||||||
|
(lib "list.ss"))
|
||||||
|
|
||||||
(module memory mzscheme
|
(provide game@)
|
||||||
(require (lib "cards.ss" "games" "cards")
|
|
||||||
(lib "mred.ss" "mred")
|
|
||||||
(lib "class.ss")
|
|
||||||
(lib "unit.ss")
|
|
||||||
(lib "list.ss"))
|
|
||||||
|
|
||||||
(provide game@)
|
|
||||||
|
|
||||||
(define game@
|
|
||||||
(unit
|
|
||||||
(import)
|
|
||||||
(export)
|
|
||||||
|
|
||||||
;; Layout width and height:
|
(define game@ (unit (import) (export)
|
||||||
(define WIDTH 5)
|
|
||||||
(define HEIGHT 4)
|
|
||||||
(define MAX-MATCHES (/ (* WIDTH HEIGHT) 2))
|
|
||||||
|
|
||||||
;; Randomize
|
|
||||||
(random-seed (modulo (current-milliseconds) 10000))
|
|
||||||
|
|
||||||
;; Set up the table
|
|
||||||
(define t (make-table "Memory" (+ 2 WIDTH) (+ 1 HEIGHT)))
|
|
||||||
(send t show #t)
|
|
||||||
(send t set-double-click-action #f)
|
|
||||||
|
|
||||||
;; Get table width & height
|
|
||||||
(define w (send t table-width))
|
|
||||||
(define h (send t table-height))
|
|
||||||
|
|
||||||
;; Set up the cards
|
|
||||||
(define deck
|
|
||||||
(let ([cards (map (lambda (name value)
|
|
||||||
(let ([bm (make-object bitmap%
|
|
||||||
(build-path
|
|
||||||
(collection-path "games" "memory" "images")
|
|
||||||
(format "~a.png" name)))])
|
|
||||||
(make-card bm #f 0 value)))
|
|
||||||
'("club" "heart" "spade" "diamond"
|
|
||||||
"happy" "unhappy"
|
|
||||||
"fish" "two-fish"
|
|
||||||
"jack" "star")
|
|
||||||
'(1 2 3 4 5 6 7 8 9 10))])
|
|
||||||
(append cards (map (lambda (c) (send c copy)) cards))))
|
|
||||||
(for-each
|
|
||||||
(lambda (card)
|
|
||||||
(send card user-can-move #f)
|
|
||||||
(send card user-can-flip #t))
|
|
||||||
deck)
|
|
||||||
|
|
||||||
;; Card width & height
|
;; Layout width and height:
|
||||||
(define cw (send (car deck) card-width))
|
(define WIDTH 5)
|
||||||
(define ch (send (car deck) card-height))
|
(define HEIGHT 4)
|
||||||
|
(define MAX-MATCHES (/ (* WIDTH HEIGHT) 2))
|
||||||
(define dx (/ cw (+ 2 WIDTH)))
|
|
||||||
(define dy (/ ch (+ 1 HEIGHT)))
|
|
||||||
|
|
||||||
(define match-x (- w cw dx))
|
|
||||||
(define match-y dy)
|
|
||||||
|
|
||||||
(define time-h (+ 12 5 5))
|
;; Randomize
|
||||||
(define time-x match-x)
|
(random-seed (modulo (current-milliseconds) 10000))
|
||||||
(define time-y (+ ch dy dy))
|
|
||||||
|
|
||||||
;; Put the cards on the table
|
;; Set up the table
|
||||||
(send t add-cards deck match-x match-y)
|
(define t (make-table "Memory" (+ 2 WIDTH) (+ 1 HEIGHT)))
|
||||||
|
(send t show #t)
|
||||||
|
(send t set-double-click-action #f)
|
||||||
|
|
||||||
;; Setup
|
;; Get table width & height
|
||||||
(define (setup)
|
(define w (send t table-width))
|
||||||
(reset-timer)
|
(define h (send t table-height))
|
||||||
(set! deck (shuffle-list deck 7))
|
|
||||||
(send t stack-cards deck)
|
|
||||||
(send t move-cards deck 0 0
|
|
||||||
(lambda (pos)
|
|
||||||
(let ([i (modulo pos WIDTH)]
|
|
||||||
[j (quotient pos WIDTH)])
|
|
||||||
(values (+ dx (* i (+ cw dx)))
|
|
||||||
(+ dy (* j (+ ch dy))))))))
|
|
||||||
|
|
||||||
;; Number of matches found so far:
|
;; Set up the cards
|
||||||
(define matches 0)
|
(define deck
|
||||||
|
(let ([cards (map (lambda (name value)
|
||||||
|
(let ([bm (make-object
|
||||||
|
bitmap%
|
||||||
|
(build-path
|
||||||
|
(collection-path "games" "memory" "images")
|
||||||
|
(format "~a.png" name)))])
|
||||||
|
(make-card bm #f 0 value)))
|
||||||
|
'("club" "heart" "spade" "diamond"
|
||||||
|
"happy" "unhappy"
|
||||||
|
"fish" "two-fish"
|
||||||
|
"jack" "star")
|
||||||
|
'(1 2 3 4 5 6 7 8 9 10))])
|
||||||
|
(append cards (map (lambda (c) (send c copy)) cards))))
|
||||||
|
(for-each (lambda (card)
|
||||||
|
(send card user-can-move #f)
|
||||||
|
(send card user-can-flip #t))
|
||||||
|
deck)
|
||||||
|
|
||||||
;; First card flipped, or #f if non flipped, yet
|
;; Card width & height
|
||||||
(define card-1 #f)
|
(define cw (send (car deck) card-width))
|
||||||
|
(define ch (send (car deck) card-height))
|
||||||
|
|
||||||
(define (flip-and-match c)
|
(define dx (/ cw (+ 2 WIDTH)))
|
||||||
(cond
|
(define dy (/ ch (+ 1 HEIGHT)))
|
||||||
[(eq? c card-1)
|
|
||||||
;; Cancel first card
|
|
||||||
(send t flip-card c)
|
|
||||||
(set! card-1 #f)]
|
|
||||||
[(not (send c face-down?))
|
|
||||||
;; Can't click a matched card, unless the game is over,
|
|
||||||
;; in which case we reset the game
|
|
||||||
(when (= matches MAX-MATCHES)
|
|
||||||
(send t flip-cards deck)
|
|
||||||
(set! matches 0)
|
|
||||||
(setup))]
|
|
||||||
[else
|
|
||||||
;; Flip over a card...
|
|
||||||
(send t flip-card c)
|
|
||||||
(send t card-to-front c)
|
|
||||||
(run-timer)
|
|
||||||
(cond
|
|
||||||
[(not card-1)
|
|
||||||
;; That was the first card
|
|
||||||
(set! card-1 c)]
|
|
||||||
[(and (equal? (send card-1 get-value)
|
|
||||||
(send c get-value))
|
|
||||||
(equal? (send card-1 get-suit)
|
|
||||||
(send c get-suit)))
|
|
||||||
;; Match
|
|
||||||
(send t pause 0.5)
|
|
||||||
(send t move-cards (list card-1 c) match-x match-y)
|
|
||||||
(set! card-1 #f)
|
|
||||||
(set! matches (add1 matches))]
|
|
||||||
[else
|
|
||||||
;; Not a match
|
|
||||||
(send t pause 0.5)
|
|
||||||
(send t flip-cards (list card-1 c))
|
|
||||||
(set! card-1 #f)])]))
|
|
||||||
(send t set-single-click-action flip-and-match)
|
|
||||||
|
|
||||||
;; The timer turns out to be the most difficult part:
|
|
||||||
(define (make-time-region secs)
|
|
||||||
(make-region time-x time-y cw time-h
|
|
||||||
(if (>= secs 6000)
|
|
||||||
"XX:XX"
|
|
||||||
(format
|
|
||||||
"~a:~a"
|
|
||||||
(substring (number->string (+ 100 (quotient secs 60))) 1)
|
|
||||||
(substring (number->string (+ 100 (modulo secs 60))) 1)))
|
|
||||||
#f))
|
|
||||||
(define start-time #f) ; in inexact milliseconds; #f means not started
|
|
||||||
(define shown-seconds 0) ; used to compute the delay until the next update
|
|
||||||
(define time-region (make-time-region 0)) ; old region, so we wan remove it
|
|
||||||
(send t add-region time-region) ; start with the initial region added
|
|
||||||
(define (show-time n)
|
|
||||||
;; Compute new time to show:
|
|
||||||
(set! shown-seconds n)
|
|
||||||
;; Update the time by removing the old region and adding a new one:
|
|
||||||
(send t begin-card-sequence)
|
|
||||||
(send t remove-region time-region)
|
|
||||||
(set! time-region (make-time-region shown-seconds))
|
|
||||||
(send t add-region time-region)
|
|
||||||
(send t end-card-sequence))
|
|
||||||
(define (get-update-delta)
|
|
||||||
;; Figure out how many milliseconds to sleep before the next update
|
|
||||||
(max 0
|
|
||||||
(inexact->exact
|
|
||||||
(floor
|
|
||||||
(- (+ start-time (* 1000 shown-seconds) 1000)
|
|
||||||
(current-inexact-milliseconds))))))
|
|
||||||
(define time-timer
|
|
||||||
(make-object timer% (lambda ()
|
|
||||||
(unless (= matches MAX-MATCHES)
|
|
||||||
(show-time
|
|
||||||
(inexact->exact
|
|
||||||
(floor (/ (- (current-inexact-milliseconds)
|
|
||||||
start-time)
|
|
||||||
1000))))
|
|
||||||
(send time-timer start (get-update-delta) #t)))))
|
|
||||||
(define (reset-timer)
|
|
||||||
(send time-timer stop)
|
|
||||||
(set! start-time #f)
|
|
||||||
(show-time 0))
|
|
||||||
(define (run-timer)
|
|
||||||
(unless start-time
|
|
||||||
(set! start-time (current-inexact-milliseconds))
|
|
||||||
(send time-timer start 1000 #t)))
|
|
||||||
|
|
||||||
;; Start the game:
|
(define match-x (- w cw dx))
|
||||||
(send t pause 0.25)
|
(define match-y dy)
|
||||||
(setup))))
|
|
||||||
|
(define time-h (+ 12 5 5))
|
||||||
|
(define time-x match-x)
|
||||||
|
(define time-y (+ ch dy dy))
|
||||||
|
|
||||||
|
;; Put the cards on the table
|
||||||
|
(send t add-cards deck match-x match-y)
|
||||||
|
|
||||||
|
;; Setup
|
||||||
|
(define (setup)
|
||||||
|
(reset-timer)
|
||||||
|
(set! deck (shuffle-list deck 7))
|
||||||
|
(send t stack-cards deck)
|
||||||
|
(send t move-cards deck 0 0
|
||||||
|
(lambda (pos)
|
||||||
|
(let ([i (modulo pos WIDTH)]
|
||||||
|
[j (quotient pos WIDTH)])
|
||||||
|
(values (+ dx (* i (+ cw dx)))
|
||||||
|
(+ dy (* j (+ ch dy))))))))
|
||||||
|
|
||||||
|
;; Number of matches found so far:
|
||||||
|
(define matches 0)
|
||||||
|
|
||||||
|
;; First card flipped, or #f if non flipped, yet
|
||||||
|
(define card-1 #f)
|
||||||
|
|
||||||
|
(define (flip-and-match c)
|
||||||
|
(cond [(eq? c card-1)
|
||||||
|
;; Cancel first card
|
||||||
|
(send t flip-card c)
|
||||||
|
(set! card-1 #f)]
|
||||||
|
[(not (send c face-down?))
|
||||||
|
;; Can't click a matched card, unless the game is over,
|
||||||
|
;; in which case we reset the game
|
||||||
|
(when (= matches MAX-MATCHES)
|
||||||
|
(send t flip-cards deck)
|
||||||
|
(set! matches 0)
|
||||||
|
(setup))]
|
||||||
|
[else
|
||||||
|
;; Flip over a card...
|
||||||
|
(send t flip-card c)
|
||||||
|
(send t card-to-front c)
|
||||||
|
(run-timer)
|
||||||
|
(cond [(not card-1)
|
||||||
|
;; That was the first card
|
||||||
|
(set! card-1 c)]
|
||||||
|
[(and (equal? (send card-1 get-value) (send c get-value))
|
||||||
|
(equal? (send card-1 get-suit) (send c get-suit)))
|
||||||
|
;; Match
|
||||||
|
(send t pause 0.5)
|
||||||
|
(send t move-cards (list card-1 c) match-x match-y)
|
||||||
|
(set! card-1 #f)
|
||||||
|
(set! matches (add1 matches))]
|
||||||
|
[else
|
||||||
|
;; Not a match
|
||||||
|
(send t pause 0.5)
|
||||||
|
(send t flip-cards (list card-1 c))
|
||||||
|
(set! card-1 #f)])]))
|
||||||
|
(send t set-single-click-action flip-and-match)
|
||||||
|
|
||||||
|
;; The timer turns out to be the most difficult part:
|
||||||
|
(define (make-time-region secs)
|
||||||
|
(make-region time-x time-y cw time-h
|
||||||
|
(if (>= secs 6000)
|
||||||
|
"XX:XX"
|
||||||
|
(format
|
||||||
|
"~a:~a"
|
||||||
|
(substring (number->string (+ 100 (quotient secs 60))) 1)
|
||||||
|
(substring (number->string (+ 100 (modulo secs 60))) 1)))
|
||||||
|
#f))
|
||||||
|
(define start-time #f) ; in inexact milliseconds; #f means not started
|
||||||
|
(define shown-seconds 0) ; used to compute the delay until the next update
|
||||||
|
(define time-region (make-time-region 0)) ; old region, so we wan remove it
|
||||||
|
(send t add-region time-region) ; start with the initial region added
|
||||||
|
(define (show-time n)
|
||||||
|
;; Compute new time to show:
|
||||||
|
(set! shown-seconds n)
|
||||||
|
;; Update the time by removing the old region and adding a new one:
|
||||||
|
(send t begin-card-sequence)
|
||||||
|
(send t remove-region time-region)
|
||||||
|
(set! time-region (make-time-region shown-seconds))
|
||||||
|
(send t add-region time-region)
|
||||||
|
(send t end-card-sequence))
|
||||||
|
(define (get-update-delta)
|
||||||
|
;; Figure out how many milliseconds to sleep before the next update
|
||||||
|
(max 0 (inexact->exact (floor (- (+ start-time (* 1000 shown-seconds) 1000)
|
||||||
|
(current-inexact-milliseconds))))))
|
||||||
|
(define time-timer
|
||||||
|
(make-object timer%
|
||||||
|
(lambda ()
|
||||||
|
(unless (= matches MAX-MATCHES)
|
||||||
|
(show-time
|
||||||
|
(inexact->exact
|
||||||
|
(floor (/ (- (current-inexact-milliseconds) start-time) 1000))))
|
||||||
|
(send time-timer start (get-update-delta) #t)))))
|
||||||
|
(define (reset-timer)
|
||||||
|
(send time-timer stop)
|
||||||
|
(set! start-time #f)
|
||||||
|
(show-time 0))
|
||||||
|
(define (run-timer)
|
||||||
|
(unless start-time
|
||||||
|
(set! start-time (current-inexact-milliseconds))
|
||||||
|
(send time-timer start 1000 #t)))
|
||||||
|
|
||||||
|
;; Start the game:
|
||||||
|
(send t pause 0.25)
|
||||||
|
(setup)))
|
||||||
|
|
|
@ -3,507 +3,475 @@
|
||||||
|
|
||||||
;;;;;;;;;;;;;;;;; Configuration ;;;;;;;;;;;;;;;;;;
|
;;;;;;;;;;;;;;;;; Configuration ;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
(module mines mzscheme
|
#lang mzscheme
|
||||||
|
|
||||||
(require (lib "etc.ss") ; defines build-vector
|
|
||||||
(lib "class.ss")
|
|
||||||
(lib "unit.ss")
|
|
||||||
(lib "mred.ss" "mred")
|
|
||||||
(lib "include-bitmap.ss" "mrlib"))
|
|
||||||
|
|
||||||
(provide game@)
|
|
||||||
|
|
||||||
;; Layout constants
|
(require (lib "etc.ss") ; defines build-vector
|
||||||
(define TILE-HW 24) ; height/width of a tile
|
(lib "class.ss")
|
||||||
(define B-WIDTH 16) ; number of tiles across
|
(lib "unit.ss")
|
||||||
(define B-HEIGHT 16) ; number of tiles down
|
(lib "mred.ss" "mred")
|
||||||
(define THE-BOMB-COUNT 30) ; number of bombs to hide
|
(lib "include-bitmap.ss" "mrlib"))
|
||||||
|
|
||||||
;; Bitmap constants
|
|
||||||
(define tile-bm (include-bitmap "images/tile.png"))
|
|
||||||
(define lclick-bm (include-bitmap "images/lclick-tile.png"))
|
|
||||||
(define rclick-bm (include-bitmap "images/rclick-tile.png"))
|
|
||||||
(define local-bm (include-bitmap "images/local-tile.png"))
|
|
||||||
(define near-bm (include-bitmap "images/near-tile.png"))
|
|
||||||
(define bomb-bm (include-bitmap "images/bomb.png"))
|
|
||||||
(define explode-bm (include-bitmap "images/explode.png"))
|
|
||||||
(define flag-bm (include-bitmap "images/flag.png"))
|
|
||||||
|
|
||||||
(define DIGIT-COLOR-NAMES
|
(provide game@)
|
||||||
;; 0th is background; 8th is foreground
|
|
||||||
(vector "WHITE" "BLUE" "FORESTGREEN" "RED" "PURPLE"
|
|
||||||
"ORANGE" "YELLOW" "BROWN" "BLACK"))
|
|
||||||
|
|
||||||
(define DIGIT-COLORS
|
|
||||||
(build-vector 9 (lambda (i)
|
|
||||||
(send the-color-database find-color
|
|
||||||
(vector-ref DIGIT-COLOR-NAMES i)))))
|
|
||||||
|
|
||||||
(define BG-COLOR (vector-ref DIGIT-COLORS 0))
|
|
||||||
(define FG-COLOR (vector-ref DIGIT-COLORS 8))
|
|
||||||
|
|
||||||
(define BLACK-COLOR (send the-color-database find-color "BLACK"))
|
;; Layout constants
|
||||||
|
(define TILE-HW 24) ; height/width of a tile
|
||||||
(define BG-PEN (make-object pen% BG-COLOR 1 'solid))
|
(define B-WIDTH 16) ; number of tiles across
|
||||||
(define FG-PEN (make-object pen% FG-COLOR 1 'solid))
|
(define B-HEIGHT 16) ; number of tiles down
|
||||||
|
(define THE-BOMB-COUNT 30) ; number of bombs to hide
|
||||||
;; A function for looping over numbers:
|
|
||||||
(define (step-while first test until f accum init)
|
;; Bitmap constants
|
||||||
(let loop ([n first][a init])
|
(define tile-bm (include-bitmap "images/tile.png"))
|
||||||
(if (test n until)
|
(define lclick-bm (include-bitmap "images/lclick-tile.png"))
|
||||||
(loop (add1 n) (accum a (f n)))
|
(define rclick-bm (include-bitmap "images/rclick-tile.png"))
|
||||||
a)))
|
(define local-bm (include-bitmap "images/local-tile.png"))
|
||||||
|
(define near-bm (include-bitmap "images/near-tile.png"))
|
||||||
;; The rest of the game is implemented in a unit so it can be started multiple times
|
(define bomb-bm (include-bitmap "images/bomb.png"))
|
||||||
(define game@
|
(define explode-bm (include-bitmap "images/explode.png"))
|
||||||
(unit
|
(define flag-bm (include-bitmap "images/flag.png"))
|
||||||
(import)
|
|
||||||
(export)
|
(define DIGIT-COLOR-NAMES
|
||||||
|
;; 0th is background; 8th is foreground
|
||||||
;; ;;;;;;;;;;;;;;; Tiles ;;;;;;;;;;;;;;;;;;
|
(vector "WHITE" "BLUE" "FORESTGREEN" "RED" "PURPLE"
|
||||||
|
"ORANGE" "YELLOW" "BROWN" "BLACK"))
|
||||||
;; Class for a tile object
|
|
||||||
(define tile:plain%
|
(define DIGIT-COLORS
|
||||||
(class object%
|
(build-vector 9 (lambda (i)
|
||||||
(define state 'covered) ; 'covered, 'flagged, 'semi-flagged, or 'uncovered
|
(send the-color-database find-color
|
||||||
(define neighbor-bomb-count 0) ; 0 to 8
|
(vector-ref DIGIT-COLOR-NAMES i)))))
|
||||||
(define area-hilite 'none) ; 'none, 'local, 'near
|
|
||||||
|
(define BG-COLOR (vector-ref DIGIT-COLORS 0))
|
||||||
(public*
|
(define FG-COLOR (vector-ref DIGIT-COLORS 8))
|
||||||
[set-state
|
|
||||||
(lambda (newstate)
|
(define BLACK-COLOR (send the-color-database find-color "BLACK"))
|
||||||
(set! state newstate))]
|
|
||||||
[get-state
|
(define BG-PEN (make-object pen% BG-COLOR 1 'solid))
|
||||||
(lambda ()
|
(define FG-PEN (make-object pen% FG-COLOR 1 'solid))
|
||||||
state)]
|
|
||||||
[set-neighbor-bomb-count
|
;; A function for looping over numbers:
|
||||||
(lambda (c)
|
(define (step-while first test until f accum init)
|
||||||
(set! neighbor-bomb-count c))]
|
(let loop ([n first][a init])
|
||||||
[get-neighbor-bomb-count
|
(if (test n until)
|
||||||
(lambda ()
|
(loop (add1 n) (accum a (f n)))
|
||||||
neighbor-bomb-count)]
|
a)))
|
||||||
[set-area-hilite
|
|
||||||
(lambda (mode)
|
;; The rest of the game is implemented in a unit so it can be started
|
||||||
(set! area-hilite mode))]
|
;; multiple times
|
||||||
[draw-text-tile
|
(define game@ (unit (import) (export)
|
||||||
(lambda (dc x y w h hilite border? str color)
|
|
||||||
(if border?
|
;; ;;;;;;;;;;;;;;; Tiles ;;;;;;;;;;;;;;;;;;
|
||||||
(send dc draw-bitmap
|
|
||||||
(case hilite
|
;; Class for a tile object
|
||||||
[(left) lclick-bm]
|
(define tile:plain%
|
||||||
[(right) rclick-bm]
|
(class object%
|
||||||
[else (case area-hilite
|
(define state 'covered) ; 'covered, 'flagged, 'semi-flagged, or 'uncovered
|
||||||
[(near) near-bm]
|
(define neighbor-bomb-count 0) ; 0 to 8
|
||||||
[(local) local-bm]
|
(define area-hilite 'none) ; 'none, 'local, 'near
|
||||||
[else tile-bm])])
|
|
||||||
x y)
|
(public*
|
||||||
(begin
|
[set-state (lambda (newstate) (set! state newstate))]
|
||||||
(send dc set-pen BG-PEN)
|
[get-state (lambda () state)]
|
||||||
(send dc draw-rectangle x y w h)))
|
[set-neighbor-bomb-count (lambda (c) (set! neighbor-bomb-count c))]
|
||||||
(when str
|
[get-neighbor-bomb-count (lambda () neighbor-bomb-count)]
|
||||||
(cond
|
[set-area-hilite (lambda (mode) (set! area-hilite mode))]
|
||||||
[(string? str)
|
[draw-text-tile
|
||||||
(send dc set-text-foreground (or color FG-COLOR))
|
(lambda (dc x y w h hilite border? str color)
|
||||||
;; Draw text centered in the tile's box:
|
(if border?
|
||||||
(let-values ([(tw th d a) (send dc get-text-extent str)])
|
(send dc draw-bitmap
|
||||||
(send dc draw-text str
|
(case hilite
|
||||||
(+ x (/ (- w tw) 2))
|
[(left) lclick-bm]
|
||||||
(+ y (/ (- h (- th d)) 2))))]
|
[(right) rclick-bm]
|
||||||
[else
|
[else (case area-hilite
|
||||||
(send dc draw-bitmap str x y 'solid BLACK-COLOR
|
[(near) near-bm]
|
||||||
(send str get-loaded-mask))])))]
|
[(local) local-bm]
|
||||||
[draw
|
[else tile-bm])])
|
||||||
(lambda (dc x y w h hilite)
|
x y)
|
||||||
(case state
|
(begin (send dc set-pen BG-PEN)
|
||||||
[(covered) (draw-text-tile dc x y w h hilite #t #f #f)]
|
(send dc draw-rectangle x y w h)))
|
||||||
[(flagged) (draw-text-tile dc x y w h hilite #t flag-bm #f)]
|
(when str
|
||||||
[(semi-flagged) (draw-text-tile dc x y w h hilite #t "?" #f)]
|
(cond [(string? str)
|
||||||
[(uncovered) (draw-text-tile
|
(send dc set-text-foreground (or color FG-COLOR))
|
||||||
dc x y w h #f #f
|
;; Draw text centered in the tile's box:
|
||||||
(if (zero? neighbor-bomb-count)
|
(let-values ([(tw th d a) (send dc get-text-extent str)])
|
||||||
#f
|
(send dc draw-text str
|
||||||
(number->string neighbor-bomb-count))
|
(+ x (/ (- w tw) 2))
|
||||||
(vector-ref DIGIT-COLORS neighbor-bomb-count))]))])
|
(+ y (/ (- h (- th d)) 2))))]
|
||||||
|
[else
|
||||||
(super-instantiate ())))
|
(send dc draw-bitmap str x y 'solid BLACK-COLOR
|
||||||
|
(send str get-loaded-mask))])))]
|
||||||
;; Class for a tile with a bomb underneath
|
[draw
|
||||||
(define tile:bomb%
|
(lambda (dc x y w h hilite)
|
||||||
(class tile:plain%
|
(case state
|
||||||
(inherit get-state draw-text-tile)
|
[(covered) (draw-text-tile dc x y w h hilite #t #f #f)]
|
||||||
(define explode-source? #f) ; draw this bomb as the one that exploded?
|
[(flagged) (draw-text-tile dc x y w h hilite #t flag-bm #f)]
|
||||||
|
[(semi-flagged) (draw-text-tile dc x y w h hilite #t "?" #f)]
|
||||||
(public*
|
[(uncovered)
|
||||||
[set-explode-source
|
(draw-text-tile
|
||||||
(lambda (s?)
|
dc x y w h #f #f
|
||||||
(set! explode-source? s?))])
|
(if (zero? neighbor-bomb-count)
|
||||||
|
#f
|
||||||
(override*
|
(number->string neighbor-bomb-count))
|
||||||
[draw
|
(vector-ref DIGIT-COLORS neighbor-bomb-count))]))])
|
||||||
(lambda (dc x y w h hilite)
|
|
||||||
(if (eq? (get-state) 'uncovered)
|
(super-instantiate ())))
|
||||||
(draw-text-tile dc x y w h #f #f
|
|
||||||
(if explode-source? explode-bm bomb-bm) #f)
|
;; Class for a tile with a bomb underneath
|
||||||
(super draw dc x y w h hilite)))])
|
(define tile:bomb%
|
||||||
|
(class tile:plain%
|
||||||
(super-instantiate ())))
|
(inherit get-state draw-text-tile)
|
||||||
|
(define explode-source? #f) ; draw this bomb as the one that exploded?
|
||||||
(define (is-bomb? x)
|
|
||||||
(is-a? x tile:bomb%))
|
(public*
|
||||||
|
[set-explode-source (lambda (s?) (set! explode-source? s?))])
|
||||||
;; ;;;;;;;;;;;;;;; Board Operations ;;;;;;;;;;;;;;;;;;
|
|
||||||
;; A board is a vector of vectors of tiles
|
(override*
|
||||||
|
[draw
|
||||||
(define board #f) ; initialized by calling make-board!
|
(lambda (dc x y w h hilite)
|
||||||
|
(if (eq? (get-state) 'uncovered)
|
||||||
(define (get-tile x y)
|
(draw-text-tile dc x y w h #f #f
|
||||||
(vector-ref (vector-ref board x) y))
|
(if explode-source? explode-bm bomb-bm) #f)
|
||||||
|
(super draw dc x y w h hilite)))])
|
||||||
(define (set-tile! x y t)
|
|
||||||
(vector-set! (vector-ref board x) y t))
|
(super-instantiate ())))
|
||||||
|
|
||||||
(define (do-surrounding x y accum start default f)
|
(define (is-bomb? x)
|
||||||
(step-while -1 <= 1
|
(is-a? x tile:bomb%))
|
||||||
(lambda (dx)
|
|
||||||
(step-while -1 <= 1
|
;; ;;;;;;;;;;;;;;; Board Operations ;;;;;;;;;;;;;;;;;;
|
||||||
(lambda (dy)
|
;; A board is a vector of vectors of tiles
|
||||||
(if (and (not (and (zero? dx) (zero? dy)))
|
|
||||||
(< -1 (+ x dx) B-WIDTH)
|
(define board #f) ; initialized by calling make-board!
|
||||||
(< -1 (+ y dy) B-HEIGHT))
|
|
||||||
(f dx dy)
|
(define (get-tile x y)
|
||||||
default))
|
(vector-ref (vector-ref board x) y))
|
||||||
accum start))
|
|
||||||
accum start))
|
(define (set-tile! x y t)
|
||||||
|
(vector-set! (vector-ref board x) y t))
|
||||||
(define (count-surrounding-bombs x y)
|
|
||||||
(do-surrounding
|
(define (do-surrounding x y accum start default f)
|
||||||
x y + 0 0
|
(step-while -1 <= 1
|
||||||
(lambda (dx dy)
|
(lambda (dx)
|
||||||
(if (is-bomb? (get-tile (+ x dx) (+ y dy)))
|
(step-while -1 <= 1
|
||||||
1
|
(lambda (dy)
|
||||||
0))))
|
(if (and (not (and (zero? dx) (zero? dy)))
|
||||||
|
(< -1 (+ x dx) B-WIDTH)
|
||||||
(define (for-each-tile f)
|
(< -1 (+ y dy) B-HEIGHT))
|
||||||
(step-while 0 < B-WIDTH
|
(f dx dy)
|
||||||
(lambda (x)
|
default))
|
||||||
(step-while 0 < B-HEIGHT
|
accum start))
|
||||||
(lambda (y)
|
accum start))
|
||||||
(f (get-tile x y) x y))
|
|
||||||
void (void)))
|
(define (count-surrounding-bombs x y)
|
||||||
void (void)))
|
(do-surrounding
|
||||||
|
x y + 0 0
|
||||||
(define (make-board!)
|
(lambda (dx dy) (if (is-bomb? (get-tile (+ x dx) (+ y dy))) 1 0))))
|
||||||
;; Create the board
|
|
||||||
(set! board
|
(define (for-each-tile f)
|
||||||
(build-vector B-WIDTH
|
(step-while 0 < B-WIDTH
|
||||||
(lambda (i)
|
(lambda (x)
|
||||||
(build-vector B-HEIGHT
|
(step-while 0 < B-HEIGHT (lambda (y) (f (get-tile x y) x y))
|
||||||
(lambda (j)
|
void (void)))
|
||||||
(make-object tile:plain%))))))
|
void (void)))
|
||||||
;; Randomly insert bombs
|
|
||||||
(let loop ([n THE-BOMB-COUNT])
|
(define (make-board!)
|
||||||
(unless (zero? n)
|
;; Create the board
|
||||||
(let rloop ()
|
(set! board
|
||||||
(let* ([x (random B-WIDTH)]
|
(build-vector B-WIDTH
|
||||||
[y (random B-HEIGHT)]
|
(lambda (i)
|
||||||
[t (get-tile x y)])
|
(build-vector B-HEIGHT
|
||||||
(if (is-a? t tile:bomb%)
|
(lambda (j) (make-object tile:plain%))))))
|
||||||
(rloop)
|
;; Randomly insert bombs
|
||||||
(begin
|
(let loop ([n THE-BOMB-COUNT])
|
||||||
(set-tile! x y (make-object tile:bomb%))
|
(unless (zero? n)
|
||||||
(loop (sub1 n))))))))
|
(let rloop ()
|
||||||
;; Set surrounding-bomb counts for each tile:
|
(let* ([x (random B-WIDTH)]
|
||||||
|
[y (random B-HEIGHT)]
|
||||||
|
[t (get-tile x y)])
|
||||||
|
(if (is-a? t tile:bomb%)
|
||||||
|
(rloop)
|
||||||
|
(begin
|
||||||
|
(set-tile! x y (make-object tile:bomb%))
|
||||||
|
(loop (sub1 n))))))))
|
||||||
|
;; Set surrounding-bomb counts for each tile:
|
||||||
|
(for-each-tile (lambda (t x y)
|
||||||
|
(send t
|
||||||
|
set-neighbor-bomb-count
|
||||||
|
(count-surrounding-bombs x y)))))
|
||||||
|
|
||||||
|
;; ;;;;;;;;;;;;;;; Graphic Interface ;;;;;;;;;;;;;;;;;;
|
||||||
|
|
||||||
|
;; Make a frame:
|
||||||
|
(define frame
|
||||||
|
(instantiate
|
||||||
|
(class frame%
|
||||||
|
(augment*
|
||||||
|
[on-close ; stop the timer, in case it's running
|
||||||
|
(lambda ()
|
||||||
|
(send board-canvas stop-timer)
|
||||||
|
(inner () on-close))])
|
||||||
|
(super-instantiate ()))
|
||||||
|
("Minesweeper")
|
||||||
|
[style '(no-resize-border metal)]))
|
||||||
|
|
||||||
|
;; Make the row of controls at the top of the frame:
|
||||||
|
(define panel (make-object horizontal-panel% frame))
|
||||||
|
(send panel stretchable-height #f)
|
||||||
|
(define (make-centering-pane parent)
|
||||||
|
(let ([p (make-object vertical-pane% parent)])
|
||||||
|
(send p set-alignment 'center 'center)
|
||||||
|
p))
|
||||||
|
|
||||||
|
(define time-display
|
||||||
|
(make-object message% "Time: 00000" (make-centering-pane panel)))
|
||||||
|
(make-object button% "Reset" (make-centering-pane panel)
|
||||||
|
(lambda (b e) (send board-canvas reset)))
|
||||||
|
(define count-display
|
||||||
|
(make-object message% "Count: 000" (make-centering-pane panel)))
|
||||||
|
|
||||||
|
(define (set-time t)
|
||||||
|
(send time-display set-label (string-append "Time: " (number->string t))))
|
||||||
|
(define (set-count c)
|
||||||
|
(send count-display set-label (string-append "Bombs: " (number->string c))))
|
||||||
|
|
||||||
|
;; Most of the work is in this class, which extends the basic canvas
|
||||||
|
;; class for drawing the Minesweeper board and handling clicks.
|
||||||
|
(define board-canvas%
|
||||||
|
(class canvas%
|
||||||
|
(init frame)
|
||||||
|
(inherit get-dc min-client-width min-client-height
|
||||||
|
stretchable-width stretchable-height)
|
||||||
|
|
||||||
|
(define clicking #f) ; #t => click in progress
|
||||||
|
(define clicking-x 0) ; x position of click in progress
|
||||||
|
(define clicking-y 0) ; y position of click in progress
|
||||||
|
(define clicking-right? #f) ; #t => right-click in progress
|
||||||
|
(define area-hilite #f) ; tile with mouse pointer over it
|
||||||
|
(define area-hilites null) ; tiles+locs hilited due to mouse-over
|
||||||
|
(define ready? #t) ; #t => accept clicks
|
||||||
|
(define start-time #f) ; time of first click
|
||||||
|
(define elapsed-time 0) ; seconds since first click
|
||||||
|
(define timer #f) ; a timer that updates elapsed-time
|
||||||
|
(define bomb-count THE-BOMB-COUNT) ; number of bombs minus the number of flags
|
||||||
|
(define cover-count (* B-HEIGHT B-WIDTH)) ; number of uncovered tiles
|
||||||
|
|
||||||
|
(public*
|
||||||
|
[stop-timer ; stop the clock
|
||||||
|
(lambda ()
|
||||||
|
(when timer
|
||||||
|
(send timer stop)
|
||||||
|
(set! timer #f)))]
|
||||||
|
[start-timer ; start the clock
|
||||||
|
(lambda ()
|
||||||
|
(set! start-time (current-seconds))
|
||||||
|
(set! timer
|
||||||
|
(make-object
|
||||||
|
(class timer% ()
|
||||||
|
(override*
|
||||||
|
[notify
|
||||||
|
(lambda ()
|
||||||
|
(let ([e (- (current-seconds) start-time)])
|
||||||
|
(when (> e elapsed-time)
|
||||||
|
(set! elapsed-time e)
|
||||||
|
(set-time e))))])
|
||||||
|
(super-instantiate ()))))
|
||||||
|
(send timer start 100 #f))] ; check time roughly every .1 secs
|
||||||
|
[end-of-game ; stop the game
|
||||||
|
(lambda (win?)
|
||||||
|
(stop-timer)
|
||||||
|
(set! ready? #f)
|
||||||
|
(set! start-time #f)
|
||||||
|
(unless win? (show-all-bombs))
|
||||||
|
(set-count THE-BOMB-COUNT))]
|
||||||
|
[explode ; stop the game because the player hit a bomb
|
||||||
|
(lambda () (end-of-game #f))]
|
||||||
|
[win ; stop the game because the player won
|
||||||
|
(lambda () (end-of-game #t))]
|
||||||
|
[reset ; quit the current game and reset the board
|
||||||
|
(lambda ()
|
||||||
|
(stop-timer)
|
||||||
|
(set! ready? #t)
|
||||||
|
(set! start-time #f)
|
||||||
|
(set! elapsed-time 0)
|
||||||
|
(set! cover-count (* B-HEIGHT B-WIDTH))
|
||||||
|
(send dc clear)
|
||||||
|
(set-time 0)
|
||||||
|
(set! bomb-count THE-BOMB-COUNT)
|
||||||
|
(set-count THE-BOMB-COUNT)
|
||||||
|
(make-board!)
|
||||||
|
(on-paint))]
|
||||||
|
[show-all-bombs ; show the location of each bomb (after end-of-game)
|
||||||
|
(lambda ()
|
||||||
(for-each-tile (lambda (t x y)
|
(for-each-tile (lambda (t x y)
|
||||||
(send t
|
(when (is-bomb? t)
|
||||||
set-neighbor-bomb-count
|
(change-state t (send t get-state) 'uncovered #f)
|
||||||
(count-surrounding-bombs x y)))))
|
(paint-one t x y)))))]
|
||||||
|
[autoclick-surrounding ; autoclick tiles (after a 0 tile is uncovered)
|
||||||
;; ;;;;;;;;;;;;;;; Graphic Interface ;;;;;;;;;;;;;;;;;;
|
(lambda (x y)
|
||||||
|
(do-surrounding
|
||||||
;; Make a frame:
|
x y void (void) (void)
|
||||||
(define frame (instantiate
|
(lambda (dx dy)
|
||||||
(class frame%
|
(let* ([x2 (+ x dx)]
|
||||||
(augment*
|
[y2 (+ y dy)]
|
||||||
[on-close ; stop the timer, in case it's running
|
[t (get-tile x2 y2)]
|
||||||
(lambda ()
|
[state (send t get-state)]
|
||||||
(send board-canvas stop-timer)
|
[nc (send t get-neighbor-bomb-count)])
|
||||||
(inner () on-close))])
|
(unless (eq? state 'uncovered)
|
||||||
(super-instantiate ()))
|
(change-state t state 'uncovered #t)
|
||||||
("Minesweeper")
|
(paint-one t x2 y2)
|
||||||
[style '(no-resize-border metal)]))
|
(when (zero? nc) (autoclick-surrounding x2 y2)))))))]
|
||||||
|
[change-state ; update counters after a tile changes
|
||||||
;; Make the row of controls at the top of the frame:
|
(lambda (t old-state new-state update-count?)
|
||||||
(define panel (make-object horizontal-panel% frame))
|
(send t set-state new-state)
|
||||||
(send panel stretchable-height #f)
|
(when (and update-count? (not (eq? new-state old-state)))
|
||||||
(define (make-centering-pane parent)
|
(when (eq? new-state 'uncovered)
|
||||||
(let ([p (make-object vertical-pane% parent)])
|
(set! cover-count (sub1 cover-count)))
|
||||||
(send p set-alignment 'center 'center)
|
(when (eq? old-state 'uncovered)
|
||||||
p))
|
(set! cover-count (add1 cover-count)))
|
||||||
|
(when (eq? new-state 'flagged)
|
||||||
(define time-display (make-object message% "Time: 00000" (make-centering-pane panel)))
|
(set! bomb-count (sub1 bomb-count))
|
||||||
(make-object button% "Reset" (make-centering-pane panel)
|
(set-count bomb-count))
|
||||||
(lambda (b e) (send board-canvas reset)))
|
(when (eq? old-state 'flagged)
|
||||||
(define count-display (make-object message% "Count: 000" (make-centering-pane panel)))
|
(set! bomb-count (add1 bomb-count))
|
||||||
|
(set-count bomb-count))))]
|
||||||
(define (set-time t)
|
[do-select ; handle a click on a tile
|
||||||
(send time-display set-label (string-append "Time: " (number->string t))))
|
(lambda (x y flag?)
|
||||||
(define (set-count c)
|
(let* ([t (get-tile x y)]
|
||||||
(send count-display set-label (string-append "Bombs: " (number->string c))))
|
[state (send t get-state)]
|
||||||
|
[new-state (case state
|
||||||
;; Most of the work is in this class, which extends the basic canvas
|
[(covered) (if flag? 'flagged 'uncovered)]
|
||||||
;; class for drawing the Minesweeper board and handling clicks.
|
[(flagged) (if flag? 'semi-flagged state)]
|
||||||
(define board-canvas%
|
[(semi-flagged) (if flag? 'covered 'uncovered)]
|
||||||
(class canvas%
|
[else state])]
|
||||||
(init frame)
|
[nc (send t get-neighbor-bomb-count)]
|
||||||
(inherit get-dc min-client-width min-client-height
|
[new-uncover? (and (eq? new-state 'uncovered)
|
||||||
stretchable-width stretchable-height)
|
(not (eq? state 'uncovered)))]
|
||||||
|
[bomb? (is-bomb? t)])
|
||||||
(define clicking #f) ; #t => click in progress
|
(change-state t state new-state #t)
|
||||||
(define clicking-x 0) ; x position of click in progress
|
(when (and new-uncover? bomb?) (send t set-explode-source #t))
|
||||||
(define clicking-y 0) ; y position of click in progress
|
(paint-one t x y)
|
||||||
(define clicking-right? #f) ; #t => right-click in progress
|
(when new-uncover?
|
||||||
(define area-hilite #f) ; tile with mouse pointer over it
|
(if bomb?
|
||||||
(define area-hilites null) ; tiles+locs hilited due to mouse-over
|
(explode)
|
||||||
(define ready? #t) ; #t => accept clicks
|
(begin
|
||||||
(define start-time #f) ; time of first click
|
(if (zero? nc)
|
||||||
(define elapsed-time 0) ; seconds since first click
|
(autoclick-surrounding x y)
|
||||||
(define timer #f) ; a timer that updates elapsed-time
|
(set-near-hilite t x y))))
|
||||||
(define bomb-count THE-BOMB-COUNT) ; number of bombs minus the number of flags
|
(when (and ready? (= cover-count THE-BOMB-COUNT)) (win)))))]
|
||||||
(define cover-count (* B-HEIGHT B-WIDTH)) ; number of uncovered tiles
|
[paint-one ; draw one tile
|
||||||
|
(lambda (t x y)
|
||||||
(public*
|
(let ([xloc (* x TILE-HW)]
|
||||||
[stop-timer ; stop the clock
|
[yloc (* y TILE-HW)])
|
||||||
(lambda ()
|
(send t draw dc xloc yloc TILE-HW TILE-HW
|
||||||
(when timer
|
(and (eq? t clicking) (if clicking-right? 'right 'left)))))]
|
||||||
(send timer stop)
|
[set-near-hilite
|
||||||
(set! timer #f)))]
|
(lambda (t x y)
|
||||||
[start-timer ; start the clock
|
(set! area-hilite t)
|
||||||
(lambda ()
|
(set! area-hilites
|
||||||
(set! start-time (current-seconds))
|
(do-surrounding
|
||||||
(set! timer
|
x y append null null
|
||||||
(make-object
|
|
||||||
(class timer% ()
|
|
||||||
(override*
|
|
||||||
[notify
|
|
||||||
(lambda ()
|
|
||||||
(let ([e (- (current-seconds) start-time)])
|
|
||||||
(when (> e elapsed-time)
|
|
||||||
(set! elapsed-time e)
|
|
||||||
(set-time e))))])
|
|
||||||
(super-instantiate ()))))
|
|
||||||
(send timer start 100 #f))] ; check time roughly every .1 secs
|
|
||||||
[end-of-game ; stop the game
|
|
||||||
(lambda (win?)
|
|
||||||
(stop-timer)
|
|
||||||
(set! ready? #f)
|
|
||||||
(set! start-time #f)
|
|
||||||
(unless win?
|
|
||||||
(show-all-bombs))
|
|
||||||
(set-count THE-BOMB-COUNT))]
|
|
||||||
[explode ; stop the game because the player hit a bomb
|
|
||||||
(lambda ()
|
|
||||||
(end-of-game #f))]
|
|
||||||
[win ; stop the game because the player won
|
|
||||||
(lambda ()
|
|
||||||
(end-of-game #t))]
|
|
||||||
[reset ; quit the current game and reset the board
|
|
||||||
(lambda ()
|
|
||||||
(stop-timer)
|
|
||||||
(set! ready? #t)
|
|
||||||
(set! start-time #f)
|
|
||||||
(set! elapsed-time 0)
|
|
||||||
(set! cover-count (* B-HEIGHT B-WIDTH))
|
|
||||||
(send dc clear)
|
|
||||||
(set-time 0)
|
|
||||||
(set! bomb-count THE-BOMB-COUNT)
|
|
||||||
(set-count THE-BOMB-COUNT)
|
|
||||||
(make-board!)
|
|
||||||
(on-paint))]
|
|
||||||
[show-all-bombs ; show the location of each bomb (after end-of-game)
|
|
||||||
(lambda ()
|
|
||||||
(for-each-tile (lambda (t x y)
|
|
||||||
(when (is-bomb? t)
|
|
||||||
(change-state t (send t get-state) 'uncovered #f)
|
|
||||||
(paint-one t x y)))))]
|
|
||||||
[autoclick-surrounding ; autoclick tiles (after a 0 tile is uncovered)
|
|
||||||
(lambda (x y)
|
|
||||||
(do-surrounding
|
|
||||||
x y void (void) (void)
|
|
||||||
(lambda (dx dy)
|
(lambda (dx dy)
|
||||||
(let* ([x2 (+ x dx)]
|
(let* ([x (+ x dx)]
|
||||||
[y2 (+ y dy)]
|
[y (+ y dy)]
|
||||||
[t (get-tile x2 y2)]
|
[t (get-tile x y)])
|
||||||
[state (send t get-state)]
|
(if (not (eq? (send t get-state) 'uncovered))
|
||||||
[nc (send t get-neighbor-bomb-count)])
|
(begin
|
||||||
(unless (eq? state 'uncovered)
|
(send t set-area-hilite 'near)
|
||||||
(change-state t state 'uncovered #t)
|
(paint-one t x y)
|
||||||
(paint-one t x2 y2)
|
(list (list t x y)))
|
||||||
(when (zero? nc)
|
null))))))]
|
||||||
(autoclick-surrounding x2 y2)))))))]
|
[clear-area-hilite
|
||||||
[change-state ; update counters after a tile changes
|
(lambda ()
|
||||||
(lambda (t old-state new-state update-count?)
|
(when area-hilite
|
||||||
(send t set-state new-state)
|
(set! area-hilite #f)
|
||||||
(when (and update-count? (not (eq? new-state old-state)))
|
(for-each (lambda (p)
|
||||||
(when (eq? new-state 'uncovered)
|
(send (car p) set-area-hilite 'none)
|
||||||
(set! cover-count (sub1 cover-count)))
|
(paint-one (car p) (cadr p) (caddr p)))
|
||||||
(when (eq? old-state 'uncovered)
|
area-hilites)
|
||||||
(set! cover-count (add1 cover-count)))
|
(set! area-hilites null)))])
|
||||||
(when (eq? new-state 'flagged)
|
(override*
|
||||||
(set! bomb-count (sub1 bomb-count))
|
[on-event ; handle a click
|
||||||
(set-count bomb-count))
|
(lambda (e)
|
||||||
(when (eq? old-state 'flagged)
|
(when ready?
|
||||||
(set! bomb-count (add1 bomb-count))
|
(unless start-time ; if the timer's not running, start it
|
||||||
(set-count bomb-count))))]
|
(when (send e button-down?)
|
||||||
[do-select ; handle a click on a tile
|
(start-timer)))
|
||||||
(lambda (x y flag?)
|
;; Find the time for an (x,y) pixel position in the canvas
|
||||||
(let* ([t (get-tile x y)]
|
(let* ([x (quotient (inexact->exact (floor (send e get-x))) TILE-HW)]
|
||||||
[state (send t get-state)]
|
[y (quotient (inexact->exact (floor (send e get-y))) TILE-HW)]
|
||||||
[new-state
|
[t (if (and (< -1 x B-WIDTH) (< -1 y B-HEIGHT))
|
||||||
(case state
|
(get-tile x y)
|
||||||
[(covered)
|
#f)]) ; not a tile
|
||||||
(if flag? 'flagged 'uncovered)]
|
(cond
|
||||||
[(flagged)
|
[(and clicking (or (not (eq? t clicking))
|
||||||
(if flag? 'semi-flagged state)]
|
(not (or (send e button-up?)
|
||||||
[(semi-flagged)
|
(send e dragging?)))))
|
||||||
(if flag? 'covered 'uncovered)]
|
;; We're already in the middle of a click, and the mouse
|
||||||
[else state])]
|
;; was moved. Paint the tile to show whether releasing the
|
||||||
[nc (send t get-neighbor-bomb-count)]
|
;; mouse button selects the tile.
|
||||||
[new-uncover? (and (eq? new-state 'uncovered)
|
(let ([old clicking])
|
||||||
(not (eq? state 'uncovered)))]
|
(set! clicking #f)
|
||||||
[bomb? (is-bomb? t)])
|
(paint-one old clicking-x clicking-y))]
|
||||||
(change-state t state new-state #t)
|
[(and t
|
||||||
(when (and new-uncover? bomb?)
|
(not (eq? (send t get-state) 'uncovered))
|
||||||
(send t set-explode-source #t))
|
(or (send e button-down?)
|
||||||
(paint-one t x y)
|
(and (send e dragging?)
|
||||||
(when new-uncover?
|
(= x clicking-x)
|
||||||
(if bomb?
|
(= y clicking-y))))
|
||||||
(explode)
|
;; Start a click on a covered tile
|
||||||
(begin
|
(clear-area-hilite)
|
||||||
(if (zero? nc)
|
(set! clicking t)
|
||||||
(autoclick-surrounding x y)
|
(set! clicking-x x)
|
||||||
(set-near-hilite t x y))))
|
(set! clicking-y y)
|
||||||
(when (and ready? (= cover-count THE-BOMB-COUNT))
|
(when (send e button-down?)
|
||||||
(win)))))]
|
(set! clicking-right?
|
||||||
[paint-one ; draw one tile
|
(or (send e button-down? 'right)
|
||||||
(lambda (t x y)
|
(send e get-control-down)
|
||||||
(let ([xloc (* x TILE-HW)]
|
(send e get-alt-down)
|
||||||
[yloc (* y TILE-HW)])
|
(send e get-meta-down))))
|
||||||
(send t draw dc xloc yloc TILE-HW TILE-HW
|
(paint-one t x y)]
|
||||||
(and (eq? t clicking)
|
[(and clicking (send e button-up?))
|
||||||
(if clicking-right? 'right 'left)))))]
|
;; User released the button
|
||||||
[set-near-hilite
|
(set! clicking #f)
|
||||||
(lambda (t x y)
|
(do-select x y clicking-right?)]
|
||||||
(set! area-hilite t)
|
[(and (not (send e leaving?))
|
||||||
(set! area-hilites
|
t
|
||||||
(do-surrounding x y append null null
|
(eq? (send t get-state) 'uncovered)
|
||||||
(lambda (dx dy)
|
(positive? (send t get-neighbor-bomb-count)))
|
||||||
(let* ([x (+ x dx)]
|
;; Moving over uncovered number
|
||||||
[y (+ y dy)]
|
(unless (eq? t area-hilite)
|
||||||
[t (get-tile x y)])
|
(clear-area-hilite)
|
||||||
(if (not (eq? (send t get-state) 'uncovered))
|
(set-near-hilite t x y))]
|
||||||
(begin
|
[(and (not (send e leaving?))
|
||||||
(send t set-area-hilite 'near)
|
t
|
||||||
(paint-one t x y)
|
(not (eq? (send t get-state) 'uncovered)))
|
||||||
(list (list t x y)))
|
;; Moving over tile
|
||||||
null))))))]
|
(unless (eq? t area-hilite)
|
||||||
[clear-area-hilite
|
(clear-area-hilite)
|
||||||
(lambda ()
|
(set! area-hilite t)
|
||||||
(when area-hilite
|
(set! area-hilites (list (list t x y)))
|
||||||
(set! area-hilite #f)
|
(send t set-area-hilite 'local)
|
||||||
(for-each (lambda (p)
|
(paint-one t x y))]
|
||||||
(send (car p) set-area-hilite 'none)
|
[else (clear-area-hilite)]))))]
|
||||||
(paint-one (car p) (cadr p) (caddr p)))
|
[on-paint ; refresh the board
|
||||||
area-hilites)
|
(lambda () (for-each-tile (lambda (tile x y) (paint-one tile x y))))])
|
||||||
(set! area-hilites null)))])
|
|
||||||
(override*
|
(super-instantiate (frame))
|
||||||
[on-event ; handle a click
|
|
||||||
(lambda (e)
|
;; Make canvas size always match the board size:
|
||||||
(when ready?
|
(min-client-width (* TILE-HW B-WIDTH))
|
||||||
(unless start-time ; if the timer's not running, start it
|
(min-client-height (* TILE-HW B-HEIGHT))
|
||||||
(when (send e button-down?)
|
(stretchable-width #f)
|
||||||
(start-timer)))
|
(stretchable-height #f)
|
||||||
;; Find the time for an (x,y) pixel position in the canvas
|
|
||||||
(let* ([x (quotient (inexact->exact (floor (send e get-x)))
|
(define dc (get-dc))
|
||||||
TILE-HW)]
|
|
||||||
[y (quotient (inexact->exact (floor (send e get-y)))
|
(reset) ; initialize the game
|
||||||
TILE-HW)]
|
(send dc set-font (make-object font% 16 'swiss 'normal 'bold #f 'default #t))
|
||||||
[t (if (and (< -1 x B-WIDTH)
|
(send dc set-text-background BG-COLOR)
|
||||||
(< -1 y B-HEIGHT))
|
(send dc set-brush (send the-brush-list find-or-create-brush
|
||||||
(get-tile x y)
|
BG-COLOR 'solid))))
|
||||||
#f)]) ; not a tile
|
|
||||||
(cond
|
;; Make the board canvas:
|
||||||
[(and clicking (or (not (eq? t clicking))
|
(define board-canvas (make-object board-canvas% frame))
|
||||||
(not (or (send e button-up?)
|
|
||||||
(send e dragging?)))))
|
;; Show the frame (and handle events):
|
||||||
;; We're already in the middle of a click, and the mouse
|
(send frame show #t)))
|
||||||
;; was moved. Paint the tile to show whether releasing the
|
|
||||||
;; mouse button selects the tile.
|
|
||||||
(let ([old clicking])
|
|
||||||
(set! clicking #f)
|
|
||||||
(paint-one old clicking-x clicking-y))]
|
|
||||||
[(and t
|
|
||||||
(not (eq? (send t get-state) 'uncovered))
|
|
||||||
(or (send e button-down?)
|
|
||||||
(and (send e dragging?)
|
|
||||||
(= x clicking-x)
|
|
||||||
(= y clicking-y))))
|
|
||||||
;; Start a click on a covered tile
|
|
||||||
(clear-area-hilite)
|
|
||||||
(set! clicking t)
|
|
||||||
(set! clicking-x x)
|
|
||||||
(set! clicking-y y)
|
|
||||||
(when (send e button-down?)
|
|
||||||
(set! clicking-right? (or (send e button-down? 'right)
|
|
||||||
(send e get-control-down)
|
|
||||||
(send e get-alt-down)
|
|
||||||
(send e get-meta-down))))
|
|
||||||
(paint-one t x y)]
|
|
||||||
[(and clicking (send e button-up?))
|
|
||||||
;; User released the button
|
|
||||||
(set! clicking #f)
|
|
||||||
(do-select x y clicking-right?)]
|
|
||||||
[(and (not (send e leaving?))
|
|
||||||
t
|
|
||||||
(eq? (send t get-state) 'uncovered)
|
|
||||||
(positive? (send t get-neighbor-bomb-count)))
|
|
||||||
;; Moving over uncovered number
|
|
||||||
(unless (eq? t area-hilite)
|
|
||||||
(clear-area-hilite)
|
|
||||||
(set-near-hilite t x y))]
|
|
||||||
[(and (not (send e leaving?))
|
|
||||||
t
|
|
||||||
(not (eq? (send t get-state) 'uncovered)))
|
|
||||||
;; Moving over tile
|
|
||||||
(unless (eq? t area-hilite)
|
|
||||||
(clear-area-hilite)
|
|
||||||
(set! area-hilite t)
|
|
||||||
(set! area-hilites (list (list t x y)))
|
|
||||||
(send t set-area-hilite 'local)
|
|
||||||
(paint-one t x y))]
|
|
||||||
[else (clear-area-hilite)]))))]
|
|
||||||
[on-paint ; refresh the board
|
|
||||||
(lambda ()
|
|
||||||
(for-each-tile (lambda (tile x y) (paint-one tile x y))))])
|
|
||||||
|
|
||||||
(super-instantiate (frame))
|
|
||||||
|
|
||||||
;; Make canvas size always match the board size:
|
|
||||||
(min-client-width (* TILE-HW B-WIDTH))
|
|
||||||
(min-client-height (* TILE-HW B-HEIGHT))
|
|
||||||
(stretchable-width #f)
|
|
||||||
(stretchable-height #f)
|
|
||||||
|
|
||||||
(define dc (get-dc))
|
|
||||||
|
|
||||||
(reset) ; initialize the game
|
|
||||||
(send dc set-font (make-object font% 16 'swiss 'normal 'bold #f 'default #t))
|
|
||||||
(send dc set-text-background BG-COLOR)
|
|
||||||
(send dc set-brush (send the-brush-list find-or-create-brush
|
|
||||||
BG-COLOR 'solid))))
|
|
||||||
|
|
||||||
;; Make the board canvas:
|
|
||||||
(define board-canvas (make-object board-canvas% frame))
|
|
||||||
|
|
||||||
;; Show the frame (and handle events):
|
|
||||||
(send frame show #t))))
|
|
||||||
|
|
||||||
|
|
|
@ -1,35 +1,37 @@
|
||||||
** To play _Paint By Numbers_, run the "Games" application. **
|
** To play Paint By Numbers, run the "PLT Games" application.
|
||||||
|
|
||||||
The object of Paint By Numbers is to discover which cells should be
|
The object of Paint By Numbers is to discover which cells should be
|
||||||
colored blue and which should be colored white. Initially, all squares are
|
colored blue and which should be colored white. Initially, all
|
||||||
grey, indicating that the correct colors are not known. The lists of
|
squares are grey, indicating that the correct colors are not known.
|
||||||
numbers to the left and above the grid are your clues to the correct color
|
The lists of numbers to the left and above the grid are your clues to
|
||||||
of each square. Each list of numbers specifies the pattern of blue squares
|
the correct color of each square. Each list of numbers specifies the
|
||||||
in the row beside it or the column below it. Each number indicates the
|
pattern of blue squares in the row beside it or the column below it.
|
||||||
length of a group of blue squares. For example, if the list of numbers
|
Each number indicates the length of a group of blue squares. For
|
||||||
beside the first row is "2 3" then you know that there is a contiguous
|
example, if the list of numbers beside the first row is "2 3" then you
|
||||||
block of two blue squares followed by a contiguous block of three blue
|
know that there is a contiguous block of two blue squares followed by
|
||||||
squares with at least one white square between them. The label does not
|
a contiguous block of three blue squares with at least one white
|
||||||
tell you where the blue squares are, only their shapes. The trick is to
|
square between them. The label does not tell you where the blue
|
||||||
gather as much information as you can about each row, and then use that
|
squares are, only their shapes. The trick is to gather as much
|
||||||
information to determine more about each column. Eventually you should be
|
information as you can about each row, and then use that information
|
||||||
able to fill in the entire puzzle.
|
to determine more about each column. Eventually you should be able to
|
||||||
|
fill in the entire puzzle.
|
||||||
|
|
||||||
Click on a square to toggle it between blue and gray. Hold down a modifier
|
Click on a square to toggle it between blue and gray. Hold down a
|
||||||
key (shift, command, meta, or alt depending on the platform) to toggle a
|
modifier key (shift, command, meta, or alt depending on the platform)
|
||||||
square between white and gray. The third button under unix and the right
|
to toggle a square between white and gray. The third button under
|
||||||
button under windows also toggles between white and gray.
|
unix and the right button under windows also toggles between white and
|
||||||
|
gray.
|
||||||
|
|
||||||
For some puzzles, hints are available. Choose the Nongram|Show Mistakes
|
For some puzzles, hints are available. Choose the Nongram|Show
|
||||||
menu item to receive the hints. This will turn all incorrectly colored
|
Mistakes menu item to receive the hints. This will turn all
|
||||||
squares red.
|
incorrectly colored squares red.
|
||||||
|
|
||||||
Thanks to Shoichiro Hattori for his puzzles! Visit him on the web at:
|
Thanks to Shoichiro Hattori for his puzzles! Visit him on the web at:
|
||||||
|
|
||||||
http://hattori.m78.com/puzzle/
|
http://hattori.m78.com/puzzle/
|
||||||
|
|
||||||
Thanks also to many of the contributors to the Kajitani web site for
|
Thanks also to many of the contributors to the Kajitani web site for
|
||||||
permission to re-distribute their puzzles. Visit them online at:
|
permission to re-distribute their puzzles. Visit them online at:
|
||||||
|
|
||||||
http://www02.so-net.ne.jp/~kajitani/index.html
|
http://www02.so-net.ne.jp/~kajitani/index.html
|
||||||
|
|
||||||
|
|
|
@ -1,52 +1,47 @@
|
||||||
_Parcheesi_
|
** To play Parcheesi, run the "PLT Games" application.
|
||||||
|
|
||||||
Parcheesi is a race game for four players. The goal is for
|
Parcheesi is a race game for four players. The goal is for each
|
||||||
each player to move their pieces from the starting position
|
player to move their pieces from the starting position (the circles in
|
||||||
(the circles in the corners) to the home square (in the
|
the corners) to the home square (in the center of the board), passing
|
||||||
center of the board), passing a nearly complete loop around
|
a nearly complete loop around the board in the counter-clockwise
|
||||||
the board in the counter-clockwise direction and then heads
|
direction and then heads up towards the main row. For example, the
|
||||||
up towards the main row. For example, the green player
|
green player enters from the bottom right, travels around the board on
|
||||||
enters from the bottom right, travels around the board on
|
the light blue squares, passing each of the corners, until it reaches
|
||||||
the light blue squares, passing each of the corners, until
|
the middle of the bottom of the board, where it turns off the light
|
||||||
it reaches the middle of the bottom of the board, where it
|
blue squares and heads into the central region.
|
||||||
turns off the light blue squares and heads into the central
|
|
||||||
region.
|
|
||||||
|
|
||||||
On each turn, the player rolls two dice and advances the
|
On each turn, the player rolls two dice and advances the pawn, based
|
||||||
pawn, based on the die rolls. Typically the players may move
|
on the die rolls. Typically the players may move a pawn for each die.
|
||||||
a pawn for each die. The pawn moves by the number of pips
|
The pawn moves by the number of pips showing on the die and all of the
|
||||||
showing on the die and all of the dice must be used to
|
dice must be used to complete a turn.
|
||||||
complete a turn.
|
|
||||||
|
|
||||||
There are some exceptions, however:
|
There are some exceptions, however:
|
||||||
|
|
||||||
- you must roll a 5 (either directly or via summing) to
|
- you must roll a 5 (either directly or via summing) to enter from
|
||||||
enter from the start area to the main ring.
|
the start area to the main ring.
|
||||||
|
|
||||||
- if two pieces of the same color occupy a square, no
|
- if two pieces of the same color occupy a square, no pieces may
|
||||||
pieces may pass that square.
|
pass that square.
|
||||||
|
|
||||||
- if an opponent's piece lands on your piece, you piece is
|
- if an opponent's piece lands on your piece, you piece is returned
|
||||||
returned to the starting area and the opponent receives
|
to the starting area and the opponent receives a bonus of 20
|
||||||
a bonus of 20 (which is treated just as if they had
|
(which is treated just as if they had rolled a 20 on the dice).
|
||||||
rolled a 20 on the dice)
|
|
||||||
|
|
||||||
- if your piece makes it home (and it must do so by exact
|
- if your piece makes it home (and it must do so by exact count) you
|
||||||
count) you get a bonus of 10, to be used as an
|
get a bonus of 10, to be used as an additional die roll.
|
||||||
additional die roll.
|
|
||||||
|
|
||||||
These rules induce a number of unexpected corner cases, but
|
These rules induce a number of unexpected corner cases, but the GUI
|
||||||
the GUI only lets you make legal moves. Watch the space
|
only lets you make legal moves. Watch the space along the bottom of
|
||||||
along the bottom of the board for reasons why a move is
|
the board for reasons why a move is illegal or why you have not used
|
||||||
illegal or why you have not used all of your die rolls.
|
all of your die rolls.
|
||||||
|
|
||||||
The automated players are:
|
The automated players are:
|
||||||
|
|
||||||
- Reckless Renee, who she tries to maximize the chances
|
- Reckless Renee, who she tries to maximize the chances that someone
|
||||||
that someone else bops her.
|
else bops her.
|
||||||
|
|
||||||
- Polite Polly, who tries to minimize the distance her
|
- Polite Polly, who tries to minimize the distance her pawns move
|
||||||
pawns move ("no, after _you_. I insist."), and
|
("no, after _you_. I insist."), and
|
||||||
|
|
||||||
- Amazing Grace, who tries to minimize the chance she gets
|
- Amazing Grace, who tries to minimize the chance she gets bopped
|
||||||
bopped while moving as far as possible.
|
while moving as far as possible.
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
To play _Pousse_, run the "Games" application.
|
** To play Pousse, run the "PLT Games" application.
|
||||||
|
|
||||||
Pousse (French for "push", pronounced "poo-ss") is a 2 person game,
|
Pousse (French for "push", pronounced "poo-ss") is a 2 person game,
|
||||||
played on an N by N board (usually 4x4). Initially the board is
|
played on an N by N board (usually 4x4). Initially the board is
|
||||||
|
@ -32,13 +32,13 @@ Note that the last marker of the row or column will be pushed off the
|
||||||
board (and must be removed from play) if there are no empty squares on
|
board (and must be removed from play) if there are no empty squares on
|
||||||
the insertion row or column.
|
the insertion row or column.
|
||||||
|
|
||||||
A row or a column is a "straight" of a given color, if it contains
|
A row or a column is a "straight" of a given color, if it contains N
|
||||||
N markers of the given color.
|
markers of the given color.
|
||||||
|
|
||||||
The game ends either when an insertion
|
The game ends either when an insertion
|
||||||
|
|
||||||
1) repeats a previous configuration of the board; in this case
|
1) repeats a previous configuration of the board; in this case the
|
||||||
the player who inserted the marker LOSES.
|
player who inserted the marker LOSES.
|
||||||
|
|
||||||
2) creates a configuration with more straights of one color than
|
2) creates a configuration with more straights of one color than
|
||||||
straights of the other color; the player whose color is dominant
|
straights of the other color; the player whose color is dominant
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
** To play _Same_, run the Games application. **
|
** To play Same, run the "PLT Games" application.
|
||||||
|
|
||||||
The object of Same is to score points by removing dots from the
|
The object of Same is to score points by removing dots from the board.
|
||||||
board. To remove a dot, click on it. As long as there is another dot
|
To remove a dot, click on it. As long as there is another dot of the
|
||||||
of the same color next to the clicked dot, it will disappear along
|
same color next to the clicked dot, it will disappear along with all
|
||||||
with all adjacent dots of the same color. After the dots disappear,
|
adjacent dots of the same color. After the dots disappear, dots in
|
||||||
dots in the rows above the deleted dots will fall into the vacated
|
the rows above the deleted dots will fall into the vacated spaces. If
|
||||||
spaces. If an entire column is wiped out, all of the dots from the
|
an entire column is wiped out, all of the dots from the right will
|
||||||
right will slide left to take up the empty column's space.
|
slide left to take up the empty column's space.
|
||||||
|
|
||||||
Your score increases for each ball removed from the board. The score
|
Your score increases for each ball removed from the board. The score
|
||||||
for each click is a function of the number of balls that
|
for each click is a function of the number of balls that disappeared.
|
||||||
disappeared. The "This Click" label shows how many points you would
|
The "This Click" label shows how many points you would score for
|
||||||
score for clicking the dots underneath the mouse pointer. The score
|
clicking the dots underneath the mouse pointer. The score varies
|
||||||
varies quadratically with the number of balls, so eliminating many
|
quadratically with the number of balls, so eliminating many balls with
|
||||||
balls with one click is advantageous.
|
one click is advantageous.
|
||||||
|
|
||||||
Click the New Game button to play again.
|
Click the New Game button to play again.
|
||||||
|
|
|
@ -1,358 +1,318 @@
|
||||||
|
#lang mzscheme
|
||||||
|
(require (lib "etc.ss")
|
||||||
|
(lib "class.ss")
|
||||||
|
(lib "unit.ss")
|
||||||
|
(lib "mred.ss" "mred"))
|
||||||
|
|
||||||
(module slidey mzscheme
|
(provide game@)
|
||||||
(require (lib "etc.ss")
|
|
||||||
(lib "class.ss")
|
|
||||||
(lib "unit.ss")
|
|
||||||
(lib "mred.ss" "mred"))
|
|
||||||
|
|
||||||
(provide game@)
|
|
||||||
|
|
||||||
(define game@
|
|
||||||
(unit
|
|
||||||
(import)
|
|
||||||
(export)
|
|
||||||
|
|
||||||
(define (get-bitmap bitmap)
|
|
||||||
(define f (make-object dialog% "Choose Size" #f #f #f #f #f '(resize-border)))
|
|
||||||
(define bm-panel (make-object vertical-panel% f))
|
|
||||||
(define bm-message (make-object message% bitmap bm-panel))
|
|
||||||
(define size-message (make-object message%
|
|
||||||
(format "Image size: ~a x ~a pixels"
|
|
||||||
(send bitmap get-width)
|
|
||||||
(send bitmap get-height))
|
|
||||||
bm-panel))
|
|
||||||
(define wide-panel (make-object vertical-panel% f '(border)))
|
|
||||||
(define sw (make-object slider% "Tiles (width)" 2 30 wide-panel
|
|
||||||
(lambda (_1 _2)
|
|
||||||
(update-horizontal-cutoff))))
|
|
||||||
(define tall-panel (make-object vertical-panel% f '(border)))
|
|
||||||
(define sh (make-object slider% "Tiles (height)" 2 30 tall-panel
|
|
||||||
(lambda (_1 _2)
|
|
||||||
(update-vertical-cutoff))))
|
|
||||||
(define button-panel (make-object horizontal-panel% f))
|
|
||||||
|
|
||||||
(define cancelled? #t)
|
|
||||||
|
|
||||||
(define cancel (make-object button% "Cancel" button-panel (lambda (_1 _2) (send f show #f))))
|
|
||||||
(define ok (make-object button% "OK" button-panel (lambda (_1 _2)
|
|
||||||
(set! cancelled? #f)
|
|
||||||
(send f show #f)) '(border)))
|
|
||||||
|
|
||||||
(define vertical-cutoff 0)
|
|
||||||
(define vertical-cutoff-message (make-object message% "" tall-panel))
|
|
||||||
|
|
||||||
(define horizontal-cutoff 0)
|
|
||||||
(define horizontal-cutoff-message (make-object message% "" wide-panel))
|
|
||||||
|
|
||||||
(define (update-vertical-cutoff)
|
|
||||||
(set! vertical-cutoff (modulo (send bitmap get-height) (send sh get-value)))
|
|
||||||
(send vertical-cutoff-message set-label
|
|
||||||
(if (= 0 vertical-cutoff)
|
|
||||||
""
|
|
||||||
(format "Vertical cutoff ~a pixels" vertical-cutoff))))
|
|
||||||
(define (update-horizontal-cutoff)
|
|
||||||
(set! horizontal-cutoff (modulo (send bitmap get-width) (send sw get-value)))
|
|
||||||
(send horizontal-cutoff-message set-label
|
|
||||||
(if (= 0 horizontal-cutoff)
|
|
||||||
""
|
|
||||||
(format "Horizontal cutoff ~a pixels" horizontal-cutoff))))
|
|
||||||
|
|
||||||
(send horizontal-cutoff-message stretchable-width #t)
|
|
||||||
(send vertical-cutoff-message stretchable-width #t)
|
|
||||||
(update-vertical-cutoff)
|
|
||||||
(update-horizontal-cutoff)
|
|
||||||
(send button-panel set-alignment 'right 'center)
|
|
||||||
(send button-panel stretchable-height #f)
|
|
||||||
(send bm-panel set-alignment 'center 'center)
|
|
||||||
(send wide-panel stretchable-height #f)
|
|
||||||
(send tall-panel stretchable-height #f)
|
|
||||||
(make-object grow-box-spacer-pane% button-panel)
|
|
||||||
(send f show #t)
|
|
||||||
|
|
||||||
(if cancelled?
|
|
||||||
(values #f #f #f)
|
|
||||||
(let* ([nb (make-object bitmap%
|
|
||||||
(- (send bitmap get-width) horizontal-cutoff)
|
|
||||||
(- (send bitmap get-height) vertical-cutoff))]
|
|
||||||
[bdc (make-object bitmap-dc% nb)])
|
|
||||||
(send bdc draw-bitmap-section bitmap 0 0 0 0
|
|
||||||
(- (send bitmap get-width) horizontal-cutoff)
|
|
||||||
(- (send bitmap get-height) vertical-cutoff))
|
|
||||||
(send bdc set-bitmap #f)
|
|
||||||
(values nb (send sw get-value) (send sh get-value)))))
|
|
||||||
|
|
||||||
(define-struct loc (x y))
|
|
||||||
;; board = (vector-of (vector-of (union #f (make-loc n1 n2))))
|
|
||||||
|
|
||||||
;; need to make sure that the bitmap divides nicely
|
|
||||||
;(define bitmap (make-object bitmap% (build-path (collection-path "games" "slidey") "11.jpg")))
|
|
||||||
;(define board-width 6)
|
|
||||||
;(define board-height 5)
|
|
||||||
|
|
||||||
(define (board-for-each board f)
|
|
||||||
(let loop ([i (vector-length board)])
|
|
||||||
(cond
|
|
||||||
[(zero? i) (void)]
|
|
||||||
[else
|
|
||||||
(let ([row (vector-ref board (- i 1))])
|
|
||||||
(let loop ([j (vector-length row)])
|
|
||||||
(cond
|
|
||||||
[(zero? j) (void)]
|
|
||||||
[else
|
|
||||||
(f (- i 1) (- j 1) (vector-ref row (- j 1)))
|
|
||||||
(loop (- j 1))])))
|
|
||||||
(loop (- i 1))])))
|
|
||||||
|
|
||||||
(define (move-one board from-i from-j to-i to-j)
|
|
||||||
(let ([from-save (board-ref board from-i from-j)]
|
|
||||||
[to-save (board-ref board to-i to-j)])
|
|
||||||
(board-set! board from-i from-j to-save)
|
|
||||||
(board-set! board to-i to-j from-save)))
|
|
||||||
|
|
||||||
(define (board-set! board i j v)
|
|
||||||
(vector-set! (vector-ref board i) j v))
|
|
||||||
(define (board-ref board i j)
|
|
||||||
(vector-ref (vector-ref board i) j))
|
|
||||||
|
|
||||||
(define (get-board-width board)
|
|
||||||
(vector-length board))
|
|
||||||
(define (get-board-height board)
|
|
||||||
(vector-length (vector-ref board 0)))
|
|
||||||
|
|
||||||
(define (randomize-board board hole-i hole-j)
|
|
||||||
(let ([board-width (get-board-width board)]
|
|
||||||
[board-height (get-board-height board)])
|
|
||||||
(let loop ([no-good #f]
|
|
||||||
[i (* 10 board-width board-height)]
|
|
||||||
[m-hole-i hole-i]
|
|
||||||
[m-hole-j hole-j])
|
|
||||||
(cond
|
|
||||||
[(zero? i) ;; move hole back to last spot
|
|
||||||
(let ([i-diff (abs (- m-hole-i hole-i))])
|
|
||||||
(let loop ([i 0])
|
|
||||||
(unless (= i i-diff)
|
|
||||||
(move-one
|
|
||||||
board
|
|
||||||
(+ m-hole-i i)
|
|
||||||
m-hole-j
|
|
||||||
(+ m-hole-i i (if (< m-hole-i hole-i) +1 -1))
|
|
||||||
m-hole-j)
|
|
||||||
(loop (+ i 1)))))
|
|
||||||
(let ([j-diff (abs (- m-hole-j hole-j))])
|
|
||||||
(let loop ([j 0])
|
|
||||||
(unless (= j j-diff)
|
|
||||||
(move-one
|
|
||||||
board
|
|
||||||
hole-i
|
|
||||||
(+ m-hole-j j)
|
|
||||||
hole-i
|
|
||||||
(+ m-hole-j j (if (< m-hole-j hole-j) +1 -1)))
|
|
||||||
(loop (+ j 1)))))]
|
|
||||||
[else
|
|
||||||
(let ([this-dir (get-random-number 4 no-good)])
|
|
||||||
(let-values ([(new-i new-j)
|
|
||||||
(case this-dir
|
|
||||||
; up
|
|
||||||
[(0) (values (- m-hole-i 1) m-hole-j)]
|
|
||||||
[(1) (values (+ m-hole-i 1) m-hole-j)]
|
|
||||||
[(2) (values m-hole-i (- m-hole-j 1))]
|
|
||||||
[(3) (values m-hole-i (+ m-hole-j 1))])])
|
|
||||||
(if (and (<= 0 new-i)
|
|
||||||
(< new-i board-width)
|
|
||||||
(<= 0 new-j)
|
|
||||||
(< new-j board-height))
|
|
||||||
(let ([next-no-good
|
|
||||||
(case this-dir
|
|
||||||
[(0) 1]
|
|
||||||
[(1) 0]
|
|
||||||
[(2) 3]
|
|
||||||
[(3) 2])])
|
|
||||||
(move-one board new-i new-j m-hole-i m-hole-j)
|
|
||||||
(loop next-no-good (- i 1) new-i new-j))
|
|
||||||
(loop no-good (- i 1) m-hole-i m-hole-j))))]))))
|
|
||||||
|
|
||||||
(define (get-random-number bound no-good)
|
|
||||||
(let ([raw (random (- bound 1))])
|
|
||||||
(cond
|
|
||||||
[(not no-good) raw]
|
|
||||||
[(< raw no-good) raw]
|
|
||||||
[else (+ raw 1)])))
|
|
||||||
|
|
||||||
(define line-brush (send the-brush-list find-or-create-brush "black" 'transparent))
|
|
||||||
(define line-pen (send the-pen-list find-or-create-pen "white" 1 'solid))
|
|
||||||
(define mistake-brush (send the-brush-list find-or-create-brush "black" 'transparent))
|
|
||||||
(define mistake-pen (send the-pen-list find-or-create-pen "red" 1 'solid))
|
|
||||||
(define pict-brush (send the-brush-list find-or-create-brush "black" 'solid))
|
|
||||||
(define pict-pen (send the-pen-list find-or-create-pen "black" 1 'solid))
|
|
||||||
(define white-brush (send the-brush-list find-or-create-brush "white" 'solid))
|
|
||||||
(define white-pen (send the-pen-list find-or-create-pen "white" 1 'solid))
|
|
||||||
|
|
||||||
(define slidey-canvas%
|
|
||||||
(class canvas%
|
|
||||||
(init-field bitmap board-width board-height)
|
|
||||||
|
|
||||||
(define show-mistakes? #f)
|
|
||||||
(define/public (show-mistakes nv)
|
|
||||||
(set! show-mistakes? nv)
|
|
||||||
(unless solved?
|
|
||||||
(on-paint)))
|
|
||||||
|
|
||||||
(define solved? #f)
|
|
||||||
|
|
||||||
(define board
|
|
||||||
(build-vector
|
|
||||||
board-width
|
|
||||||
(lambda (i)
|
|
||||||
(build-vector
|
|
||||||
board-height
|
|
||||||
(lambda (j)
|
|
||||||
(make-loc i j))))))
|
|
||||||
(define hole-i (- board-width 1))
|
|
||||||
(define hole-j (- board-height 1))
|
|
||||||
(board-set! board hole-i hole-j #f)
|
|
||||||
|
|
||||||
(define/override (on-paint)
|
|
||||||
(if solved?
|
|
||||||
(send (get-dc) draw-bitmap bitmap 0 0)
|
|
||||||
(board-for-each
|
|
||||||
board
|
|
||||||
(lambda (i j v)
|
|
||||||
(draw-cell i j)))))
|
|
||||||
|
|
||||||
(define/override (on-event evt)
|
(define game@ (unit (import) (export)
|
||||||
(unless solved?
|
|
||||||
(cond
|
(define (get-bitmap bitmap)
|
||||||
[(send evt button-down? 'left)
|
(define f (make-object dialog% "Choose Size" #f #f #f #f #f '(resize-border)))
|
||||||
(let-values ([(i j) (xy->ij (send evt get-x) (send evt get-y))])
|
(define bm-panel (make-object vertical-panel% f))
|
||||||
(slide i j))]
|
(define bm-message (make-object message% bitmap bm-panel))
|
||||||
[else (void)])))
|
(define size-message
|
||||||
(inherit get-client-size get-dc)
|
(make-object message% (format "Image size: ~a x ~a pixels"
|
||||||
|
(send bitmap get-width)
|
||||||
(define/private (check-end-condition)
|
(send bitmap get-height))
|
||||||
(let ([answer #t])
|
bm-panel))
|
||||||
(board-for-each
|
(define wide-panel (make-object vertical-panel% f '(border)))
|
||||||
board
|
(define sw (make-object slider% "Tiles (width)" 2 30 wide-panel
|
||||||
(lambda (i j v)
|
(lambda (_1 _2) (update-horizontal-cutoff))))
|
||||||
(when v
|
(define tall-panel (make-object vertical-panel% f '(border)))
|
||||||
(unless (and (= i (loc-x v))
|
(define sh (make-object slider% "Tiles (height)" 2 30 tall-panel
|
||||||
(= j (loc-y v)))
|
(lambda (_1 _2) (update-vertical-cutoff))))
|
||||||
(set! answer #f)))))
|
(define button-panel (make-object horizontal-panel% f))
|
||||||
(when answer
|
|
||||||
(set! solved? #t))))
|
(define cancelled? #t)
|
||||||
|
|
||||||
(define/private (slide i j)
|
(define cancel (make-object button% "Cancel" button-panel
|
||||||
(cond
|
(lambda (_1 _2) (send f show #f))))
|
||||||
[(= j hole-j)
|
(define ok (make-object button% "OK" button-panel
|
||||||
(let loop ([new-hole-i hole-i])
|
(lambda (_1 _2)
|
||||||
(cond
|
(set! cancelled? #f)
|
||||||
[(= new-hole-i i) (void)]
|
(send f show #f)) '(border)))
|
||||||
[else
|
|
||||||
(let ([next (if (< i hole-i)
|
(define vertical-cutoff 0)
|
||||||
sub1
|
(define vertical-cutoff-message (make-object message% "" tall-panel))
|
||||||
add1)])
|
|
||||||
(move-one board (next new-hole-i) hole-j new-hole-i hole-j)
|
(define horizontal-cutoff 0)
|
||||||
(draw-cell new-hole-i hole-j)
|
(define horizontal-cutoff-message (make-object message% "" wide-panel))
|
||||||
(draw-cell (next new-hole-i) hole-j)
|
|
||||||
(loop (next new-hole-i)))]))
|
(define (update-vertical-cutoff)
|
||||||
(set! hole-i i)
|
(set! vertical-cutoff (modulo (send bitmap get-height) (send sh get-value)))
|
||||||
(check-end-condition)
|
(send vertical-cutoff-message set-label
|
||||||
(when solved?
|
(if (= 0 vertical-cutoff)
|
||||||
(on-paint))]
|
""
|
||||||
[(= i hole-i)
|
(format "Vertical cutoff ~a pixels" vertical-cutoff))))
|
||||||
(let loop ([new-hole-j hole-j])
|
(define (update-horizontal-cutoff)
|
||||||
(cond
|
(set! horizontal-cutoff (modulo (send bitmap get-width) (send sw get-value)))
|
||||||
[(= new-hole-j j) (void)]
|
(send horizontal-cutoff-message set-label
|
||||||
[else
|
(if (= 0 horizontal-cutoff)
|
||||||
(let ([next (if (< j hole-j)
|
""
|
||||||
sub1
|
(format "Horizontal cutoff ~a pixels" horizontal-cutoff))))
|
||||||
add1)])
|
|
||||||
(move-one board hole-i (next new-hole-j) hole-i new-hole-j)
|
(send horizontal-cutoff-message stretchable-width #t)
|
||||||
(draw-cell hole-i new-hole-j)
|
(send vertical-cutoff-message stretchable-width #t)
|
||||||
(draw-cell hole-i (next new-hole-j))
|
(update-vertical-cutoff)
|
||||||
(loop (next new-hole-j)))]))
|
(update-horizontal-cutoff)
|
||||||
(set! hole-j j)
|
(send button-panel set-alignment 'right 'center)
|
||||||
(check-end-condition)
|
(send button-panel stretchable-height #f)
|
||||||
(when solved?
|
(send bm-panel set-alignment 'center 'center)
|
||||||
(on-paint))]
|
(send wide-panel stretchable-height #f)
|
||||||
[else (void)]))
|
(send tall-panel stretchable-height #f)
|
||||||
|
(make-object grow-box-spacer-pane% button-panel)
|
||||||
(define/private (xy->ij x y)
|
(send f show #t)
|
||||||
(let-values ([(w h) (get-client-size)])
|
|
||||||
(values
|
(if cancelled?
|
||||||
(inexact->exact (floor (* board-width (/ x w))))
|
(values #f #f #f)
|
||||||
(inexact->exact (floor (* board-height (/ y h)))))))
|
(let* ([nb (make-object bitmap%
|
||||||
|
(- (send bitmap get-width) horizontal-cutoff)
|
||||||
(define/private (ij->xywh i j)
|
(- (send bitmap get-height) vertical-cutoff))]
|
||||||
(let-values ([(w h) (get-client-size)])
|
[bdc (make-object bitmap-dc% nb)])
|
||||||
(let ([cell-w (/ w board-width)]
|
(send bdc draw-bitmap-section bitmap 0 0 0 0
|
||||||
[cell-h (/ h board-height)])
|
(- (send bitmap get-width) horizontal-cutoff)
|
||||||
(values (* i cell-w)
|
(- (send bitmap get-height) vertical-cutoff))
|
||||||
(* j cell-h)
|
(send bdc set-bitmap #f)
|
||||||
cell-w
|
(values nb (send sw get-value) (send sh get-value)))))
|
||||||
cell-h))))
|
|
||||||
(define/private (draw-cell draw-i draw-j)
|
(define-struct loc (x y))
|
||||||
(let-values ([(xd yd wd hd) (ij->xywh draw-i draw-j)])
|
;; board = (vector-of (vector-of (union #f (make-loc n1 n2))))
|
||||||
(let* ([dc (get-dc)]
|
|
||||||
[indicies (board-ref board draw-i draw-j)])
|
;; need to make sure that the bitmap divides nicely
|
||||||
(if indicies
|
;;(define bitmap (make-object bitmap% (build-path (collection-path "games" "slidey") "11.jpg")))
|
||||||
(let ([bm-i (loc-x indicies)]
|
;;(define board-width 6)
|
||||||
[bm-j (loc-y indicies)])
|
;;(define board-height 5)
|
||||||
(let-values ([(xs ys ws hs) (ij->xywh bm-i bm-j)])
|
|
||||||
(send dc set-pen pict-pen)
|
(define (board-for-each board f)
|
||||||
(send dc set-brush pict-brush)
|
(let loop ([i (vector-length board)])
|
||||||
(send dc draw-bitmap-section bitmap xd yd xs ys wd hd)
|
(unless (zero? i)
|
||||||
(if (and show-mistakes?
|
(let ([row (vector-ref board (- i 1))])
|
||||||
(or (not (= draw-i bm-i))
|
(let loop ([j (vector-length row)])
|
||||||
(not (= draw-j bm-j))))
|
(unless (zero? j)
|
||||||
(begin
|
(f (- i 1) (- j 1) (vector-ref row (- j 1)))
|
||||||
(send dc set-pen mistake-pen)
|
(loop (- j 1)))))
|
||||||
(send dc set-brush mistake-brush))
|
(loop (- i 1)))))
|
||||||
(begin
|
|
||||||
(send dc set-pen line-pen)
|
(define (move-one board from-i from-j to-i to-j)
|
||||||
(send dc set-brush line-brush)))
|
(let ([from-save (board-ref board from-i from-j)]
|
||||||
(send dc draw-rectangle xd yd wd hd)))
|
[to-save (board-ref board to-i to-j)])
|
||||||
(begin
|
(board-set! board from-i from-j to-save)
|
||||||
(send dc set-pen white-pen)
|
(board-set! board to-i to-j from-save)))
|
||||||
(send dc set-brush white-brush)
|
|
||||||
(send dc draw-rectangle xd yd wd hd))))))
|
(define (board-set! board i j v)
|
||||||
|
(vector-set! (vector-ref board i) j v))
|
||||||
(inherit stretchable-width stretchable-height min-client-width min-client-height)
|
(define (board-ref board i j)
|
||||||
(super-instantiate ())
|
(vector-ref (vector-ref board i) j))
|
||||||
(randomize-board board hole-i hole-j)
|
|
||||||
(stretchable-width #f)
|
(define (get-board-width board)
|
||||||
(stretchable-height #f)
|
(vector-length board))
|
||||||
(min-client-width (send bitmap get-width))
|
(define (get-board-height board)
|
||||||
(min-client-height (send bitmap get-height))))
|
(vector-length (vector-ref board 0)))
|
||||||
|
|
||||||
(define f (make-object frame% "Slidey"))
|
(define (randomize-board board hole-i hole-j)
|
||||||
(define p (make-object horizontal-panel% f))
|
(let ([board-width (get-board-width board)]
|
||||||
(send p set-alignment 'center 'center)
|
[board-height (get-board-height board)])
|
||||||
(define slidey-canvas (make-object slidey-canvas%
|
(let loop ([no-good #f]
|
||||||
(make-object bitmap%
|
[i (* 10 board-width board-height)]
|
||||||
(build-path (collection-path "games" "slidey") "11.jpg"))
|
[m-hole-i hole-i]
|
||||||
6 6 p))
|
[m-hole-j hole-j])
|
||||||
(define bp (make-object horizontal-panel% f))
|
(cond
|
||||||
(send bp stretchable-height #f)
|
[(zero? i) ;; move hole back to last spot
|
||||||
(define show-mistakes
|
(let ([i-diff (abs (- m-hole-i hole-i))])
|
||||||
(make-object check-box% "Show misplaced pieces" bp
|
(let loop ([i 0])
|
||||||
(lambda ___ (send slidey-canvas show-mistakes (send show-mistakes get-value)))))
|
(unless (= i i-diff)
|
||||||
(make-object grow-box-spacer-pane% bp)
|
(move-one board (+ m-hole-i i)
|
||||||
|
m-hole-j (+ m-hole-i i (if (< m-hole-i hole-i) +1 -1))
|
||||||
(define (change-bitmap)
|
m-hole-j)
|
||||||
(let ([fn (get-file)])
|
(loop (+ i 1)))))
|
||||||
(when fn
|
(let ([j-diff (abs (- m-hole-j hole-j))])
|
||||||
(let ([bm (make-object bitmap% fn)])
|
(let loop ([j 0])
|
||||||
(cond
|
(unless (= j j-diff)
|
||||||
[(send bm ok?)
|
(move-one board hole-i (+ m-hole-j j)
|
||||||
(let-values ([(bitmap w h) (get-bitmap bm)])
|
hole-i (+ m-hole-j j (if (< m-hole-j hole-j) +1 -1)))
|
||||||
(when bitmap
|
(loop (+ j 1)))))]
|
||||||
(send p change-children (lambda (l) null))
|
[else
|
||||||
(set! slidey-canvas (make-object slidey-canvas% bitmap w h p))))]
|
(let ([this-dir (get-random-number 4 no-good)])
|
||||||
|
(let-values ([(new-i new-j)
|
||||||
|
(case this-dir
|
||||||
|
;; up
|
||||||
|
[(0) (values (- m-hole-i 1) m-hole-j)]
|
||||||
|
[(1) (values (+ m-hole-i 1) m-hole-j)]
|
||||||
|
[(2) (values m-hole-i (- m-hole-j 1))]
|
||||||
|
[(3) (values m-hole-i (+ m-hole-j 1))])])
|
||||||
|
(if (and (<= 0 new-i)
|
||||||
|
(< new-i board-width)
|
||||||
|
(<= 0 new-j)
|
||||||
|
(< new-j board-height))
|
||||||
|
(let ([next-no-good
|
||||||
|
(case this-dir [(0) 1] [(1) 0] [(2) 3] [(3) 2])])
|
||||||
|
(move-one board new-i new-j m-hole-i m-hole-j)
|
||||||
|
(loop next-no-good (- i 1) new-i new-j))
|
||||||
|
(loop no-good (- i 1) m-hole-i m-hole-j))))]))))
|
||||||
|
|
||||||
|
(define (get-random-number bound no-good)
|
||||||
|
(let ([raw (random (- bound 1))])
|
||||||
|
(cond [(not no-good) raw]
|
||||||
|
[(< raw no-good) raw]
|
||||||
|
[else (+ raw 1)])))
|
||||||
|
|
||||||
|
(define line-brush
|
||||||
|
(send the-brush-list find-or-create-brush "black" 'transparent))
|
||||||
|
(define line-pen (send the-pen-list find-or-create-pen "white" 1 'solid))
|
||||||
|
(define mistake-brush
|
||||||
|
(send the-brush-list find-or-create-brush "black" 'transparent))
|
||||||
|
(define mistake-pen (send the-pen-list find-or-create-pen "red" 1 'solid))
|
||||||
|
(define pict-brush (send the-brush-list find-or-create-brush "black" 'solid))
|
||||||
|
(define pict-pen (send the-pen-list find-or-create-pen "black" 1 'solid))
|
||||||
|
(define white-brush (send the-brush-list find-or-create-brush "white" 'solid))
|
||||||
|
(define white-pen (send the-pen-list find-or-create-pen "white" 1 'solid))
|
||||||
|
|
||||||
|
(define slidey-canvas%
|
||||||
|
(class canvas%
|
||||||
|
(init-field bitmap board-width board-height)
|
||||||
|
|
||||||
|
(define show-mistakes? #f)
|
||||||
|
(define/public (show-mistakes nv)
|
||||||
|
(set! show-mistakes? nv)
|
||||||
|
(unless solved? (on-paint)))
|
||||||
|
|
||||||
|
(define solved? #f)
|
||||||
|
|
||||||
|
(define board
|
||||||
|
(build-vector
|
||||||
|
board-width
|
||||||
|
(lambda (i) (build-vector board-height (lambda (j) (make-loc i j))))))
|
||||||
|
(define hole-i (- board-width 1))
|
||||||
|
(define hole-j (- board-height 1))
|
||||||
|
(board-set! board hole-i hole-j #f)
|
||||||
|
|
||||||
|
(define/override (on-paint)
|
||||||
|
(if solved?
|
||||||
|
(send (get-dc) draw-bitmap bitmap 0 0)
|
||||||
|
(board-for-each board (lambda (i j v) (draw-cell i j)))))
|
||||||
|
|
||||||
|
(define/override (on-event evt)
|
||||||
|
(unless solved?
|
||||||
|
(when (send evt button-down? 'left)
|
||||||
|
(let-values ([(i j) (xy->ij (send evt get-x) (send evt get-y))])
|
||||||
|
(slide i j)))))
|
||||||
|
(inherit get-client-size get-dc)
|
||||||
|
|
||||||
|
(define/private (check-end-condition)
|
||||||
|
(let ([answer #t])
|
||||||
|
(board-for-each
|
||||||
|
board
|
||||||
|
(lambda (i j v)
|
||||||
|
(when v
|
||||||
|
(unless (and (= i (loc-x v)) (= j (loc-y v)))
|
||||||
|
(set! answer #f)))))
|
||||||
|
(when answer (set! solved? #t))))
|
||||||
|
|
||||||
|
(define/private (slide i j)
|
||||||
|
(cond
|
||||||
|
[(= j hole-j)
|
||||||
|
(let loop ([new-hole-i hole-i])
|
||||||
|
(unless (= new-hole-i i)
|
||||||
|
(let ([next (if (< i hole-i) sub1 add1)])
|
||||||
|
(move-one board (next new-hole-i) hole-j new-hole-i hole-j)
|
||||||
|
(draw-cell new-hole-i hole-j)
|
||||||
|
(draw-cell (next new-hole-i) hole-j)
|
||||||
|
(loop (next new-hole-i)))))
|
||||||
|
(set! hole-i i)
|
||||||
|
(check-end-condition)
|
||||||
|
(when solved? (on-paint))]
|
||||||
|
[(= i hole-i)
|
||||||
|
(let loop ([new-hole-j hole-j])
|
||||||
|
(unless (= new-hole-j j)
|
||||||
|
(let ([next (if (< j hole-j)
|
||||||
|
sub1
|
||||||
|
add1)])
|
||||||
|
(move-one board hole-i (next new-hole-j) hole-i new-hole-j)
|
||||||
|
(draw-cell hole-i new-hole-j)
|
||||||
|
(draw-cell hole-i (next new-hole-j))
|
||||||
|
(loop (next new-hole-j)))))
|
||||||
|
(set! hole-j j)
|
||||||
|
(check-end-condition)
|
||||||
|
(when solved?
|
||||||
|
(on-paint))]
|
||||||
|
[else (void)]))
|
||||||
|
|
||||||
|
(define/private (xy->ij x y)
|
||||||
|
(let-values ([(w h) (get-client-size)])
|
||||||
|
(values (inexact->exact (floor (* board-width (/ x w))))
|
||||||
|
(inexact->exact (floor (* board-height (/ y h)))))))
|
||||||
|
|
||||||
|
(define/private (ij->xywh i j)
|
||||||
|
(let-values ([(w h) (get-client-size)])
|
||||||
|
(let ([cell-w (/ w board-width)]
|
||||||
|
[cell-h (/ h board-height)])
|
||||||
|
(values (* i cell-w) (* j cell-h) cell-w cell-h))))
|
||||||
|
(define/private (draw-cell draw-i draw-j)
|
||||||
|
(let-values ([(xd yd wd hd) (ij->xywh draw-i draw-j)])
|
||||||
|
(let* ([dc (get-dc)]
|
||||||
|
[indicies (board-ref board draw-i draw-j)])
|
||||||
|
(if indicies
|
||||||
|
(let ([bm-i (loc-x indicies)]
|
||||||
|
[bm-j (loc-y indicies)])
|
||||||
|
(let-values ([(xs ys ws hs) (ij->xywh bm-i bm-j)])
|
||||||
|
(send dc set-pen pict-pen)
|
||||||
|
(send dc set-brush pict-brush)
|
||||||
|
(send dc draw-bitmap-section bitmap xd yd xs ys wd hd)
|
||||||
|
(if (and show-mistakes?
|
||||||
|
(or (not (= draw-i bm-i))
|
||||||
|
(not (= draw-j bm-j))))
|
||||||
|
(begin (send dc set-pen mistake-pen)
|
||||||
|
(send dc set-brush mistake-brush))
|
||||||
|
(begin (send dc set-pen line-pen)
|
||||||
|
(send dc set-brush line-brush)))
|
||||||
|
(send dc draw-rectangle xd yd wd hd)))
|
||||||
|
(begin (send dc set-pen white-pen)
|
||||||
|
(send dc set-brush white-brush)
|
||||||
|
(send dc draw-rectangle xd yd wd hd))))))
|
||||||
|
|
||||||
|
(inherit stretchable-width stretchable-height
|
||||||
|
min-client-width min-client-height)
|
||||||
|
(super-instantiate ())
|
||||||
|
(randomize-board board hole-i hole-j)
|
||||||
|
(stretchable-width #f)
|
||||||
|
(stretchable-height #f)
|
||||||
|
(min-client-width (send bitmap get-width))
|
||||||
|
(min-client-height (send bitmap get-height))))
|
||||||
|
|
||||||
|
(define f (make-object frame% "Slidey"))
|
||||||
|
(define p (make-object horizontal-panel% f))
|
||||||
|
(send p set-alignment 'center 'center)
|
||||||
|
(define slidey-canvas
|
||||||
|
(make-object slidey-canvas%
|
||||||
|
(make-object bitmap%
|
||||||
|
(build-path (collection-path "games" "slidey") "11.jpg"))
|
||||||
|
6 6 p))
|
||||||
|
(define bp (make-object horizontal-panel% f))
|
||||||
|
(send bp stretchable-height #f)
|
||||||
|
(define show-mistakes
|
||||||
|
(make-object check-box% "Show misplaced pieces" bp
|
||||||
|
(lambda ___ (send slidey-canvas show-mistakes (send show-mistakes get-value)))))
|
||||||
|
(make-object grow-box-spacer-pane% bp)
|
||||||
|
|
||||||
|
(define (change-bitmap)
|
||||||
|
(let ([fn (get-file)])
|
||||||
|
(when fn
|
||||||
|
(let ([bm (make-object bitmap% fn)])
|
||||||
|
(cond
|
||||||
|
[(send bm ok?)
|
||||||
|
(let-values ([(bitmap w h) (get-bitmap bm)])
|
||||||
|
(when bitmap
|
||||||
|
(send p change-children (lambda (l) null))
|
||||||
|
(set! slidey-canvas (make-object slidey-canvas% bitmap w h p))))]
|
||||||
[else (message-box "Slidey" (format "Unrecognized image format: ~a" fn))])))))
|
[else (message-box "Slidey" (format "Unrecognized image format: ~a" fn))])))))
|
||||||
|
|
||||||
(define mb (make-object menu-bar% f))
|
(define mb (make-object menu-bar% f))
|
||||||
(define file-menu (make-object menu% "File" mb))
|
(define file-menu (make-object menu% "File" mb))
|
||||||
(make-object menu-item% "Open Image" file-menu (lambda (_1 _2) (change-bitmap)) #\o)
|
(make-object menu-item% "Open Image" file-menu (lambda (_1 _2) (change-bitmap)) #\o)
|
||||||
(make-object menu-item% "Close Window" file-menu (lambda (_1 _2) (send f show #f)) #\w)
|
(make-object menu-item% "Close Window" file-menu (lambda (_1 _2) (send f show #f)) #\w)
|
||||||
|
|
||||||
(send f show #t))))
|
(send f show #t)
|
||||||
|
|
||||||
|
))
|
||||||
|
|
|
@ -1,30 +1,30 @@
|
||||||
** To play _Spider_, run the "Games" application. **
|
** To play Spider, run the "PLT Games" application.
|
||||||
|
|
||||||
Spider is a solitaire card game played with 104 cards. The cards can
|
Spider is a solitaire card game played with 104 cards. The cards can
|
||||||
include either a single suit, two suits, or four suites. (Choose your
|
include either a single suit, two suits, or four suites. (Choose your
|
||||||
variant through the "Options" item in the "Edit" menu.)
|
variant through the "Options" item in the "Edit" menu.)
|
||||||
|
|
||||||
Terminology:
|
Terminology:
|
||||||
|
|
||||||
* Tableau: one of the ten stacks of cards in the play area. The game
|
* Tableau: one of the ten stacks of cards in the play area. The game
|
||||||
starts with six cards in the first four tableaus, and five cards in
|
starts with six cards in the first four tableaus, and five cards in
|
||||||
the rest; only the topmost card is face up, and others are revealed
|
the rest; only the topmost card is face up, and others are revealed
|
||||||
when they become the topmost card of the tableau.
|
when they become the topmost card of the tableau.
|
||||||
|
|
||||||
* Sequence: a group of cards on the top of a tableau that are in the
|
* Sequence: a group of cards on the top of a tableau that are in the
|
||||||
same suit, and that are in sequence, with the lowest numbered card
|
same suit, and that are in sequence, with the lowest numbered card
|
||||||
topmost (i.e., closer to the bottom of the screen). King is high
|
topmost (i.e., closer to the bottom of the screen). King is high
|
||||||
and ace is low.
|
and ace is low.
|
||||||
|
|
||||||
The object of the game is to create a sequence with ace through king,
|
The object of the game is to create a sequence with ace through king,
|
||||||
at which point the sequence is removed from play. Create eight such
|
at which point the sequence is removed from play. Create eight such
|
||||||
sequences to win the game.
|
sequences to win the game.
|
||||||
|
|
||||||
On each move, you can either:
|
On each move, you can either:
|
||||||
|
|
||||||
* Move a sequence from any tableau to one whose topmost card (i.e.,
|
* Move a sequence from any tableau to one whose topmost card (i.e.,
|
||||||
closest to the bottom of the screen) has a value that's one more
|
closest to the bottom of the screen) has a value that's one more
|
||||||
than the sequence's value. Note that if the top card of the target
|
than the sequence's value. Note that if the top card of the target
|
||||||
tableau has the same suit as the sequence, a larger sequence is
|
tableau has the same suit as the sequence, a larger sequence is
|
||||||
formed, but the target tableau's card is not required to have the
|
formed, but the target tableau's card is not required to have the
|
||||||
same suit.
|
same suit.
|
||||||
|
@ -32,12 +32,12 @@ On each move, you can either:
|
||||||
* Move a sequence to an empty tableau.
|
* Move a sequence to an empty tableau.
|
||||||
|
|
||||||
* Deal ten cards from the deck (in the upper left corder), one to
|
* Deal ten cards from the deck (in the upper left corder), one to
|
||||||
each tableau. This move is allowed only if no tableau is empty.
|
each tableau. This move is allowed only if no tableau is empty.
|
||||||
|
|
||||||
To move a sequence, either drag it to the target tableau, or click the
|
To move a sequence, either drag it to the target tableau, or click the
|
||||||
sequence and then click the top card of the target tableau (or the
|
sequence and then click the top card of the target tableau (or the
|
||||||
place where a single card would be for an empty tableau). Click a
|
place where a single card would be for an empty tableau). Click a
|
||||||
select card to de-select it. Clicking a card that is not a valid
|
select card to de-select it. Clicking a card that is not a valid
|
||||||
target for the currently selected sequence causes the clicked card's
|
target for the currently selected sequence causes the clicked card's
|
||||||
sequence to be selected (if the card is face up in a sequence).
|
sequence to be selected (if the card is face up in a sequence).
|
||||||
|
|
||||||
|
|
|
@ -1,445 +1,412 @@
|
||||||
|
#lang mzscheme
|
||||||
|
|
||||||
(module spider mzscheme
|
(require (lib "cards.ss" "games" "cards")
|
||||||
|
(lib "class.ss")
|
||||||
(require (lib "cards.ss" "games" "cards")
|
(lib "mred.ss" "mred")
|
||||||
(lib "class.ss")
|
(lib "list.ss")
|
||||||
(lib "mred.ss" "mred")
|
(lib "file.ss")
|
||||||
(lib "list.ss")
|
(lib "unit.ss")
|
||||||
(lib "file.ss")
|
"../show-help.ss")
|
||||||
(lib "unit.ss")
|
|
||||||
"../show-help.ss")
|
|
||||||
|
|
||||||
(define (list-first-n l n)
|
(define (list-first-n l n)
|
||||||
(if (zero? n)
|
(if (zero? n)
|
||||||
null
|
null
|
||||||
(cons (car l) (list-first-n (cdr l) (sub1 n)))))
|
(cons (car l) (list-first-n (cdr l) (sub1 n)))))
|
||||||
(define (vector-copy v)
|
(define (vector-copy v)
|
||||||
(list->vector (vector->list v)))
|
(list->vector (vector->list v)))
|
||||||
|
|
||||||
(provide game@)
|
(provide game@)
|
||||||
|
(define game@ (unit (import) (export)
|
||||||
(define game@
|
|
||||||
(unit
|
|
||||||
(import)
|
|
||||||
(export)
|
|
||||||
|
|
||||||
(define t (make-table "Spider" 11 6))
|
|
||||||
|
|
||||||
(define num-suits (get-preference 'spider:num-suits (lambda () 2)))
|
(define t (make-table "Spider" 11 6))
|
||||||
|
|
||||||
(define (make-spider-deck)
|
(define num-suits (get-preference 'spider:num-suits (lambda () 2)))
|
||||||
(let-values ([(suits copies)
|
|
||||||
(case num-suits
|
|
||||||
[(1) (values '(spades) 4)]
|
|
||||||
[(2) (values '(spades hearts) 2)]
|
|
||||||
[(4) (values '(spades hearts clubs diamonds) 1)])])
|
|
||||||
(let ([l (filter (lambda (c)
|
|
||||||
(memq (send c get-suit) suits))
|
|
||||||
(make-deck))])
|
|
||||||
(let loop ([n (* 2 copies)])
|
|
||||||
(if (zero? n)
|
|
||||||
null
|
|
||||||
(append (map (lambda (c) (send c copy)) l)
|
|
||||||
(loop (sub1 n))))))))
|
|
||||||
|
|
||||||
(define deck (make-spider-deck))
|
(define (make-spider-deck)
|
||||||
|
(let-values ([(suits copies)
|
||||||
|
(case num-suits
|
||||||
|
[(1) (values '(spades) 4)]
|
||||||
|
[(2) (values '(spades hearts) 2)]
|
||||||
|
[(4) (values '(spades hearts clubs diamonds) 1)])])
|
||||||
|
(let ([l (filter (lambda (c) (memq (send c get-suit) suits)) (make-deck))])
|
||||||
|
(let loop ([n (* 2 copies)])
|
||||||
|
(if (zero? n)
|
||||||
|
null
|
||||||
|
(append (map (lambda (c) (send c copy)) l) (loop (sub1 n))))))))
|
||||||
|
|
||||||
(define draw-pile deck)
|
(define deck (make-spider-deck))
|
||||||
|
|
||||||
(define CARD-WIDTH (send (car deck) card-width))
|
(define draw-pile deck)
|
||||||
(define CARD-HEIGHT (send (car deck) card-height))
|
|
||||||
|
|
||||||
(define dx (quotient CARD-WIDTH 11))
|
(define CARD-WIDTH (send (car deck) card-width))
|
||||||
(define dy dx)
|
(define CARD-HEIGHT (send (car deck) card-height))
|
||||||
|
|
||||||
(define stacks (make-vector 10 null))
|
(define dx (quotient CARD-WIDTH 11))
|
||||||
(define dones (make-vector 8 null))
|
(define dy dx)
|
||||||
|
|
||||||
(define done-count 0)
|
(define stacks (make-vector 10 null))
|
||||||
|
(define dones (make-vector 8 null))
|
||||||
|
|
||||||
(define old-states null)
|
(define done-count 0)
|
||||||
|
|
||||||
(define-struct state (draw-pile stacks dones done-count face-down?s))
|
(define old-states null)
|
||||||
|
|
||||||
(define mb (make-object menu-bar% t))
|
(define-struct state (draw-pile stacks dones done-count face-down?s))
|
||||||
|
|
||||||
(define file-menu (make-object menu% "&File" mb))
|
(define mb (make-object menu-bar% t))
|
||||||
|
|
||||||
(new menu-item%
|
(define file-menu (make-object menu% "&File" mb))
|
||||||
[label "&Reset Game..."]
|
|
||||||
[parent file-menu]
|
|
||||||
[callback (lambda (i e)
|
|
||||||
(when (eq? 'yes (message-box "Reset Game"
|
|
||||||
"Are you sure you want to reset the game?"
|
|
||||||
t
|
|
||||||
'(yes-no)))
|
|
||||||
(reset-game!)))])
|
|
||||||
|
|
||||||
(new separator-menu-item% [parent file-menu])
|
(new menu-item%
|
||||||
|
[label "&Reset Game..."]
|
||||||
|
[parent file-menu]
|
||||||
|
[callback
|
||||||
|
(lambda (i e)
|
||||||
|
(when (eq? 'yes (message-box "Reset Game"
|
||||||
|
"Are you sure you want to reset the game?"
|
||||||
|
t
|
||||||
|
'(yes-no)))
|
||||||
|
(reset-game!)))])
|
||||||
|
|
||||||
(new menu-item%
|
(new separator-menu-item% [parent file-menu])
|
||||||
[label "&Close"]
|
|
||||||
[parent file-menu]
|
|
||||||
[shortcut #\W]
|
|
||||||
[callback (lambda (i e) (send t show #f))])
|
|
||||||
|
|
||||||
(define edit-menu (make-object menu% "&Edit" mb))
|
(new menu-item%
|
||||||
|
[label "&Close"]
|
||||||
|
[parent file-menu]
|
||||||
|
[shortcut #\W]
|
||||||
|
[callback (lambda (i e) (send t show #f))])
|
||||||
|
|
||||||
(define undo
|
(define edit-menu (make-object menu% "&Edit" mb))
|
||||||
(new menu-item%
|
|
||||||
[label "&Undo"]
|
|
||||||
[parent edit-menu]
|
|
||||||
[shortcut #\Z]
|
|
||||||
[callback (lambda (i e)
|
|
||||||
(pop-state!))]))
|
|
||||||
|
|
||||||
(new separator-menu-item% [parent edit-menu])
|
(define undo
|
||||||
|
(new menu-item%
|
||||||
|
[label "&Undo"]
|
||||||
|
[parent edit-menu]
|
||||||
|
[shortcut #\Z]
|
||||||
|
[callback (lambda (i e) (pop-state!))]))
|
||||||
|
|
||||||
(new menu-item%
|
(new separator-menu-item% [parent edit-menu])
|
||||||
[label "&Options..."]
|
|
||||||
[parent edit-menu]
|
(new menu-item%
|
||||||
[callback (lambda (i e)
|
[label "&Options..."]
|
||||||
(define d (new dialog%
|
[parent edit-menu]
|
||||||
[label "Spider Options"]
|
[callback (lambda (i e)
|
||||||
[parent t]
|
(define d
|
||||||
[stretchable-width #f]
|
(new dialog%
|
||||||
[stretchable-height #f]))
|
[label "Spider Options"]
|
||||||
(define suits (new radio-box%
|
[parent t]
|
||||||
[label #f]
|
[stretchable-width #f]
|
||||||
[parent (new group-box-panel%
|
[stretchable-height #f]))
|
||||||
[parent d]
|
(define suits
|
||||||
[label "Number of Suits"]
|
(new radio-box%
|
||||||
[stretchable-width #f]
|
[label #f]
|
||||||
[stretchable-height #f])]
|
[parent (new group-box-panel%
|
||||||
[choices '("1 (easiest)" "2" "4 (hardest)")]))
|
[parent d]
|
||||||
(define bottom-panel (new horizontal-panel%
|
[label "Number of Suits"]
|
||||||
[parent d]
|
[stretchable-width #f]
|
||||||
[alignment '(right center)]
|
[stretchable-height #f])]
|
||||||
[stretchable-height #f]))
|
[choices '("1 (easiest)" "2" "4 (hardest)")]))
|
||||||
(new button%
|
(define bottom-panel
|
||||||
[parent bottom-panel]
|
(new horizontal-panel%
|
||||||
[label "&Cancel"]
|
[parent d]
|
||||||
[callback (lambda (b e) (send d show #f))])
|
[alignment '(right center)]
|
||||||
(new button%
|
[stretchable-height #f]))
|
||||||
[parent bottom-panel]
|
(new button%
|
||||||
[label "&Ok"]
|
[parent bottom-panel]
|
||||||
[style '(border)]
|
[label "&Cancel"]
|
||||||
[callback (lambda (b e)
|
[callback (lambda (b e) (send d show #f))])
|
||||||
(let ([n (expt 2 (send suits get-selection))])
|
(new button%
|
||||||
(if (not (= n num-suits))
|
[parent bottom-panel]
|
||||||
(when (eq? 'yes
|
[label "&Ok"]
|
||||||
(message-box "Warning"
|
[style '(border)]
|
||||||
"Reset the game for new suit count?"
|
[callback (lambda (b e)
|
||||||
d
|
(let ([n (expt 2 (send suits get-selection))])
|
||||||
'(yes-no)))
|
(if (not (= n num-suits))
|
||||||
(set! num-suits n)
|
(when (eq? 'yes
|
||||||
(put-preferences '(spider:num-suits) (list n))
|
(message-box "Warning"
|
||||||
(send d show #f)
|
"Reset the game for new suit count?"
|
||||||
(reset-game!))
|
d
|
||||||
(send d show #f))))])
|
'(yes-no)))
|
||||||
(send suits set-selection (case num-suits [(1) 0][(2) 1][(4) 2]))
|
(set! num-suits n)
|
||||||
(send d center)
|
(put-preferences '(spider:num-suits) (list n))
|
||||||
(send d show #t))])
|
(send d show #f)
|
||||||
|
(reset-game!))
|
||||||
|
(send d show #f))))])
|
||||||
|
(send suits set-selection (case num-suits [(1) 0][(2) 1][(4) 2]))
|
||||||
|
(send d center)
|
||||||
|
(send d show #t))])
|
||||||
|
|
||||||
(define help (show-help '("games" "spider") "Spider Rules" #f))
|
(define help (show-help '("games" "spider") "Spider Rules" #f))
|
||||||
(new menu-item%
|
(new menu-item%
|
||||||
[label "&Rules"]
|
[label "&Rules"]
|
||||||
[parent (make-object menu% "&Help" mb)]
|
[parent (make-object menu% "&Help" mb)]
|
||||||
[callback (lambda (i e)
|
[callback (lambda (i e) (help))])
|
||||||
(help))])
|
|
||||||
|
|
||||||
(define (push-state!)
|
(define (push-state!)
|
||||||
(when (null? old-states)
|
(when (null? old-states)
|
||||||
(send undo enable #t))
|
(send undo enable #t))
|
||||||
(set! old-states (cons (make-state
|
(set! old-states
|
||||||
draw-pile
|
(cons (make-state draw-pile
|
||||||
(vector-copy stacks)
|
(vector-copy stacks)
|
||||||
(vector-copy dones)
|
(vector-copy dones)
|
||||||
done-count
|
done-count
|
||||||
(map (lambda (c) (send c face-down?)) deck))
|
(map (lambda (c) (send c face-down?)) deck))
|
||||||
old-states)))
|
old-states)))
|
||||||
|
|
||||||
(define (pop-state!)
|
(define (pop-state!)
|
||||||
(let ([state (car old-states)])
|
(let ([state (car old-states)])
|
||||||
(send t begin-card-sequence)
|
(send t begin-card-sequence)
|
||||||
(set! old-states (cdr old-states))
|
(set! old-states (cdr old-states))
|
||||||
(set! draw-pile (state-draw-pile state))
|
(set! draw-pile (state-draw-pile state))
|
||||||
(set! stacks (state-stacks state))
|
(set! stacks (state-stacks state))
|
||||||
(set! dones (state-dones state))
|
(set! dones (state-dones state))
|
||||||
(set! done-count (state-done-count state))
|
(set! done-count (state-done-count state))
|
||||||
(for-each (lambda (c fd?)
|
(for-each (lambda (c fd?)
|
||||||
(send c user-can-move #f)
|
(send c user-can-move #f)
|
||||||
(unless (eq? (send c face-down?) fd?)
|
(unless (eq? (send c face-down?) fd?) (send c flip)))
|
||||||
(send c flip)))
|
deck (state-face-down?s state))
|
||||||
deck (state-face-down?s state))
|
(send t move-cards draw-pile dx dy)
|
||||||
(send t move-cards draw-pile dx dy)
|
(send t stack-cards draw-pile)
|
||||||
(send t stack-cards draw-pile)
|
(let loop ([i 0])
|
||||||
(let loop ([i 0])
|
(unless (= i (vector-length stacks))
|
||||||
(unless (= i (vector-length stacks))
|
(send t stack-cards (vector-ref stacks i))
|
||||||
(send t stack-cards (vector-ref stacks i))
|
(loop (add1 i))))
|
||||||
(loop (add1 i))))
|
(let loop ([i 0])
|
||||||
(let loop ([i 0])
|
(unless (= i (vector-length dones)) (move-dones i) (loop (add1 i))))
|
||||||
(unless (= i (vector-length dones))
|
(shift-stacks)
|
||||||
(move-dones i)
|
(when (null? old-states) (send undo enable #f))
|
||||||
(loop (add1 i))))
|
(send t end-card-sequence)))
|
||||||
(shift-stacks)
|
|
||||||
(when (null? old-states)
|
|
||||||
(send undo enable #f))
|
|
||||||
(send t end-card-sequence)))
|
|
||||||
|
|
||||||
(define (find-stack find)
|
(define (find-stack find)
|
||||||
(let loop ([i 0])
|
(let loop ([i 0])
|
||||||
(if (= i (vector-length stacks))
|
(if (= i (vector-length stacks))
|
||||||
#f
|
#f
|
||||||
(let ([l (vector-ref stacks i)])
|
(let ([l (vector-ref stacks i)])
|
||||||
(if (and (pair? l)
|
(if (and (pair? l) (memq find l))
|
||||||
(memq find l))
|
i
|
||||||
i
|
(loop (add1 i)))))))
|
||||||
(loop (add1 i)))))))
|
|
||||||
|
|
||||||
(define (remove-from-stack! cards)
|
(define (remove-from-stack! cards)
|
||||||
(let* ([i (find-stack (car cards))]
|
(let* ([i (find-stack (car cards))]
|
||||||
[l (vector-ref stacks i)])
|
[l (vector-ref stacks i)])
|
||||||
(vector-set! stacks i (list-tail l (length cards)))))
|
(vector-set! stacks i (list-tail l (length cards)))))
|
||||||
|
|
||||||
(define (stacked-cards card)
|
(define (stacked-cards card)
|
||||||
(let ([i (find-stack card)])
|
(let ([i (find-stack card)])
|
||||||
(if i
|
(if i
|
||||||
(reverse
|
(reverse (let loop ([l (vector-ref stacks i)])
|
||||||
(let loop ([l (vector-ref stacks i)])
|
(cond [(not (send (car l) user-can-move)) null]
|
||||||
(cond
|
[(eq? (car l) card) (list card)]
|
||||||
[(not (send (car l) user-can-move)) null]
|
[else (cons (car l) (loop (cdr l)))])))
|
||||||
[(eq? (car l) card) (list card)]
|
#f)))
|
||||||
[else (cons (car l) (loop (cdr l)))])))
|
|
||||||
#f)))
|
|
||||||
|
|
||||||
(define (drag-ok? cards i)
|
(define (drag-ok? cards i)
|
||||||
(let ([c (car cards)]
|
(let ([c (car cards)]
|
||||||
[l (vector-ref stacks i)])
|
[l (vector-ref stacks i)])
|
||||||
(and l
|
(and l
|
||||||
(or (null? l)
|
(or (null? l)
|
||||||
(= (send (car l) get-value)
|
(= (send (car l) get-value)
|
||||||
(add1 (send c get-value)))))))
|
(add1 (send c get-value)))))))
|
||||||
|
|
||||||
(let loop ([i 0])
|
(let loop ([i 0])
|
||||||
(unless (= i (vector-length stacks))
|
(unless (= i (vector-length stacks))
|
||||||
null
|
null
|
||||||
(let ([r (make-region (+ dx (* i (+ CARD-WIDTH dx)))
|
(let ([r (make-region (+ dx (* i (+ CARD-WIDTH dx)))
|
||||||
(+ dy CARD-HEIGHT dy)
|
(+ dy CARD-HEIGHT dy)
|
||||||
CARD-WIDTH
|
CARD-WIDTH
|
||||||
(- (* CARD-HEIGHT 5) dy dy)
|
(- (* CARD-HEIGHT 5) dy dy)
|
||||||
#f
|
#f
|
||||||
(lambda (cards)
|
(lambda (cards)
|
||||||
(when (drag-ok? cards i)
|
(when (drag-ok? cards i)
|
||||||
(move-to-stack cards i))))])
|
(move-to-stack cards i))))])
|
||||||
(set-region-interactive-callback!
|
(set-region-interactive-callback!
|
||||||
r
|
r
|
||||||
(lambda (on? cards)
|
(lambda (on? cards)
|
||||||
(let ([ok? (and on? (drag-ok? cards i))])
|
(let ([ok? (and on? (drag-ok? cards i))])
|
||||||
(for-each (lambda (c)
|
(for-each (lambda (c) (send c snap-back-after-move (not ok?)))
|
||||||
(send c snap-back-after-move (not ok?)))
|
cards)
|
||||||
cards)
|
(let ([l (vector-ref stacks i)])
|
||||||
(let ([l (vector-ref stacks i)])
|
(unless (null? l) (send (car l) dim ok?))))))
|
||||||
(unless (null? l)
|
(send t add-region r)
|
||||||
(send (car l) dim ok?))))))
|
(loop (add1 i)))))
|
||||||
(send t add-region r)
|
|
||||||
(loop (add1 i)))))
|
|
||||||
|
|
||||||
|
|
||||||
(define (move-to-stack cards i)
|
|
||||||
(unselect)
|
|
||||||
(let ([l (vector-ref stacks i)])
|
|
||||||
(unless (null? l)
|
|
||||||
(send (car l) dim #f)))
|
|
||||||
(push-state!)
|
|
||||||
(remove-from-stack! cards)
|
|
||||||
(vector-set! stacks i
|
|
||||||
(append (reverse cards)
|
|
||||||
(vector-ref stacks i)))
|
|
||||||
(for-each (lambda (c)
|
|
||||||
(send c snap-back-after-move #t))
|
|
||||||
cards)
|
|
||||||
(shift-stacks))
|
|
||||||
|
|
||||||
(define selected null)
|
(define (move-to-stack cards i)
|
||||||
|
(unselect)
|
||||||
|
(let ([l (vector-ref stacks i)])
|
||||||
|
(unless (null? l) (send (car l) dim #f)))
|
||||||
|
(push-state!)
|
||||||
|
(remove-from-stack! cards)
|
||||||
|
(vector-set! stacks i (append (reverse cards) (vector-ref stacks i)))
|
||||||
|
(for-each (lambda (c) (send c snap-back-after-move #t)) cards)
|
||||||
|
(shift-stacks))
|
||||||
|
|
||||||
(define (select cards)
|
(define selected null)
|
||||||
(unselect)
|
|
||||||
(set! selected cards)
|
|
||||||
(for-each (lambda (c) (send c dim #t))
|
|
||||||
selected))
|
|
||||||
|
|
||||||
(define (unselect)
|
(define (select cards)
|
||||||
(for-each (lambda (c) (send c dim #f))
|
(unselect)
|
||||||
selected)
|
(set! selected cards)
|
||||||
(set! selected null))
|
(for-each (lambda (c) (send c dim #t)) selected))
|
||||||
|
|
||||||
(define (move-dones i)
|
(define (unselect)
|
||||||
(send t move-cards (vector-ref dones i)
|
(for-each (lambda (c) (send c dim #f)) selected)
|
||||||
(- (* 10 CARD-WIDTH) dx (* i (+ CARD-WIDTH dx)))
|
(set! selected null))
|
||||||
dy))
|
|
||||||
|
|
||||||
(define (draw push?)
|
(define (move-dones i)
|
||||||
(when push?
|
(send t move-cards (vector-ref dones i)
|
||||||
(push-state!))
|
(- (* 10 CARD-WIDTH) dx (* i (+ CARD-WIDTH dx)))
|
||||||
(let ([drawn-cards
|
dy))
|
||||||
(let loop ([i 0])
|
|
||||||
(if (or (= i (vector-length stacks))
|
|
||||||
(null? draw-pile))
|
|
||||||
null
|
|
||||||
(if (vector-ref stacks i)
|
|
||||||
(let ([a (car draw-pile)])
|
|
||||||
(vector-set! stacks i (cons a
|
|
||||||
(vector-ref stacks i)))
|
|
||||||
(send a flip)
|
|
||||||
(set! draw-pile (cdr draw-pile))
|
|
||||||
(cons a (loop (add1 i))))
|
|
||||||
(loop (add1 i)))))])
|
|
||||||
(send t card-to-front (car drawn-cards))
|
|
||||||
(send t stack-cards drawn-cards))
|
|
||||||
(shift-stacks))
|
|
||||||
|
|
||||||
(define (check-complete)
|
|
||||||
(let loop ([i 0])
|
|
||||||
(unless (= i (vector-length stacks))
|
|
||||||
(let ([l (vector-ref stacks i)])
|
|
||||||
(when (and (pair? l)
|
|
||||||
(= 1 (send (car l) get-value)))
|
|
||||||
(let ([suit (send (car l) get-suit)])
|
|
||||||
(let loop ([j 2][a (list (car l))][l (cdr l)])
|
|
||||||
(cond
|
|
||||||
[(= j 14)
|
|
||||||
;; Complete set - move 13 cards to a done pile
|
|
||||||
(vector-set! dones done-count a)
|
|
||||||
(move-dones done-count)
|
|
||||||
(set! done-count (add1 done-count))
|
|
||||||
(for-each (lambda (c)
|
|
||||||
(send c user-can-move #f))
|
|
||||||
a)
|
|
||||||
(vector-set! stacks i l)]
|
|
||||||
[(and (pair? l)
|
|
||||||
(= j (send (car l) get-value))
|
|
||||||
(equal? suit (send (car l) get-suit)))
|
|
||||||
(loop (add1 j) (cons (car l) a) (cdr l))]
|
|
||||||
[else (void)])))))
|
|
||||||
(loop (add1 i)))))
|
|
||||||
|
|
||||||
(define (shift-stacks)
|
(define (draw push?)
|
||||||
(unselect)
|
(when push? (push-state!))
|
||||||
(check-complete)
|
(let ([drawn-cards
|
||||||
(let ([cards (apply append (map reverse (vector->list stacks)))]
|
(let loop ([i 0])
|
||||||
[deltas (list->vector
|
(if (or (= i (vector-length stacks)) (null? draw-pile))
|
||||||
(let loop ([i 0])
|
null
|
||||||
(if (= i (vector-length stacks))
|
(if (vector-ref stacks i)
|
||||||
null
|
(let ([a (car draw-pile)])
|
||||||
(append
|
(vector-set! stacks i (cons a (vector-ref stacks i)))
|
||||||
(let* ([l (vector-ref stacks i)]
|
(send a flip)
|
||||||
[ddy (min (quotient CARD-HEIGHT 3)
|
(set! draw-pile (cdr draw-pile))
|
||||||
(quotient (- (* CARD-HEIGHT 4)
|
(cons a (loop (add1 i))))
|
||||||
dy dy dy)
|
(loop (add1 i)))))])
|
||||||
(max 1 (sub1 (length l)))))])
|
(send t card-to-front (car drawn-cards))
|
||||||
(let loop ([l l][dy 0])
|
(send t stack-cards drawn-cards))
|
||||||
(if (null? l)
|
(shift-stacks))
|
||||||
null
|
|
||||||
(cons (list (* i (+ CARD-WIDTH dx)) dy)
|
|
||||||
(loop (cdr l) (+ dy ddy))))))
|
|
||||||
(loop (add1 i))))))])
|
|
||||||
(send t move-cards cards dx (+ CARD-HEIGHT dy dy)
|
|
||||||
(lambda (i) (apply values (vector-ref deltas i))))
|
|
||||||
|
|
||||||
(let loop ([i 0])
|
|
||||||
(unless (= i (vector-length stacks))
|
|
||||||
(let ([l (vector-ref stacks i)])
|
|
||||||
(when (pair? l)
|
|
||||||
(when (send (car l) face-down?)
|
|
||||||
(send t flip-card (car l)))
|
|
||||||
(send (car l) user-can-move #t)
|
|
||||||
(let loop ([l (cdr l)][prev (car l)])
|
|
||||||
(unless (null? l)
|
|
||||||
(if (and (not (send (car l) face-down?))
|
|
||||||
(equal? (send prev get-suit)
|
|
||||||
(send (car l) get-suit))
|
|
||||||
(= (add1 (send prev get-value))
|
|
||||||
(send (car l) get-value)))
|
|
||||||
(begin
|
|
||||||
(send (car l) user-can-move #t)
|
|
||||||
(loop (cdr l) (car l)))
|
|
||||||
(for-each (lambda (c)
|
|
||||||
(send c user-can-move #f))
|
|
||||||
l))))))
|
|
||||||
(loop (add1 i))))))
|
|
||||||
|
|
||||||
(send t set-double-click-action void)
|
|
||||||
|
|
||||||
(send t set-single-click-action
|
(define (check-complete)
|
||||||
(lambda (c)
|
(let loop ([i 0])
|
||||||
(cond
|
(unless (= i (vector-length stacks))
|
||||||
[(and (pair? draw-pile)
|
(let ([l (vector-ref stacks i)])
|
||||||
(eq? c (car draw-pile)))
|
(when (and (pair? l) (= 1 (send (car l) get-value)))
|
||||||
(if (ormap null? (vector->list stacks))
|
(let ([suit (send (car l) get-suit)])
|
||||||
(bell)
|
(let loop ([j 2][a (list (car l))][l (cdr l)])
|
||||||
(draw #t))]
|
(cond
|
||||||
[(and (pair? selected) (eq? c (car selected)))
|
[(= j 14)
|
||||||
(unselect)]
|
;; Complete set - move 13 cards to a done pile
|
||||||
[(and (pair? selected)
|
(vector-set! dones done-count a)
|
||||||
(let ([i (find-stack c)])
|
(move-dones done-count)
|
||||||
(and i
|
(set! done-count (add1 done-count))
|
||||||
(not (equal? i (find-stack (car selected))))
|
(for-each (lambda (c) (send c user-can-move #f)) a)
|
||||||
(drag-ok? selected i)
|
(vector-set! stacks i l)]
|
||||||
i)))
|
[(and (pair? l)
|
||||||
=> (lambda (i)
|
(= j (send (car l) get-value))
|
||||||
(send t card-to-front (car (last-pair selected)))
|
(equal? suit (send (car l) get-suit)))
|
||||||
(send t stack-cards (reverse selected))
|
(loop (add1 j) (cons (car l) a) (cdr l))]
|
||||||
(move-to-stack selected i))]
|
[else (void)])))))
|
||||||
[(stacked-cards c)
|
(loop (add1 i)))))
|
||||||
=> (lambda (cards) (select cards))])))
|
|
||||||
|
|
||||||
;; Add a region for each stack to receive clicks when
|
(define (shift-stacks)
|
||||||
;; the stack is empty:
|
(unselect)
|
||||||
(let loop ([i 0])
|
(check-complete)
|
||||||
(unless (= i (vector-length stacks))
|
(let ([cards (apply append (map reverse (vector->list stacks)))]
|
||||||
(send t add-region (make-button-region
|
[deltas (list->vector
|
||||||
(+ dx (* i (+ CARD-WIDTH dx)))
|
(let loop ([i 0])
|
||||||
(+ dy CARD-HEIGHT dy)
|
(if (= i (vector-length stacks))
|
||||||
CARD-WIDTH CARD-HEIGHT
|
null
|
||||||
#f
|
(append
|
||||||
(lambda ()
|
(let* ([l (vector-ref stacks i)]
|
||||||
(when (and (null? (vector-ref stacks i))
|
[ddy (min (quotient CARD-HEIGHT 3)
|
||||||
(pair? selected))
|
(quotient (- (* CARD-HEIGHT 4)
|
||||||
(move-to-stack selected i)))))
|
dy dy dy)
|
||||||
(loop (add1 i))))
|
(max 1 (sub1 (length l)))))])
|
||||||
|
(let loop ([l l][dy 0])
|
||||||
|
(if (null? l)
|
||||||
|
null
|
||||||
|
(cons (list (* i (+ CARD-WIDTH dx)) dy)
|
||||||
|
(loop (cdr l) (+ dy ddy))))))
|
||||||
|
(loop (add1 i))))))])
|
||||||
|
(send t move-cards cards dx (+ CARD-HEIGHT dy dy)
|
||||||
|
(lambda (i) (apply values (vector-ref deltas i))))
|
||||||
|
|
||||||
(send t set-button-action 'left 'drag-raise/above)
|
(let loop ([i 0])
|
||||||
(send t set-button-action 'middle 'drag-raise/above)
|
(unless (= i (vector-length stacks))
|
||||||
(send t set-button-action 'right 'drag-raise/above)
|
(let ([l (vector-ref stacks i)])
|
||||||
|
(when (pair? l)
|
||||||
|
(when (send (car l) face-down?) (send t flip-card (car l)))
|
||||||
|
(send (car l) user-can-move #t)
|
||||||
|
(let loop ([l (cdr l)][prev (car l)])
|
||||||
|
(unless (null? l)
|
||||||
|
(if (and (not (send (car l) face-down?))
|
||||||
|
(equal? (send prev get-suit)
|
||||||
|
(send (car l) get-suit))
|
||||||
|
(= (add1 (send prev get-value))
|
||||||
|
(send (car l) get-value)))
|
||||||
|
(begin (send (car l) user-can-move #t)
|
||||||
|
(loop (cdr l) (car l)))
|
||||||
|
(for-each (lambda (c) (send c user-can-move #f))
|
||||||
|
l))))))
|
||||||
|
(loop (add1 i))))))
|
||||||
|
|
||||||
(define (reset-game!)
|
(send t set-double-click-action void)
|
||||||
(send t remove-cards deck)
|
|
||||||
(set! deck (make-spider-deck))
|
(send t set-single-click-action
|
||||||
(send t add-cards deck dx dy)
|
(lambda (c)
|
||||||
(send t begin-card-sequence)
|
(cond
|
||||||
(unselect)
|
[(and (pair? draw-pile)
|
||||||
(send undo enable #f)
|
(eq? c (car draw-pile)))
|
||||||
(set! draw-pile (shuffle-list deck 7))
|
(if (ormap null? (vector->list stacks)) (bell) (draw #t))]
|
||||||
(for-each (lambda (c)
|
[(and (pair? selected) (eq? c (car selected)))
|
||||||
(unless (send c face-down?)
|
(unselect)]
|
||||||
(send c flip))
|
[(and (pair? selected)
|
||||||
(send c user-can-flip #f)
|
(let ([i (find-stack c)])
|
||||||
(send c user-can-move #f)
|
(and i
|
||||||
(send c snap-back-after-move #t))
|
(not (equal? i (find-stack (car selected))))
|
||||||
draw-pile)
|
(drag-ok? selected i)
|
||||||
(set! stacks (make-vector 10 null))
|
i)))
|
||||||
(set! dones (make-vector 8 null))
|
=> (lambda (i)
|
||||||
(set! done-count 0)
|
(send t card-to-front (car (last-pair selected)))
|
||||||
(set! old-states null)
|
(send t stack-cards (reverse selected))
|
||||||
(send t stack-cards draw-pile)
|
(move-to-stack selected i))]
|
||||||
(let loop ([i 0])
|
[(stacked-cards c) => (lambda (cards) (select cards))])))
|
||||||
(unless (= i (vector-length stacks))
|
|
||||||
(let ([n (if (< i 4) 5 4)])
|
;; Add a region for each stack to receive clicks when
|
||||||
(vector-set! stacks i (list-first-n draw-pile n))
|
;; the stack is empty:
|
||||||
(set! draw-pile (list-tail draw-pile n)))
|
(let loop ([i 0])
|
||||||
(loop (add1 i))))
|
(unless (= i (vector-length stacks))
|
||||||
(draw #f)
|
(send t add-region (make-button-region
|
||||||
(send t end-card-sequence))
|
(+ dx (* i (+ CARD-WIDTH dx)))
|
||||||
(reset-game!)
|
(+ dy CARD-HEIGHT dy)
|
||||||
(send t show #t))))
|
CARD-WIDTH CARD-HEIGHT
|
||||||
|
#f
|
||||||
|
(lambda ()
|
||||||
|
(when (and (null? (vector-ref stacks i))
|
||||||
|
(pair? selected))
|
||||||
|
(move-to-stack selected i)))))
|
||||||
|
(loop (add1 i))))
|
||||||
|
|
||||||
|
(send t set-button-action 'left 'drag-raise/above)
|
||||||
|
(send t set-button-action 'middle 'drag-raise/above)
|
||||||
|
(send t set-button-action 'right 'drag-raise/above)
|
||||||
|
|
||||||
|
(define (reset-game!)
|
||||||
|
(send t remove-cards deck)
|
||||||
|
(set! deck (make-spider-deck))
|
||||||
|
(send t add-cards deck dx dy)
|
||||||
|
(send t begin-card-sequence)
|
||||||
|
(unselect)
|
||||||
|
(send undo enable #f)
|
||||||
|
(set! draw-pile (shuffle-list deck 7))
|
||||||
|
(for-each (lambda (c)
|
||||||
|
(unless (send c face-down?) (send c flip))
|
||||||
|
(send c user-can-flip #f)
|
||||||
|
(send c user-can-move #f)
|
||||||
|
(send c snap-back-after-move #t))
|
||||||
|
draw-pile)
|
||||||
|
(set! stacks (make-vector 10 null))
|
||||||
|
(set! dones (make-vector 8 null))
|
||||||
|
(set! done-count 0)
|
||||||
|
(set! old-states null)
|
||||||
|
(send t stack-cards draw-pile)
|
||||||
|
(let loop ([i 0])
|
||||||
|
(unless (= i (vector-length stacks))
|
||||||
|
(let ([n (if (< i 4) 5 4)])
|
||||||
|
(vector-set! stacks i (list-first-n draw-pile n))
|
||||||
|
(set! draw-pile (list-tail draw-pile n)))
|
||||||
|
(loop (add1 i))))
|
||||||
|
(draw #f)
|
||||||
|
(send t end-card-sequence))
|
||||||
|
(reset-game!)
|
||||||
|
(send t show #t)
|
||||||
|
|
||||||
|
))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user