#lang racket/base (require racket/class racket/unit racket/list racket/gui/base racket/math "../show-scribbling.ss" "same-lib.rkt") (provide game@) (define game@ (unit (import) (export) (define board-width 20) (define board-height 16) ;; build-board : (-> (vectorof (vectorof (vector (union num #f) boolean)))) ; this represents the board. Each entry is the color index of ; the piece and a node to mark for the depth-first traversal. ; #f for the color index indicates an eliminated piece. (define (build-board) (define board (build-vector board-width (lambda (i) (build-vector board-height (lambda (j) (vector (random (length colors)) #f)))))) (for* ([x (in-range 1 board-width)] [y (in-range 1 board-height)]) (when (zero? (random 5)) (define-values (prev-x prev-y) (if (zero? (random 2)) (values x (- y 1)) (values (- x 1) y))) (define this-vector (board-ref board x y)) (define prev-vector (board-ref board prev-x prev-y)) (vector-set! this-vector 0 (vector-ref prev-vector 0)))) board) (define board (build-board)) (define game-over? #f) ;; adds up as the user clicks (define clicked-score 0) (define (calc-score n) (* n n)) (define (reset-score) (set! clicked-score 0) (set-score-label)) (define (update-score balls-going-away) (set! clicked-score (+ clicked-score (calc-score balls-going-away))) (set-score-label)) (define (set-score-label) (define cells-filled-in 0) (for ([v (in-vector board)]) (for ([v (in-vector v)]) (when (vector-ref v 0) (set! cells-filled-in (+ cells-filled-in 1))))) (define bonus-start 50) ;; bonus for getting down to 49 (or fewer) balls (define bonus-per-ball 100) ;; number of points for clearing each of those last 'bonus-start' balls (define bonus (if (<= cells-filled-in bonus-start) (* bonus-per-ball (- bonus-start cells-filled-in)) 0)) (send score-message set-label (format "~a + ~a = ~a" clicked-score bonus (+ clicked-score bonus)))) (define same-canvas% (class canvas% (inherit get-dc get-client-size) (define/private (get-width) (let-values ([(w h) (get-client-size)]) w)) (define/private (get-height) (let-values ([(w h) (get-client-size)]) h)) (define/private (get-x-step) (/ (get-width) board-width)) (define/private (get-y-step) (/ (get-height) board-height)) (define mouse-current-x #f) (define mouse-current-y #f) (define mouse-clicked-x #f) (define mouse-clicked-y #f) (define background-valid? #f) (define background #f) (define/public (invalidate-board-bitmap) (set! background-valid? #f)) (define/override (on-size w h) (define-values (cw ch) (get-client-size)) (when background (unless (and (= cw (send background get-width)) (= ch (send background get-height))) (set! background #f) (set! background-valid? #f)))) (define/override (on-paint) (define-values (cw ch) (get-client-size)) (define dc (get-dc)) (send dc set-smoothing 'smoothed) (build-background) (send dc set-scale 1 1) (send dc draw-bitmap background 0 0) (define current-blob (and mouse-current-x (find-same-colors board board-width board-height mouse-current-x mouse-current-y))) (cond [(and mouse-clicked-x mouse-current-x (equal? mouse-clicked-x mouse-current-x) (equal? mouse-clicked-y mouse-current-y)) ;; don't know what to do here (define blob (find-same-colors board board-width board-height mouse-current-x mouse-current-y)) (unless (null? blob) (define color (vector-ref (board-ref board mouse-current-x mouse-current-y) 0)) (define-values (cw ch) (get-client-size)) (update-dc-scale dc cw ch board-width board-height) (update-pen/draw-blob blob dc color mouse-current-x mouse-current-y mouse-clicked-x mouse-clicked-y))] [mouse-current-x (define blob (find-same-colors board board-width board-height mouse-current-x mouse-current-y)) (unless (null? blob) (define color (vector-ref (board-ref board mouse-current-x mouse-current-y) 0)) (define-values (cw ch) (get-client-size)) (update-dc-scale dc cw ch board-width board-height) (update-pen/draw-blob blob dc color mouse-current-x mouse-current-y mouse-clicked-x mouse-clicked-y))]) (when game-over? (update-dc-scale dc cw ch board-width board-height) (paint-game-over))) (define/private (build-background) (unless background-valid? (define-values (cw ch) (get-client-size)) (unless background (set! background (make-bitmap cw ch))) (define bdc (make-object bitmap-dc% background)) (draw-board bdc board-width board-height board cw ch #f #f #f #f) (send bdc set-bitmap #f) (set! background-valid? #t))) (define/private (paint-game-over) (define dc (get-dc)) (define game-over "Game Over") (send dc set-font (send the-font-list find-or-create-font 24 'decorative 'normal 'normal #f)) (define border 5) (define-values (text-width text-height d l) (send dc get-text-extent game-over)) (define x (- (/ (* cell-w board-width) 2) (/ text-width 2))) (define y (- (/ (* cell-h board-height) 2) (/ text-height 2))) (send dc set-pen "white" 1' transparent) (send dc set-brush "white" 'solid) (send dc set-alpha .8) (send dc draw-rectangle (- x border border) (- y border) (+ text-width border border border border) (+ text-height border border)) (send dc set-alpha 1) (send dc draw-text game-over x y)) (inherit refresh) (define/override (on-event evt) (define x (send evt get-x)) (define y (send evt get-y)) (define-values (cw ch) (get-client-size)) (define bx (floor (* (/ x cw) board-width))) (define by (floor (* (/ y ch) board-height))) (unless (<= 0 bx (- board-width 1)) (set! bx #f)) (unless (<= 0 by (- board-height 1)) (set! by #f)) (when (send evt leaving?) (set! bx #f) (set! by #f)) (when (send evt button-up?) (when (and (equal? mouse-clicked-x bx) (equal? mouse-clicked-y by)) (define removed-ball-count (make-a-move mouse-clicked-x mouse-clicked-y board board-width board-height)) (when removed-ball-count (update-score removed-ball-count) (invalidate-board-bitmap) (update-game-over) (refresh)))) (define-values (new-mouse-clicked-x new-mouse-clicked-y) (cond [(send evt button-down?) (values bx by)] [(send evt button-up?) (values #f #f)] [else (values mouse-clicked-x mouse-clicked-y)])) (define this-score-needs-update? #f) (unless (and (equal? mouse-clicked-x new-mouse-clicked-x) (equal? mouse-clicked-y new-mouse-clicked-y)) (set! mouse-clicked-x new-mouse-clicked-x) (set! mouse-clicked-y new-mouse-clicked-y) (set! this-score-needs-update? #t) (refresh)) (unless (and (equal? bx mouse-current-x) (equal? by mouse-current-y)) (set! mouse-current-x bx) (set! mouse-current-y by) (set! this-score-needs-update? #t) (refresh)) (when this-score-needs-update? (update-this-score (if mouse-clicked-x mouse-clicked-x mouse-current-x) (if mouse-clicked-y mouse-clicked-y mouse-current-y)))) (define/private (update-this-score x y) (send this-score-message set-label (cond [(and x y) (define num (length (find-same-colors board board-width board-height x y))) (if (= num 1) "" (format "~a" (calc-score num)))] [else ""]))) (define/public-final (update-game-over) (set! game-over? (not (let loop ([i board-width] [continue? #f]) (cond [(zero? i) continue?] [else (or continue? (loop (sub1 i) (let loop ([j board-height] [continue? continue?]) (cond [(zero? j) continue?] [else (or continue? (loop (sub1 j) (> (length (find-same-colors board board-width board-height (sub1 i) (sub1 j))) 1)))]))))]))))) (super-new))) (define semaphore (make-semaphore 0)) (define same-frame% (class frame% [define/augment on-close (lambda () (semaphore-post semaphore) (inner (void) on-close))] (super-new [style '(metal)]))) (define (new-game-callback redraw?) (set! game-over? #f) (set! board (build-board)) (reset-score) (send canvas invalidate-board-bitmap) (send canvas update-game-over) (when redraw? (send canvas refresh))) (define frame (make-object same-frame% "Same")) (define panel (make-object vertical-panel% frame)) (define canvas (make-object same-canvas% panel)) (define hp (new horizontal-panel% [parent panel] [stretchable-height #f])) (new message% [label "Total Score: "] [parent hp]) (define score-message (new message% [label "10000 + 10000 = 20000"] ;; get a reasonable min size [parent hp] [stretchable-width #t])) (new message% [label "This Score: "] [parent hp]) (define this-score-message (new message% [label "10000"] ;; get a reasonable min size [parent hp] [stretchable-width #t])) (define button (make-object button% "New Game" hp (lambda x (new-game-callback #t)))) (define help-button (make-object button% "Help" hp (let ([show-help (show-scribbling '(lib "games/scribblings/games.scrbl") "same")]) (lambda (_1 _2) (show-help))))) (send canvas update-game-over) (reset-score) (send canvas min-width (ceiling (* board-width cell-w #e2.5))) (send canvas min-height (ceiling (* board-height cell-h #e2.5))) (send frame show #t) (void (yield semaphore)))) ; (make-same-bitmap "same.png")