bloatrunner/game/model/hero.scm

166 lines
5.7 KiB
Scheme

;; Game state and logic for the hero entity
(define-module (game model hero)
#:use-module (ice-9 pretty-print)
#:use-module (srfi srfi-1)
#:use-module (srfi srfi-9)
#:use-module (srfi srfi-9 gnu)
#:use-module (srfi srfi-64)
#:use-module (chickadee math vector)
#:use-module (game util pipe)
#:use-module (game model level)
#:export (hero-load
hero-state
hero-bloat
hero-position hero-with-position
hero-x hero-with-x
hero-y hero-with-y
hero-update
))
(define-immutable-record-type <hero>
(%make-hero position state bloat)
hero?
(position hero-position hero-with-position)
(state hero-state hero-with-state) ;; 'go-left 'go-right 'climb 'fall 'stationary
(bloat hero-bloat hero-with-bloat) ;; 0.0 to 2.0, how much the hero is inflated
)
(define default-state 'falling)
(define default-bloat 1.0)
(define (hero-load level)
(let ((initial-position (level-find-hero level)))
(%make-hero initial-position default-state default-bloat)))
(define (hero-x hero)
"return the x coordinate as an integer"
(inexact->exact (floor (vec2-x (hero-position hero)))))
(define (hero-y hero)
"return the y coordinate as an integer"
(inexact->exact (floor (vec2-y (hero-position hero)))))
(define (hero-with-x hero x)
(hero-with-position hero (vec2 x (hero-y hero))))
(define (hero-with-y hero y)
(hero-with-position hero (vec2 (hero-x hero) y)))
;; return next state of hero
;; @returns 'go-left 'go-right 'climb 'fall 'stationary
(define (next-state hero level inputs)
(let* ((position (hero-position hero))
(state (hero-state hero))
(tile-below (level-tile-at level (vec2 (hero-x hero) (1- (hero-y hero))))))
(cond
((equal? (level-tile-at level position) 'ladder) 'climb)
((> (floor-remainder (hero-y hero) level-cell-size) 0) 'falling)
((equal? tile-below 'empty) 'falling)
(else (cond
((member 'left inputs) 'go-left)
((member 'right inputs) 'go-right)
(else 'stationary))))))
(define (input->movement input distance)
(case input
((left) (vec2 (- distance) 0))
((right) (vec2 distance 0))
((up) (vec2 0 distance))
((down) (vec2 0 (- distance)))
(else (vec2 0 0))))
(define (next-position position state inputs distance)
(case state
((falling) (vec2- position (vec2 0 distance)))
((climb) (vec2+ position (input->movement (car inputs) distance)))
((go-left) (vec2- position (vec2 distance 0)))
((go-right) (vec2+ position (vec2 distance 0)))
((stationary) position)
((ladder) (fold (lambda (input movement)
(vec2+ movement (input->movement input distance)))
position
inputs))
(else position)
))
(define speed 50.0)
(define (hero-update hero level inputs dt)
(let* ((state (next-state hero level inputs))
(distance (* dt speed))
(position (next-position (hero-position hero) state inputs distance))
(tile (level-tile-at level position))
)
(hero-with-state
(if (member tile '(empty ladder other))
(hero-with-position hero position)
(hero-with-position hero (vec2 (hero-x hero) (hero-y hero))) ;; round position
)
state)))
;; Tests
[test-begin "hero-model"]
(let* ((level (level-parse "WWWWW\nW...W\nW.P.W\nW..HW\nWWWWW\n"))
(hero (hero-load level))
(default-position (level-find-hero level)))
(test-assert (hero? hero))
(test-equal default-position (hero-position hero))
(test-equal default-state (hero-state hero))
(test-equal default-bloat (hero-bloat hero))
(test-equal (vec2 10 20) (hero-position (hero-with-position hero (vec2 10 20))))
(test-equal default-position (hero-position hero))
(test-equal 'go-left (hero-state (hero-with-state hero 'go-left)))
(test-equal default-state (hero-state hero))
(test-equal 1.5 (hero-bloat (hero-with-bloat hero 1.5)))
(test-equal default-bloat (hero-bloat hero))
(test-equal 16 (hero-x (hero-with-position hero (vec2 16.34 20.78))))
(test-equal 20 (hero-y (hero-with-position hero (vec2 16.34 20.78))))
(test-equal 'falling (next-state hero level '()))
(let ((hero (hero-with-position hero (vec2 16 16))))
(test-equal 'stationary (next-state hero level '()))
(test-equal 'go-left (next-state hero level '(left)))
(test-equal 'go-right (next-state hero level '(right))))
(let ((hero (hero-with-position hero (vec2 48 32))))
(test-equal 'stationary (next-state hero level '())))
(let ((hero (hero-with-position hero (vec2 48 31))))
(test-equal 'climb (next-state hero level '())))
(let ((hero (hero-with-position hero (vec2 32 31))))
(test-equal 'falling (next-state hero level '())))
(test-equal (vec2 32.0 31.0) (next-position (vec2 32.0 32.0) 'falling '() 1.0))
(test-equal (vec2 31.0 32.0) (next-position (vec2 32.0 32.0) 'go-left '() 1.0))
(test-equal (vec2 33.0 32.0) (next-position (vec2 32.0 32.0) 'go-right '() 1.0))
(test-equal (vec2 32.0 32.0) (next-position (vec2 32.0 32.0) 'stationary '() 1.0))
(test-equal (vec2 31.0 32.0) (next-position (vec2 32.0 32.0) 'climb '(left) 1.0))
(test-equal (vec2 33.0 32.0) (next-position (vec2 32.0 32.0) 'climb '(right) 1.0))
(test-equal (vec2 32.0 31.0) (next-position (vec2 32.0 32.0) 'climb '(down) 1.0))
(test-equal (vec2 32.0 33.0) (next-position (vec2 32.0 32.0) 'climb '(up) 1.0))
(test-equal
(vec2 32.0 16.0)
(hero-position
(hero-update
(hero-with-state
(hero-with-position hero (vec2 32 16.3))
'falling)
level
'()
0.017
)
))
)
(test-end "hero-model")