racket/collects/games/gofish/gofish.rkt
2010-04-27 16:50:15 -06:00

327 lines
11 KiB
Racket

#lang mzscheme
(require games/cards mred mzlib/class mzlib/unit mzlib/list)
(provide game@)
(define game@ (unit (import) (export)
;; Player record
(define-struct player (r hand-r discard-r count-r ; regions
hand discarded ; cards
tried)) ; memory for simulating players
;; Player names
(define PLAYER-1-NAME "Opponent 1")
(define PLAYER-2-NAME "Opponent 2")
(define YOUR-NAME "You")
;; Initial card count
(define DEAL-COUNT 7)
;; Messages
(define YOUR-TURN-MESSAGE
"Your turn. (Drag a match to your discard box or drag a card to an opponent.)")
(define GO-FISH-MESSAGE
"Go Fish! (Drag a card from the center deck to your box.)")
(define MATCH-MESSAGE "Match!")
(define GAME-OVER-MESSAGE "GAME OVER")
;; Region layout constants
(define MARGIN 10)
(define SUBMARGIN 10)
(define LABEL-H 15)
;; Randomize
(random-seed (modulo (current-milliseconds) 10000))
;; Set up the table
(define t (make-table "Go Fish" 8 4.5))
(define status-pane (send t create-status-pane))
(send t add-scribble-button status-pane
'(lib "games/scribblings/games.scrbl") "gofish")
(send t show #t)
(send t set-double-click-action #f)
(send t set-button-action 'left 'drag-raise/one)
(send t set-button-action 'middle 'drag/one)
(send t set-button-action 'right 'drag/one)
;; Get table width & height
(define w (send t table-width))
(define h (send t table-height))
;; Set up the cards
(define deck (shuffle-list (make-deck) 7))
(for-each (lambda (card)
(send card snap-back-after-move #t)
(send card user-can-flip #f))
deck)
;; Function for dealing or drawing cards
(define (deal n)
(let loop ([n n][d deck])
(if (zero? n)
(begin (set! deck d) null)
(cons (car d) (loop (sub1 n) (cdr d))))))
;; Card width & height
(define cw (send (car deck) card-width))
(define ch (send (car deck) card-height))
;; Put the cards on the table
(send t add-cards deck (/ (- w cw) 2) (- (/ (- h ch) 2) (/ ch 3)))
;; Player region size
(define pw (- (/ (- w cw) 2) (* 2 MARGIN)))
(define ph (- (/ (- h (/ ch 3)) 2) (* 2 MARGIN)))
;; Region-makers
(define (make-hand-region r)
(define m SUBMARGIN)
(make-region (+ m (region-x r)) (+ LABEL-H m (region-y r))
(- (region-w r) (* 3 m) cw) (- (region-h r) LABEL-H (* 2 m))
#f #f))
(define (make-discard-region r)
(make-region (- (+ (region-x r) (region-w r)) SUBMARGIN cw)
(- (+ (region-y r) (region-h r)) SUBMARGIN ch)
cw ch
#f #f))
(define (make-discard-count-region r c cb)
(make-region
(- (+ (region-x r) (region-w r)) SUBMARGIN cw (/ SUBMARGIN 2))
(- (+ (region-y r) (region-h r)) SUBMARGIN ch LABEL-H (/ SUBMARGIN 2))
(+ cw SUBMARGIN) (+ ch LABEL-H SUBMARGIN)
(number->string c)
cb))
;; Define the initial regions
(define player-1-region
(make-region MARGIN MARGIN pw ph PLAYER-1-NAME void))
(define player-2-region
(make-region (- w MARGIN pw) MARGIN pw ph PLAYER-2-NAME void))
(define you-region
(make-region MARGIN (- h MARGIN ph) (- w (* 2 MARGIN)) ph YOUR-NAME void))
;; Player setup
(define (create-player r discard-callback)
(let ([p (make-player
r
(make-hand-region r)
(make-discard-region r)
(make-discard-count-region r 0 discard-callback)
(deal DEAL-COUNT)
null
null)])
(send t add-region r)
(send t add-region (player-count-r p))
(for-each (lambda (card)
(send t card-to-front card)) (reverse (player-hand p)))
(send t move-cards-to-region (player-hand p) (player-hand-r p))
p))
(define player-1 (create-player player-1-region #f))
(define player-2 (create-player player-2-region #f))
(define you (create-player you-region
;; Dragging to your discard pile checks to see if
;; the card makes a match:
(lambda (cards)
(check-hand you (car cards))
(send t set-status YOUR-TURN-MESSAGE))))
;; More card setup: Opponents's cards and deck initally can't be moved
(for-each (lambda (card) (send card user-can-move #f))
(append (player-hand player-1) (player-hand player-2) deck))
;; More card setup: Show your cards
(send t flip-cards (player-hand you))
;; Function to update the display for a player record
(define (rearrange-cards p)
;; Stack cards in 3D first-to-last
(send t stack-cards (player-discarded p))
(send t stack-cards (player-hand p))
;; Move them to their regions
(send t move-cards-to-region (player-discarded p) (player-discard-r p))
(send t move-cards-to-region (player-hand p) (player-hand-r p))
;; Recreate the counter region to reset the count
(send t begin-card-sequence)
(send t remove-region (player-count-r p))
(set-player-count-r! p (make-discard-count-region
(player-r p) (/ (length (player-discarded p)) 2)
(region-callback (player-count-r p))))
(send t add-region (player-count-r p))
(send t end-card-sequence))
;; Function to search for an equivalent card
(define (find-equiv card hand)
(ormap (lambda (c)
(and (not (eq? c card))
(= (send card get-value) (send c get-value))
c))
hand))
;; Function to check for a match involving `card' already in the player's hand
(define (check-hand player card)
(let* ([h (player-hand player)]
[found (find-equiv card h)])
(if found
(begin
;; Make sure the matching cards are face-up and pause for the user
(send t cards-face-up (list found card))
(send t set-status MATCH-MESSAGE)
;; The players has a match! Move the card from the player's hand
;; to his discard pile
(set-player-hand! player (remove* (list card found) h))
(set-player-discarded! player
(list* found card (player-discarded player)))
;; The dicarded cards can no longer be moved
(send card user-can-move #f)
(send found user-can-move #f)
;; Move the cards to their new places
(rearrange-cards player)
;; Slower
#t)
#f)))
;; Function to enable/disable moving your cards
(define (enable-your-cards on?)
(for-each (lambda (c) (send c user-can-move on?)) (player-hand you)))
;; Callbacks communicate back to the main loop via these
(define something-happened (make-semaphore 1))
(define go-fish? #f)
;; Function for trying to get a card from another player
(define (ask-player-for-match getter giver card)
(let* ([h (player-hand giver)]
[found (find-equiv card h)])
(if found
(begin
;; The giver player has a matching card - give it to the getter
(set-player-hand! giver (remq found h))
(set-player-hand! getter (cons found (player-hand getter)))
;; Make sure the matching cards are face-up and pause for the user
(send t cards-face-up (list found card))
;; Move the cards around
(check-hand getter card)
(rearrange-cards giver)
#t)
;; The giver player doesn't have it - Go Fish!
#f)))
;; Callback for dragging a card to an opponent
(define (player-callback player)
(lambda (cards)
(set! go-fish? (not (ask-player-for-match you player (car cards))))
(semaphore-post something-happened)))
;; Visual info to go fish
(define wiggle-top-card
(lambda ()
(let ([top (car deck)]
[x (/ (- w cw) 2)]
[y (- (/ (- h ch) 2) (/ ch 3))])
(send t move-card top (- x 10) y)
(send t move-card top (+ x 10) y)
(send t move-card top x y))))
;; Callback for going fishing
(define fishing
(lambda (cards)
(send t flip-card (car deck))
(set-player-hand! you (append (deal 1) (player-hand you)))
(rearrange-cards you)
(semaphore-post something-happened)))
;; Function to simulate a player
(define (simulate-player player other-player k)
;; Try cards in the players hand that haven't been tried
(let ([cards-to-try (remq* (player-tried player) (player-hand player))])
(if (null? cards-to-try)
(begin
;; No cards to try. Reset the history and start over
(set-player-tried! player null)
(simulate-player player other-player k))
;; Pick a random card and a random opponent
(let ([c (list-ref cards-to-try (random (length cards-to-try)))]
[o (list-ref (list you other-player) (random 2))])
(set-player-tried! player (cons c (player-tried player)))
;; Show you the card-to-ask
(send t flip-card c)
;; Hilight player-to-ask
(send t hilite-region (player-r o))
;; Wait a moment
(sleep 0.3)
;; Unhilight player-to-ask
(send t unhilite-region (player-r o))
(if (ask-player-for-match player o c)
;; Got it - go again
(check-done
(lambda ()
(simulate-player player other-player k)))
;; Go fish
(begin
;; Wait a bit, then turn the asked-for card back over
(sleep 0.3)
(send t flip-card c)
(if (null? deck)
;; No more cards; pass
(k)
(begin
;; Draw a card
(set-player-hand! player (append (deal 1) (player-hand player)))
(rearrange-cards player)
(if (check-hand player (car (player-hand player)))
;; Drew a good card - keep going
(check-done
(lambda ()
(simulate-player player other-player k)))
;; End of our turn
(k))))))))))
;; Function to check for end-of-game
(define (check-done k)
(if (ormap (lambda (p) (null? (player-hand p))) (list player-1 player-2 you))
(begin (enable-your-cards #f)
(send t set-status GAME-OVER-MESSAGE))
(k)))
;; Look in opponents' initial hands for matches (Since each player gets 7
;; cards, it's impossible to run out of cards this way)
(define (find-initial-matches player)
(when (ormap (lambda (card) (check-hand player card)) (player-hand player))
;; Found a match in the hand
(find-initial-matches player)))
(find-initial-matches player-1)
(find-initial-matches player-2)
;; Run the game loop
(let loop ()
(set-region-callback! (player-r you) #f)
(set-region-callback! (player-r player-1) (player-callback player-1))
(set-region-callback! (player-r player-2) (player-callback player-2))
(send t set-status YOUR-TURN-MESSAGE)
(yield something-happened)
(if go-fish?
(begin
(if (if (null? deck)
;; No more cards; pass
#f
;; Draw a card (wait for the user to drag it)
(begin (send t set-status GO-FISH-MESSAGE)
(wiggle-top-card)
(enable-your-cards #f)
(set-region-callback! (player-r player-1) #f)
(set-region-callback! (player-r player-2) #f)
(set-region-callback! (player-r you) fishing)
(send (car deck) user-can-move #t)
(yield something-happened)
(enable-your-cards #t)
(check-hand you (car (player-hand you)))))
(check-done loop)
(begin (send t set-status PLAYER-1-NAME)
(simulate-player
player-1 player-2
(lambda ()
(send t set-status PLAYER-2-NAME)
(simulate-player player-2 player-1 loop))))))
(check-done loop)))))