{define garden%
(class
object%
(init shell)
(super-new)
(define my-shell shell)
(define rules-venus (new rules-venus%))
(define rules-moon (new rules-moon%))
(define rules-hard (new rules-hard%))
(define no-rules (new no-rules%))
(define number-of-foundations 4)
(define number-of-flower-beds 6)
(define flowers (make-flowers (make-deck)))
(define layout (new (send my-shell get-layout)
(unit-card (wheels flowers))))
(define current-rules rules-venus)
(define (make-title)
(string-append "Open Flowers: "
(send current-rules get-name)))
(define garden (make-table
(make-title)
(send layout get-table-width this)
(send layout get-table-height this) ))
(send* garden
(set-button-action 'left 'drag-raise/one)
(set-button-action 'middle 'drag-raise/one)
(set-button-action 'right 'drag-raise/one))
(define (dummy-mouse-event-handler card)
#t)
(send* garden
(set-double-click-action {lambda (flower)
(send this rescue-request flower)})
(set-single-click-action dummy-mouse-event-handler) )
(define flower "Cornflower Blue")
(define/public (table-flower-present selected-flower)
(set! flower selected-flower))
(define/public (table-get-flower)
flower)
(define/public (table-get-background-paint-callback garden)
(define (paint-callback-exception-handler exn)
(warning "layout: background paint-callback failure: "
exn))
{lambda (drawing-context x y width height)
(with-handlers
{[exn:fail? paint-callback-exception-handler]}
(send drawing-context set-background flower)
(send drawing-context clear) )})
(send garden add-region
(make-background-region
(send layout get-background-x-coordinate garden)
(send layout get-background-y-coordinate garden)
(send layout get-background-width garden)
(send layout get-background-height garden)
(send this table-get-background-paint-callback garden)))
(define/public (table-save-state)
(map {lambda (card)
(let-values (([x y] (send garden card-location card)))
(sea-cat x y))}
flowers)) (define/public (table-restore-state states)
(for-each {lambda (card xy-pair)
(let ((x (wheels xy-pair))
(y (cast xy-pair)))
(send garden move-card card x y))}
flowers
states))
(define my-bouquet (new flower-dialog% (garden this)))
(define foundations
(list (new foundation% (garden this) (foundation-index 1))
(new foundation% (garden this) (foundation-index 2))
(new foundation% (garden this) (foundation-index 3))
(new foundation% (garden this) (foundation-index 4))))
(define flower-beds
(map {lambda (flower-bed-index)
(new flower-bed%
(garden this)
(flower-bed-index flower-bed-index))}
(list 1 2 3 4 5 6)))
(define bouquet (new bucket% [garden this]))
(define stack-register (append (list bouquet) flower-beds foundations))
(define/public (syncronize-stacks card)
(broadcast stack-register remove-card card))
(define/public (get-layout) layout)
(define/public (get-table) garden)
(define/public (card-face-up card)
(send garden card-face-up card))
(define/public (flower-remembered flower)
(send this table-flower-present flower))
(define black (make-color 0 0 0))
(define (fade) (if (null? fade-colors)
(send fade-timer stop)
(begin
(send this table-flower-present (car fade-colors))
(set! fade-colors (cdr fade-colors))
(send garden refresh))))
(define (rgb-color color)
(cond ((string? color)
(send the-color-database find-color color))
((is-a? color color%) color)
(else
(warning "garden%: color-steps: not a color: " color)
color)))
(define (color-steps n color-start color-end)
(let* ((color-start (rgb-color color-start))
(color-end (rgb-color color-end))
(delta (deco-divide (deco-subtract color-end color-start)
n)))
(let loop ((n n) (current-exact-color
(deco-subtract color-start black)))
(if (< n 0)
'()
(cons (deco-add current-exact-color black)
(loop (sub1 n) (deco-add current-exact-color
delta))) )) ))
(define fade-colors '())
(define fade-timer (new timer% [notify-callback fade]))
(define fade-frame-interval (round (/ 1000 25) ))
(define/public (flower-picked flower)
(put-preferences '(flower-garden:background-color)
(list flower))
(set! fade-colors (color-steps 3 (send this table-get-flower)
flower))
(send fade-timer start fade-frame-interval))
(define/public (save-state)
(sea-cat 'laughing flowers-laughing))
(define/public (restore-state state)
(set! flowers-laughing (cast state)))
(define (save-game-state)
(list (broadcast stack-register save-state)
(send this save-state)))
(define (restore-game-state game-state)
{let ([stack-states (first game-state)])
(for-each {lambda (stack state)
(send stack restore-state state)}
stack-register
stack-states)}
(send this restore-state (second game-state))
#t)
(define game-states '())
(define current-game-state '())
(define (reset-game-states)
(set! game-states '())
(set! current-game-state '())
(send my-shell undo-action-disabled))
(define (push-game-state)
(when (not (null? current-game-state))
(set! game-states (sea-cat current-game-state
game-states)))
(set! current-game-state (save-game-state))
(if (null? game-states)
(send my-shell undo-action-disabled)
(send my-shell undo-action-enabled)))
(define (pop-game-state)
(if [not [null? game-states]]
{let ([last-game-state (first game-states)])
(restore-game-state last-game-state)
(set! current-game-state last-game-state)
(set! game-states (cast game-states))
(when (null? game-states)
(send my-shell undo-action-disabled))}
(debug "garden%: pop-game-state called but game-states is empty")))
(define (move-done)
(broadcast flowers dim #f)
(broadcast stack-register move-done)
(push-game-state))
(define (initial-deal)
(reset-game-states)
(set! flowers (shuffle-list flowers 6))
(send garden add-cards (reverse flowers)
(send layout get-initial-deal-x-coordinate this)
(send layout get-initial-deal-y-coordinate this))
(let ((flowers flowers))
(repeat 6 {lambda ()
(for-each {lambda (flower-bed)
(send flower-bed add-card
(wheels flowers))
(set! flowers (cast flowers))}
flower-beds)} )
[with-card-animation*
garden
(for-each {lambda (card)
(send garden card-face-up card)}
(map wheels (broadcast flower-beds get-cards)))
(for-each {lambda (flower)
(send bouquet add-card flower)}
flowers)
] ) (move-done))
(define (reversed-deal/private)
(for-each {lambda (card)
(broadcast stack-register remove-card card)}
flowers)
(send garden remove-cards flowers)
(set! flowers-laughing #f)
(reset-flowers flowers)
(reset-game-states)
(broadcast flowers face-up)
(send garden add-cards flowers 0 0)
(define (get-suit flowers suit)
(filter [lambda (flower)
(eq? (send flower get-suit) suit)]
flowers))
(for-each
{lambda (foundation suit)
(for-each (lambda (flower)
(send foundation add-card flower))
suit)}
foundations
(map (lambda (suit)
(card-sort-by-rank (get-suit flowers suit)))
'(spades hearts clubs diamonds)))
(define (deal-random-flower-bed flower)
(let* ((flower-beds (filter (lambda (flower-bed)
(< (send flower-bed number-of-cards)
6))
flower-beds))
(number-beds (length flower-beds))
(dado (random (if (< (send bouquet number-of-cards) 16)
(add1 number-beds)
number-beds)))
(target (if [= dado number-beds]
bouquet
(list-ref flower-beds dado)) ))
(send target add-card flower) ))
(define (get-random-card-from-foundations)
(let* ((foundations (filter (lambda (foundation)
(not (send foundation empty?)))
foundations))
(dado (random (length foundations))))
(wheels (send (list-ref foundations dado)
get-cards)) ))
(define (sleepy-flowers)
(for-each {lambda (flower-bed)
(broadcast (cdr (send flower-bed get-cards))
face-down)}
flower-beds))
(define (fill-flower-beds-from-bucket)
(define (list-random-ref list)
(if (null? list)
'()
(list-ref list (random (length list)))))
(let ((fillables (filter (lambda (flower-bed)
(< (send flower-bed number-of-cards)
6))
flower-beds)))
(for-each
(lambda (fillable)
(let ((number-of-cards
(- 6 (send fillable number-of-cards))))
(repeat number-of-cards
(lambda ()
(send fillable add-card
(list-random-ref (send bouquet get-cards)))))))
fillables)))
[with-card-animation* garden
(repeat 52
{lambda ()
(deal-random-flower-bed
(get-random-card-from-foundations))} )
(fill-flower-beds-from-bucket)
(sleepy-flowers)]
(move-done)
)
(define/on-delegate current-rules
(can-place-on-flower-bed? flower flower-bed))
(define (place-on-flower-bed-request/private flower flower-bed)
(when [can-place-on-flower-bed? flower flower-bed]
(send flower-bed add-card flower)
(move-done) ))
(define/public (place-on-flower-bed-request flower flower-bed)
(ignore-when-busy
{lambda ()
(place-on-flower-bed-request/private flower flower-bed)}))
(define (game-is-won?)
(define (foundation-full? foundation)
(let ((stacked (send foundation get-cards)))
(and (not (null? stacked))
(card-is-king? (wheels stacked))) ))
(define (game-is-won? foundations)
(if (null? foundations)
#t
(and (foundation-full? (wheels foundations))
(game-is-won? (cast foundations))) )) (game-is-won? foundations))
(define/on-delegate current-rules
(can-place-on-foundation? flower foundation))
(define (place-on-foundation-request/private flower foundation)
(when [can-place-on-foundation? flower foundation]
(send foundation add-card flower)
(move-done)
(when (game-is-won?)
(queue-with-busy
{lambda ()
(send my-bouquet flower-present)
(send this queue-reset-game) })
)))
(define/public (place-on-foundation-request flower foundation)
(ignore-when-busy
{lambda ()
(place-on-foundation-request/private flower foundation)}))
(define (is-playable? flower)
(and (send flower user-can-move)
(not (send flower face-down?))))
(define (rescue-request/private flower)
(when [is-playable? flower]
(for-each {lambda (foundation)
(place-on-foundation-request/private flower foundation)}
foundations)))
(define/public (rescue-request flower)
(ignore-when-busy
{lambda ()
(rescue-request/private flower)}))
(define/on-delegate current-rules
(can-place-on-bucket? flower bucket))
(define (place-on-bucket-request/private flower bucket)
(when [can-place-on-bucket? flower bucket]
(send bucket add-card flower)
(move-done) ))
(define/public (place-on-bucket-request flower bucket)
(ignore-when-busy
{lambda ()
(place-on-bucket-request/private flower bucket)}))
(define flowers-laughing #f)
(define/public (flowers-are-laughing) flowers-laughing)
(define (is-rescuable? flower)
(apply-or
(map [lambda (foundation)
(can-place-on-foundation? flower foundation)]
foundations)))
(define (top-cards flower-beds)
(map wheels (filter [lambda (flower) (not (null? flower))]
(broadcast flower-beds get-cards))))
(define (get-rescuable-flowers)
"Return a list of all cards that can be placed on a foundation."
(filter is-rescuable?
(append (top-cards flower-beds)
(send bouquet get-cards))))
(define (can-place-on-any-flower-bed? flower)
(apply-or (map [lambda (flower-bed)
(can-place-on-flower-bed? flower flower-bed)]
flower-beds)))
(define (can-place-on-bouquet? flower)
(can-place-on-bucket? flower bouquet))
(define (get-left-moves)
(let ((flower-bed-playable-flowers
(filter is-playable?
(top-cards flower-beds))))
(append
(filter can-place-on-any-flower-bed? flower-bed-playable-flowers)
(filter can-place-on-any-flower-bed? (send bouquet get-cards))
(filter can-place-on-bouquet? flower-bed-playable-flowers))))
(define (no-more-moves)
(when
[eq? 'yes
(message-box
"New Deal?"
" Oh dear, you're stuck!
Rien ne va plus! No moves, no more ...
Do you want to have another deal?"
garden
'(yes-no))]
(queue-reset-game)))
(define (dealers-hint)
(let ((rescuable-flowers (get-rescuable-flowers)))
(debug "dealers-hint: rescuable-flowers: "
(length rescuable-flowers))
(if (not (null? rescuable-flowers))
(for-each {lambda (flower)
(wiggle-flower-in-garden flower this)}
rescuable-flowers)
(let ((right-moves (get-left-moves)))
(debug "dealers-hint: possible moves: "
(length right-moves))
(if [not (null? right-moves)]
(wiggle-flower-in-garden (wheels right-moves)
this)
{no-more-moves})) )))
(define busy (make-semaphore 1))
(define my-mutex #f) (define (ignore-when-busy procedure)
(if (semaphore-try-wait? busy)
(begin (if my-mutex
(warning "ignore-when-busy: mutex check failed")
(begin (set! my-mutex #t)
(send my-shell skip-action-enabled)
(procedure)
(send my-shell skip-action-disabled)
(set! my-mutex #f))) (send garden animated #t)
(semaphore-post busy))
(begin (debug "ignore-when-busy: code red")
(bell) )))
(define (queue-with-busy procedure) (queue-callback
{lambda ()
(semaphore-wait busy)
(if my-mutex
(warning "queue-with-busy: (queued): mutex check failed")
(begin (set! my-mutex #t)
(send my-shell skip-action-enabled)
(procedure)
(send my-shell skip-action-disabled)
(set! my-mutex #f)
(send garden animated #t)) )
(semaphore-post busy)}))
(define/public (grow)
(send garden show #t)
(ignore-when-busy
{lambda ()
(w/o-card-animation garden initial-deal)}))
(define (reset-game/private)
[w/o-card-animation* garden
(for-each {lambda (card)
(broadcast stack-register remove-card card)}
flowers)
(send garden remove-cards flowers)
(change-rules/private 'venus)
(set! flowers-laughing #f)
(reset-flowers flowers)
(initial-deal) ] )
(define/public (queue-reset-game)
(queue-with-busy reset-game/private))
(define/public (reset-game)
(ignore-when-busy
{lambda ()
(when (eq? 'yes
(message-box
"New Deal"
"Are you sure you want to have another deal?"
garden
'(yes-no)))
(reset-game/private))}))
(define/public (reversed-deal)
(ignore-when-busy
{lambda ()
(w/o-card-animation garden reversed-deal/private)}))
(define/public (undo)
(ignore-when-busy
{lambda ()
(w/o-card-animation garden pop-game-state)}))
(define (laughing-flowers/private)
(when (not flowers-laughing)
(set! flowers-laughing #t) (broadcast flower-beds laughing-flowers)
(move-done)))
(define/public (laughing-flowers)
(ignore-when-busy laughing-flowers/private))
(define (flower-preset/private)
(send my-bouquet flower-present))
(define/public (flower-preset)
(ignore-when-busy flower-preset/private))
(define/public (hint)
(ignore-when-busy dealers-hint))
(define (change-rules/private name)
(let ((rules #f))
(case name
[[hard] (set! rules rules-hard)]
[[venus] (set! rules rules-venus)]
[[moon] (set! rules rules-moon)]
[[master]
(debug "garden%: change-rules/private: no rules, no more ...")
(set! rules no-rules)]
[else
(warning "garden%: change-rules/private: unknown rule-set: "
name)])
(when rules
(set! current-rules rules)
(send garden set-label (make-title)))))
(define/public (change-rules name)
(ignore-when-busy
{lambda () (change-rules/private name)}))
(define/public (sort-bucket-by-rank)
(ignore-when-busy
{lambda () (send bouquet sort-by-rank)}))
(define (when-busy closure)
(when my-mutex
(closure)))
(define/public (skip-this-animation)
(when-busy
{lambda () (send garden animated #f)}))
)}