123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477 |
- (define MAX-MENU-HEIGHT 4)
- (define MAX-MENU-WIDTH 36)
- (define (new-menu callback undo-callback item-list)
- (make-menu
- (list->vector item-list)
- 0
- 0
- callback
- undo-callback))
- (define (menu-select! menu)
- (define menu-item (vector-ref (menu-items menu) (menu-cursor menu)))
- (when (menu-callback menu)
- ((menu-callback menu) (cdr menu-item))))
- (define (menu-close! menu)
- (when (menu-undo-callback menu)
- ((menu-undo-callback menu))))
- (define (menu-length menu)
- (vector-length (menu-items menu)))
- (define (menu-bottom-item menu)
- (+ MAX-MENU-HEIGHT (menu-top-item menu) -1))
- (define (menu-bottom-item-set! menu new-val)
- (menu-top-item-set! menu (- new-val MAX-MENU-HEIGHT -1)))
- (define (menu-scroll! menu direction)
- (unless (memq direction '(up down))
- (error "menu-scroll!" "Direction not recognized" direction))
- (let ((new-cursor-val (+ (if (eq? direction 'up) -1 1)
- (menu-cursor menu))))
- (unless (or (< new-cursor-val 0)
- (>= new-cursor-val (menu-length menu)))
- (menu-cursor-set! menu new-cursor-val)
- (cond
- ((< new-cursor-val (menu-top-item menu))
- (menu-top-item-set! menu new-cursor-val))
- ((> new-cursor-val (menu-bottom-item menu))
- (menu-bottom-item-set! menu new-cursor-val))))))
- (define (render-menu menu)
- (let loop ((result '())
- (i (menu-top-item menu)))
- (if (and (< i (menu-length menu))
- (<= i (menu-bottom-item menu)))
- (loop (cons (string-append (if (= i (menu-cursor menu))
- "> "
- " ")
- (string-take
- (string-pad-right (car (vector-ref (menu-items menu) i))
- MAX-MENU-WIDTH)
- MAX-MENU-WIDTH))
- result)
- (+ i 1))
- (string-join (reverse result) "\n"))))
- (define (kill-game! global-state)
- ((global-event-handler global-state) 'kill))
- (define (play-victory-sound! global-state)
- ((global-event-handler global-state) 'victory-sound))
- (define (play-level-music! global-state)
- ((global-event-handler global-state) 'level-music))
- (define (open-level! global-state game-selected level-number)
- (play-level-music! global-state)
- (global-level-set! global-state (game-file-ref game-selected level-number))
- (global-menu-set! global-state #f))
- (define (restart-level! global-state)
- (define g (level-game-file (global-level global-state)))
- (define n (level-number (global-level global-state)))
- (open-level! global-state g n))
- (define (level-up! global-state)
- (define g (level-game-file (global-level global-state)))
- (define n (level-number (global-level global-state)))
- (open-level! global-state g (+ n 1)))
- (define (open-pause-menu! global-state)
- (define parent-menu (global-menu global-state))
- (define (on-enter command)
- (case command
- ((restart)
- (restart-level! global-state))
- (else
- (open-main-menu! global-state))))
- (define (on-exit)
- (global-menu-set! global-state parent-menu))
- (global-menu-set! global-state
- (new-menu on-enter on-exit '(("Restart level" . restart)
- ("Exit to main menu" . main)))))
- (define (open-victory-menu! global-state)
- (define (on-enter command)
- (level-up! global-state))
- (global-menu-set! global-state
- (new-menu on-enter #f '(("Next level" . next)))))
- (define (open-main-menu! global-state)
- (define (on-enter command)
- (case command
- ((new)
- (open-pick-game-menu! global-state))
- (else
- (kill-game! global-state))))
- (global-level-set! global-state #f)
- (global-menu-set! global-state
- (new-menu on-enter #f '(("Pick game" . new)
- ("Exit program" . exit)))))
- (define (open-pick-game-menu! global-state)
- (define parent-menu (global-menu global-state))
- (define (on-enter command)
- (open-pick-level-menu! global-state command this-menu))
- (define (on-exit)
- (global-menu-set! global-state parent-menu))
- (define this-menu (new-menu on-enter on-exit xsb-level-menu-list))
- (global-menu-set! global-state this-menu))
- (define (open-pick-level-menu! global-state game-selection parent-menu)
- (define (make-menu-list-from-progress p)
- (define a (assoc game-selection p))
- (if a
- (let loop ((i 1)
- (out '()))
- (if (<= i (+ 1 (cdr a)))
- (loop (+ i 1)
- (cons (cons (string-append "Level " (number->string i))
- i)
- out))
- (reverse out)))
- '(("Level 1" . 1))))
- (define (on-enter command)
- (let ((levels (file-id->game-file game-selection)))
- (open-level! global-state levels (- command 1))))
- (define (on-exit)
- (global-menu-set! global-state parent-menu))
- (define menu-list (make-menu-list-from-progress (global-progress global-state)))
- (global-menu-set! global-state (new-menu on-enter on-exit menu-list)))
- (define (increment-progress! global-state)
- (define file-id (level-game-file (global-level global-state)))
- (define level (level-number (global-level global-state)))
- (define (assoc-set l k v)
- (let loop ((in l)
- (out '()))
- (if (null? in)
- (cons (cons k v) l)
- (if (equal? (car (car in)) k)
- (append (reverse out)
- (list (cons k v))
- (cdr in))
- (loop (cdr in)
- (cons (car in) out))))))
- (define p (assoc file-id (global-progress global-state)))
- (when (or (not p)
- (> level (cdr p)))
- (let ((new-progress (assoc-set (global-progress global-state)
- file-id
- level)))
- (global-progress-set! global-state new-progress))))
- (define (move-player-and-check-complete! global-state direction)
- (define current-level-state (global-level global-state))
- (move-player! current-level-state direction)
- (when (level-complete? current-level-state)
- (play-victory-sound! global-state)
- (increment-progress! global-state)
- (open-victory-menu! global-state)))
- (define (try-undo-last-move! global-state)
- (define current-level-state (global-level global-state))
- (when (level-last-move current-level-state)
- (undo-move-player! current-level-state)))
- (define (string->level-data s)
- (vector-map string->vector (list->vector (string-split s "\n"))))
- (define (level->string l)
- (string-join (vector->list (vector-map vector->string (level-data l))) "\n"))
- (define (char->tile-type c)
- (cond
- ((char=? c #\space) 'floor)
- ((char=? c #\.) 'goal-square)
- ((char=? c #\*) 'box-on-goal-square)
- ((char=? c #\$) 'box)
- ((char=? c #\+) 'player-on-goal-square)
- ((char=? c #\@) 'player)
- ((char=? c #\#) 'wall)
- (else (error "tile char not recognized" c))))
- (define (level-last-move level)
- (define result (level-move-history level))
- (if (null? result) #f (car result)))
- (define (render-level-status level)
- (define game-name (game-name-lookup (game-file-id (level-game-file level))))
- (define level-no (number->string (level-number level)))
- (define move-ct (number->string (length (level-move-history level))))
- (string-append
- (string-pad-left game-name 16)
- "-"
- (string-pad-left level-no 3 #\0)
- "\n"
- (string-pad-left move-ct 4 #\0)))
- (define (level-ref l i j)
- (define data (level-data l))
- (make-tile
- i
- j
- (char->tile-type
- (if (and (>= i 0)
- (< i (vector-length data)))
- (let ((v (vector-ref data i)))
- (if (and (>= j 0)
- (< j (vector-length v)))
- (vector-ref v j)
- #\space))
- #\space))))
- (define (level-set! l i j tile-type)
- (define v (cdr (assq tile-type '((floor . #\space)
- (goal-square . #\.)
- (box-on-goal-square . #\*)
- (box . #\$)
- (player-on-goal-square . #\+)
- (player . #\@)
- (wall . #\#)))))
- (vector-set! (vector-ref (level-data l) i) j v))
- (define (level-add-history! l history-item)
- (level-move-history-set! l (cons history-item (level-move-history l))))
- (define (level-remove-history! l)
- (level-move-history-set! l (cdr (level-move-history l))))
- (define (tile-player? t)
- (let ((tt (tile-type t)))
- (or (eq? tt 'player)
- (eq? tt 'player-on-goal-square))))
- (define (find-next-tile level tile direction)
- (define i (tile-row tile))
- (define j (tile-col tile))
- (cond
- ((eq? direction 'left)
- (level-ref level i (- j 1)))
- ((eq? direction 'right)
- (level-ref level i (+ j 1)))
- ((eq? direction 'up)
- (level-ref level (- i 1) j))
- ((eq? direction 'down)
- (level-ref level (+ i 1) j))
- (else
- (error "find-next-tile" "Direction not recognized" direction))))
- (define (player-position l)
- (define rows (level-rows l))
- (define cols (level-cols l))
- (let loop ((i 0)
- (j 0))
- (when (>= i rows)
- (error "player-position" "Player position not found"))
- (if (< j cols)
- (if (tile-player? (level-ref l i j))
- (list i j)
- (loop i (+ j 1)))
- (loop (+ i 1) 0))))
- (define (level-complete? l)
- (define rows (level-rows l))
- (define cols (level-cols l))
- (let loop ((i 0)
- (j 0))
- (or (>= i rows)
- (if (< j cols)
- (and (not (memq (tile-type (level-ref l i j))
- '(goal-square player-on-goal-square)))
- (loop i (+ j 1)))
- (loop (+ i 1) 0)))))
- (define (find-player l)
- (define results (player-position l))
- (define i (list-ref results 0))
- (define j (list-ref results 1))
- (level-ref l i j))
- (define (tile->list-repr t)
- (list (tile-row t) (tile-col t) (tile-type t)))
- (define (opposite-direction sym)
- (cond
- ((eq? sym 'up) 'down)
- ((eq? sym 'down) 'up)
- ((eq? sym 'left) 'right)
- (else 'left)))
- (define (undo-move-player! level)
- (define player (find-player level))
- (define last-move (level-last-move level))
- (define previous-direction
- (cond
- ((memv last-move '(#\u #\U)) 'up)
- ((memv last-move '(#\d #\D)) 'down)
- ((memv last-move '(#\l #\L)) 'left)
- ((memv last-move '(#\r #\R)) 'right)))
- (define direction (opposite-direction previous-direction))
- (define did-push? (memv last-move '(#\U #\D #\L #\R)))
- (define next (find-next-tile level player direction))
- (define previous (find-next-tile level player previous-direction))
- (define player-type (tile-type player))
- (define next-type (tile-type next))
- (define previous-type (tile-type previous))
- (level-remove-history! level)
- (cond
- (did-push?
- (level-set! level (tile-row previous)
- (tile-col previous)
- (if (eq? previous-type 'box)
- 'floor
- 'goal-square))
- (level-set! level (tile-row player)
- (tile-col player)
- (if (eq? player-type 'player)
- 'box
- 'box-on-goal-square))
- (level-set! level (tile-row next)
- (tile-col next)
- (if (eq? next-type 'floor)
- 'player
- 'player-on-goal-square)))
- (else
- (level-set! level (tile-row next)
- (tile-col next)
- (if (eq? next-type 'floor)
- 'player
- 'player-on-goal-square))
- (level-set! level (tile-row player)
- (tile-col player)
- (if (eq? player-type 'player)
- 'floor
- 'goal-square)))))
- (define (move-player! level direction)
- (define player (find-player level))
- (define next (find-next-tile level player direction))
- (define next-next (find-next-tile level next direction))
- (let ((player-type (tile-type player))
- (next-type (tile-type next))
- (next-next-type (tile-type next-next)))
- (cond
- ((memq next-type '(floor goal-square))
- (level-add-history! level
- (cond
- ((eq? direction 'up) #\u)
- ((eq? direction 'down) #\d)
- ((eq? direction 'left) #\l)
- ((eq? direction 'right) #\r)))
- (level-set! level (tile-row next)
- (tile-col next)
- (if (eq? next-type 'floor)
- 'player
- 'player-on-goal-square))
- (level-set! level (tile-row player)
- (tile-col player)
- (if (eq? player-type 'player)
- 'floor
- 'goal-square)))
- ((and (memq next-type '(box box-on-goal-square))
- (memq next-next-type '(floor goal-square)))
- (level-add-history! level
- (cond
- ((eq? direction 'up) #\U)
- ((eq? direction 'down) #\D)
- ((eq? direction 'left) #\L)
- ((eq? direction 'right) #\R)))
- (level-set! level (tile-row next-next)
- (tile-col next-next)
- (if (eq? next-next-type 'floor)
- 'box
- 'box-on-goal-square))
- (level-set! level (tile-row next)
- (tile-col next)
- (if (eq? next-type 'box)
- 'player
- 'player-on-goal-square))
- (level-set! level (tile-row player)
- (tile-col player)
- (if (eq? player-type 'player)
- 'floor
- 'goal-square))))))
- (define (read-level-list-data port)
- (let loop ((in-tiles? #f)
- (in-comment? #f)
- (current-level '())
- (level-list '()))
- (define c (read-char port))
- (cond
- ((eof-object? c)
- ;; eof
- (reverse (map (lambda (l)
- (string->level-data
- (string-join
- (reverse
- (map list->string (map reverse l)))
- "\n")))
- level-list)))
- ((char=? c #\newline)
- ;; new line
- (if in-tiles?
- (loop #f
- #f
- (cons '() current-level)
- level-list)
- (loop #f
- #f
- '()
- (if (null? current-level)
- level-list
- (cons current-level level-list)))))
- ((memv c '(#\space #\. #\* #\$ #\+ #\@ #\#))
- ;; tile
- (if in-comment?
- (loop in-tiles?
- #t
- current-level
- level-list)
- (loop #t
- #f
- (if (null? current-level)
- (list (list c))
- (cons (cons c (car current-level))
- (cdr current-level)))
- level-list)))
- ((char=? c #\return)
- ;; ignore returns
- (loop in-tiles?
- in-comment?
- current-level
- level-list))
- (else
- ;; comment
- (loop in-tiles?
- #t
- current-level
- level-list)))))
- (define (game-file-ref game-selected level-number)
- (define data (list-ref (game-file-level-list game-selected) level-number))
- (define rows (vector-length data))
- (define cols (apply max (map vector-length (vector->list data))))
- (make-level
- rows
- cols
- (vector-map vector-copy data)
- '()
- game-selected
- level-number))
- (define (game-file-length game-selected)
- (length (game-file-level-list game-selected)))
- (define (file-id->game-file file-id)
- (define f (fake-open-input-file file-id))
- (let ((result (read-level-list-data f)))
- (close-input-port f)
- (make-game-file result file-id)))
|