diff --git a/pacman-project/adt/draw.rkt b/pacman-project/adt/draw.rkt index 3bf7acf..34f1fdd 100644 --- a/pacman-project/adt/draw.rkt +++ b/pacman-project/adt/draw.rkt @@ -23,23 +23,38 @@ ;; make-draw :: number, number -> draw ;; Creates the draw object that handles all rendering. (define (make-draw width height) - (let ((window (make-window width height "Pacman"))) + (let ((window (make-window width height "PAC-MAN"))) - ((window 'set-background!) "black") + ((window 'set-background!) color-background) ;; ;; Layers (order determines draw order) ;; + (define header-layer ((window 'new-layer!))) (define maze-layer ((window 'new-layer!))) (define coins-layer ((window 'new-layer!))) (define key-layer ((window 'new-layer!))) (define pacman-layer ((window 'new-layer!))) (define ui-layer ((window 'new-layer!))) - (define pause-layer ((window 'new-layer!))) + (define overlay-layer ((window 'new-layer!))) ;; - ;; Maze tiles + ;; Header bar — static, drawn once + ;; + + (define header-tile (make-tile width header-height)) + ((header-layer 'add-drawable!) header-tile) + + ;; draw-header! :: -> / + ;; Draws the static header with the PAC-MAN title. + (define (draw-header!) + ((header-tile 'draw-rectangle!) 0 0 width header-height color-header-bg) + ((header-tile 'draw-text!) + "PAC-MAN" header-title-size header-title-x header-title-y color-title)) + + ;; + ;; Maze tile ;; (define maze-tile (make-tile width height)) @@ -60,7 +75,7 @@ ((key-sprite 'set-scale!) sprite-scale-key) ((key-layer 'add-drawable!) key-sprite) - ;; Key UI indicator (next to the score) + ;; Key UI indicator (shown in header when taken) (define key-ui-sprite (make-bitmap-tile "pacman-sprites/key.png")) ((key-ui-sprite 'set-scale!) sprite-scale-key-ui) ((key-ui-sprite 'set-x!) key-ui-x) @@ -83,7 +98,7 @@ (define time-since-last-animation 0) ;; - ;; UI tiles + ;; UI tile (score + time — redrawn on change) ;; (define ui-tile (make-tile width height)) @@ -95,9 +110,9 @@ (define cached-score -1) (define cached-time "") - (define cached-key-taken? #f) (define key-sprite-swapped? #f) (define cached-paused? #f) + (define cached-time-up? #f) (define coins-dirty? #t) ;; @@ -117,8 +132,7 @@ ;; ;; draw-maze! :: maze -> / - ;; Draws all walls and doors. Called once at startup and after door - ;; removal — never per-frame. + ;; Draws all walls and doors. (define (draw-maze! maze) ((maze-tile 'clear!)) ((maze 'for-each-cell) @@ -130,17 +144,17 @@ (grid->pixel-y row) (- cell-size-px maze-wall-shrink) (- cell-size-px maze-wall-shrink) - "blue")) + color-wall)) ((= cell-type cell-type-door) ((maze-tile 'draw-rectangle!) (grid->pixel-x col) (grid->pixel-y row) (- cell-size-px maze-wall-shrink) (- cell-size-px maze-wall-shrink) - "pink")))))) + color-door)))))) ;; draw-coins! :: maze -> / - ;; Redraws all coins. Only called when coins-dirty? is true. + ;; Redraws all coins as small centered dots. (define (draw-coins! maze) ((coins-tile 'clear!)) ((maze 'for-each-cell) @@ -149,9 +163,16 @@ ((coins-tile 'draw-rectangle!) (+ (grid->pixel-x col) coin-inset) (+ (grid->pixel-y row) coin-inset) - (- cell-size-px (* 2 coin-inset) 6) - (- cell-size-px (* 2 coin-inset) 6) - "yellow"))))) + coin-size + coin-size + color-coin))))) + + ;; init-key-position! :: key -> / + ;; Sets the key sprite to its grid position. Called once at startup. + (define (init-key-position! key-obj) + (let ((pos (key-obj 'position))) + ((key-sprite 'set-x!) (grid->pixel-x (pos 'col))) + ((key-sprite 'set-y!) (grid->pixel-y (pos 'row))))) ;; draw-key! :: key -> / ;; Swaps the key sprite once when taken. No-op on subsequent frames. @@ -170,8 +191,7 @@ (set! time-since-last-animation 0))) ;; draw-pacman! :: pacman -> / - ;; Updates Pac-Man position and rotation. Lightweight — just sets - ;; properties on an existing sprite. + ;; Updates Pac-Man position and rotation. (define (draw-pacman! pacman) (let* ((pos (pacman 'position)) (direction (pacman 'direction))) @@ -190,44 +210,60 @@ (when (or (not (= current-score cached-score)) (not (string=? current-time cached-time))) ((ui-tile 'clear!)) - ;; Score + ;; Score label + ((ui-tile 'draw-text!) + "SCORE" score-label-size score-label-x score-label-y color-text) + ;; Score value ((ui-tile 'draw-text!) (number->string current-score) - score-text-size score-text-x score-text-y "white") - ;; Separator line + score-value-size score-value-x score-value-y color-text) + ;; Sidebar separator ((ui-tile 'draw-rectangle!) - separator-x 0 separator-width height "white") - ;; Time limit + sidebar-x 0 sidebar-width height color-wall) + ;; Time label ((ui-tile 'draw-text!) - "Time remaining:" time-text-size time-label-x time-label-y "white") + "TIME" time-label-size time-label-x time-label-y color-text) + ;; Time value ((ui-tile 'draw-text!) current-time - score-text-size time-value-x time-value-y "white") + time-value-size time-value-x time-value-y color-text) ;; Update cache (set! cached-score current-score) (set! cached-time current-time)))) + ;; draw-game-over! :: boolean -> / + ;; Shows GAME OVER when time is up. + (define (draw-game-over! time-up?) + (when (and time-up? (not cached-time-up?)) + (let ((overlay-tile (make-tile width height))) + ((overlay-layer 'add-drawable!) overlay-tile) + ((overlay-tile 'draw-rectangle!) + 0 (- game-over-text-y 20) 672 100 color-background) + ((overlay-tile 'draw-text!) + "GAME OVER" game-over-text-size + game-over-text-x game-over-text-y color-game-over)) + (set! cached-time-up? #t))) + ;; draw-pause! :: boolean -> / ;; Only redraws when pause state actually changes. (define (draw-pause! paused?) (when (not (eq? paused? cached-paused?)) - ((pause-layer 'empty!)) + ((overlay-layer 'empty!)) (when paused? - (let ((pause-tile (make-tile width height))) - ((pause-layer 'add-drawable!) pause-tile) - ((pause-tile 'draw-rectangle!) 0 90 670 height "black") - ((pause-tile 'draw-text!) "Game Paused" 40 200 400 "red"))) + (let ((overlay-tile (make-tile width height))) + ((overlay-layer 'add-drawable!) overlay-tile) + ((overlay-tile 'draw-rectangle!) + 0 maze-offset-y 672 (- height maze-offset-y) color-pause-bg) + ((overlay-tile 'draw-text!) + "PAUSED" pause-text-size + pause-text-x pause-text-y color-pause-text))) (set! cached-paused? paused?))) ;; mark-coins-dirty! :: -> / - ;; Called by the game when a coin is eaten, so coins are redrawn - ;; next frame. (define (mark-coins-dirty!) (set! coins-dirty? #t)) ;; mark-maze-dirty! :: -> / - ;; Called by the game when a door is removed, so the maze is - ;; redrawn next frame. (define (mark-maze-dirty!) (set! coins-dirty? #t)) @@ -238,7 +274,8 @@ ;; draw-game! :: game -> / ;; Draws the full game. Only redraws changed elements. (define (draw-game! game) - (let ((level (game 'level))) + (let* ((level (game 'level)) + (timer (level 'timer))) ;; Always update (lightweight sprite property sets) (draw-pacman! (level 'pacman)) ;; Only redraw when dirty / changed @@ -247,8 +284,9 @@ (draw-coins! (level 'maze)) (draw-maze! (level 'maze)) (set! coins-dirty? #f)) - (draw-ui! (level 'score) (level 'timer)) - (draw-pause! (level 'paused?)))) + (draw-ui! (level 'score) timer) + (draw-pause! (level 'paused?)) + (draw-game-over! ((timer 'time-up?))))) ;; ;; Callback registration @@ -265,12 +303,16 @@ ;; start-drawing! :: game -> / ;; Starts drawing by setting the draw callback. (define (start-drawing! game) - ;; Initial draw (one-time) - (draw-maze! ((game 'level) 'maze)) - (draw-coins! ((game 'level) 'maze)) - (set! coins-dirty? #f) - ((window 'set-draw-callback!) - (lambda () (draw-game! game)))) + (let ((level (game 'level))) + ;; Static elements (drawn once) + (draw-header!) + (draw-maze! (level 'maze)) + (draw-coins! (level 'maze)) + (init-key-position! (level 'key)) + (set! coins-dirty? #f) + ;; Register draw callback + ((window 'set-draw-callback!) + (lambda () (draw-game! game))))) ;; ;; Dispatch