diff --git a/pacman-project/adt/draw.rkt b/pacman-project/adt/draw.rkt index 7625c7b..16f444b 100644 --- a/pacman-project/adt/draw.rkt +++ b/pacman-project/adt/draw.rkt @@ -256,27 +256,45 @@ (for-each (lambda (gds) ((gds 'animate!))) ghost-draw-states) (set! time-since-last-animation 0))) - ;; draw-pacman! :: pacman -> / - (define (draw-pacman! pacman) + ;; lerp :: number, number, number -> number + ;; Linear interpolation between a and b by factor t (0..1). + (define (lerp a b t) + (+ a (* t (- b a)))) + + ;; draw-pacman! :: pacman, number -> / + ;; Draws Pac-Man with smooth interpolation between tiles. + (define (draw-pacman! pacman progress) (let* ((pos (pacman 'position)) + (row (pos 'row)) + (col (pos 'col)) + (prev-row (pacman 'prev-row)) + (prev-col (pacman 'prev-col)) + (t (min progress 1)) + (render-col (lerp prev-col col t)) + (render-row (lerp prev-row row t)) (direction (pacman 'direction))) - ((pacman-sprite 'set-x!) (grid->pixel-x (pos 'col))) - ((pacman-sprite 'set-y!) (grid->pixel-y (pos 'row))) + ((pacman-sprite 'set-x!) (grid->pixel-x render-col)) + ((pacman-sprite 'set-y!) (grid->pixel-y render-row)) (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-ghosts! :: list -> / - ;; Updates all ghost sprite positions and directions. + ;; Updates all ghost sprite positions with smooth interpolation. (define (draw-ghosts! ghosts) (for-each (lambda (ghost ghost-draw) (let* ((pos (ghost 'position)) (row (pos 'row)) (col (pos 'col)) + (prev-row (ghost 'prev-row)) + (prev-col (ghost 'prev-col)) + (t (min (/ (ghost 'movement-timer) ghost-speed-ms) 1)) + (render-row (lerp prev-row row t)) + (render-col (lerp prev-col col t)) (dir (ghost 'direction))) - ((ghost-draw 'update!) row col dir))) + ((ghost-draw 'update!) render-row render-col dir))) ghosts ghost-draw-states)) ;; draw-ui! :: score, timer -> / @@ -343,9 +361,10 @@ ;; draw-game! :: game -> / (define (draw-game! game) (let* ((level (game 'level)) - (timer (level 'timer))) + (timer (level 'timer)) + (pac-progress (/ (level 'pacman-movement-timer) pacman-speed-ms))) ;; Always update (lightweight sprite property sets) - (draw-pacman! (level 'pacman)) + (draw-pacman! (level 'pacman) pac-progress) (draw-ghosts! (level 'ghosts)) ;; Only redraw when dirty / changed (draw-key! (level 'key)) diff --git a/pacman-project/adt/ghost.rkt b/pacman-project/adt/ghost.rkt index 8dadcc1..b34f891 100644 --- a/pacman-project/adt/ghost.rkt +++ b/pacman-project/adt/ghost.rkt @@ -27,7 +27,9 @@ (scatter-target (make-position scatter-row scatter-col)) (house-timer exit-delay) (movement-timer 0) - (reverse-queued? #f)) + (reverse-queued? #f) + (prev-row start-row) + (prev-col start-col)) ;; direction! :: symbol -> / (define (direction! new-dir) @@ -44,10 +46,20 @@ (set! mode new-mode)) ;; move! :: number, number -> / + ;; Saves previous position, then moves by delta. (define (move! delta-row delta-col) + (set! prev-row (position 'row)) + (set! prev-col (position 'col)) ((position 'row!) (+ (position 'row) delta-row)) ((position 'col!) (+ (position 'col) delta-col))) + ;; sync-prev! :: -> / + ;; Sets previous position to current. Call after teleportation + ;; or ghost house exit to prevent long-range interpolation. + (define (sync-prev!) + (set! prev-row (position 'row)) + (set! prev-col (position 'col))) + ;; consume-reverse! :: -> boolean ;; Returns #t and clears the flag if a reverse was queued. (define (consume-reverse!) @@ -67,6 +79,10 @@ (define (reset-movement-timer!) (set! movement-timer 0)) + ;; set-movement-timer! :: number -> / + (define (set-movement-timer! val) + (set! movement-timer val)) + ;; advance-movement-timer! :: number -> number ;; Returns updated movement timer value. (define (advance-movement-timer! delta-time) @@ -82,6 +98,9 @@ ((eq? msg 'mode) mode) ((eq? msg 'mode!) mode!) ((eq? msg 'move!) move!) + ((eq? msg 'prev-row) prev-row) + ((eq? msg 'prev-col) prev-col) + ((eq? msg 'sync-prev!) sync-prev!) ((eq? msg 'scatter-target) scatter-target) ((eq? msg 'in-house?) (eq? mode 'in-house)) ((eq? msg 'consume-reverse!) consume-reverse!) @@ -89,6 +108,7 @@ ((eq? msg 'movement-timer) movement-timer) ((eq? msg 'advance-movement-timer!) advance-movement-timer!) ((eq? msg 'reset-movement-timer!) reset-movement-timer!) + ((eq? msg 'set-movement-timer!) set-movement-timer!) (else (error "Ghost ADT -- Unknown message:" msg)))) dispatch-ghost)))) diff --git a/pacman-project/adt/level.rkt b/pacman-project/adt/level.rkt index 7c9bfc4..fb7ee5c 100644 --- a/pacman-project/adt/level.rkt +++ b/pacman-project/adt/level.rkt @@ -263,9 +263,11 @@ ;; Handle tunnel teleportation (cond ((< next-col 0) - ((pos 'col!) (- (maze 'cols) 1))) + ((pos 'col!) (- (maze 'cols) 1)) + ((ghost 'sync-prev!))) ((>= next-col (maze 'cols)) - ((pos 'col!) 0)) + ((pos 'col!) 0) + ((ghost 'sync-prev!))) (else ((ghost 'move!) (car delta) (cdr delta)))))) @@ -289,6 +291,7 @@ ((pos 'row!) ghost-house-exit-row) ((pos 'col!) ghost-house-exit-col) ((ghost 'direction!) 'left) + ((ghost 'sync-prev!)) (on-ghosts-changed!))) ;; @@ -348,12 +351,12 @@ (when (and was-in-house? (not (ghost 'in-house?))) (exit-ghost-house! ghost) ((ghost 'mode!) global-mode))) - ;; Movement tick for active ghosts + ;; Movement tick for active ghosts (carry residual time) (when (not (ghost 'in-house?)) (let ((mt ((ghost 'advance-movement-timer!) delta-time))) (when (>= mt ghost-speed-ms) (advance-ghost! ghost) - ((ghost 'reset-movement-timer!)))))) + ((ghost 'set-movement-timer!) (- mt ghost-speed-ms)))))) ghosts)) ;; @@ -389,7 +392,9 @@ ((pac-pos 'row!) row)) ((>= col (maze 'cols)) ((pac-pos 'col!) 0) - ((pac-pos 'row!) row))))) + ((pac-pos 'row!) row))) + ;; Prevent interpolation across the entire map + ((pacman 'sync-prev!)))) ;; ;; Movement logic @@ -427,13 +432,19 @@ ;; advance-pacman! :: -> / (define (advance-pacman!) (when (not (or ((timer 'time-up?)) game-over?)) - (let ((current-dir (pacman 'direction))) + (let* ((current-dir (pacman 'direction)) + (pos-before-row ((pacman 'position) 'row)) + (pos-before-col ((pacman 'position) 'col))) (cond ((and queued-direction (can-move? queued-direction)) (move-pacman! queued-direction) (set! queued-direction #f)) ((can-move? current-dir) - (move-pacman! current-dir)))))) + (move-pacman! current-dir))) + ;; If pacman didn't actually move, sync prev to avoid drift + (when (and (= ((pacman 'position) 'row) pos-before-row) + (= ((pacman 'position) 'col) pos-before-col)) + ((pacman 'sync-prev!)))))) ;; ;; Pause logic @@ -465,11 +476,11 @@ (define (update! delta-time) (when (not (or paused? game-over?)) ((timer 'decrease!) delta-time) - ;; Pac-Man movement + ;; Pac-Man movement (carry residual for smoother interpolation) (set! movement-timer (+ movement-timer delta-time)) (when (>= movement-timer pacman-speed-ms) (advance-pacman!) - (set! movement-timer 0) + (set! movement-timer (- movement-timer pacman-speed-ms)) (check-ghost-collision!)) ;; Ghost mode cycling and movement (update-ghost-mode! delta-time) @@ -502,6 +513,7 @@ ((eq? msg 'timer) timer) ((eq? msg 'paused?) paused?) ((eq? msg 'game-over?) game-over?) + ((eq? msg 'pacman-movement-timer) movement-timer) ((eq? msg 'key-press!) key-press!) ((eq? msg 'update!) update!) ((eq? msg 'set-on-coins-changed!) set-on-coins-changed!) diff --git a/pacman-project/adt/pacman.rkt b/pacman-project/adt/pacman.rkt index 7430c55..d70e8de 100644 --- a/pacman-project/adt/pacman.rkt +++ b/pacman-project/adt/pacman.rkt @@ -4,8 +4,9 @@ ;; Pac-Man ADT ;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Manages the logical state of the player: grid position and current -;; direction. Contains NO graphics code. +;; Manages the logical state of the player: grid position, direction, and +;; previous position for smooth rendering interpolation. Contains NO +;; graphics code. (define-library (pacman-project adt pacman) (import (scheme base) @@ -18,7 +19,9 @@ ;; Creates a Pac-Man object at the given start position (row, col). (define (make-pacman start-row start-col) (let ((position (make-position start-row start-col)) - (direction 'right)) + (direction 'right) + (prev-row start-row) + (prev-col start-col)) ;; position! :: position -> / (define (position! new-position) @@ -29,11 +32,20 @@ (set! direction new-direction)) ;; move! :: number, number -> / - ;; Moves Pac-Man by a delta on the grid. + ;; Saves previous position, then moves by delta on the grid. (define (move! delta-row delta-col) + (set! prev-row (position 'row)) + (set! prev-col (position 'col)) ((position 'row!) (+ (position 'row) delta-row)) ((position 'col!) (+ (position 'col) delta-col))) + ;; sync-prev! :: -> / + ;; Sets previous position to current. Call after teleportation + ;; to prevent interpolation across the map. + (define (sync-prev!) + (set! prev-row (position 'row)) + (set! prev-col (position 'col))) + ;; dispatch-pacman :: symbol -> any (define (dispatch-pacman msg) (cond ((eq? msg 'position) position) @@ -41,6 +53,9 @@ ((eq? msg 'direction) direction) ((eq? msg 'direction!) direction!) ((eq? msg 'move!) move!) + ((eq? msg 'prev-row) prev-row) + ((eq? msg 'prev-col) prev-col) + ((eq? msg 'sync-prev!) sync-prev!) (else (error "Pac-Man ADT -- Unknown message:" msg)))) dispatch-pacman))))