198 lines
5.2 KiB
Scheme
198 lines
5.2 KiB
Scheme
(define-module (game model level)
|
|
#:use-module (game util assets)
|
|
#:use-module (ice-9 match)
|
|
#:use-module (ice-9 pretty-print)
|
|
#:use-module (ice-9 textual-ports)
|
|
#:use-module (chickadee math vector)
|
|
#:use-module (srfi srfi-1)
|
|
#:use-module (srfi srfi-9)
|
|
#:use-module (srfi srfi-64)
|
|
#:export (level-width
|
|
level-height
|
|
level-tiles
|
|
level-entities
|
|
level-hero
|
|
level-goal
|
|
level-parse-file
|
|
level-parse
|
|
level-tile-at
|
|
level-cell-size
|
|
level-find-hero
|
|
level-find-goal
|
|
level-find-others
|
|
level-find-keys
|
|
level-find-good-foods
|
|
level-find-bad-foods
|
|
level-tile-blocked?
|
|
level-same-cell?
|
|
coord->cell
|
|
))
|
|
|
|
(define-record-type <level>
|
|
(%make-level width height tiles)
|
|
level?
|
|
(width level-width)
|
|
(height level-height)
|
|
(tiles level-tiles)
|
|
(goal level-goal)
|
|
(hero level-hero)
|
|
(entities level-entities))
|
|
|
|
(define (parse-tile c)
|
|
(case c
|
|
((#\W) 'wall)
|
|
((#\B) 'brick)
|
|
((#\H) 'ladder)
|
|
((#\P) 'hero)
|
|
((#\O) 'other)
|
|
((#\G) 'goal)
|
|
((#\K) 'key)
|
|
((#\F) 'bad-food)
|
|
((#\f) 'good-food)
|
|
(else 'empty)))
|
|
|
|
(define (content->lines content)
|
|
(filter (lambda (line) (not (string-null? line)))
|
|
(map
|
|
(lambda (line)
|
|
(string-trim-right
|
|
(car (string-split line #\-))
|
|
#\space))
|
|
(string-split content #\newline))))
|
|
|
|
|
|
(define (parse-lines lines)
|
|
(fold append
|
|
'()
|
|
(map
|
|
(lambda (line)
|
|
(map parse-tile (string->list line)))
|
|
lines)))
|
|
|
|
(define (level-parse content)
|
|
(let* ((lines (content->lines content))
|
|
(width (string-length (car lines)))
|
|
(height (length lines))
|
|
(tiles (list->vector
|
|
(parse-lines lines))))
|
|
(%make-level width height tiles)))
|
|
|
|
(define (level-parse-file filename)
|
|
(level-parse (call-with-input-file (assets-file-name filename) get-string-all)))
|
|
|
|
|
|
(define (level-tile-at-row-col level row col )
|
|
(let ((index (+ (* row (level-width level)) col)))
|
|
(vector-ref (level-tiles level) index)))
|
|
|
|
(define level-cell-size 16)
|
|
|
|
(define (coord->cell coord)
|
|
(inexact->exact (floor (/ coord level-cell-size))))
|
|
|
|
(define (level-tile-at level position )
|
|
(let ((x (vec2-x position))
|
|
(y (vec2-y position)))
|
|
(level-tile-at-row-col level (coord->cell y) (coord->cell x))))
|
|
|
|
(define (level-tile-blocked? level position)
|
|
(memq (level-tile-at level position) '(wall brick)))
|
|
|
|
(define (gen-coords a b)
|
|
(append-map
|
|
(lambda (r)
|
|
(map
|
|
(lambda (c)
|
|
(list r c))
|
|
(iota b)))
|
|
(iota a))
|
|
)
|
|
|
|
(define (level-find-tile level tile)
|
|
(map
|
|
(lambda (rc) (vec2 (* level-cell-size (cadr rc)) (* level-cell-size (car rc))))
|
|
(filter
|
|
(lambda (rc)
|
|
(equal?
|
|
(level-tile-at-row-col level (car rc) (cadr rc))
|
|
tile))
|
|
(gen-coords (level-height level) (level-width level)))))
|
|
|
|
(define (level-find-hero level)
|
|
(car (level-find-tile level 'hero)))
|
|
|
|
(define (level-find-goal level)
|
|
(car (level-find-tile level 'goal)))
|
|
|
|
(define (level-find-others level)
|
|
(level-find-tile level 'other))
|
|
|
|
(define (level-find-keys level)
|
|
(level-find-tile level 'key))
|
|
|
|
(define (level-find-good-foods level)
|
|
(level-find-tile level 'good-food))
|
|
|
|
(define (level-find-bad-foods level)
|
|
(level-find-tile level 'bad-food))
|
|
|
|
(define (level-same-cell? a b)
|
|
(and (= (coord->cell (vec2-x a)) (coord->cell (vec2-x b)))
|
|
(= (coord->cell (vec2-y a)) (coord->cell (vec2-y b)))))
|
|
|
|
;; Tests
|
|
(test-begin "level")
|
|
|
|
(test-equal (content->lines "WWWWW\nWP GW\nWWWWW\n") '("WWWWW" "WP GW" "WWWWW"))
|
|
|
|
(let ((level (level-parse "WWWWW\nWP GW\nWWWWW\n")))
|
|
(test-assert (level? level))
|
|
(test-equal (level-width level) 5)
|
|
(test-equal (level-height level) 3)
|
|
(test-equal
|
|
(level-tiles level)
|
|
#(wall wall wall wall wall wall hero empty goal wall wall wall wall wall wall)))
|
|
|
|
(test-equal "check if comments are ignored"
|
|
(level-tiles (level-parse "--- ignore this line\nWWWWW\nWP GW - must be ignored\nWWWWW\n"))
|
|
#(wall wall wall wall wall wall hero empty goal wall wall wall wall wall wall))
|
|
|
|
;; regression test for character parsing
|
|
(test-equal (parse-tile #\W) 'wall)
|
|
(test-equal (parse-tile #\B) 'brick)
|
|
(test-equal (parse-tile #\H) 'ladder)
|
|
(test-equal (parse-tile #\P) 'hero)
|
|
(test-equal (parse-tile #\O) 'other)
|
|
(test-equal (parse-tile #\G) 'goal)
|
|
(test-equal (parse-tile #\space) 'empty)
|
|
(test-equal (parse-tile #\F) 'bad-food)
|
|
(test-equal (parse-tile #\f) 'good-food)
|
|
|
|
(test-equal (level-width (level-parse-file "assets/levels/level-001.map")) 40)
|
|
(test-equal (level-height (level-parse-file "assets/levels/level-001.map")) 30)
|
|
|
|
(test-equal
|
|
(level-tile-at-row-col (level-parse "WWWWW\nWP GW\nWWWWW\n") 1 1)
|
|
'hero)
|
|
|
|
(test-equal
|
|
(level-tile-at-row-col (level-parse "WWWWW\nWP GW\nWWWWW\n") 1 3)
|
|
'goal)
|
|
|
|
(test-equal (gen-coords 2 3) '((0 0) (0 1) (0 2) (1 0) (1 1) (1 2)))
|
|
|
|
(let ((level (level-parse "WWWWW\nWO OW\nWP GW\nWWWWW\n")))
|
|
(test-equal
|
|
(vec2 16 16)
|
|
(level-find-hero level))
|
|
|
|
(test-equal
|
|
(vec2 48 16)
|
|
(level-find-goal level))
|
|
|
|
(test-equal
|
|
(list (vec2 16 32) (vec2 48 32))
|
|
(level-find-others level))
|
|
)
|
|
|
|
(test-end "level")
|