(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-tile-blocked? level-same-cell? )) (define-record-type (%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) (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-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 (level-width (level-parse-file "assets/levels/level-1.map")) 40) (test-equal (level-height (level-parse-file "assets/levels/level-1.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")