Split the games
collection out of the main repository.
The `games` collection is now at https://github.com/racket/games
|
@ -1,11 +0,0 @@
|
|||
games
|
||||
Copyright (c) 2010-2014 PLT Design Inc.
|
||||
|
||||
This package is distributed under the GNU Lesser General Public
|
||||
License (LGPL). This means that you can link this package into proprietary
|
||||
applications, provided you follow the rules stated in the LGPL. You
|
||||
can also modify this package; if you distribute a modified version,
|
||||
you must distribute it under the terms of the LGPL, which in
|
||||
particular means that you must release the source code for the
|
||||
modified software. See http://www.gnu.org/copyleft/lesser.html
|
||||
for more information.
|
Before Width: | Height: | Size: 961 B |
|
@ -1,317 +0,0 @@
|
|||
#|
|
||||
|
||||
possible to disable dragging but still allow double-clicking?
|
||||
possible to remap single click (instead of double click)?
|
||||
|
||||
|#
|
||||
|
||||
#lang racket/base
|
||||
|
||||
(require games/cards racket/gui racket/unit string-constants
|
||||
"../show-scribbling.rkt")
|
||||
|
||||
(provide game@)
|
||||
(define game@ (unit (import) (export)
|
||||
|
||||
(define table (make-table "Aces" 6 5))
|
||||
|
||||
(make-object button% (string-constant help-menu-label) table
|
||||
(let ([show-help (show-scribbling '(lib "games/scribblings/games.scrbl")
|
||||
"aces")])
|
||||
(lambda x (show-help))))
|
||||
|
||||
(define draw-pile null)
|
||||
|
||||
(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
|
||||
(define card-space 30)
|
||||
|
||||
(define-struct stack (x y cards) #:mutable)
|
||||
|
||||
(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-struct state (draw-pile stacks))
|
||||
|
||||
;; extract-current-state : -> state
|
||||
(define (extract-current-state)
|
||||
(make-state (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
|
||||
(send table remove-cards draw-pile)
|
||||
(for ([stack (in-list stacks)])
|
||||
(send table remove-cards (stack-cards stack)))
|
||||
|
||||
;; restore old state
|
||||
(set! draw-pile (state-draw-pile state))
|
||||
(for ([stack (in-list stacks)]
|
||||
[cards (in-list (state-stacks state))])
|
||||
(set-stack-cards! stack cards))
|
||||
|
||||
;; restore GUI
|
||||
(for ([draw-pile-card (in-list draw-pile)])
|
||||
(send table add-card draw-pile-card 0 0))
|
||||
(send table move-cards-to-region draw-pile draw-pile-region)
|
||||
(for ([draw-pile-card (in-list (reverse draw-pile))])
|
||||
(send table card-face-down draw-pile-card)
|
||||
(send table card-to-front draw-pile-card))
|
||||
|
||||
(for ([stack (in-list stacks)])
|
||||
(define 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)))
|
||||
(send table end-card-sequence))
|
||||
|
||||
;; undo-stack : (listof state)
|
||||
(define undo-stack null)
|
||||
|
||||
;; redo-stack : (listof state)
|
||||
(define redo-stack null)
|
||||
|
||||
;; 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
|
||||
;; 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 ([stack (in-list stacks)])
|
||||
(send table remove-cards (stack-cards stack)))
|
||||
|
||||
(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 ([card (in-list deck)])
|
||||
(send card user-can-move #f)
|
||||
(send card user-can-flip #f))
|
||||
(set! draw-pile (cddddr deck))
|
||||
(set-stack car)
|
||||
(set-stack cadr)
|
||||
(set-stack caddr)
|
||||
(set-stack cadddr))
|
||||
|
||||
(for ([stack (in-list stacks)])
|
||||
(send table add-cards
|
||||
(stack-cards stack)
|
||||
(stack-x stack)
|
||||
(stack-y stack)
|
||||
(position-cards stack))
|
||||
(for ([card (in-list (stack-cards stack))]) (send card flip)))
|
||||
|
||||
(send table add-cards-to-region draw-pile draw-pile-region))
|
||||
|
||||
(define (move-from-deck)
|
||||
(save-undo)
|
||||
(unless (null? draw-pile)
|
||||
(define (move-one 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)
|
||||
(define stack (list-ref stacks i))
|
||||
(define-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)
|
||||
(define old-cards (map stack-cards stacks))
|
||||
(for ([stack (in-list stacks)])
|
||||
(set-stack-cards! stack (remq card (stack-cards stack))))
|
||||
(for ([stack (in-list stacks)]
|
||||
[old-cards (in-list 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)))))
|
||||
|
||||
(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)))
|
||||
|
||||
(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)])
|
||||
(and (not (member #f suits/false))
|
||||
(memq 'clubs suits/false)
|
||||
(memq 'diamonds suits/false)
|
||||
(memq 'hearts suits/false)
|
||||
(memq 'spades suits/false)))))
|
||||
|
||||
(define (won?)
|
||||
(and (game-over?)
|
||||
(andmap (lambda (x)
|
||||
(define 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)
|
||||
(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,4 +0,0 @@
|
|||
#lang info
|
||||
|
||||
(define game "aces.rkt")
|
||||
(define game-set "Card Games")
|
Before Width: | Height: | Size: 977 B |
|
@ -1,437 +0,0 @@
|
|||
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
;;
|
||||
;; Blackjack
|
||||
;;
|
||||
;; The standard rules apply. Specifics:
|
||||
;;
|
||||
;; 1 player (not counting the dealer)
|
||||
;;
|
||||
;; 4 decks, reshuffled after 3/4 of the cards are used
|
||||
;;
|
||||
;; Dealer stands on soft 17s
|
||||
;;
|
||||
;; Splitting allowed only on the first two cards, and only if they
|
||||
;; are equal; 10 and the face cards are all considered equal for
|
||||
;; splitting
|
||||
;;
|
||||
;; Doubling allowed on all unsplit hands, not on split hands
|
||||
;;
|
||||
;; No blackjacks after splitting
|
||||
;;
|
||||
;; No surrender
|
||||
;;
|
||||
;; No insurance
|
||||
;;
|
||||
;; 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)
|
||||
;;
|
||||
;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
||||
#lang racket
|
||||
|
||||
(require games/cards racket/gui racket/class racket/unit)
|
||||
|
||||
(provide game@)
|
||||
(define game@ (unit (import) (export)
|
||||
|
||||
;; Number of decks to use
|
||||
(define DECK-COUNT 4)
|
||||
|
||||
;; Region layout constants
|
||||
(define MARGIN 10)
|
||||
(define SUBMARGIN 10)
|
||||
(define LABEL-H 15)
|
||||
|
||||
;; Randomize
|
||||
(random-seed (modulo (current-milliseconds) 10000))
|
||||
|
||||
;; Reshuffle when 3/4 of the deck is used
|
||||
(define min-deck-size (/ (* DECK-COUNT 52) 4))
|
||||
|
||||
;; Set up the table
|
||||
(define t (make-table "Blackjack" 6 3))
|
||||
(define status-pane (send t create-status-pane))
|
||||
(send t add-scribble-button status-pane
|
||||
'(lib "games/scribblings/games.scrbl") "blackjack")
|
||||
(send t show #t)
|
||||
(send t set-double-click-action #f)
|
||||
(send t set-button-action 'left 'drag/one)
|
||||
(send t set-button-action 'middle 'drag/one)
|
||||
(send t set-button-action 'right 'drag/one)
|
||||
|
||||
;; Get table width & height
|
||||
(define w (send t table-width))
|
||||
(define h (send t table-height))
|
||||
|
||||
;; Build the deck
|
||||
(define deck
|
||||
(let loop ([n DECK-COUNT])
|
||||
(if (zero? n)
|
||||
null
|
||||
(append (make-deck) (loop (sub1 n))))))
|
||||
|
||||
;; Card width & height
|
||||
(define cw (send (car deck) card-width))
|
||||
(define ch (send (car deck) card-height))
|
||||
|
||||
;; Size of buttons
|
||||
(define BUTTON-HEIGHT 16)
|
||||
(define BUTTON-WIDTH cw)
|
||||
|
||||
;; Cards are not movable
|
||||
(for-each (lambda (card) (send* card (user-can-move #f) (user-can-flip #f)))
|
||||
deck)
|
||||
|
||||
;; Set up card regions
|
||||
(define deck-region
|
||||
(make-region MARGIN MARGIN cw ch #f #f))
|
||||
|
||||
(define discard-region
|
||||
(make-region (- w cw MARGIN) MARGIN cw ch #f #f))
|
||||
|
||||
(define dealer-region
|
||||
(make-region (+ cw (* 2 MARGIN)) MARGIN
|
||||
(- w (* 2 cw) (* 4 MARGIN)) ch
|
||||
#f #f))
|
||||
|
||||
(define player-region
|
||||
(make-region (+ cw (* 2 MARGIN)) (- h (* 2 MARGIN) ch BUTTON-HEIGHT)
|
||||
(- w (* 2 cw) (* 4 MARGIN)) ch
|
||||
#f #f))
|
||||
|
||||
;; In case of split, we need more regions
|
||||
(define ww (* 3/2 cw))
|
||||
(define player-2-region
|
||||
(make-region MARGIN (region-y player-region)
|
||||
(- w ww (* 3 MARGIN)) (region-h player-region)
|
||||
#f #f))
|
||||
(define player-2-wait-region
|
||||
(make-region (region-x player-2-region) (region-y player-2-region)
|
||||
ww (region-h player-2-region)
|
||||
#f #f))
|
||||
(define player-1-region
|
||||
(make-region (- w MARGIN (region-w player-2-region)) (region-y player-2-region)
|
||||
(region-w player-2-region) (region-h player-2-region)
|
||||
#f #f))
|
||||
(define player-1-wait-region
|
||||
(make-region (- (+ (region-x player-1-region) (region-w player-1-region)) ww)
|
||||
(region-y player-1-region)
|
||||
ww (region-h player-1-region)
|
||||
#f #f))
|
||||
(define (make-border-region r)
|
||||
(define hm (/ MARGIN 2))
|
||||
(make-region (- (region-x r) hm) (- (region-y r) hm)
|
||||
(+ (region-w r) MARGIN) (+ (region-h r) MARGIN)
|
||||
"" #f))
|
||||
(define player-1-border (make-border-region player-1-region))
|
||||
(define player-2-border (make-border-region player-2-region))
|
||||
|
||||
;; Player buttons
|
||||
(define (make-button title pos)
|
||||
(make-button-region (+ (/ (- w (* 4 BUTTON-WIDTH) (* 3 MARGIN)) 2)
|
||||
(* pos (+ BUTTON-WIDTH MARGIN)))
|
||||
(- h MARGIN BUTTON-HEIGHT)
|
||||
BUTTON-WIDTH BUTTON-HEIGHT
|
||||
title void))
|
||||
(define hit-button (make-button "Hit" 1))
|
||||
(define stand-button (make-button "Stand" 2))
|
||||
(define split-button (make-button "Split" 0))
|
||||
(define double-button (make-button "Double" 3))
|
||||
|
||||
;; Put the cards on the table
|
||||
(send t add-cards-to-region deck deck-region)
|
||||
|
||||
;; Function to compute the normal or minimum value of a card
|
||||
(define (min-card-value c)
|
||||
(let ([v (send c get-value)]) (if (> v 10) 10 v)))
|
||||
|
||||
;; Function to compute the value of a hand, counting aces as 1 or 11
|
||||
;; to get the highest total possible under 21
|
||||
(define (best-total l)
|
||||
(let* ([ace? (lambda (is?) (lambda (c) (eq? is? (= (send c get-value) 1))))]
|
||||
[aces (filter (ace? #t) l)]
|
||||
[others (filter (ace? #f) l)]
|
||||
[base (apply + (map min-card-value others))])
|
||||
(let loop ([l aces][base base])
|
||||
(cond [(null? l) base]
|
||||
[(<= (+ base (* (length aces) 11)) 21)
|
||||
(+ base (* (length aces) 11))]
|
||||
[else (loop (cdr l) (add1 base))]))))
|
||||
|
||||
;; Function to test whether a hand is a bust
|
||||
(define (bust? p)
|
||||
(> (best-total p) 21))
|
||||
|
||||
;; Very simple betting...
|
||||
(define money 100)
|
||||
(define (update-money! d)
|
||||
(set! money (+ money d))
|
||||
(send t set-status (format "You have $~a. (Each bet is $2.)" money)))
|
||||
|
||||
;; Let's play!
|
||||
(let shuffle-loop ()
|
||||
;; Shuffle the cards, none are discarded, yet
|
||||
(let* ([deck (shuffle-list deck 7)]
|
||||
[discard null]
|
||||
[deal (lambda (n)
|
||||
(let deal ([n n])
|
||||
(if (zero? n)
|
||||
null
|
||||
(let ([c (car deck)])
|
||||
(set! deck (cdr deck))
|
||||
(cons c (deal (sub1 n)))))))])
|
||||
;; Put the shuffled deck in place
|
||||
(send t move-cards-to-region deck deck-region)
|
||||
(send t stack-cards deck)
|
||||
;; Loop rounds over while there's enough cards in the deck
|
||||
(let loop ()
|
||||
;; All bets are $2
|
||||
(update-money! -2)
|
||||
;; Deal to player
|
||||
(let ([p (deal 2)]
|
||||
[p2 null] ; in case of splitting
|
||||
[double? #f]) ; in case of doubling (flag is needed to adjust money)
|
||||
;; Move the player's cards into place and show them
|
||||
(send t move-cards-to-region p player-region)
|
||||
(send t cards-face-up p)
|
||||
;; Deal to dealer
|
||||
(let ([d (deal 2)])
|
||||
;; Move the dealer's cards into place and show one
|
||||
(send t move-cards-to-region d dealer-region)
|
||||
(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 different 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)))
|
||||
(set-region-callback!
|
||||
double-button
|
||||
(lambda ()
|
||||
;; Note the double for adjusting money on a win
|
||||
(set! double? #t)
|
||||
;; Double the bet
|
||||
(update-money! -2)
|
||||
;; Deal one more card
|
||||
((region-callback hit-button))
|
||||
;; No more cards or actions, but if the player busted, the
|
||||
;; hit callback has already continued
|
||||
(unless (bust? p) (semaphore-post continue))))
|
||||
;; Split allowed?
|
||||
(when (= (min-card-value (car p)) (min-card-value (cadr p)))
|
||||
;; Yes, we can split. If the player hits the split button, we
|
||||
;; have to split the cards, deal one more to each split half
|
||||
;; and adjust the callbacks for hit and stand. (If aces are
|
||||
;; split, the round is over.)
|
||||
(send t add-region split-button)
|
||||
(set-region-callback!
|
||||
split-button
|
||||
(lambda ()
|
||||
;; Double our bet...
|
||||
(update-money! -2)
|
||||
;; Split the hand
|
||||
(set! p2 (list (cadr p)))
|
||||
(set! p (list (car p)))
|
||||
;; Move the split halves to the "waiting" area. The active
|
||||
;; area is reserved for hands that are being played
|
||||
(send t move-cards-to-region p player-1-wait-region)
|
||||
(send t move-cards-to-region p2 player-2-wait-region)
|
||||
;; Deal one more card to each half and move them into
|
||||
;; place
|
||||
(set! p (append (deal 1) p))
|
||||
(set! p2 (append (deal 1) p2))
|
||||
(send t stack-cards p)
|
||||
(send t stack-cards p2)
|
||||
(send t move-cards-to-region p player-1-wait-region)
|
||||
(send t move-cards-to-region p2 player-2-wait-region)
|
||||
;; Show the newly dealt cards
|
||||
(send t flip-cards (list (car p) (car p2)))
|
||||
;; No more splits, no doubling
|
||||
(send t remove-region split-button)
|
||||
(send t remove-region double-button)
|
||||
;; Function called when the last split hand is done
|
||||
(let* ([close-split
|
||||
(lambda ()
|
||||
;; Unhilite the second hand
|
||||
(send t remove-region player-2-border)
|
||||
(send t move-cards-to-region p2 player-2-wait-region)
|
||||
;; Let the main loop finish up
|
||||
(semaphore-post continue))]
|
||||
;; Callback to swicth from the first split hand to
|
||||
;; the second
|
||||
[switch
|
||||
(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,4 +0,0 @@
|
|||
#lang info
|
||||
|
||||
(define game "blackjack.rkt")
|
||||
(define game-set "Card Games")
|
|
@ -1,4 +0,0 @@
|
|||
#lang racket
|
||||
|
||||
(require "cards/cards.rkt")
|
||||
(provide (all-from-out "cards/cards.rkt"))
|
|
@ -1,16 +0,0 @@
|
|||
(module base racket
|
||||
(require racket/class
|
||||
"make-cards.rkt" "classes.rkt" "card-class.rkt")
|
||||
|
||||
(provide make-table make-deck make-card
|
||||
table<%> card<%>)
|
||||
|
||||
(define table<%> (class->interface table%))
|
||||
(define card<%> (class->interface card%))
|
||||
|
||||
(define make-table
|
||||
(lambda ([title "Cards"][w 7][h 3])
|
||||
(make-object table% title w h)))
|
||||
|
||||
(define (make-deck)
|
||||
(map (lambda (l) (send l copy)) deck-of-cards)))
|
|
@ -1,256 +0,0 @@
|
|||
|
||||
(module card-class racket/base
|
||||
(require racket/class
|
||||
racket/shared
|
||||
(prefix-in mred: racket/gui)
|
||||
"snipclass.rkt"
|
||||
"region.rkt")
|
||||
|
||||
(provide card%)
|
||||
|
||||
(define prev-regions #f)
|
||||
(define prev-region-dc #f)
|
||||
|
||||
(define (rotate-bm bm cw?)
|
||||
(let ([w (send bm get-width)]
|
||||
[h (send bm get-height)])
|
||||
(let ([bm2 (mred:make-bitmap h w)]
|
||||
[s (make-bytes (* w h 4))]
|
||||
[s2 (make-bytes (* h w 4))])
|
||||
(send bm get-argb-pixels 0 0 w h s)
|
||||
(for ([i (in-range w)])
|
||||
(for ([j (in-range h)])
|
||||
(let ([src-pos (* (+ i (* j w)) 4)])
|
||||
(bytes-copy! s2
|
||||
(if cw?
|
||||
(* (+ (- (- h j) 1) (* i h)) 4)
|
||||
(* (+ j (* (- (- w i) 1) h)) 4))
|
||||
s src-pos (+ src-pos 4)))))
|
||||
(let ([dc (make-object mred:bitmap-dc% bm2)])
|
||||
(send dc set-argb-pixels 0 0 h w s2)
|
||||
(send dc set-bitmap #f))
|
||||
bm2)))
|
||||
|
||||
(define orientations (shared ([o (list* 'n 'e 's 'w o)]) o))
|
||||
(define (find-head l s)
|
||||
(if (eq? (car l) s)
|
||||
l
|
||||
(find-head (cdr l) s)))
|
||||
|
||||
(define card%
|
||||
(class mred:snip%
|
||||
(init -suit-id -value -width -height -front -back -mk-dim-front -mk-dim-back -rotated-bms)
|
||||
(inherit set-snipclass set-count get-admin)
|
||||
(define suit-id -suit-id)
|
||||
(define value -value)
|
||||
(define width -width)
|
||||
(define height -height)
|
||||
(define rotated 'n)
|
||||
(define front -front)
|
||||
(define back -back)
|
||||
(define mk-dim-front -mk-dim-front)
|
||||
(define mk-dim-back -mk-dim-back)
|
||||
(define dim-front #f)
|
||||
(define dim-back #f)
|
||||
(define is-dim? #f)
|
||||
(define flipped? #f)
|
||||
(define semi-flipped? #f)
|
||||
(define can-flip? #t)
|
||||
(define can-move? #t)
|
||||
(define snap-back? #f)
|
||||
(define stay-region #f)
|
||||
(define home-reg #f)
|
||||
(define rotated-bms -rotated-bms)
|
||||
(private*
|
||||
[refresh
|
||||
(lambda ()
|
||||
(let ([a (get-admin)])
|
||||
(when a
|
||||
(send a needs-update this 0 0 width height))))]
|
||||
[refresh-size
|
||||
(lambda ()
|
||||
(let ([a (get-admin)])
|
||||
(when a
|
||||
(send a resized this #f)))
|
||||
(refresh))]
|
||||
[check-dim
|
||||
(lambda ()
|
||||
(when is-dim?
|
||||
(if flipped?
|
||||
(unless dim-back
|
||||
(set! dim-back (mk-dim-back)))
|
||||
(unless dim-front
|
||||
(set! dim-front (mk-dim-front))))))]
|
||||
[get-rotated
|
||||
(lambda (bm dir)
|
||||
(if (eq? dir 'n)
|
||||
bm
|
||||
(or (hash-ref rotated-bms (cons dir bm) #f)
|
||||
(let ([rotated-bm (case dir
|
||||
[(w) (rotate-bm bm #f)]
|
||||
[(e) (rotate-bm bm #t)]
|
||||
[(s) (rotate-bm (rotate-bm bm #t) #t)])])
|
||||
(hash-set! rotated-bms (cons dir bm) rotated-bm)
|
||||
rotated-bm))))])
|
||||
(public*
|
||||
[face-down? (lambda () flipped?)]
|
||||
[flip
|
||||
(lambda ()
|
||||
(set! flipped? (not flipped?))
|
||||
(refresh))]
|
||||
[semi-flip
|
||||
(lambda ()
|
||||
(set! semi-flipped? (not semi-flipped?))
|
||||
(refresh))]
|
||||
[face-up (lambda () (when flipped? (flip)))]
|
||||
[face-down (lambda () (unless flipped? (flip)))]
|
||||
[dim (case-lambda
|
||||
[() is-dim?]
|
||||
[(v)
|
||||
(unless (eq? is-dim? (and v #t))
|
||||
(set! is-dim? (and v #t))
|
||||
(refresh))])]
|
||||
[orientation (lambda () (case rotated
|
||||
[(n) 0]
|
||||
[(e) 270]
|
||||
[(w) 90]
|
||||
[(s) 180]))]
|
||||
[rotate (lambda (mode)
|
||||
(let ([delta (case mode
|
||||
[(0 360) 0]
|
||||
[(cw -90 270) 1]
|
||||
[(ccw 90 -270) 3]
|
||||
[(180 -180) 2]
|
||||
[else (error 'rotate "bad mode: ~e" mode)])])
|
||||
(set! rotated (list-ref (find-head orientations rotated) delta))
|
||||
(if (odd? delta)
|
||||
(let ([w width])
|
||||
(set! width height)
|
||||
(set! height w)
|
||||
(refresh-size))
|
||||
(refresh))))]
|
||||
[get-suit-id
|
||||
(lambda () suit-id)]
|
||||
[get-suit
|
||||
(lambda ()
|
||||
(case suit-id
|
||||
[(1) 'clubs]
|
||||
[(2) 'diamonds]
|
||||
[(3) 'hearts]
|
||||
[(4) 'spades]
|
||||
[else 'unknown]))]
|
||||
[get-value
|
||||
(lambda () value)]
|
||||
[user-can-flip
|
||||
(case-lambda
|
||||
[() can-flip?]
|
||||
[(f) (set! can-flip? (and f #t))])]
|
||||
[user-can-move
|
||||
(case-lambda
|
||||
[() can-move?]
|
||||
[(f) (set! can-move? (and f #t))])]
|
||||
[snap-back-after-move
|
||||
(case-lambda
|
||||
[() snap-back?]
|
||||
[(f) (set! snap-back? (and f #t))])]
|
||||
[stay-in-region
|
||||
(case-lambda
|
||||
[() stay-region]
|
||||
[(r) (set! stay-region r)])]
|
||||
[home-region
|
||||
(case-lambda
|
||||
[() home-reg]
|
||||
[(r) (set! home-reg r)])]
|
||||
[card-width (lambda () width)]
|
||||
[card-height (lambda () height)])
|
||||
(override*
|
||||
[resize
|
||||
(lambda (w h) (void))]
|
||||
[get-extent
|
||||
(lambda (dc x y w h descent space lspace rspace)
|
||||
(map
|
||||
(lambda (b)
|
||||
(when b
|
||||
(set-box! b 0)))
|
||||
(list descent space lspace rspace))
|
||||
(when w (set-box! w width))
|
||||
(when h (set-box! h height)))]
|
||||
[draw
|
||||
(lambda (dc x y left top right bottom dx dy draw-caret)
|
||||
(check-dim)
|
||||
(let ([do-draw
|
||||
(lambda (x y)
|
||||
(send dc draw-bitmap
|
||||
(let ([bm (if flipped?
|
||||
(if is-dim? dim-back back)
|
||||
(if is-dim? dim-front front))])
|
||||
(get-rotated bm rotated))
|
||||
x y))])
|
||||
(if semi-flipped?
|
||||
(let-values ([(sx sy) (send dc get-scale)])
|
||||
(case rotated
|
||||
[(n s)
|
||||
(send dc set-scale (/ sx 2) sy)
|
||||
(do-draw (+ (* 2 x) (/ width 2)) y)
|
||||
(send dc set-scale sx sy)]
|
||||
[(e w)
|
||||
(send dc set-scale sx (/ sy 2))
|
||||
(do-draw x (+ (* 2 y) (/ height 2)))
|
||||
(send dc set-scale sx sy)]))
|
||||
(do-draw x y))))]
|
||||
[copy (lambda ()
|
||||
(let ([rotated? (memq rotated '(e w))])
|
||||
(make-object card% suit-id value
|
||||
(if rotated? height width)
|
||||
(if rotated? width height )
|
||||
front back
|
||||
(lambda ()
|
||||
(unless dim-front
|
||||
(set! dim-front (mk-dim-front)))
|
||||
dim-front)
|
||||
(lambda ()
|
||||
(unless dim-back
|
||||
(set! dim-back (mk-dim-back)))
|
||||
dim-back)
|
||||
rotated-bms)))])
|
||||
(define save-x (box 0))
|
||||
(define save-y (box 0))
|
||||
(public*
|
||||
[remember-location
|
||||
(lambda (pb)
|
||||
(send pb get-snip-location this save-x save-y))]
|
||||
[back-to-original-location
|
||||
(lambda (pb)
|
||||
(when snap-back?
|
||||
(send pb move-to this (unbox save-x) (unbox save-y)))
|
||||
(when home-reg
|
||||
(let ([xbox (box 0)]
|
||||
[ybox (box 0)])
|
||||
(send pb get-snip-location this xbox ybox #f)
|
||||
;; Completely in the region?
|
||||
(let* ([l (unbox xbox)]
|
||||
[rl (region-x home-reg)]
|
||||
[r (+ l width)]
|
||||
[rr (+ rl (region-w home-reg))]
|
||||
[t (unbox ybox)]
|
||||
[rt (region-y home-reg)]
|
||||
[b (+ t height)]
|
||||
[rb (+ rt (region-h home-reg))])
|
||||
(when (or (< l rl) (> r rr)
|
||||
(< t rt) (> b rb))
|
||||
;; Out of the region - completely or partly?
|
||||
(if (and (or (<= rl l rr) (<= rl r rr))
|
||||
(or (<= rt t rb) (<= rt b rb)))
|
||||
;; Just slightly out
|
||||
(send pb move-to this
|
||||
(min (max l rl) (- rr width))
|
||||
(min (max t rt) (- rb height)))
|
||||
;; Completely out
|
||||
(send pb move-to this (unbox save-x) (unbox save-y))))))))])
|
||||
|
||||
(super-make-object)
|
||||
(set-count 1)
|
||||
(set-snipclass sc)
|
||||
(flip))))
|
||||
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
(module cards racket
|
||||
(require "base.rkt"
|
||||
"utils.rkt"
|
||||
"region.rkt")
|
||||
|
||||
(provide table<%> card<%>
|
||||
region struct:region
|
||||
make-region
|
||||
region? region-x region-y region-w region-h
|
||||
region-label region-callback region-interactive-callback
|
||||
set-region-callback!
|
||||
set-region-interactive-callback!
|
||||
make-button-region
|
||||
make-background-region
|
||||
|
||||
make-deck make-card
|
||||
make-table
|
||||
|
||||
shuffle-list))
|
|
@ -1,517 +0,0 @@
|
|||
#lang scribble/doc
|
||||
@(require scribble/manual
|
||||
(for-label games/cards
|
||||
racket/gui/base))
|
||||
|
||||
@title{Cards: Virtual Playing Cards Library}
|
||||
|
||||
@defmodule[games/cards]{The @racketmodname[games/cards]
|
||||
module provides a toolbox for creating card games.}
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
@section{Creating Tables and Cards}
|
||||
|
||||
@defproc[(make-table [title string? "Cards"]
|
||||
[w exact-nonnegative-integer? 7]
|
||||
[h exact-nonnegative-integer? 3])
|
||||
table<%>]{
|
||||
|
||||
Returns a table. The table is named by @racket[title], and it is
|
||||
@racket[w] cards wide and @racket[h] cards high (assuming a standard
|
||||
card of 71 by 96 pixels). The table is not initially shown;
|
||||
@racket[(send table show #t)] shows it.}
|
||||
|
||||
@defproc[(make-deck)
|
||||
(listof card<%>)]{
|
||||
|
||||
Returns a list of 52 cards, one for each suit-value combination. The
|
||||
cards are all face-down, sorted lowest-suit then lowest-value. A card
|
||||
can only be on one table at a time.}
|
||||
|
||||
@defproc[(make-card [front-bm (is-a?/c bitmap?)]
|
||||
[back-bm (or/c (is-a?/c bitmap%) false/c)]
|
||||
[suit-id any/c]
|
||||
[value any/c])
|
||||
(is-a?/c card<%>)]{
|
||||
|
||||
Returns a single card given a bitmap for the front, an optional bitmap
|
||||
for the back, and arbitrary values for the card's suit and value
|
||||
(which are returned by the card's @method[card<%> get-value] and
|
||||
@method[card<%> get-suit-id] methods). All provided bitmaps should be
|
||||
the same size.}
|
||||
|
||||
@defproc[(shuffle-list [lst list?] [n exact-nonnegative-integer?])
|
||||
list?]{
|
||||
|
||||
Shuffles the given @racket[lst] @racket[n] times, returning the new
|
||||
list. Shuffling simulates an actual shuffle: the list is split into
|
||||
halves which are merged back together by repeatedly pulling the top
|
||||
card off one of the halves, randomly selecting one half or the
|
||||
other. According to some mathematical theorem, 7 is a large enough
|
||||
@racket[n] to get a perfect shuffle.}
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
@section{Regions and Buttons}
|
||||
|
||||
@defstruct[region ([x real?]
|
||||
[y real?]
|
||||
[w (and/c real? (not/c negative?))]
|
||||
[h (and/c real? (not/c negative?))]
|
||||
[label (or/c string? false/c)]
|
||||
[(callback #:mutable) (or/c ((listof (is-a?/c card<%>)) . -> . any)
|
||||
false/c)])]{
|
||||
|
||||
The @racket[x], @racket[y], @racket[w], and @racket[h] fields
|
||||
determine the region's location on the table.
|
||||
|
||||
When @racket[label] is a string, it is drawn in the region in 12-pixel
|
||||
text, centered horizontally and 5 pixels down from the region's top
|
||||
outline. If label is @racket[#f], no label or box is drawn for the
|
||||
region.
|
||||
|
||||
The @racket[callback] procedure takes a list of cards that were
|
||||
dragged to the region; if callback is @racket[#f], the region is not
|
||||
active (i.e., dragging cards to the region doesn't highlight the
|
||||
region box). The region remains hilited until the callback returns.
|
||||
|
||||
The only available mutator on the structure is
|
||||
@racket[set-region-callback!]. The structure created by
|
||||
@racket[make-region] actually has extra hidden fields.}
|
||||
|
||||
@defproc[(make-button-region [x real?]
|
||||
[y real?]
|
||||
[w (and/c real? (not/c negative?))]
|
||||
[h (and/c real? (not/c negative?))]
|
||||
[label (or/c string? false/c)]
|
||||
[callback (or/c ((listof (is-a?/c card<%>)) . -> . any)
|
||||
false/c)])
|
||||
region?]{
|
||||
|
||||
Returns a region like one made by @racket[make-region], but the is
|
||||
drawn slightly differently and it reacts differently to cards and the
|
||||
mouse. The label is drawn in the middle of the box instead of at the
|
||||
top, and the callback is called with no arguments when the user
|
||||
clicks the region (instead of dragging cards to the region).}
|
||||
|
||||
@defproc[(make-background-region [x real?]
|
||||
[y real?]
|
||||
[w (and/c real? (not/c negative?))]
|
||||
[h (and/c real? (not/c negative?))]
|
||||
[paint-callback
|
||||
((is-a?/c dc<%>) real? real? real? real? . -> . any)])
|
||||
region?]{
|
||||
|
||||
Returns a region that does not respond to mouse clicks, but which has
|
||||
a general paint callback. The @racket[paint-callback] function is
|
||||
called with a drawing context, x and y offsets, and the width and
|
||||
height (which are always @racket[w] and @racket[h]). The x and y
|
||||
offsets can be different than the supplied @racket[x] and @racket[y]
|
||||
when part of the table is drawn offscreen. Regions are painted in the
|
||||
order that they are added to a table, and all regions are painted
|
||||
before any card. The @racket[paint-callback] procedure should not
|
||||
assume a particular state for the drawing context (i.e.,current brush
|
||||
or pen), and it should restore any modified drawing context state
|
||||
before returning.}
|
||||
|
||||
@defproc[(set-region-interactive-callback!
|
||||
[r region?]
|
||||
[callback (or/c (boolean? (listof (is-a?/c card<%>)) . -> . any)
|
||||
false/c)])
|
||||
void?]{
|
||||
|
||||
Sets a callback procedure that is invoked when a region is
|
||||
(un)hilited as the user drags a set of cards to the region. The
|
||||
callback is provided two arguments: a boolean indicating whether the
|
||||
region is hilited, and the list of cards being dragged. Like
|
||||
region-callback, the default is @racket[#f], which indicates that the
|
||||
region has no interactive callback (but does not affect whether the
|
||||
region is hilited as cards are dragged). The final unhilite (when
|
||||
cards are potentially delivered) does not trigger this callback.}
|
||||
|
||||
|
||||
@defproc[(region-interactive-callback [r region?])
|
||||
(boolean? (listof (is-a?/c card<%>)) . -> . any)]{
|
||||
|
||||
Gets the current callback that is installed via
|
||||
@racket[set-region-interaction-callback!].}
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
@section{Table Methods}
|
||||
|
||||
@definterface[table<%> (frame%)]{
|
||||
|
||||
Create an instance with @racket[make-table].
|
||||
|
||||
@defmethod[(add-card [card (is-a?/c card<%>)]
|
||||
[x real?]
|
||||
[y real?])
|
||||
void?]{
|
||||
|
||||
Adds @racket[card] to the table with its top-left corner at
|
||||
(@racket[x], @racket[y]) in table pixels.}
|
||||
|
||||
@defmethod[(add-cards [cards (listof (is-a?/c card<%>))]
|
||||
[x real?]
|
||||
[y real?]
|
||||
[offset-proc (exact-nonnegative-integer?
|
||||
. -> . (values real? real?))
|
||||
(lambda (i) (values 0 0))])
|
||||
void?]{
|
||||
|
||||
Adds a list of cards at (@racket[x], @racket[y]). The optional
|
||||
@racket[offset-proc] procedure is called with an index @racket[_i]
|
||||
(counting from 0) and should return two values: @racket[_dx] and
|
||||
@racket[_dy]; the @racket[_i]th card is the placed at @racket[(+ x
|
||||
+dx)] and @racket[(+ y _dy)]. The cards are added in order on top of
|
||||
cards already one the table such that the first card in
|
||||
@racket[cards] is topmost.}
|
||||
|
||||
@defmethod[(add-cards-to-region [cards (listof (is-a?/c card<%>))]
|
||||
[region? r])
|
||||
void?]{
|
||||
|
||||
Adds @racket[cards] to fill the region @racket[r], fanning them out
|
||||
bottom-right to top-left, assuming that all cards in @racket[cards]
|
||||
have the same width and height. The region @racket[r] does not have
|
||||
to be added to the table.}
|
||||
|
||||
@defmethod[(remove-card [card (is-a?/c card<%>)])
|
||||
void?]{
|
||||
|
||||
Removes @racket[card] from the table.}
|
||||
|
||||
@defmethod[(remove-cards [cards (listof (is-a?/c card<%>))])
|
||||
void?]{
|
||||
|
||||
Removes @racket[cards] from the table.}
|
||||
|
||||
@defmethod[(move-card [card (is-a?/c card<%>)]
|
||||
[x real?]
|
||||
[y real?])
|
||||
void?]{
|
||||
|
||||
Moves @racket[card], which must be on the same already. The movement
|
||||
of the cards is animated. If the cards are in snap-back-after-move
|
||||
mode and a drag is active, snapping back will use the new location.}
|
||||
|
||||
@defmethod[(move-cards [cards (listof (is-a?/c card<%>))]
|
||||
[x real?]
|
||||
[y real?]
|
||||
[offset-proc (exact-nonnegative-integer?
|
||||
. -> . (values real? real?))
|
||||
(lambda (i) (values 0 0))])
|
||||
void?]{
|
||||
|
||||
Like @method[table<%> add-cards], but moves cards that are already on
|
||||
the table like @method[table<%> move-card]. All of the cards are
|
||||
moved at once.}
|
||||
|
||||
@defmethod[(move-cards-to-region [cards (listof (is-a?/c card<%>))]
|
||||
[region? r])
|
||||
void?]{
|
||||
|
||||
Like @method[table<%> add-cards-to-region], but moves cards that are
|
||||
already on the table like @racket[move-card]. All of the cards are
|
||||
moved at once.}
|
||||
|
||||
|
||||
@defmethod*[([(flip-card [card (is-a?/c card<%>)]) void?]
|
||||
[(flip-cards [cards (listof (is-a?/c card<%>))]) void?])]{
|
||||
|
||||
Flips @racket[card] or all @racket[cards] over (at once) with
|
||||
animation.}
|
||||
|
||||
@defmethod*[([(card-face-up [card (is-a?/c card<%>)]) void?]
|
||||
[(cards-face-up [cards (listof (is-a?/c card<%>))]) void?]
|
||||
[(card-face-down [card (is-a?/c card<%>)]) void?]
|
||||
[(cards-face-down [cards (listof (is-a?/c card<%>))]) void?])]{
|
||||
|
||||
Like @method[table<%> flip-cards], but only for @racket[card] or
|
||||
elements of @racket[cards] that are currently face down/up.}
|
||||
|
||||
@defmethod*[([(rotate-card [card (is-a?/c card<%>)]
|
||||
[mode (or/c 'cw 'ccw 0 90 -90 180 -180 270 -270 360)])
|
||||
void?]
|
||||
[(rotate-cards [cards (listof (is-a?/c card<%>))]
|
||||
[mode (or/c 'cw 'ccw 0 90 -90 180 -180 270 -270 360)])
|
||||
void?])]{
|
||||
|
||||
Rotates @racket[card] or all @racket[cards] (at once, currently
|
||||
without animation, but animation may be added in the future).
|
||||
The center of each card is kept in place, except that the card is
|
||||
moved as necessary to keep it on the table. See @xmethod[card<%>
|
||||
rotate] for information on @racket[mode].}
|
||||
|
||||
@defmethod*[([(card-to-front [card (is-a?/c card<%>)]) void?]
|
||||
[(card-to-back [card (is-a?/c card<%>)]) void?])]{
|
||||
|
||||
Moves @racket[card] before/behind of all other cards.}
|
||||
|
||||
@defmethod[(stack-cards [cards (listof (is-a?/c card<%>))]) void?]{
|
||||
|
||||
The first card in @racket[cards] is not moved; the second card is
|
||||
moved to follow immediately behind the first one, then
|
||||
@method[table<%> stack-cards] is called on @racket[(cdr cards)]. If
|
||||
@racket[cards] is empty or contains only one card, no action is
|
||||
taken.}
|
||||
|
||||
@defmethod[(card-location [card (is-a?/c card<%>)])
|
||||
(values real? real?)]{
|
||||
|
||||
Returns the location of the given card; an exception is raised if the
|
||||
card is not on the table.}
|
||||
|
||||
@defmethod[(all-cards) (listof (is-a?/c card<%>))]{
|
||||
|
||||
Returns a list of all cards on the table in stacking order from front
|
||||
to back.}
|
||||
|
||||
@defmethod*[([(table-width) exact-nonnegative-integer?]
|
||||
[(table-height) exact-nonnegative-integer?])]{
|
||||
|
||||
Returns the width/height of the table in pixels.}
|
||||
|
||||
@defmethod*[([(begin-card-sequence) void?]
|
||||
[(end-card-sequence) void?])]{
|
||||
|
||||
Starts/ends a sequence of card or region changes that won't be
|
||||
animated or updated until the end of the sequence. Sequences can be
|
||||
nested via matching @racketidfont{begin-}/@racketidfont{end-} pairs.}
|
||||
|
||||
@defmethod[(add-region [r region?]) void]{
|
||||
|
||||
Adds the region @racket[r] to the table; regions are drawn in the
|
||||
order that they are added to the table, and when a region added later
|
||||
is hilighted, it can obscure regions added earlier.}
|
||||
|
||||
@defmethod[(remove-region [r region?]) void]{
|
||||
|
||||
Removes the region @racket[r] from the table.}
|
||||
|
||||
@defmethod*[([(hilite-region [r region?]) void?]
|
||||
[(unhilite-region [r region?]) void?])]{
|
||||
|
||||
Manual (un)hilite, usually for animation.}
|
||||
|
||||
@defmethod[(set-button-action [which (one-of/c 'left 'middle 'right)]
|
||||
[action symbol?])
|
||||
void?]{
|
||||
|
||||
Sets the way that a mouse click is handled for a particular button
|
||||
indicated by @racket[which]. The @racket[action] argument must be one
|
||||
of the following:
|
||||
|
||||
@itemize[
|
||||
|
||||
@item{@racket['drag/one] --- drag only the clicked-on card.}
|
||||
|
||||
@item{@racket['drag-raise/one] --- like drag/one, but raise the
|
||||
card to the top on a click.}
|
||||
|
||||
@item{@racket['drag/above] --- drag the card along with any card
|
||||
on top of the card (i.e., more towards the front and
|
||||
overlapping with the card). The on-top-of relation
|
||||
is closed transitively.}
|
||||
|
||||
@item{@racket['drag-raise/above] --- like @racket['drag/above],
|
||||
but raises.}
|
||||
|
||||
@item{@racket['drag-below] --- drag the card along with any card
|
||||
underneath the card (i.e., more towards the back and
|
||||
overlapping with the card). The underneath relation
|
||||
is closed transitively.}
|
||||
|
||||
@item{@racket['drag-raise/below] --- like @racket['drag/below],
|
||||
but raises.}
|
||||
]
|
||||
|
||||
The initial settings are: @racket['drag-raise/above] for
|
||||
@racket['left], @racket['drag/one] for @racket['middle], and
|
||||
@racket['drag/below] for @racket['right].}
|
||||
|
||||
@defmethod[(set-double-click-action
|
||||
[proc ((is-a?/c card<%>) . -> . any)])
|
||||
void?]{
|
||||
|
||||
Sets the procedure to be called when a card is double-clicked. The
|
||||
procedure is called with the double-clicked card. The default
|
||||
procedure flips the cards along with its on-top-of cards, raises the
|
||||
cards, and reverses the front-to-back order of the cards}
|
||||
|
||||
@defmethod[(set-single-click-action
|
||||
[proc ((is-a?/c card<%>) . -> . any)])
|
||||
void?]{
|
||||
|
||||
Sets the procedure to be called when a card is single-clicked, after
|
||||
the button action is initiated. (If the card is double-clicked, this
|
||||
action is invoked for the first click, then the double-click action
|
||||
is invoked.) The default action does nothing.}
|
||||
|
||||
@defmethod[(pause [secs real?]) void?]{
|
||||
|
||||
Pauses, allowing the table display to be updated (unless a sequence
|
||||
is active), but does not let the user click on the cards.}
|
||||
|
||||
@defmethod*[([(animated) boolean?]
|
||||
[(animated [on? any/c]) void?])]{
|
||||
|
||||
Gets/sets animation enabled/diabled.}
|
||||
|
||||
@defmethod[(create-status-pane) (is-a?/c pane%)]{
|
||||
|
||||
Creates a pane with a status message (initially empty) and returns
|
||||
the pane so that you can add additional controls.}
|
||||
|
||||
@defmethod[(set-status [str sring]) void?]{
|
||||
|
||||
Sets the text message in the status pane.}
|
||||
|
||||
@defmethod[(add-help-button [pane (is-a?/c area-container<%>)]
|
||||
[coll-path (listof string?)]
|
||||
[str string?]
|
||||
[tt? any/c])
|
||||
void?]{
|
||||
|
||||
Adds a @onscreen{Help} button to the given pane, where clicking the
|
||||
button opens a new window to display @filepath{doc.txt} from the given
|
||||
collection. The @racket[str] argument is used for the help window
|
||||
title. If @racket[tt?] is true, then @filepath{doc.txt} is displayed
|
||||
verbatim, otherwise it is formatted as for @racket[show-help] from
|
||||
@racketmodname[games/show-help].}
|
||||
|
||||
@defmethod[(add-scribble-button [pane (is-a?/c area-container<%>)]
|
||||
[mod-path module-path?]
|
||||
[tag string?])
|
||||
void?]{
|
||||
|
||||
Adds a @onscreen{Help} button to the given pane, where clicking the
|
||||
button opens Scribble-based documentation, as with
|
||||
@racket[show-scribbling] from @racketmodname[games/show-scribbling].}
|
||||
|
||||
}
|
||||
|
||||
@; ----------------------------------------------------------------------
|
||||
@section{Card Methods}
|
||||
|
||||
@definterface[card<%> ()]{
|
||||
|
||||
Create instances with @racket[make-deck] or @racket[make-card].
|
||||
|
||||
@defmethod[(card-width) exact-nonnegative-integer?]{
|
||||
|
||||
Returns the width of the card in pixels. If the card is rotated 90 or
|
||||
270 degrees, the result is the card's original height.}
|
||||
|
||||
@defmethod[(card-height) exact-nonnegative-integer?]{
|
||||
|
||||
Returns the height of the card in pixels. If the card is rotated 90 or
|
||||
270 degrees, the result is the card's original width.}
|
||||
|
||||
@defmethod[(flip) void?]{
|
||||
|
||||
Flips the card without animation. This method is useful for flipping
|
||||
a card before it is added to a table.}
|
||||
|
||||
@defmethod[(face-up) void?]{
|
||||
|
||||
Makes the card face up without animation.}
|
||||
|
||||
@defmethod[(face-down) void?]{
|
||||
|
||||
Makes the card face down without animation.}
|
||||
|
||||
@defmethod[(face-down?) boolean?]{
|
||||
|
||||
Returns @racket[#t] if the card is currently face down.}
|
||||
|
||||
@defmethod[(rotate [mode (or/c 'cw 'ccw 0 90 -90 180 -180 270 -270 360)]) void?]{
|
||||
|
||||
Rotates the card. Unlike using the @xmethod[table<%> rotate-card] method,
|
||||
the card's top-left position is kept in place.
|
||||
|
||||
If @racket[mode] is @racket['cw], the card is
|
||||
rotated clockwise; if @racket[mode] is @racket['ccw], the card is
|
||||
rotated counter-clockwise; if @racket[mode] is one of the allowed
|
||||
numbers, the card is rotated the corresponding amount in degrees
|
||||
counter-clockwise.}
|
||||
|
||||
@defmethod[(orientation) (or/c 0 90 180 270)]{
|
||||
|
||||
Returns the orientation of the card, where @racket[0] corresponds to
|
||||
its initial state, @racket[90] is rotated 90 degrees counter-clockwise, and so on.}
|
||||
|
||||
@defmethod[(get-suit-id) any/c]{
|
||||
|
||||
Normally returns @racket[1], @racket[2], @racket[3], or @racket[4]
|
||||
(see @method[card<%> get-suit] for corresponding suit names), but the
|
||||
result can be anything for a card created by @racket[make-card].}
|
||||
|
||||
@defmethod[(get-suit) symbol?]{
|
||||
|
||||
Returns @racket['clubs], @racket['diamonds], @racket['hearts],
|
||||
@racket['spades], or @racket['unknown], depending on whether
|
||||
@method[card<%> get-suit-id] returns @racket[1], @racket[2],
|
||||
@racket[3], @racket[4], or something else.}
|
||||
|
||||
@defmethod[(get-value) any/c]{
|
||||
|
||||
Normally returns @racket[1] (Ace), @racket[2], ... @racket[10],
|
||||
@racket[11] (Jack), @racket[12] (Queen), or @racket[13] (King), but
|
||||
the result can be anything for a card created by @racket[make-card].}
|
||||
|
||||
@defmethod*[([(user-can-flip) boolean?]
|
||||
[(user-can-flip [can? any/c]) void?])]{
|
||||
|
||||
Gets/sets whether the user can flip the card interactively, usually
|
||||
by double-clicking it. Initially @racket[#t].}
|
||||
|
||||
@defmethod*[([(user-can-move) boolean?]
|
||||
[(user-can-move [can? any/c]) void?])]{
|
||||
|
||||
Gets/sets whether the user can move the card interactively, usually
|
||||
by dragging it. Disabling moves has the side-effect of disabling
|
||||
raises and double-clicks. Initially @racket[#t].}
|
||||
|
||||
@defmethod*[([(snap-back-after-move) boolean?]
|
||||
[(snap-back-after-move [on? any/c]) void?])]{
|
||||
|
||||
Assuming user can move the card interactively, gets/sets whether the
|
||||
card stays where the user dragged it or snaps back to its original
|
||||
place. Initially @racket[#f].
|
||||
|
||||
A region's @italic{interactive} callback can disable snap-back for a
|
||||
card so that the card can be delivered to the region. (A region's
|
||||
normal callback cannot release the card, because it's too late.)}
|
||||
|
||||
@defmethod*[([(stay-in-region) (or/c region? false/c)]
|
||||
[(stay-in-region [r (or/c region? false/c)]) void?])]{
|
||||
|
||||
|
||||
Gets/sets a constraining region @racket[r]. If @racket[r] is not
|
||||
@racket[#f], the user cannot move the card out of @racket[r].
|
||||
Initially @racket[#f].}
|
||||
|
||||
@defmethod*[([(home-region) (or/c region? false/c)]
|
||||
[(home-region [r (or/c region? false/c)]) void?])]{
|
||||
|
||||
Gets/sets a home region @racket[r]. If @racket[r] is not @racket[#f],
|
||||
then the user can move the card freely within the region, but it
|
||||
snaps back if moved completely out of the region. If moved partly out
|
||||
of the region, the card is moved enough to get completely back
|
||||
in. Initially @racket[#f].
|
||||
|
||||
A region's @italic{interactive} callback can disable snap-back for a
|
||||
card so that the card can be delivered to the region. (A region's
|
||||
normal callback cannot release the card, because it's too late.)}
|
||||
|
||||
@defmethod*[([(dim) boolean?]
|
||||
[(dim [can? any/c]) void?])]{
|
||||
|
||||
Gets/sets a hilite on the card, which is rendered by drawing it dimmer
|
||||
than normal.}
|
||||
|
||||
@defmethod[(copy) (is-a?/c card<%>)]{
|
||||
|
||||
Makes a new card with the same suit and value.}
|
||||
|
||||
}
|
|
@ -1,752 +0,0 @@
|
|||
(module classes racket/base
|
||||
(require racket/class
|
||||
(prefix-in mred: racket/gui)
|
||||
(prefix-in util: "utils.rkt")
|
||||
"constants.rkt"
|
||||
"make-cards.rkt"
|
||||
"region.rkt"
|
||||
string-constants
|
||||
"../show-help.rkt"
|
||||
"../show-scribbling.rkt")
|
||||
|
||||
(provide pasteboard%
|
||||
table%)
|
||||
|
||||
(define pasteboard%
|
||||
(class mred:pasteboard%
|
||||
(inherit begin-edit-sequence end-edit-sequence get-admin
|
||||
invalidate-bitmap-cache
|
||||
find-next-selected-snip find-first-snip find-snip
|
||||
set-before set-after
|
||||
add-selected is-selected? no-selected set-selected remove-selected
|
||||
get-snip-location move-to
|
||||
dc-location-to-editor-location
|
||||
set-selection-visible)
|
||||
|
||||
(define select-one? #t)
|
||||
(define select-backward? #f)
|
||||
(define raise-to-front? #f)
|
||||
(define button-map '((left #f #f #t)
|
||||
(middle #t #f #t)
|
||||
(right #f #t #f)))
|
||||
|
||||
(define do-on-double-click 'flip)
|
||||
(define do-on-single-click void)
|
||||
|
||||
(define selecting? #f)
|
||||
(define dragging? #f)
|
||||
(define bg-click? #f)
|
||||
(define click-base #f)
|
||||
(define last-click #f)
|
||||
(define regions null)
|
||||
(private*
|
||||
[get-snip-bounds
|
||||
(lambda (s)
|
||||
(let ([xbox (box 0)]
|
||||
[ybox (box 0)])
|
||||
(get-snip-location s xbox ybox #f)
|
||||
(let ([l (unbox xbox)]
|
||||
[t (unbox ybox)])
|
||||
(get-snip-location s xbox ybox #t)
|
||||
(values l t (unbox xbox) (unbox ybox)))))]
|
||||
[for-each-selected
|
||||
(lambda (f)
|
||||
(let loop ([snip (find-next-selected-snip #f)])
|
||||
(when snip
|
||||
(f snip)
|
||||
(loop (find-next-selected-snip snip)))))]
|
||||
[make-overlapping-list
|
||||
(lambda (s so-far behind?)
|
||||
(let-values ([(sl st sr sb) (get-snip-bounds s)])
|
||||
(let loop ([t (find-first-snip)][so-far so-far][get? (not behind?)])
|
||||
(cond
|
||||
[(not t) so-far]
|
||||
[(eq? s t) (if behind?
|
||||
(loop (send t next) so-far #t)
|
||||
so-far)]
|
||||
[get?
|
||||
(let ([l (if (and (not (memq t so-far))
|
||||
(let-values ([(tl tt tr tb)
|
||||
(get-snip-bounds t)])
|
||||
(and (or (<= sl tl sr)
|
||||
(<= sl tr sr))
|
||||
(or (<= st tt sb)
|
||||
(<= st tb sb)))))
|
||||
(make-overlapping-list t (cons t so-far) behind?)
|
||||
so-far)])
|
||||
(loop (send t next) l #t))]
|
||||
[else
|
||||
(loop (send t next) so-far #f)]))))]
|
||||
[get-reverse-selected-list
|
||||
(lambda ()
|
||||
(let loop ([s (find-next-selected-snip #f)][l null])
|
||||
(if s
|
||||
(loop (find-next-selected-snip s) (cons s l))
|
||||
l)))]
|
||||
[shuffle
|
||||
(lambda (selected-list) ; cards to shuffle in back->front order
|
||||
(let* ([permuted-list
|
||||
(util:shuffle-list selected-list 7)]
|
||||
[get-pos
|
||||
(lambda (s)
|
||||
(let ([xb (box 0)]
|
||||
[yb (box 0)])
|
||||
(get-snip-location s xb yb)
|
||||
(cons (unbox xb) (unbox yb))))]
|
||||
[sel-loc-list (map get-pos selected-list)]
|
||||
[perm-loc-list (map get-pos permuted-list)])
|
||||
(for-each
|
||||
(lambda (s start-pos end-pos)
|
||||
(let* ([sx (car start-pos)]
|
||||
[sy (cdr start-pos)]
|
||||
[ex (car end-pos)]
|
||||
[ey (cdr end-pos)]
|
||||
[steps (max 1 (floor (/ 50 (length selected-list))))])
|
||||
(let loop ([i 1])
|
||||
(unless (> i steps)
|
||||
(let ([x (+ sx (* (/ i steps) (- ex sx)))]
|
||||
[y (+ sy (* (/ i steps) (- ey sy)))])
|
||||
(move-to s x y)
|
||||
(mred:flush-display)
|
||||
(loop (add1 i)))))))
|
||||
permuted-list perm-loc-list sel-loc-list)
|
||||
(let loop ([l permuted-list])
|
||||
(unless (null? l)
|
||||
(set-before (car l) #f)
|
||||
(loop (cdr l))))
|
||||
(no-selected)))]
|
||||
[update-region
|
||||
(lambda (region)
|
||||
(let-values ([(sx sy sw sh) (get-region-box region)])
|
||||
(invalidate-bitmap-cache sx sy sw sh)))])
|
||||
(public*
|
||||
[only-front-selected
|
||||
(lambda ()
|
||||
(let loop ([s (find-next-selected-snip #f)][ok (find-first-snip)])
|
||||
(when s
|
||||
(if (eq? s ok)
|
||||
(loop (find-next-selected-snip s)
|
||||
(send ok next))
|
||||
(let loop ([s s][l (list s)])
|
||||
(let ([next (find-next-selected-snip s)])
|
||||
(if next
|
||||
(loop next (cons s l))
|
||||
(for-each (lambda (s)
|
||||
(remove-selected s))
|
||||
l))))))))])
|
||||
(override*
|
||||
[on-paint
|
||||
(lambda (before? dc l t r b dx dy caret)
|
||||
(when before?
|
||||
(for-each
|
||||
(lambda (region)
|
||||
(when (region-paint-callback region)
|
||||
(let-values ([(sx sy sw sh) (get-region-box region)])
|
||||
((region-paint-callback region) dc (+ dx sx) (+ dy sy) sw sh)))
|
||||
(when (region-label region)
|
||||
(let ([old-b (send dc get-brush)]
|
||||
[old-p (send dc get-pen)])
|
||||
(let-values ([(sx sy sw sh) (get-region-box region)])
|
||||
(send dc set-brush white-brush)
|
||||
(send dc set-pen no-pen)
|
||||
(send dc draw-rectangle (+ dx sx) (+ dy sy) sw sh)
|
||||
(send dc set-pen dark-gray-pen)
|
||||
(draw-roundish-rectangle dc (+ dx sx) (+ dy sy) sw sh)
|
||||
(let ([text (region-label region)])
|
||||
(if (string? text)
|
||||
(let ([old-f (send dc get-font)])
|
||||
(send dc set-font nice-font)
|
||||
(let-values ([(x y d a) (send dc get-text-extent text)])
|
||||
(send dc draw-text text
|
||||
(+ dx sx (/ (- sw x) 2))
|
||||
(if (region-button? region)
|
||||
;; Since we use size-in-pixels, the letters
|
||||
;; should really be 12 pixels high (including
|
||||
;; the descender), but the space above the letter
|
||||
;; can vary by font; center on 12, splitting
|
||||
;; the difference for the descender
|
||||
(+ dy sy (/ (- sh 12) 2) (- 12 y (/ d -2)))
|
||||
(+ dy sy 5))))
|
||||
(send dc set-font old-f))
|
||||
(send dc draw-bitmap text
|
||||
(+ dx sx (/ (- sw (send text get-width)) 2))
|
||||
(+ dy sy (/ (- sh (send text get-height)) 2))
|
||||
'solid black-color
|
||||
(send text get-loaded-mask))))
|
||||
(when (region-hilite? region)
|
||||
(send dc set-brush hilite-brush)
|
||||
(send dc set-pen no-pen)
|
||||
(send dc draw-rectangle (+ dx sx 1) (+ dy sy 1) (- sw 2) (- sh 2))))
|
||||
(send dc set-brush old-b)
|
||||
(send dc set-pen old-p))))
|
||||
regions)))])
|
||||
(augment*
|
||||
[after-select
|
||||
(lambda (s on?)
|
||||
(inner (void) after-select s on?)
|
||||
(unless (or (not on?) selecting?)
|
||||
(set! selecting? #t)
|
||||
(if select-one?
|
||||
(when raise-to-front?
|
||||
(set-before s #f))
|
||||
(begin
|
||||
(begin-edit-sequence)
|
||||
(let ([l (make-overlapping-list s (list s) select-backward?)])
|
||||
(for-each (lambda (i) (add-selected i)) l))
|
||||
(when raise-to-front?
|
||||
(let loop ([snip (find-next-selected-snip #f)][prev #f])
|
||||
(when snip
|
||||
(if prev
|
||||
(set-after snip prev)
|
||||
(set-before snip #f))
|
||||
(loop (find-next-selected-snip snip) snip))))
|
||||
(end-edit-sequence)))
|
||||
(set! selecting? #f)))]
|
||||
[on-interactive-move
|
||||
(lambda (e)
|
||||
(inner (void) on-interactive-move e)
|
||||
(for-each (lambda (region) (set-region-decided-start?! region #f)) regions)
|
||||
(for-each-selected (lambda (snip) (send snip remember-location this)))
|
||||
(set! dragging? #t))])
|
||||
(override*
|
||||
[interactive-adjust-move
|
||||
(lambda (snip xb yb)
|
||||
(super interactive-adjust-move snip xb yb)
|
||||
(let-values ([(l t r b) (get-snip-bounds snip)])
|
||||
(let-values ([(rl rt rw rh)
|
||||
(let ([r (send snip stay-in-region)])
|
||||
(if r
|
||||
(values (region-x r) (region-y r)
|
||||
(region-w r) (region-h r))
|
||||
(let ([wb (box 0)][hb (box 0)])
|
||||
(send (get-admin) get-view #f #f wb hb)
|
||||
(values 0 0 (unbox wb) (unbox hb)))))])
|
||||
(let ([max-x (- (+ rl rw) (- r l))]
|
||||
[max-y (- (+ rt rh) (- b t))])
|
||||
(when (< (unbox xb) rl)
|
||||
(set-box! xb rl))
|
||||
(when (> (unbox xb) max-x)
|
||||
(set-box! xb max-x))
|
||||
(when (< (unbox yb) rt)
|
||||
(set-box! yb rt))
|
||||
(when (> (unbox yb) max-y)
|
||||
(set-box! yb max-y))))))])
|
||||
(augment*
|
||||
[after-interactive-move
|
||||
(lambda (e)
|
||||
(when dragging?
|
||||
(set! dragging? #f)
|
||||
(inner (void) after-interactive-move e)
|
||||
(for-each-selected (lambda (snip) (send snip back-to-original-location this)))
|
||||
(let ([cards (get-reverse-selected-list)])
|
||||
(only-front-selected) ; in case overlap changed
|
||||
(for-each
|
||||
(lambda (region)
|
||||
(when (region-hilite? region)
|
||||
(mred:queue-callback
|
||||
; Call it outside the current edit sequence
|
||||
(lambda ()
|
||||
((region-callback region) cards)
|
||||
(unhilite-region region)))))
|
||||
regions))))])
|
||||
(override*
|
||||
[on-default-event
|
||||
(lambda (e)
|
||||
(let ([click (let ([c (or (and (send e button-down? 'left) 'left)
|
||||
(and (send e button-down? 'right) 'right)
|
||||
(and (send e button-down? 'middle) 'middle))])
|
||||
(cond
|
||||
[(eq? c last-click) c]
|
||||
[(not last-click) c]
|
||||
;; Move/drag event has different mouse button,
|
||||
;; and there was no mouse up. Don't accept the
|
||||
;; click, yet.
|
||||
[else #f]))])
|
||||
(set! last-click click)
|
||||
(when click
|
||||
(let* ([actions (cdr (assoc click button-map))]
|
||||
[one? (list-ref actions 0)]
|
||||
[backward? (list-ref actions 1)]
|
||||
[raise? (list-ref actions 2)])
|
||||
(unless (and (eq? backward? select-backward?)
|
||||
(eq? one? select-one?)
|
||||
(eq? raise? raise-to-front?))
|
||||
(set! select-one? one?)
|
||||
(set! select-backward? backward?)
|
||||
(set! raise-to-front? raise?)
|
||||
(no-selected))))
|
||||
(let*-values ([(lx ly) (dc-location-to-editor-location
|
||||
(send e get-x)
|
||||
(send e get-y))]
|
||||
[(s) (find-snip lx ly)])
|
||||
; Clicking on a "selected" card unselects others
|
||||
; in this interface
|
||||
(when (send e button-down?)
|
||||
(unless (or (not click-base) (not s) (eq? s click-base))
|
||||
(no-selected))
|
||||
(set! click-base s))
|
||||
(when (and dragging? click-base (send click-base user-can-move))
|
||||
(for-each
|
||||
(lambda (region)
|
||||
(when (and (not (region-button? region))
|
||||
(region-callback region)
|
||||
(or (not (region-decided-start? region))
|
||||
(region-can-select? region)))
|
||||
(let-values ([(sx sy sw sh) (get-region-box region)])
|
||||
(let ([in? (and (<= sx lx (+ sx sw))
|
||||
(<= sy ly (+ sy sh)))])
|
||||
(unless (region-decided-start? region)
|
||||
(set-region-decided-start?! region #t)
|
||||
(set-region-can-select?! region (not in?)))
|
||||
(when (and (not (eq? in? (region-hilite? region)))
|
||||
(region-can-select? region))
|
||||
(set-region-hilite?! region in?)
|
||||
(when (region-interactive-callback region)
|
||||
((region-interactive-callback region) in? (get-reverse-selected-list)))
|
||||
(invalidate-bitmap-cache sx sy sw sh))))))
|
||||
regions))
|
||||
; Can't move => no raise, either
|
||||
(unless (or (not click-base) (send click-base user-can-move))
|
||||
(set! raise-to-front? #f))
|
||||
(let ([was-bg? bg-click?])
|
||||
(if (send e button-down?)
|
||||
(set! bg-click? (not s))
|
||||
(when (and bg-click? (not (send e dragging?)))
|
||||
(set! bg-click? #f)))
|
||||
(unless bg-click?
|
||||
(super on-default-event e))
|
||||
(when (and bg-click? dragging?)
|
||||
;; We didn't call super on-default-event, so we need
|
||||
;; to explicitly end the drag:
|
||||
(after-interactive-move e))
|
||||
(when bg-click?
|
||||
; Check for clicking on a button region:
|
||||
(for-each
|
||||
(lambda (region)
|
||||
(when (and (region-button? region)
|
||||
(region-callback region))
|
||||
(let-values ([(sx sy sw sh) (get-region-box region)])
|
||||
(let ([in? (and (<= sx lx (+ sx sw))
|
||||
(<= sy ly (+ sy sh)))])
|
||||
(unless (region-decided-start? region)
|
||||
(set-region-decided-start?! region #t)
|
||||
(set-region-can-select?! region in?))
|
||||
(when (and (not (eq? in? (region-hilite? region)))
|
||||
(region-can-select? region))
|
||||
(set-region-hilite?! region in?)
|
||||
(invalidate-bitmap-cache sx sy sw sh))))))
|
||||
regions))
|
||||
(when (and was-bg? (not bg-click?))
|
||||
; Callback hilighted button:
|
||||
(for-each
|
||||
(lambda (region)
|
||||
(when (region-button? region)
|
||||
(set-region-decided-start?! region #f)
|
||||
(when (region-hilite? region)
|
||||
(mred:queue-callback
|
||||
; Call it outside the current edit sequence
|
||||
(lambda ()
|
||||
((region-callback region))
|
||||
(unhilite-region region))))))
|
||||
regions)))
|
||||
(when (and (send e button-down?)
|
||||
click-base
|
||||
(not (send click-base user-can-move)))
|
||||
(no-selected)))
|
||||
(when (and click click-base)
|
||||
(do-on-single-click click-base))))]
|
||||
[on-double-click
|
||||
(lambda (s e)
|
||||
(cond
|
||||
[(eq? do-on-double-click 'flip)
|
||||
(begin-edit-sequence)
|
||||
(let ([l (get-reverse-selected-list)])
|
||||
(for-each
|
||||
(lambda (s)
|
||||
(when (send s user-can-flip)
|
||||
(send s flip)))
|
||||
l)
|
||||
(let loop ([l (reverse l)])
|
||||
(unless (null? l)
|
||||
(set-before (car l) #f)
|
||||
(loop (cdr l)))))
|
||||
(no-selected)
|
||||
(end-edit-sequence)]
|
||||
[do-on-double-click
|
||||
(do-on-double-click s)]
|
||||
[else (void)]))])
|
||||
(public*
|
||||
[get-all-list
|
||||
(lambda ()
|
||||
(let loop ([t (find-first-snip)][accum null])
|
||||
(cond
|
||||
[(not t) (reverse accum)]
|
||||
[else (loop (send t next) (cons t accum))])))]
|
||||
[get-full-box
|
||||
(lambda ()
|
||||
(let ([xb (box 0)][yb (box 0)]
|
||||
[wb (box 0)][hb (box 0)])
|
||||
(send (get-admin) get-view xb yb wb hb)
|
||||
(values 0 0 (unbox wb) (unbox hb))))]
|
||||
[get-region-box
|
||||
(lambda (region)
|
||||
(values (region-x region)
|
||||
(region-y region)
|
||||
(region-w region)
|
||||
(region-h region)))]
|
||||
[add-region
|
||||
(lambda (r)
|
||||
(set! regions (append regions (list r)))
|
||||
(update-region r))]
|
||||
[remove-region
|
||||
(lambda (r)
|
||||
(set! regions (remq r regions))
|
||||
(update-region r))]
|
||||
[unhilite-region
|
||||
(lambda (region)
|
||||
(set-region-hilite?! region #f)
|
||||
(update-region region))]
|
||||
[hilite-region
|
||||
(lambda (region)
|
||||
(set-region-hilite?! region #t)
|
||||
(update-region region))]
|
||||
[set-double-click-action
|
||||
(lambda (a)
|
||||
(set! do-on-double-click a))]
|
||||
[set-single-click-action
|
||||
(lambda (a)
|
||||
(set! do-on-single-click a))]
|
||||
[set-button-action
|
||||
(lambda (button action)
|
||||
(let ([map
|
||||
(case action
|
||||
[(drag/one) (list #t #f #f)]
|
||||
[(drag-raise/one) (list #t #f #t)]
|
||||
[(drag/above) (list #f #f #f)]
|
||||
[(drag-raise/above) (list #f #f #t)]
|
||||
[(drag/below) (list #f #t #f)]
|
||||
[(drag-raise/below) (list #f #t #t)]
|
||||
[else (error 'set-button-action "unknown action: ~s" action)])])
|
||||
(set! button-map
|
||||
(cons
|
||||
(cons button map)
|
||||
(remq (assoc button button-map)
|
||||
button-map)))))])
|
||||
(super-make-object)
|
||||
(set-selection-visible #f)))
|
||||
|
||||
(define table%
|
||||
(class mred:frame%
|
||||
(init title w h)
|
||||
(inherit reflow-container)
|
||||
(augment*
|
||||
[on-close
|
||||
(lambda ()
|
||||
(exit))])
|
||||
(public*
|
||||
[table-width (lambda ()
|
||||
(reflow-container)
|
||||
(let-values ([(x y w h) (send pb get-full-box)])
|
||||
w))]
|
||||
[table-height (lambda ()
|
||||
(reflow-container)
|
||||
(let-values ([(x y w h) (send pb get-full-box)])
|
||||
h))]
|
||||
[begin-card-sequence
|
||||
(lambda ()
|
||||
(set! in-sequence (add1 in-sequence))
|
||||
(send pb begin-edit-sequence))]
|
||||
[end-card-sequence
|
||||
(lambda ()
|
||||
(send pb end-edit-sequence)
|
||||
(set! in-sequence (sub1 in-sequence)))]
|
||||
[add-card
|
||||
(lambda (card x y)
|
||||
(position-cards (list card) x y (lambda (p) (values 0 0)) add-cards-callback))]
|
||||
[add-cards
|
||||
(lambda (cards x y [offset (lambda (p) (values 0 0))])
|
||||
(position-cards cards x y offset add-cards-callback))]
|
||||
[add-cards-to-region
|
||||
(lambda (cards region)
|
||||
(position-cards-in-region cards region add-cards-callback))]
|
||||
[move-card
|
||||
(lambda (card x y)
|
||||
(position-cards (list card) x y (lambda (p) (values 0 0)) move-cards-callback))]
|
||||
[move-cards
|
||||
(lambda (cards x y [offset (lambda (p) (values 0 0))])
|
||||
(position-cards cards x y offset move-cards-callback))]
|
||||
[move-cards-to-region
|
||||
(lambda (cards region)
|
||||
(position-cards-in-region cards region (lambda (c x y) (send pb move-to c x y))))]
|
||||
[card-location
|
||||
(lambda (card)
|
||||
(let ([x (box 0)]
|
||||
[y (box 0)])
|
||||
(unless (send pb get-snip-location card x y)
|
||||
(raise-mismatch-error 'card-location "card not on table: " card))
|
||||
(values (unbox x) (unbox y))))]
|
||||
[all-cards
|
||||
(lambda ()
|
||||
(send pb get-all-list))]
|
||||
[remove-card
|
||||
(lambda (card)
|
||||
(remove-cards (list card)))]
|
||||
[remove-cards
|
||||
(lambda (cards)
|
||||
(begin-card-sequence)
|
||||
(for-each (lambda (c) (send pb release-snip c)) cards)
|
||||
(end-card-sequence))]
|
||||
[flip-card
|
||||
(lambda (card)
|
||||
(flip-cards (list card)))]
|
||||
[flip-cards
|
||||
(lambda (cards)
|
||||
(if (or (not animate?) (positive? in-sequence))
|
||||
(for-each (lambda (c) (send c flip)) cards)
|
||||
(let ([flip-step
|
||||
(lambda (go)
|
||||
(let ([start (current-milliseconds)])
|
||||
(begin-card-sequence)
|
||||
(go)
|
||||
(end-card-sequence)
|
||||
(pause (max 0 (- (/ ANIMATION-TIME ANIMATION-STEPS)
|
||||
(/ (- (current-milliseconds) start) 1000))))))])
|
||||
(flip-step (lambda () (for-each (lambda (c) (send c semi-flip)) cards)))
|
||||
(flip-step (lambda () (for-each (lambda (c) (send c flip)) cards)))
|
||||
(flip-step (lambda () (for-each (lambda (c) (send c semi-flip)) cards))))))]
|
||||
[rotate-card
|
||||
(lambda (card mode) (rotate-cards (list card) mode))]
|
||||
[rotate-cards
|
||||
(lambda (cards mode)
|
||||
(begin-card-sequence)
|
||||
(let ([tw (table-width)]
|
||||
[th (table-height)])
|
||||
(map (lambda (c)
|
||||
(let ([w (send c card-width)]
|
||||
[h (send c card-height)])
|
||||
(send c rotate mode)
|
||||
(let ([w2 (send c card-width)]
|
||||
[h2 (send c card-height)]
|
||||
[x (box 0)]
|
||||
[y (box 0)])
|
||||
(send pb get-snip-location c x y)
|
||||
(send pb move-to c
|
||||
(min (max 0 (+ (unbox x) (/ (- w w2) 2))) (- tw w2))
|
||||
(min (max 0 (+ (unbox y) (/ (- h h2) 2))) (- th h2))))))
|
||||
cards)
|
||||
(end-card-sequence)))]
|
||||
[card-face-up
|
||||
(lambda (card)
|
||||
(cards-face-up (list card)))]
|
||||
[cards-face-up
|
||||
(lambda (cards)
|
||||
(flip-cards (filter (lambda (c) (send c face-down?)) cards)))]
|
||||
[card-face-down
|
||||
(lambda (card)
|
||||
(cards-face-down (list card)))]
|
||||
[cards-face-down
|
||||
(lambda (cards)
|
||||
(flip-cards (filter (lambda (c) (not (send c face-down?))) cards)))]
|
||||
[card-to-front
|
||||
(lambda (card)
|
||||
(send pb set-before card #f))]
|
||||
[card-to-back
|
||||
(lambda (card)
|
||||
(send pb set-after card #f))]
|
||||
[stack-cards
|
||||
(lambda (cards)
|
||||
(unless (null? cards)
|
||||
(send pb only-front-selected) ; in case overlap changes
|
||||
(begin-card-sequence)
|
||||
(let loop ([l (cdr cards)][behind (car cards)])
|
||||
(unless (null? l)
|
||||
(send pb set-after (car l) behind)
|
||||
(loop (cdr l) (car l))))
|
||||
(end-card-sequence)))]
|
||||
[add-region
|
||||
(lambda (r)
|
||||
(send pb add-region r))]
|
||||
[remove-region
|
||||
(lambda (r)
|
||||
(send pb remove-region r))]
|
||||
[hilite-region
|
||||
(lambda (r)
|
||||
(send pb hilite-region r))]
|
||||
[unhilite-region
|
||||
(lambda (r)
|
||||
(send pb unhilite-region r))]
|
||||
[set-button-action
|
||||
(lambda (button action)
|
||||
(send pb set-button-action button action))]
|
||||
[set-double-click-action
|
||||
(lambda (a)
|
||||
(send pb set-double-click-action a))]
|
||||
[set-single-click-action
|
||||
(lambda (a)
|
||||
(send pb set-single-click-action a))]
|
||||
[pause
|
||||
(lambda (duration)
|
||||
(let ([s (make-semaphore)]
|
||||
[a (alarm-evt (+ (current-inexact-milliseconds)
|
||||
(* duration 1000)))]
|
||||
[enabled? (send c is-enabled?)])
|
||||
;; Can't move the cards during this time:
|
||||
(send c enable #f)
|
||||
(mred:yield a)
|
||||
(when enabled?
|
||||
(send c enable #t))))]
|
||||
[animated
|
||||
(case-lambda
|
||||
[() animate?]
|
||||
[(on?) (set! animate? (and on? #t))])]
|
||||
[create-status-pane
|
||||
(lambda ()
|
||||
(let ([p (make-object mred:horizontal-pane% this)])
|
||||
(set! msg (new mred:message%
|
||||
[parent p]
|
||||
[label ""]
|
||||
[stretchable-width #t]))
|
||||
p))]
|
||||
[set-status
|
||||
(lambda (str)
|
||||
(when msg
|
||||
(send msg set-label str)))]
|
||||
[add-help-button
|
||||
(lambda (pane where title tt?)
|
||||
(new mred:button%
|
||||
(parent pane)
|
||||
(label (string-constant help-menu-label))
|
||||
(callback
|
||||
(let ([show-help (show-help where title tt?)])
|
||||
(lambda x
|
||||
(show-help))))))]
|
||||
[add-scribble-button
|
||||
(lambda (pane mod tag)
|
||||
(new mred:button%
|
||||
(parent pane)
|
||||
(label (string-constant help-menu-label))
|
||||
(callback
|
||||
(let ([show-help (show-scribbling mod tag)])
|
||||
(lambda x
|
||||
(show-help))))))])
|
||||
(begin
|
||||
(define msg #f)
|
||||
(define add-cards-callback
|
||||
(lambda (card x y)
|
||||
(send pb insert card #f x y)))
|
||||
(define move-cards-callback
|
||||
(lambda (card x y)
|
||||
(send pb move-to card x y)
|
||||
(send card remember-location pb))))
|
||||
(begin
|
||||
(define animate? #t)
|
||||
(define in-sequence 0))
|
||||
(private*
|
||||
[position-cards
|
||||
(lambda (cards x y offset set)
|
||||
(let ([positions (let loop ([l cards][n 0])
|
||||
(if (null? l)
|
||||
null
|
||||
(let-values ([(dx dy) (offset n)])
|
||||
(cons (cons (+ x dx) (+ y dy))
|
||||
(loop (cdr l) (add1 n))))))])
|
||||
(if (or (not animate?) (positive? in-sequence) (eq? set add-cards-callback))
|
||||
(begin
|
||||
(begin-card-sequence)
|
||||
(for-each (lambda (c p) (set c (car p) (cdr p))) cards positions)
|
||||
(end-card-sequence))
|
||||
(let-values ([(moving-cards
|
||||
source-xs
|
||||
source-ys
|
||||
dest-xs
|
||||
dest-ys)
|
||||
(let loop ([cl cards][pl positions])
|
||||
(if (null? cl)
|
||||
(values null null null null null)
|
||||
(let-values ([(mcl sxl syl dxl dyl) (loop (cdr cl) (cdr pl))]
|
||||
[(card) (car cl)]
|
||||
[(x y) (values (caar pl) (cdar pl))])
|
||||
(let ([xb (box 0)][yb (box 0)])
|
||||
(send pb get-snip-location card xb yb)
|
||||
(let ([sx (unbox xb)][sy (unbox yb)])
|
||||
(if (and (= x sx) (= y sy))
|
||||
(values mcl sxl syl dxl dyl)
|
||||
(values (cons card mcl)
|
||||
(cons sx sxl)
|
||||
(cons sy syl)
|
||||
(cons x dxl)
|
||||
(cons y dyl))))))))])
|
||||
(let ([time-scale
|
||||
;; An animation speed that looks good for
|
||||
;; long moves looks too slow for short
|
||||
;; moves. So scale the time back by as much
|
||||
;; as 50% if the max distance for all cards
|
||||
;; is short.
|
||||
(let ([max-delta (max (apply max 0 (map (lambda (sx dx)
|
||||
(abs (- sx dx)))
|
||||
source-xs dest-xs))
|
||||
(apply max 0 (map (lambda (sy dy)
|
||||
(abs (- sy dy)))
|
||||
source-ys dest-ys)))])
|
||||
(if (max-delta . < . 100)
|
||||
(/ (+ max-delta 100) 200.0)
|
||||
1))])
|
||||
(let loop ([n 1])
|
||||
(unless (> n ANIMATION-STEPS)
|
||||
(let ([start (current-milliseconds)]
|
||||
[scale (lambda (s d)
|
||||
(+ s (* (/ n ANIMATION-STEPS) (- d s))))])
|
||||
(begin-card-sequence)
|
||||
(for-each
|
||||
(lambda (c sx sy dx dy)
|
||||
(set c (scale sx dx) (scale sy dy)))
|
||||
moving-cards
|
||||
source-xs source-ys
|
||||
dest-xs dest-ys)
|
||||
(end-card-sequence)
|
||||
(pause (max 0 (- (/ (* time-scale ANIMATION-TIME) ANIMATION-STEPS)
|
||||
(/ (- (current-milliseconds) start) 1000))))
|
||||
(loop (add1 n))))))))
|
||||
;; In case overlap changed:
|
||||
(send pb only-front-selected)))]
|
||||
[position-cards-in-region
|
||||
(lambda (cards r set)
|
||||
(unless (null? cards)
|
||||
(let-values ([(x y w h) (send pb get-region-box r)]
|
||||
[(len) (sub1 (length cards))]
|
||||
[(cw ch) (values (send (car cards) card-width)
|
||||
(send (car cards) card-height))])
|
||||
(let* ([pretty (lambda (cw) (+ (* (add1 len) cw) (* len PRETTY-CARD-SEP-AMOUNT)))]
|
||||
[pw (pretty cw)]
|
||||
[ph (pretty ch)])
|
||||
(let-values ([(x w) (if (> w pw)
|
||||
(values (+ x (/ (- w pw) 2)) pw)
|
||||
(values x w))]
|
||||
[(y h) (if (> h ph)
|
||||
(values (+ y (/ (- h ph) 2)) ph)
|
||||
(values y h))])
|
||||
(position-cards cards x y
|
||||
(lambda (p)
|
||||
(if (zero? len)
|
||||
(values (/ (- w cw) 2)
|
||||
(/ (- h ch) 2))
|
||||
(values (* (- len p) (/ (- w cw) len))
|
||||
(* (- len p) (/ (- h ch) len)))))
|
||||
set))))))])
|
||||
(super-new [label title] [style '(metal no-resize-border)])
|
||||
(begin
|
||||
(define c (make-object mred:editor-canvas% this #f '(no-vscroll no-hscroll)))
|
||||
(define pb (make-object pasteboard%)))
|
||||
(send c min-client-width (+ 10 (inexact->exact (floor (* w (send back get-width))))))
|
||||
(send c min-client-height (+ 10 (inexact->exact (floor (* h (send back get-height))))))
|
||||
(send c stretchable-width #f)
|
||||
(send c stretchable-height #f)
|
||||
(send this stretchable-width #f)
|
||||
(send this stretchable-height #f)
|
||||
(send c set-editor pb)))
|
||||
|
||||
(define (draw-roundish-rectangle dc x y w h)
|
||||
(send dc draw-line (+ x 1) y (+ x w -2) y)
|
||||
(send dc draw-line (+ x 1) (+ y h -1) (+ x w -2) (+ y h -1))
|
||||
(send dc draw-line x (+ y 1) x (+ y h -2))
|
||||
(send dc draw-line (+ x w -1) (+ y 1) (+ x w -1) (+ y h -2))))
|
|
@ -1,57 +0,0 @@
|
|||
|
||||
(module constants racket
|
||||
(require racket/class
|
||||
racket/gui)
|
||||
|
||||
(provide ANIMATION-STEPS
|
||||
ANIMATION-TIME
|
||||
|
||||
PRETTY-CARD-SEP-AMOUNT
|
||||
|
||||
white-brush
|
||||
hilite-brush
|
||||
black-pen
|
||||
dark-gray-pen
|
||||
no-pen
|
||||
black-color
|
||||
nice-font)
|
||||
|
||||
(define ANIMATION-STEPS 5)
|
||||
(define ANIMATION-TIME 0.3)
|
||||
|
||||
(define PRETTY-CARD-SEP-AMOUNT 5)
|
||||
|
||||
(define black-color
|
||||
(make-object color% "black"))
|
||||
|
||||
(define white-brush
|
||||
(send the-brush-list
|
||||
find-or-create-brush
|
||||
"white" 'solid))
|
||||
|
||||
(define hilite-brush
|
||||
(send the-brush-list
|
||||
find-or-create-brush
|
||||
black-color 'hilite))
|
||||
|
||||
(define black-pen
|
||||
(send the-pen-list
|
||||
find-or-create-pen
|
||||
black-color 1 'solid))
|
||||
|
||||
(define dark-gray-pen
|
||||
(send the-pen-list
|
||||
find-or-create-pen
|
||||
"dark gray" 1 'solid))
|
||||
|
||||
(define no-pen
|
||||
(send the-pen-list
|
||||
find-or-create-pen
|
||||
black-color 1 'transparent))
|
||||
|
||||
(define nice-font
|
||||
(send the-font-list
|
||||
find-or-create-font
|
||||
12 'decorative 'normal 'bold
|
||||
#f 'default #t)))
|
||||
|
|
@ -1,301 +0,0 @@
|
|||
#lang racket/base
|
||||
|
||||
;; Code used to generate card images, just in case the cards
|
||||
;; need to be regenarted in a similar way. This code relies
|
||||
;; on fonts that were available on a Mac OS X 10.9 machine
|
||||
;; at the time that the cards were generated.
|
||||
|
||||
(module generate racket
|
||||
(require racket/draw)
|
||||
|
||||
(define W 71)
|
||||
(define H 96)
|
||||
|
||||
(define IW 41)
|
||||
(define IH 73)
|
||||
|
||||
;; Numbers: Lucida Grande
|
||||
;; Letters: Helvetica
|
||||
;; Suits: Osaka
|
||||
|
||||
(define (extract-color)
|
||||
(define bm0 (read-bitmap "card-1-1.png"))
|
||||
(define c (new color%))
|
||||
(send (send bm0 make-dc) get-pixel (sub1 (sub1 W)) 20 c)
|
||||
(list
|
||||
(send c red)
|
||||
(send c green)
|
||||
(send c blue)))
|
||||
|
||||
;; Extract the core of a face card so that it can be re-decorated.
|
||||
;; some by-hand editing may be needed to generate images
|
||||
;; in "faces-clean". Create initial cards by scaling up the originals
|
||||
;; such as ImageMagick's `-adaptive-resize` mode.
|
||||
(define (extract-faces)
|
||||
(define D 0)
|
||||
(define dh 2)
|
||||
(define tw (* 2 (+ D IW)))
|
||||
(define th (- (* 2 (+ D IH)) (* 2 dh)))
|
||||
(define T 200)
|
||||
|
||||
(for* ([val (in-range 10 13)]
|
||||
[suit 4])
|
||||
|
||||
(define (white-out bstr x y)
|
||||
(define Zx (cond
|
||||
[(and (= val 11) (= suit 3)) 26]
|
||||
[(and (= val 12) (= suit 0)) 28]
|
||||
[(and (= val 12) (= suit 2)) 28]
|
||||
[else 32]))
|
||||
(define Zy 40)
|
||||
(when (or (and (< 0 x Zx) (< 0 y Zy))
|
||||
(and (< (- tw Zx) x tw) (< (- th Zy) y th)))
|
||||
(define p (* 4 (+ x (* y tw))))
|
||||
(define r (bytes-ref bstr (+ p 1)))
|
||||
(define g (bytes-ref bstr (+ p 2)))
|
||||
(define b (bytes-ref bstr (+ p 3)))
|
||||
(unless (and (r . > . T)
|
||||
(g . > . T)
|
||||
(b . > . T))
|
||||
(bytes-set! bstr (+ p 1) 255)
|
||||
(bytes-set! bstr (+ p 2) 255)
|
||||
(bytes-set! bstr (+ p 3) 255)
|
||||
(white-out bstr (- x 1) y)
|
||||
(white-out bstr (+ x 1) y)
|
||||
(white-out bstr x (- y 1))
|
||||
(white-out bstr x (+ y 1)))))
|
||||
|
||||
(define old-bm (read-bitmap
|
||||
(collection-file-path (format "card-~a-~a@2x.png" val suit) "games/cards/hicolor")))
|
||||
(define bm2 (make-bitmap tw th))
|
||||
(define dc (send bm2 make-dc))
|
||||
(define dx (quotient (- W (+ D IW)) 2))
|
||||
(define dy (quotient (- H (+ D IH)) 2))
|
||||
(send dc draw-bitmap-section old-bm 0 0 (* 2 dx) (+ (* 2 dy) dh) tw th)
|
||||
(define bstr (make-bytes (* tw th 4)))
|
||||
(send bm2 get-argb-pixels 0 0 tw th bstr)
|
||||
(define P 16)
|
||||
(white-out bstr P P)
|
||||
(white-out bstr (- tw P) (- th P))
|
||||
(send bm2 set-argb-pixels 0 0 tw th bstr)
|
||||
(send bm2 save-file (format "faces/face-~a-~a.png" val suit) 'png)))
|
||||
|
||||
(define (card suit val)
|
||||
(define bm (make-bitmap W H #:backing-scale 2))
|
||||
(define dc (send bm make-dc))
|
||||
|
||||
(define clip-path (new dc-path%))
|
||||
(define R 4)
|
||||
(send clip-path move-to R 0)
|
||||
(send clip-path arc (- W R) 0 R R (* pi 1/2) 0 #f)
|
||||
(send clip-path arc (- W R) (- H R) R R 0 (* pi -1/2) #f)
|
||||
(send clip-path arc 0 (- H R) R R (* pi -1/2) (* pi -1) #f)
|
||||
(send clip-path arc 0 0 R R (* pi -1) (* pi -3/2) #f)
|
||||
(send clip-path close)
|
||||
(define region (new region%))
|
||||
(send region set-path clip-path)
|
||||
(send dc set-clipping-region region)
|
||||
|
||||
(send dc set-pen "black" 1 'transparent)
|
||||
(send dc set-brush (make-color 236 236 186) 'solid)
|
||||
(send dc draw-rectangle 0 0 W H)
|
||||
|
||||
(send dc set-smoothing 'smoothed)
|
||||
(send dc set-brush "black" 'transparent)
|
||||
(define tr (send dc get-transformation))
|
||||
(send dc translate -1.0 -1.0)
|
||||
(send dc set-pen (make-color 200 200 180) 2 'solid)
|
||||
(send dc draw-path clip-path 0 0)
|
||||
(send dc set-transformation tr)
|
||||
(send dc set-pen (make-color 150 150 100) 2 'solid)
|
||||
(send dc draw-path clip-path 0 0)
|
||||
(send dc set-smoothing 'aligned)
|
||||
|
||||
(cond
|
||||
[(and (not suit) (not val))
|
||||
(define dx 4)
|
||||
(send dc set-pen "black" 1 'transparent)
|
||||
(send dc set-brush (make-color 0 170 0) 'solid)
|
||||
(send dc draw-rectangle dx dx (- W dx dx) (- H dx dx))
|
||||
(send dc set-clipping-rect dx dx (- W dx dx) (- H dx dx))
|
||||
(send dc set-text-foreground (make-color 0 240 0))
|
||||
(send dc set-font (make-font #:face "Athelas" #:size 16))
|
||||
(define-values (lw lh ld la) (send dc get-text-extent "\u3BB"))
|
||||
(define d (/ (- W dx dx) 8))
|
||||
(for* ([j (in-range -4 5)]
|
||||
[i (in-range 8)])
|
||||
(define x (+ dx (* i d)))
|
||||
(define y (+ -2 (/ W 2) (* j (+ 1 (/ (- H dx dx) 8)))))
|
||||
(define flip? (odd? (+ i j)))
|
||||
(send dc draw-text "\u3BB"
|
||||
(+ x (if flip? 0 lw))
|
||||
(+ y (if flip? 0 lh))
|
||||
#f 0 (if flip? 0 pi)))]
|
||||
[else
|
||||
(define pip
|
||||
(case suit
|
||||
[(0) "\u2663"] ; club
|
||||
[(1) "\u2666"] ; diamond
|
||||
[(2) "\u2665"] ; heart
|
||||
[(3) "\u2660"])) ; spade
|
||||
(define color
|
||||
(case suit
|
||||
[(0 3) "black"]
|
||||
[(1 2) (make-color 240 0 0)]))
|
||||
(define num
|
||||
(case val
|
||||
[(0) "A"]
|
||||
[(9) "I0"]
|
||||
[(10) "J"]
|
||||
[(11) "Q"]
|
||||
[(12) "K"]
|
||||
[else (format "~a" (add1 val))]))
|
||||
(define squish
|
||||
(case val
|
||||
[(9) #e0.9]
|
||||
[(10) #e1.1]
|
||||
[(11) #e0.8]
|
||||
[else 1]))
|
||||
|
||||
(send dc set-text-foreground color)
|
||||
|
||||
(when (val . > . 9)
|
||||
(define (get)
|
||||
(read-bitmap (format "faces-clean/face-~a-~a.png" val suit)))
|
||||
(define old-bm (get))
|
||||
(define old-bm2 (get))
|
||||
|
||||
(define odc (send old-bm make-dc))
|
||||
(send odc rotate pi)
|
||||
(send odc translate (- (send old-bm get-width)) (- (send old-bm get-height)))
|
||||
(send odc set-clipping-rect 0 0 (send old-bm get-width) (quotient (send old-bm get-height) 2))
|
||||
(send odc draw-bitmap old-bm2 0 0)
|
||||
|
||||
(define tr (send dc get-transformation))
|
||||
(send dc scale 0.5 0.5)
|
||||
(define D 0)
|
||||
(define dh 2)
|
||||
(define dx (quotient (- W (+ D IW)) 2))
|
||||
(define dy (quotient (- H (+ D IH)) 2))
|
||||
(send dc draw-bitmap old-bm (* 2 dx) (+ (* 2 dy) dh))
|
||||
(send dc set-transformation tr)
|
||||
(send dc set-pen (make-color 150 150 100) 1 'solid)
|
||||
(send dc draw-rectangle (- dx 1) (- dy 0) (+ IW 2) (+ IH 0)))
|
||||
|
||||
(define number? (<= 1 val 8))
|
||||
|
||||
(define (half first?)
|
||||
(send dc set-font (make-font #:face (if number?
|
||||
"Lucida Grande"
|
||||
"Helvetica")
|
||||
#:weight 'bold #:size 18))
|
||||
|
||||
(define tr (send dc get-transformation))
|
||||
(send dc scale squish 1)
|
||||
(send dc draw-text num (/ 1 squish) (if number? 0 4) #t)
|
||||
(define-values (nw nh nd na) (send dc get-text-extent num))
|
||||
(send dc set-transformation tr)
|
||||
|
||||
(send dc set-font (make-font #:face "Osaka" #:size 12))
|
||||
(define-values (spw sph spd spa) (send dc get-text-extent pip))
|
||||
(send dc draw-text pip (+ 1 (quotient (- (floor (* squish nw)) spw) 2)) 20)
|
||||
|
||||
(send dc set-font (make-font #:face "Osaka" #:size 16))
|
||||
(define-values (pw ph pd pa) (send dc get-text-extent pip))
|
||||
|
||||
(define dx (quotient (- W IW) 2))
|
||||
(define dy (quotient (- H IH) 2))
|
||||
(define dy2 (+ dy ph (/ (- (/ IH 2) ph ph) 2)))
|
||||
|
||||
(define (pips n)
|
||||
(case n
|
||||
[(1)
|
||||
(when first?
|
||||
(cond
|
||||
[(and (= val 0) (= suit 3))
|
||||
(define S 6)
|
||||
|
||||
(define plt (read-bitmap (collection-file-path "PLT-206.png" "icons")))
|
||||
(define w (send plt get-width))
|
||||
(define h (send plt get-height))
|
||||
|
||||
(define spade (make-bitmap w h))
|
||||
(define sdc (send spade make-dc))
|
||||
(define f (make-font #:face (send (send dc get-font) get-face) #:size (* 4 64)))
|
||||
(define-values (pw ph pd pa) (send sdc get-text-extent pip f))
|
||||
(send sdc set-font f)
|
||||
(send sdc draw-text pip (quotient (- w pw) 2) (quotient (- h ph) 2))
|
||||
|
||||
(define bstr (make-bytes (* w h 4)))
|
||||
(send plt get-argb-pixels 0 0 w h bstr)
|
||||
(define sbstr (make-bytes (* w h 4)))
|
||||
(send spade get-argb-pixels 0 0 w h sbstr)
|
||||
|
||||
(for ([i (in-range 0 (* w h 4) 4)])
|
||||
(define a (quotient (+ (bytes-ref bstr (+ i 1))
|
||||
(bytes-ref bstr (+ i 2))
|
||||
(bytes-ref bstr (+ i 3)))
|
||||
3))
|
||||
(bytes-set! bstr i (bytes-ref sbstr i))
|
||||
(bytes-set! bstr (+ i 1) a)
|
||||
(bytes-set! bstr (+ i 2) a)
|
||||
(bytes-set! bstr (+ i 3) a))
|
||||
(send plt set-argb-pixels 0 0 w h bstr)
|
||||
(define tr (send dc get-transformation))
|
||||
(send dc scale (/ 1 S) (/ 1 S))
|
||||
(send dc draw-bitmap plt
|
||||
(* S (/ (- W (quotient w S)) 2))
|
||||
(* S (/ (- H (quotient h S)) 2)))
|
||||
(send dc set-transformation tr)]
|
||||
[else
|
||||
(send dc draw-text pip (quotient (- W pw) 2) (quotient (- H ph) 2))]))]
|
||||
[(2)
|
||||
(send dc draw-text pip (+ dx (quotient (- IW pw) 2)) dy)]
|
||||
[(3)
|
||||
(pips 1)
|
||||
(pips 2)]
|
||||
[(4)
|
||||
(send dc draw-text pip dx dy)
|
||||
(send dc draw-text pip (- W dx pw) dy)]
|
||||
[(5)
|
||||
(pips 4)
|
||||
(pips 1)]
|
||||
[(6)
|
||||
(pips 4)
|
||||
(when first?
|
||||
(send dc draw-text pip dx (quotient (- H ph) 2))
|
||||
(send dc draw-text pip (- W dx pw) (quotient (- H ph) 2)))]
|
||||
[(7)
|
||||
(pips 6)
|
||||
(when first?
|
||||
(send dc draw-text pip (quotient (- W pw) 2) (+ dy (- (quotient IH 3) (quotient ph 2)))))]
|
||||
[(8)
|
||||
(pips 4)
|
||||
(send dc draw-text pip dx dy2)
|
||||
(send dc draw-text pip (- W dx pw) dy2)]
|
||||
[(9)
|
||||
(pips 8)
|
||||
(pips 1)]
|
||||
[(10)
|
||||
(pips 8)
|
||||
(send dc draw-text pip (quotient (- W pw) 2) (+ dy (- (quotient IH 4) (quotient ph 2))))]
|
||||
[else
|
||||
(send dc set-brush "white" 'solid)
|
||||
(send dc set-pen "black" 1 'transparent)
|
||||
(send dc draw-rectangle dx (+ dy 2) (- pw 2) (+ ph 3))
|
||||
(send dc draw-text pip (- dx 1) (+ dy 2))]))
|
||||
|
||||
(pips (add1 val)))
|
||||
|
||||
(half #t)
|
||||
(send dc rotate pi)
|
||||
(send dc translate (- W) (- H))
|
||||
(half #f)])
|
||||
|
||||
bm)
|
||||
|
||||
(for* ([s 4]
|
||||
[n 13])
|
||||
(send (card s n) save-file (format "/tmp/cards/card-~a-~a@2x.png" n s) 'png #:unscaled? #t))
|
||||
|
||||
(send (card #f #f) save-file "/tmp/cards/card-back@2x.png" 'png #:unscaled? #t))
|
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.6 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 6.4 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 4.6 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3.9 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 15 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 16 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 13 KiB |
Before Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 14 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.0 KiB |
Before Width: | Height: | Size: 2.0 KiB |
Before Width: | Height: | Size: 4.5 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 5.9 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 5.0 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 5.6 KiB |
Before Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 6.3 KiB |
Before Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 2.0 KiB |