diff --git a/pacman-project/adt/draw.rkt b/pacman-project/adt/draw.rkt index 8ad7728..3bf7acf 100644 --- a/pacman-project/adt/draw.rkt +++ b/pacman-project/adt/draw.rkt @@ -7,6 +7,10 @@ ;; All graphics logic is isolated in this ADT. Game logic knows nothing about ;; pixels, windows, or sprites. Grid-to-pixel conversion happens exclusively ;; here. +;; +;; Performance: coins, UI, maze, key, and pause are only redrawn when their +;; underlying state changes. The draw callback tracks previous values and +;; skips unchanged elements. (define-library (pacman-project adt draw) (import (scheme base) @@ -85,6 +89,17 @@ (define ui-tile (make-tile width height)) ((ui-layer 'add-drawable!) ui-tile) + ;; + ;; Change tracking — skip redraws when state hasn't changed + ;; + + (define cached-score -1) + (define cached-time "") + (define cached-key-taken? #f) + (define key-sprite-swapped? #f) + (define cached-paused? #f) + (define coins-dirty? #t) + ;; ;; Coordinate conversion ;; @@ -102,8 +117,10 @@ ;; ;; draw-maze! :: maze -> / - ;; Draws all walls and doors. + ;; Draws all walls and doors. Called once at startup and after door + ;; removal — never per-frame. (define (draw-maze! maze) + ((maze-tile 'clear!)) ((maze 'for-each-cell) (lambda (row col cell-type) (cond @@ -123,7 +140,7 @@ "pink")))))) ;; draw-coins! :: maze -> / - ;; Draws all coins in the maze. + ;; Redraws all coins. Only called when coins-dirty? is true. (define (draw-coins! maze) ((coins-tile 'clear!)) ((maze 'for-each-cell) @@ -137,15 +154,12 @@ "yellow"))))) ;; draw-key! :: key -> / - ;; Draws the key at its position, or shows it in UI if taken. + ;; Swaps the key sprite once when taken. No-op on subsequent frames. (define (draw-key! key-obj) - (if (key-obj 'taken?) - (begin - ((key-layer 'remove-drawable!) key-sprite) - ((key-layer 'add-drawable!) key-ui-sprite)) - (let ((pos (key-obj 'position))) - ((key-sprite 'set-x!) (grid->pixel-x (pos 'col))) - ((key-sprite 'set-y!) (grid->pixel-y (pos 'row)))))) + (when (and (key-obj 'taken?) (not key-sprite-swapped?)) + ((key-layer 'remove-drawable!) key-sprite) + ((key-layer 'add-drawable!) key-ui-sprite) + (set! key-sprite-swapped? #t))) ;; animate-pacman! :: number -> / ;; Advances the Pac-Man sprite animation based on elapsed time. @@ -156,58 +170,83 @@ (set! time-since-last-animation 0))) ;; draw-pacman! :: pacman -> / - ;; Draws Pac-Man at its current position with correct rotation. + ;; Updates Pac-Man position and rotation. Lightweight — just sets + ;; properties on an existing sprite. (define (draw-pacman! pacman) (let* ((pos (pacman 'position)) (direction (pacman 'direction))) - ;; Set position ((pacman-sprite 'set-x!) (grid->pixel-x (pos 'col))) ((pacman-sprite 'set-y!) (grid->pixel-y (pos 'row))) - ;; Set rotation based on direction (cond ((eq? direction 'right) ((pacman-sprite 'rotate!) rotation-right)) ((eq? direction 'left) ((pacman-sprite 'rotate!) rotation-left)) ((eq? direction 'up) ((pacman-sprite 'rotate!) rotation-up)) ((eq? direction 'down) ((pacman-sprite 'rotate!) rotation-down))))) ;; draw-ui! :: score, timer -> / - ;; Draws the score and time limit on screen. + ;; Redraws score and time only when their values have changed. (define (draw-ui! score timer) - ((ui-tile 'clear!)) - ;; Score - ((ui-tile 'draw-text!) - (number->string (score 'points)) - score-text-size score-text-x score-text-y "white") - ;; Separator line - ((ui-tile 'draw-rectangle!) - separator-x 0 separator-width height "white") - ;; Time limit - ((ui-tile 'draw-text!) - "Time remaining:" time-text-size time-label-x time-label-y "white") - ((ui-tile 'draw-text!) - ((timer 'format-time)) - score-text-size time-value-x time-value-y "white")) + (let ((current-score (score 'points)) + (current-time ((timer 'format-time)))) + (when (or (not (= current-score cached-score)) + (not (string=? current-time cached-time))) + ((ui-tile 'clear!)) + ;; Score + ((ui-tile 'draw-text!) + (number->string current-score) + score-text-size score-text-x score-text-y "white") + ;; Separator line + ((ui-tile 'draw-rectangle!) + separator-x 0 separator-width height "white") + ;; Time limit + ((ui-tile 'draw-text!) + "Time remaining:" time-text-size time-label-x time-label-y "white") + ((ui-tile 'draw-text!) + current-time + score-text-size time-value-x time-value-y "white") + ;; Update cache + (set! cached-score current-score) + (set! cached-time current-time)))) ;; draw-pause! :: boolean -> / - ;; Shows or hides the pause screen. + ;; Only redraws when pause state actually changes. (define (draw-pause! paused?) - ((pause-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")))) + (when (not (eq? paused? cached-paused?)) + ((pause-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"))) + (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)) ;; ;; Main draw function ;; ;; draw-game! :: game -> / - ;; Draws the full game (registered as draw callback). + ;; Draws the full game. Only redraws changed elements. (define (draw-game! game) (let ((level (game 'level))) + ;; Always update (lightweight sprite property sets) (draw-pacman! (level 'pacman)) + ;; Only redraw when dirty / changed (draw-key! (level 'key)) - (draw-coins! (level 'maze)) + (when coins-dirty? + (draw-coins! (level 'maze)) + (draw-maze! (level 'maze)) + (set! coins-dirty? #f)) (draw-ui! (level 'score) (level 'timer)) (draw-pause! (level 'paused?)))) @@ -226,9 +265,10 @@ ;; start-drawing! :: game -> / ;; Starts drawing by setting the draw callback. (define (start-drawing! game) - ;; Initial maze and coins draw (one-time) + ;; 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)))) @@ -241,6 +281,8 @@ ((eq? msg 'set-key-callback!) set-key-callback!) ((eq? msg 'start-drawing!) start-drawing!) ((eq? msg 'animate-pacman!) animate-pacman!) + ((eq? msg 'mark-coins-dirty!) mark-coins-dirty!) + ((eq? msg 'mark-maze-dirty!) mark-maze-dirty!) (else (error "Draw ADT -- Unknown message:" msg)))) dispatch-draw))))