Fix(Key) + UI: Fix key sprite position and arcade-style visual overhaul

Key bug fix:
- Key sprite position was never set on startup, appearing at (0,0)
- Added init-key-position! called in start-drawing! to place key sprite
  at its grid position on load

Arcade UI polish:
- Header bar with "PAC-MAN" title in yellow
- "SCORE" label above score value in header
- Sidebar separator uses wall color instead of white block
- "TIME" label with large countdown in sidebar area
- Coins now rendered as small centered dots (coin-size constant)
- Arcade color palette: #2121DE walls, #FFB851 coins, #FFB8FF doors
- "GAME OVER" overlay in red when time expires
- "PAUSED" overlay covers maze area only (not header)
- Window title set to "PAC-MAN"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-23 11:27:32 +01:00
parent 3f12a740da
commit b18aa49e8b

View File

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