#lang mzscheme (require games/cards mred mzlib/class mzlib/list mzlib/file mzlib/unit "../show-scribbling.ss") (define (list-first-n l n) (if (zero? n) null (cons (car l) (list-first-n (cdr l) (sub1 n))))) (define (vector-copy v) (list->vector (vector->list v))) (provide game@) (define game@ (unit (import) (export) (define t (make-table "Spider" 11 6)) (define num-suits (get-preference 'spider:num-suits (lambda () 2))) (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 deck (make-spider-deck)) (define draw-pile deck) (define CARD-WIDTH (send (car deck) card-width)) (define CARD-HEIGHT (send (car deck) card-height)) (define dx (quotient CARD-WIDTH 11)) (define dy dx) (define stacks (make-vector 10 null)) (define dones (make-vector 8 null)) (define done-count 0) (define old-states null) (define-struct state (draw-pile stacks dones done-count face-down?s)) (define mb (make-object menu-bar% t)) (define file-menu (make-object menu% "&File" mb)) (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 separator-menu-item% [parent file-menu]) (new menu-item% [label "&Close"] [parent file-menu] [shortcut #\W] [callback (lambda (i e) (send t show #f))]) (define edit-menu (make-object menu% "&Edit" mb)) (define undo (new menu-item% [label "&Undo"] [parent edit-menu] [shortcut #\Z] [callback (lambda (i e) (pop-state!))])) (new separator-menu-item% [parent edit-menu]) (new menu-item% [label "&Options..."] [parent edit-menu] [callback (lambda (i e) (define d (new dialog% [label "Spider Options"] [parent t] [stretchable-width #f] [stretchable-height #f])) (define suits (new radio-box% [label #f] [parent (new group-box-panel% [parent d] [label "Number of Suits"] [stretchable-width #f] [stretchable-height #f])] [choices '("1 (easiest)" "2" "4 (hardest)")])) (define bottom-panel (new horizontal-panel% [parent d] [alignment '(right center)] [stretchable-height #f])) (new button% [parent bottom-panel] [label "&Cancel"] [callback (lambda (b e) (send d show #f))]) (new button% [parent bottom-panel] [label "&Ok"] [style '(border)] [callback (lambda (b e) (let ([n (expt 2 (send suits get-selection))]) (if (not (= n num-suits)) (when (eq? 'yes (message-box "Warning" "Reset the game for new suit count?" d '(yes-no))) (set! num-suits n) (put-preferences '(spider:num-suits) (list n)) (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-scribbling '(lib "games/scribblings/games.scrbl") "spider")) (new menu-item% [label "&Rules"] [parent (make-object menu% "&Help" mb)] [callback (lambda (i e) (help))]) (define (push-state!) (when (null? old-states) (send undo enable #t)) (set! old-states (cons (make-state draw-pile (vector-copy stacks) (vector-copy dones) done-count (map (lambda (c) (send c face-down?)) deck)) old-states))) (define (pop-state!) (let ([state (car old-states)]) (send t begin-card-sequence) (set! old-states (cdr old-states)) (set! draw-pile (state-draw-pile state)) (set! stacks (state-stacks state)) (set! dones (state-dones state)) (set! done-count (state-done-count state)) (for-each (lambda (c fd?) (send c user-can-move #f) (unless (eq? (send c face-down?) fd?) (send c flip))) deck (state-face-down?s state)) (send t move-cards draw-pile dx dy) (send t stack-cards draw-pile) (let loop ([i 0]) (unless (= i (vector-length stacks)) (send t stack-cards (vector-ref stacks i)) (loop (add1 i)))) (let loop ([i 0]) (unless (= i (vector-length dones)) (move-dones i) (loop (add1 i)))) (shift-stacks) (when (null? old-states) (send undo enable #f)) (send t end-card-sequence))) (define (find-stack find) (let loop ([i 0]) (if (= i (vector-length stacks)) #f (let ([l (vector-ref stacks i)]) (if (and (pair? l) (memq find l)) i (loop (add1 i))))))) (define (remove-from-stack! cards) (let* ([i (find-stack (car cards))] [l (vector-ref stacks i)]) (vector-set! stacks i (list-tail l (length cards))))) (define (stacked-cards card) (let ([i (find-stack card)]) (if i (reverse (let loop ([l (vector-ref stacks i)]) (cond [(not (send (car l) user-can-move)) null] [(eq? (car l) card) (list card)] [else (cons (car l) (loop (cdr l)))]))) #f))) (define (drag-ok? cards i) (let ([c (car cards)] [l (vector-ref stacks i)]) (and l (or (null? l) (= (send (car l) get-value) (add1 (send c get-value))))))) (let loop ([i 0]) (unless (= i (vector-length stacks)) null (let ([r (make-region (+ dx (* i (+ CARD-WIDTH dx))) (+ dy CARD-HEIGHT dy) CARD-WIDTH (- (* CARD-HEIGHT 5) dy dy) #f (lambda (cards) (when (drag-ok? cards i) (move-to-stack cards i))))]) (set-region-interactive-callback! r (lambda (on? cards) (let ([ok? (and on? (drag-ok? cards i))]) (for-each (lambda (c) (send c snap-back-after-move (not ok?))) cards) (let ([l (vector-ref stacks i)]) (unless (null? l) (send (car l) dim ok?)))))) (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 (select cards) (unselect) (set! selected cards) (for-each (lambda (c) (send c dim #t)) selected)) (define (unselect) (for-each (lambda (c) (send c dim #f)) selected) (set! selected null)) (define (move-dones i) (send t move-cards (vector-ref dones i) (- (* 10 CARD-WIDTH) dx (* i (+ CARD-WIDTH dx))) dy)) (define (draw push?) (when push? (push-state!)) (let ([drawn-cards (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) (unselect) (check-complete) (let ([cards (apply append (map reverse (vector->list stacks)))] [deltas (list->vector (let loop ([i 0]) (if (= i (vector-length stacks)) null (append (let* ([l (vector-ref stacks i)] [ddy (min (quotient CARD-HEIGHT 3) (quotient (- (* CARD-HEIGHT 4) dy dy dy) (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)))) (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 (lambda (c) (cond [(and (pair? draw-pile) (eq? c (car draw-pile))) (if (ormap null? (vector->list stacks)) (bell) (draw #t))] [(and (pair? selected) (eq? c (car selected))) (unselect)] [(and (pair? selected) (let ([i (find-stack c)]) (and i (not (equal? i (find-stack (car selected)))) (drag-ok? selected i) i))) => (lambda (i) (send t card-to-front (car (last-pair selected))) (send t stack-cards (reverse selected)) (move-to-stack selected i))] [(stacked-cards c) => (lambda (cards) (select cards))]))) ;; Add a region for each stack to receive clicks when ;; the stack is empty: (let loop ([i 0]) (unless (= i (vector-length stacks)) (send t add-region (make-button-region (+ dx (* i (+ CARD-WIDTH dx))) (+ dy CARD-HEIGHT dy) 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) ))