Implement four ghosts (Blinky, Pinky, Inky, Clyde) with authentic Pac-Man AI: Blinky chases directly, Pinky targets 2 tiles ahead (with original up-direction bug), Inky uses vector doubling from Blinky, Clyde switches to scatter within 8-tile radius. Includes chase/scatter mode cycling, ghost house exit with staggered delays, directional sprite rendering with animation, and ghost-pacman collision detection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
95 lines
3.8 KiB
Racket
95 lines
3.8 KiB
Racket
#lang r7rs
|
|
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
;; Ghost ADT ;;
|
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
|
|
|
;; A ghost has a position, direction, movement mode, and type identity.
|
|
;; The targeting AI is handled by the level — this ADT only stores state.
|
|
;; Each ghost instance represents one of the four ghosts (Blinky, Inky,
|
|
;; Pinky, Clyde). Contains NO graphics code.
|
|
|
|
(define-library (pacman-project adt ghost)
|
|
(import (scheme base)
|
|
(pacman-project adt position))
|
|
(export make-ghost)
|
|
|
|
(begin
|
|
|
|
;; make-ghost :: symbol, number, number, number, number, number -> ghost
|
|
;; Creates a ghost of the given type at start-row/col with a scatter
|
|
;; corner target and a house exit delay (0 = starts outside).
|
|
(define (make-ghost ghost-type start-row start-col
|
|
scatter-row scatter-col exit-delay)
|
|
(let ((position (make-position start-row start-col))
|
|
(direction 'left)
|
|
(mode (if (= exit-delay 0) 'scatter 'in-house))
|
|
(scatter-target (make-position scatter-row scatter-col))
|
|
(house-timer exit-delay)
|
|
(movement-timer 0)
|
|
(reverse-queued? #f))
|
|
|
|
;; direction! :: symbol -> /
|
|
(define (direction! new-dir)
|
|
(set! direction new-dir))
|
|
|
|
;; mode! :: symbol -> /
|
|
;; Sets the ghost mode. When switching between chase/scatter,
|
|
;; the ghost must reverse direction (queued for next move).
|
|
(define (mode! new-mode)
|
|
(when (and (not (eq? mode new-mode))
|
|
(not (eq? mode 'in-house))
|
|
(not (eq? new-mode 'in-house)))
|
|
(set! reverse-queued? #t))
|
|
(set! mode new-mode))
|
|
|
|
;; move! :: number, number -> /
|
|
(define (move! delta-row delta-col)
|
|
((position 'row!) (+ (position 'row) delta-row))
|
|
((position 'col!) (+ (position 'col) delta-col)))
|
|
|
|
;; consume-reverse! :: -> boolean
|
|
;; Returns #t and clears the flag if a reverse was queued.
|
|
(define (consume-reverse!)
|
|
(if reverse-queued?
|
|
(begin (set! reverse-queued? #f) #t)
|
|
#f))
|
|
|
|
;; update-house-timer! :: number -> /
|
|
;; Decreases house timer. When it reaches 0, ghost exits.
|
|
(define (update-house-timer! delta-time)
|
|
(when (eq? mode 'in-house)
|
|
(set! house-timer (- house-timer delta-time))
|
|
(when (<= house-timer 0)
|
|
(set! mode 'scatter))))
|
|
|
|
;; reset-movement-timer! :: -> /
|
|
(define (reset-movement-timer!)
|
|
(set! movement-timer 0))
|
|
|
|
;; advance-movement-timer! :: number -> number
|
|
;; Returns updated movement timer value.
|
|
(define (advance-movement-timer! delta-time)
|
|
(set! movement-timer (+ movement-timer delta-time))
|
|
movement-timer)
|
|
|
|
;; dispatch-ghost :: symbol -> any
|
|
(define (dispatch-ghost msg)
|
|
(cond ((eq? msg 'position) position)
|
|
((eq? msg 'direction) direction)
|
|
((eq? msg 'direction!) direction!)
|
|
((eq? msg 'type) ghost-type)
|
|
((eq? msg 'mode) mode)
|
|
((eq? msg 'mode!) mode!)
|
|
((eq? msg 'move!) move!)
|
|
((eq? msg 'scatter-target) scatter-target)
|
|
((eq? msg 'in-house?) (eq? mode 'in-house))
|
|
((eq? msg 'consume-reverse!) consume-reverse!)
|
|
((eq? msg 'update-house-timer!) update-house-timer!)
|
|
((eq? msg 'movement-timer) movement-timer)
|
|
((eq? msg 'advance-movement-timer!) advance-movement-timer!)
|
|
((eq? msg 'reset-movement-timer!) reset-movement-timer!)
|
|
(else (error "Ghost ADT -- Unknown message:" msg))))
|
|
|
|
dispatch-ghost))))
|