summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Thompson <dthompson2@worcester.edu>2023-10-25 08:20:48 -0400
committerDavid Thompson <dthompson2@worcester.edu>2023-10-25 08:38:11 -0400
commit3a1afc62724f1631f23b5aa044856f7d09db14eb (patch)
treecbede9ae3bc131b244b99d1005b85ec5cef32df0
parent2f1022f1d65b148524dc8f0c824aaca6e9392996 (diff)
Lots of changes!
* Update reflect library * Text alginment * Tiled map compilation!
-rw-r--r--.gitignore1
-rw-r--r--Makefile6
-rw-r--r--audio/player-shoot.wavbin35490 -> 7640 bytes
-rw-r--r--boot.js3
-rw-r--r--compile-map.scm565
-rw-r--r--game.scm2037
-rw-r--r--images/map.asebin552 -> 1090 bytes
-rw-r--r--images/map.pngbin237 -> 401 bytes
-rw-r--r--js-runtime/reflect.js1
-rw-r--r--js-runtime/reflect.wasmbin4260 -> 4260 bytes
-rw-r--r--level.tmx141
-rw-r--r--manifest.scm58
-rw-r--r--tiles.tsx4
13 files changed, 1783 insertions, 1033 deletions
diff --git a/.gitignore b/.gitignore
index eb8e01a..f0ba99c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/game.wasm
/algj2023.zip
+/level.scm
diff --git a/Makefile b/Makefile
index 7099d64..6a91bd5 100644
--- a/Makefile
+++ b/Makefile
@@ -1,2 +1,8 @@
+game.wasm: game.scm level.scm
+ /gnu/store/i7qi9wsyhxzv3w4y992qgx5j9g5dcza0-profile/bin/guile -L $(HOME)/Code/guile-hoot/module game.scm
+
+level.scm: level.tmx
+ guile compile-map.scm > level.scm
+
bundle:
rm algj2023.zip && zip algj2023.zip -r audio/ images/*.png js-runtime/ boot.js game.css game.wasm index.html
diff --git a/audio/player-shoot.wav b/audio/player-shoot.wav
index db38cce..4059eb7 100644
--- a/audio/player-shoot.wav
+++ b/audio/player-shoot.wav
Binary files differ
diff --git a/boot.js b/boot.js
index b30e95b..d62791e 100644
--- a/boot.js
+++ b/boot.js
@@ -95,6 +95,9 @@ async function load() {
setFont(context, font) {
context.font = font;
},
+ setTextAlign(context, align) {
+ context.textAlign = align;
+ },
clearRect(context, x, y, w, h) {
context.clearRect(x, y, w, h);
},
diff --git a/compile-map.scm b/compile-map.scm
new file mode 100644
index 0000000..11b8d9e
--- /dev/null
+++ b/compile-map.scm
@@ -0,0 +1,565 @@
+;;; Adapted from the Chickadee Game Toolkit
+;;; Copyright © 2018, 2020, 2021, 2023 David Thompson <dthompson2@worcester.edu>
+;;;
+;;; Licensed under the Apache License, Version 2.0 (the "License");
+;;; you may not use this file except in compliance with the License.
+;;; You may obtain a copy of the License at
+;;;
+;;; http://www.apache.org/licenses/LICENSE-2.0
+;;;
+;;; Unless required by applicable law or agreed to in writing, software
+;;; distributed under the License is distributed on an "AS IS" BASIS,
+;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+;;; See the License for the specific language governing permissions and
+;;; limitations under the License.
+
+(use-modules (ice-9 match)
+ (ice-9 pretty-print)
+ ((rnrs base) #:select (mod))
+ (rnrs bytevectors)
+ (srfi srfi-1)
+ (srfi srfi-11)
+ (srfi srfi-9)
+ (srfi srfi-43)
+ (sxml simple)
+ (sxml xpath))
+
+(define-record-type <vec2>
+ (make-vec2 x y)
+ vec2?
+ (x vec2-x)
+ (y vec2-y))
+
+(define-record-type <rect>
+ (make-rect x y width height)
+ rect?
+ (x rect-x)
+ (y rect-y)
+ (width rect-width)
+ (height rect-height))
+
+(define-record-type <color>
+ (make-color r g b a)
+ color?
+ (r color-r)
+ (g color-g)
+ (b color-b)
+ (a color-a))
+
+(define-record-type <image>
+ (make-image src width height trans)
+ image?
+ (src image-src)
+ (width image-width)
+ (height image-height)
+ (trans image-trans))
+
+
+;;;
+;;; Tileset
+;;;
+
+(define-record-type <animation-frame>
+ (make-animation-frame id duration)
+ animation-frame?
+ (id animation-frame-id)
+ (duration animation-frame-duration))
+
+(define-record-type <animation>
+ (%make-animation frames duration)
+ animation?
+ (frames animation-frames)
+ (duration animation-duration))
+
+(define (make-animation atlas first-gid frame-spec)
+ (let ((frames (map (match-lambda
+ ((id duration)
+ (make-animation-frame (- id first-gid) duration)))
+ frame-spec)))
+ (%make-animation (list->vector frames)
+ (fold (lambda (frame memo)
+ (+ (animation-frame-duration frame) memo))
+ 0 frames))))
+
+(define (animation-frame-for-time animation time)
+ (let* ((time (mod time (animation-duration animation)))
+ (frames (animation-frames animation)))
+ (let loop ((i 0)
+ (t 0))
+ (let* ((frame (vector-ref frames i))
+ (d (animation-frame-duration frame)))
+ (if (< time (+ t d))
+ frame
+ (loop (+ i 1) (+ t d)))))))
+
+(define-record-type <tile>
+ (make-tile id image animation properties)
+ tile?
+ (id tile-id)
+ (image tile-image)
+ (animation tile-animation)
+ (properties tile-properties))
+
+(define (animated-tile? tile)
+ (animation? (tile-animation tile)))
+
+(define (tile-frame-for-time tile time)
+ (let ((animation (tile-animation tile)))
+ (and animation (animation-frame-for-time animation time))))
+
+(define-record-type <tileset>
+ (%make-tileset name first-gid tile-width tile-height
+ margin spacing rows columns tiles properties)
+ tileset?
+ (name tileset-name)
+ (first-gid tileset-first-gid)
+ (tile-width tileset-tile-width)
+ (tile-height tileset-tile-height)
+ (margin tileset-margin)
+ (spacing tileset-spacing)
+ (rows tileset-rows)
+ (columns tileset-columns)
+ (tiles tileset-tiles)
+ (properties tileset-properties))
+
+(define (tileset-dimensions image tile-width tile-height margin spacing)
+ (values (inexact->exact
+ (ceiling (/ (- (image-width image) margin)
+ (+ tile-width spacing))))
+ (inexact->exact
+ (ceiling (/ (- (image-height image) margin)
+ (+ tile-height spacing))))))
+
+(define* (make-tileset image tile-width tile-height #:key
+ (first-gid 1) (margin 0) (spacing 0)
+ (name "anonymous") (properties '())
+ (custom-tiles '()))
+ (let-values (((columns rows)
+ (tileset-dimensions image tile-width tile-height margin spacing)))
+ (let* ((tiles (make-vector (* columns rows))))
+ (do ((i 0 (+ i 1)))
+ ((= i (vector-length tiles)))
+ (let* ((id (+ first-gid i))
+ (custom (or (assv-ref custom-tiles id) '()))
+ (animation (assq-ref custom 'animation))
+ (properties (assq-ref custom 'properties))
+ (tile (make-tile id i
+ (and animation
+ (make-animation image first-gid animation))
+ (or properties '()))))
+ (vector-set! tiles i tile)))
+ (%make-tileset name first-gid tile-width tile-height margin spacing
+ rows columns tiles properties))))
+
+(define (tileset-size tileset)
+ (vector-length (tileset-tiles tileset)))
+
+(define (tileset-ref tileset i)
+ (vector-ref (tileset-tiles tileset) (- i (tileset-first-gid tileset))))
+
+
+;;;
+;;; Object Layer
+;;;
+
+(define-record-type <object-layer>
+ (%make-object-layer name objects properties)
+ object-layer?
+ (name object-layer-name)
+ (objects object-layer-objects)
+ (properties object-layer-properties))
+
+(define-record-type <polygon>
+ (make-polygon points)
+ polygon?
+ (points polygon-points))
+
+(define-record-type <map-object>
+ (%make-map-object id name type shape properties)
+ map-object?
+ (id map-object-id)
+ (name map-object-name)
+ (type map-object-type)
+ (shape map-object-shape)
+ (properties map-object-properties))
+
+
+;;;
+;;; Tile Layer
+;;;
+
+(define-record-type <map-tile>
+ (%make-map-tile tile flipped-horizontally? flipped-vertically?
+ flipped-diagonally?)
+ map-tile?
+ (tile map-tile-ref)
+ (flipped-horizontally? map-tile-flipped-horizontally?)
+ (flipped-vertically? map-tile-flipped-vertically?)
+ (flipped-diagonally? map-tile-flipped-diagonally?))
+
+(define* (make-map-tile tile #:key flipped-horizontally?
+ flipped-vertically? flipped-diagonally?)
+ (%make-map-tile tile flipped-horizontally? flipped-vertically?
+ flipped-diagonally?))
+
+(define-record-type <tile-layer>
+ (%make-tile-layer name width height properties tiles)
+ tile-layer?
+ (name tile-layer-name)
+ (width tile-layer-width)
+ (height tile-layer-height)
+ (properties tile-layer-properties)
+ (tiles tile-layer-tiles))
+
+(define* (make-tile-layer width height tile-width tile-height #:key
+ (name "anonymous")
+ (properties '()))
+ (%make-tile-layer name width height properties (make-vector (* width height))))
+
+(define (tile-layer-bounds-check layer x y)
+ (unless (and (>= x 0) (>= y 0)
+ (< x (tile-layer-width layer))
+ (< y (tile-layer-height layer)))
+ (error "tile layer coordinates out of bounds" layer x y)))
+
+(define (tile-layer-ref layer x y)
+ (vector-ref (tile-layer-tiles layer) (+ (* y (tile-layer-width layer)) x)))
+
+(define (tile-layer-set! layer x y tile)
+ (vector-set! (tile-layer-tiles layer) (+ (* y (tile-layer-width layer)) x) tile))
+
+
+;;;
+;;; Tile Map
+;;;
+
+(define-record-type <tile-map>
+ (%make-tile-map orientation width height tile-width tile-height
+ tilesets layers properties)
+ tile-map?
+ (orientation tile-map-orientation)
+ (width tile-map-width)
+ (height tile-map-height)
+ (tile-width tile-map-tile-width)
+ (tile-height tile-map-tile-height)
+ (tilesets tile-map-tilesets)
+ (layers tile-map-layers)
+ (properties tile-map-properties))
+
+(define* (make-tile-map width height tile-width tile-height #:key
+ (orientation 'orthogonal) (tilesets '())
+ (layers '()) (properties '()))
+ "Make a tile map that is WIDTH x HEIGHT tiles in size and each tile
+is TILE-WIDTH x TILE-HEIGHT pixels in size. TILESETS is a list of
+tilesets to be associated with the map. LAYERS is a list of object
+and/or tile layers, sorted from bottom to top. PROPERTIES is an alist
+of arbitrary custom data to associate with the map. Currently, only
+the default ORIENTATION value of 'orthogonal' is supported."
+ (unless (eq? orientation 'orthogonal)
+ (error "unsupport tile map orientation" orientation))
+ (%make-tile-map orientation width height tile-width tile-height
+ tilesets (list->vector layers) properties))
+
+(define (tile-map-layer-ref tile-map name)
+ "Return the map layer named NAME."
+ (define (layer-name layer)
+ (if (tile-layer? layer)
+ (tile-layer-name layer)
+ (object-layer-name layer)))
+ (let ((layers (tile-map-layers tile-map)))
+ (let loop ((i 0))
+ (cond
+ ((= i (vector-length layers))
+ #f)
+ ((string=? name (layer-name (vector-ref layers i)))
+ (vector-ref layers i))
+ (else
+ (loop (+ i 1)))))))
+
+(define (point->tile tile-map x y)
+ "Translate the pixel coordinates (X, Y) into tile coordinates."
+ (values (inexact->exact (floor (/ x (tile-map-tile-width tile-map))))
+ (inexact->exact (floor (/ y (tile-map-tile-height tile-map))))))
+
+(define* (load-tile-map file-name)
+ "Load the Tiled TMX formatted map in FILE-NAME."
+ (define map-directory
+ (if (absolute-file-name? file-name)
+ (dirname file-name)
+ (string-append (getcwd) "/" (dirname file-name))))
+ (define (scope file-name)
+ (string-append map-directory "/" file-name))
+ (define* (attr node name #:optional (parse identity))
+ (let ((result ((sxpath `(@ ,name *text*)) node)))
+ (if (null? result)
+ #f
+ (parse (car result)))))
+ (define (parse-color-channel s start)
+ (/ (string->number (substring s start (+ start 2)) 16) 255.0))
+ (define (parse-property node)
+ (let ((name (attr node 'name string->symbol))
+ (type (or (attr node 'type string->symbol) 'string))
+ (value (attr node 'value)))
+ (cons name
+ (match type
+ ((or 'string 'file) value)
+ ('bool (not (string=? value "false")))
+ ((or 'int 'float) (string->number value))
+ ('color
+ (make-color (parse-color-channel value 3)
+ (parse-color-channel value 5)
+ (parse-color-channel value 7)
+ (parse-color-channel value 1)))
+ (_ (error "unsupported property type" type))))))
+ (define (parse-image node)
+ (let ((source (attr node 'source))
+ (width (string->number (attr node 'width)))
+ (height (string->number (attr node 'height)))
+ (trans (attr node 'trans)))
+ (make-image (scope source) width height trans)))
+ (define (invert-tile-id id first-gid rows columns)
+ (let ((id* (- id first-gid)))
+ (+ (* (- rows (floor (/ id* columns)) 1)
+ columns)
+ (modulo id* columns)
+ first-gid)))
+ (define (parse-frame node first-gid rows columns)
+ (let ((tile-id (attr node 'tileid string->number))
+ (duration (attr node 'duration string->number)))
+ (list (+ first-gid (invert-tile-id tile-id 0 rows columns))
+ (/ duration 1000.0))))
+ (define (parse-tiles nodes first-gid rows columns)
+ (let ((frames (sxpath '(animation frame)))
+ (properties (sxpath '(properties property))))
+ (fold (lambda (node memo)
+ (let ((id (+ first-gid
+ (invert-tile-id (attr node 'id string->number)
+ 0 rows columns))))
+ (cons `(,id . ((animation . ,(map (lambda (f)
+ (parse-frame f first-gid
+ rows columns))
+ (frames node)))
+ (properties . ,(map parse-property
+ (properties node)))))
+ memo)))
+ '()
+ nodes)))
+ (define (first-gid node)
+ (attr node 'firstgid string->number))
+ (define (parse-tileset node first-gid)
+ (let* ((name (attr node 'name))
+ (tile-width (attr node 'tilewidth string->number))
+ (tile-height (attr node 'tileheight string->number))
+ (margin (or (attr node 'margin string->number) 0))
+ (spacing (or (attr node 'spacing string->number) 0))
+ (image (parse-image ((sxpath '(image)) node)))
+ ;; (tiles (call-with-values
+ ;; (lambda ()
+ ;; (texture-tileset-dimensions texture
+ ;; tile-width
+ ;; tile-height
+ ;; #:margin margin
+ ;; #:spacing spacing))
+ ;; (lambda (columns rows)
+ ;; (parse-tiles ((sxpath '(tile)) node) first-gid rows columns))))
+ (properties (map parse-property
+ ((sxpath '(properties property)) node))))
+ (make-tileset image tile-width tile-height
+ #:margin margin
+ #:spacing spacing
+ #:name name
+ #:first-gid first-gid
+ #:properties properties
+ #:custom-tiles '())))
+ (define (parse-external-tileset node)
+ (let* ((first-gid (attr node 'firstgid string->number))
+ (source (scope (attr node 'source)))
+ (tree (call-with-input-file source xml->sxml)))
+ (parse-tileset (car ((sxpath '(tileset)) tree)) first-gid)))
+ (define (parse-tileset* node)
+ (if (attr node 'source)
+ (parse-external-tileset node)
+ (parse-tileset node (first-gid node))))
+ (define (tile-gid->map-tile raw-gid tilesets x y tile-width tile-height)
+ ;; The top 3 bits of the tile gid are flags for various types of
+ ;; flipping.
+ (let* ((flipped-horizontally? (> (logand raw-gid #x80000000) 0))
+ (flipped-vertically? (> (logand raw-gid #x40000000) 0))
+ (flipped-diagonally? (> (logand raw-gid #x20000000) 0))
+ ;; Remove the upper 3 bits to get the true tile id.
+ (gid (logand raw-gid #x1FFFFFFF))
+ (tileset (find (lambda (t)
+ (and (>= gid (tileset-first-gid t))
+ (< gid (+ (tileset-first-gid t)
+ (tileset-size t)))))
+ tilesets))
+ (tw (tileset-tile-width tileset))
+ (th (tileset-tile-height tileset))
+ (first-gid (tileset-first-gid tileset))
+ (rows (tileset-rows tileset))
+ (columns (tileset-columns tileset))
+ (id (invert-tile-id gid first-gid rows columns)))
+ (make-map-tile (tileset-ref tileset id)
+ #:flipped-horizontally? flipped-horizontally?
+ #:flipped-vertically? flipped-vertically?
+ #:flipped-diagonally? flipped-diagonally?)))
+ (define (tile-gids->map-tiles gids width height tilesets)
+ (let ((tiles (make-vector (* width height))))
+ (let y-loop ((y 0)
+ (rows gids))
+ (when (< y height)
+ (match rows
+ ((row . rest)
+ (let x-loop ((x 0)
+ (columns row))
+ (when (< x width)
+ (match columns
+ ((gid . rest)
+ (vector-set! tiles
+ (+ (* width y) x)
+ (if (zero? gid)
+ #f
+ (tile-gid->map-tile gid tilesets
+ x y width height)))
+ (x-loop (+ x 1) rest)))))
+ (y-loop (+ y 1) rest)))))
+ tiles))
+ (define (parse-csv lines width height tilesets)
+ (let ((gids (map (lambda (line)
+ (filter-map (lambda (s)
+ (and (not (string-null? s))
+ (string->number s)))
+ (string-split line #\,)))
+ (take (drop (string-split lines #\newline) 1) height))))
+ (tile-gids->map-tiles gids width height tilesets)))
+ (define (parse-layer-data node width height tilesets)
+ (let ((encoding (attr node 'encoding string->symbol))
+ (data (car ((sxpath '(*text*)) node))))
+ (match encoding
+ ('csv (parse-csv data width height tilesets))
+ (_ (error "unsupported tile layer encoding" encoding)))))
+ (define (parse-tile-layer node tile-width tile-height tilesets)
+ (let* ((name (attr node 'name))
+ (width (attr node 'width string->number))
+ (height (attr node 'height string->number))
+ (tiles (parse-layer-data ((sxpath '(data)) node)
+ width height tilesets))
+ (properties (map parse-property
+ ((sxpath '(properties property)) node)))
+ (layer (make-tile-layer width height tile-width tile-height
+ #:name name
+ #:properties properties)))
+ (do ((y 0 (+ y 1)))
+ ((= y height))
+ (do ((x 0 (+ x 1)))
+ ((= x width))
+ (tile-layer-set! layer x y (vector-ref tiles (+ (* y width) x)))))
+ layer))
+ (define (parse-polygon node pixel-height)
+ (make-polygon
+ (list->vector
+ (map (lambda (s)
+ (match (string-split s #\,)
+ ((x y)
+ (make-vec2 (string->number x) (string->number y)))))
+ (string-split (attr node 'points) #\space)))))
+ (define (parse-object node pixel-height)
+ (let* ((id (attr node 'id string->number))
+ (name (attr node 'name))
+ (type (attr node 'type string->symbol))
+ (x (attr node 'x string->number))
+ (y (attr node 'y string->number))
+ (width (attr node 'width string->number))
+ (height (attr node 'height string->number))
+ (shape (if (and width height)
+ (make-rect x y width height)
+ (parse-polygon (car ((sxpath '(polygon)) node))
+ pixel-height)))
+ (properties (map parse-property
+ ((sxpath '(properties property)) node))))
+ (%make-map-object id name type shape properties)))
+ (define (parse-object-layer node pixel-height)
+ (let ((name (attr node 'name))
+ (objects (map (lambda (node)
+ (parse-object node pixel-height))
+ ((sxpath '(object)) node)))
+ (properties (map parse-property
+ ((sxpath '(properties property)) node))))
+ (%make-object-layer name objects properties)))
+ (let* ((tree (call-with-input-file file-name xml->sxml))
+ (m ((sxpath '(map)) tree))
+ (version (attr m 'version))
+ (orientation (attr m 'orientation string->symbol))
+ (width (attr m 'width string->number))
+ (height (attr m 'height string->number))
+ (tile-width (attr m 'tilewidth string->number))
+ (tile-height (attr m 'tileheight string->number))
+ (properties ((sxpath '(map properties property)) tree))
+ (tilesets (map parse-tileset* ((sxpath '(map tileset)) tree)))
+ (layers ((node-or (sxpath '(map layer))
+ (sxpath '(map objectgroup)))
+ tree)))
+ (make-tile-map width height tile-width tile-height
+ #:orientation orientation
+ #:tilesets tilesets
+ #:layers (map (lambda (node)
+ (match node
+ (('layer . _)
+ (parse-tile-layer node tile-width tile-height tilesets))
+ (('objectgroup . _)
+ (parse-object-layer node (* height tile-height)))))
+ layers)
+ #:properties (map parse-property properties))))
+
+(define tile-map (load-tile-map "level.tmx"))
+
+(define (compile-tile-layer layer)
+ `(vector
+ ,@(map (lambda (y)
+ (bytevector-copy
+ (list->f64vector
+ (concatenate
+ (filter-map (lambda (x)
+ (let ((tile (tile-layer-ref layer x y)))
+ (and tile
+ (list (* (exact->inexact x) 16.0)
+ (* (exact->inexact
+ (- (tile-id (map-tile-ref tile)) 1))
+ 16.0)))))
+ (iota (tile-layer-width layer)))))))
+ (iota (tile-layer-height layer)))))
+
+(define (compile-collision-layer layer)
+ (u8-list->bytevector
+ (append-map (lambda (y)
+ (map (lambda (x)
+ (if (tile-layer-ref layer x y) 1 0))
+ (iota (tile-layer-width layer))))
+ (iota (tile-layer-height layer)))))
+
+(define (compile-object-layer layer)
+ (let ((table (make-hash-table))
+ (tw (tile-map-tile-width tile-map))
+ (th (tile-map-tile-height tile-map)))
+ (for-each (lambda (obj)
+ (let* ((type (map-object-type obj))
+ (r (map-object-shape obj))
+ (x (/ (rect-x r) tw))
+ (y (/ (rect-y r) th)))
+ ;; (format (current-error-port) "obj: ~a ~a ~a ~a\n" (rect-x r) (rect-y r) x y)
+ (hashv-set! table y
+ (cons `(make-level-object ,x (quote ,type))
+ (hashv-ref table y '())))))
+ (object-layer-objects layer))
+ `(vector
+ ,@(map (lambda (y)
+ `(list ,@(hashv-ref table y '())))
+ (iota (tile-map-height tile-map))))))
+
+(pretty-print
+ `(make-level
+ ,(tile-map-height tile-map)
+ ,(compile-tile-layer (tile-map-layer-ref tile-map "foreground"))
+ ,(compile-tile-layer (tile-map-layer-ref tile-map "background"))
+ ,(compile-collision-layer (tile-map-layer-ref tile-map "collision"))
+ ,(compile-object-layer (tile-map-layer-ref tile-map "objects"))))
diff --git a/game.scm b/game.scm
index 6c7343a..23618c2 100644
--- a/game.scm
+++ b/game.scm
@@ -1,1077 +1,1048 @@
-(let ()
- ;; Host imports
- (define-foreign current-window
- "window" "get"
- -> (ref extern))
- (define-foreign window-inner-width
- "window" "innerWidth"
- (ref extern) -> i32)
- (define-foreign window-inner-height
- "window" "innerHeight"
- (ref extern) -> i32)
- (define-foreign request-animation-frame
- "window" "requestAnimationFrame"
- (ref eq) -> none)
- (define-foreign timeout
- "window" "setTimeout"
- (ref eq) f64 -> i32)
+(use-modules (hoot compile)
+ (ice-9 binary-ports)
+ (wasm assemble))
- (define-foreign current-document
- "document" "get"
- -> (ref extern))
- (define-foreign document-body
- "document" "body"
- -> (ref extern))
- (define-foreign get-element-by-id
- "document" "getElementById"
- (ref string) -> (ref null extern))
- (define-foreign make-text-node
- "document" "createTextNode"
- (ref string) -> (ref extern))
- (define-foreign make-element
- "document" "createElement"
- (ref string) -> (ref extern))
+(define src
+ `(let ()
+ ;; Host imports
+ (define-foreign current-window
+ "window" "get"
+ -> (ref extern))
+ (define-foreign window-inner-width
+ "window" "innerWidth"
+ (ref extern) -> i32)
+ (define-foreign window-inner-height
+ "window" "innerHeight"
+ (ref extern) -> i32)
+ (define-foreign request-animation-frame
+ "window" "requestAnimationFrame"
+ (ref eq) -> none)
+ (define-foreign timeout
+ "window" "setTimeout"
+ (ref eq) f64 -> i32)
- (define-foreign element-value
- "element" "value"
- (ref extern) -> (ref string))
- (define-foreign set-element-value!
- "element" "setValue"
- (ref extern) (ref string) -> none)
- (define-foreign set-element-width!
- "element" "setWidth"
- (ref extern) i32 -> none)
- (define-foreign set-element-height!
- "element" "setHeight"
- (ref extern) i32 -> none)
- (define-foreign append-child!
- "element" "appendChild"
- (ref extern) (ref extern) -> (ref extern))
- (define-foreign remove!
- "element" "remove"
- (ref extern) -> none)
- (define-foreign replace-with!
- "element" "replaceWith"
- (ref extern) (ref extern) -> none)
- (define-foreign set-attribute!
- "element" "setAttribute"
- (ref extern) (ref string) (ref string) -> none)
- (define-foreign remove-attribute!
- "element" "removeAttribute"
- (ref extern) (ref string) -> none)
- (define-foreign add-event-listener!
- "element" "addEventListener"
- (ref extern) (ref string) (ref eq) -> none)
- (define-foreign remove-event-listener!
- "element" "removeEventListener"
- (ref extern) (ref string) (ref eq) -> none)
- (define-foreign clone-element
- "element" "clone"
- (ref extern) -> (ref extern))
+ (define-foreign current-document
+ "document" "get"
+ -> (ref extern))
+ (define-foreign document-body
+ "document" "body"
+ -> (ref extern))
+ (define-foreign get-element-by-id
+ "document" "getElementById"
+ (ref string) -> (ref null extern))
+ (define-foreign make-text-node
+ "document" "createTextNode"
+ (ref string) -> (ref extern))
+ (define-foreign make-element
+ "document" "createElement"
+ (ref string) -> (ref extern))
- (define-foreign prevent-default!
- "event" "preventDefault"
- (ref extern) -> none)
- (define-foreign keyboard-event-code
- "event" "keyboardCode"
- (ref extern) -> (ref string))
+ (define-foreign element-value
+ "element" "value"
+ (ref extern) -> (ref string))
+ (define-foreign set-element-value!
+ "element" "setValue"
+ (ref extern) (ref string) -> none)
+ (define-foreign set-element-width!
+ "element" "setWidth"
+ (ref extern) i32 -> none)
+ (define-foreign set-element-height!
+ "element" "setHeight"
+ (ref extern) i32 -> none)
+ (define-foreign append-child!
+ "element" "appendChild"
+ (ref extern) (ref extern) -> (ref extern))
+ (define-foreign remove!
+ "element" "remove"
+ (ref extern) -> none)
+ (define-foreign replace-with!
+ "element" "replaceWith"
+ (ref extern) (ref extern) -> none)
+ (define-foreign set-attribute!
+ "element" "setAttribute"
+ (ref extern) (ref string) (ref string) -> none)
+ (define-foreign remove-attribute!
+ "element" "removeAttribute"
+ (ref extern) (ref string) -> none)
+ (define-foreign add-event-listener!
+ "element" "addEventListener"
+ (ref extern) (ref string) (ref eq) -> none)
+ (define-foreign remove-event-listener!
+ "element" "removeEventListener"
+ (ref extern) (ref string) (ref eq) -> none)
+ (define-foreign clone-element
+ "element" "clone"
+ (ref extern) -> (ref extern))
- (define-foreign get-context
- "canvas" "getContext"
- (ref extern) (ref string) -> (ref extern))
- (define-foreign set-fill-color!
- "canvas" "setFillColor"
- (ref extern) (ref string) -> none)
- (define-foreign set-font!
- "canvas" "setFont"
- (ref extern) (ref string) -> none)
- (define-foreign clear-rect
- "canvas" "clearRect"
- (ref extern) f64 f64 f64 f64 -> none)
- (define-foreign fill-rect
- "canvas" "fillRect"
- (ref extern) f64 f64 f64 f64 -> none)
- (define-foreign fill-text
- "canvas" "fillText"
- (ref extern) (ref string) f64 f64 -> none)
- (define-foreign draw-image
- "canvas" "drawImage"
- (ref extern) (ref extern) f64 f64 f64 f64 f64 f64 f64 f64 -> none)
- (define-foreign set-scale!
- "canvas" "setScale"
- (ref extern) f64 f64 -> none)
- (define-foreign set-transform!
- "canvas" "setTransform"
- (ref extern) f64 f64 f64 f64 f64 f64 -> none)
- (define-foreign set-image-smoothing-enabled!
- "canvas" "setImageSmoothingEnabled"
- (ref extern) i32 -> none)
+ (define-foreign prevent-default!
+ "event" "preventDefault"
+ (ref extern) -> none)
+ (define-foreign keyboard-event-code
+ "event" "keyboardCode"
+ (ref extern) -> (ref string))
- (define-foreign load-audio
- "audio" "new"
- (ref string) -> (ref extern))
- (define-foreign audio-play
- "audio" "play"
- (ref extern) -> none)
- (define-foreign audio-volume
- "audio" "volume"
- (ref extern) -> f64)
- (define-foreign set-audio-volume!
- "audio" "setVolume"
- (ref extern) f64 -> none)
+ (define-foreign get-context
+ "canvas" "getContext"
+ (ref extern) (ref string) -> (ref extern))
+ (define-foreign set-fill-color!
+ "canvas" "setFillColor"
+ (ref extern) (ref string) -> none)
+ (define-foreign set-font!
+ "canvas" "setFont"
+ (ref extern) (ref string) -> none)
+ (define-foreign set-text-align!
+ "canvas" "setTextAlign"
+ (ref extern) (ref string) -> none)
+ (define-foreign clear-rect
+ "canvas" "clearRect"
+ (ref extern) f64 f64 f64 f64 -> none)
+ (define-foreign fill-rect
+ "canvas" "fillRect"
+ (ref extern) f64 f64 f64 f64 -> none)
+ (define-foreign fill-text
+ "canvas" "fillText"
+ (ref extern) (ref string) f64 f64 -> none)
+ (define-foreign draw-image
+ "canvas" "drawImage"
+ (ref extern) (ref extern) f64 f64 f64 f64 f64 f64 f64 f64 -> none)
+ (define-foreign set-scale!
+ "canvas" "setScale"
+ (ref extern) f64 f64 -> none)
+ (define-foreign set-transform!
+ "canvas" "setTransform"
+ (ref extern) f64 f64 f64 f64 f64 f64 -> none)
+ (define-foreign set-image-smoothing-enabled!
+ "canvas" "setImageSmoothingEnabled"
+ (ref extern) i32 -> none)
- (define-foreign load-image
- "image" "new"
- (ref string) -> (ref extern))
+ (define-foreign load-audio
+ "audio" "new"
+ (ref string) -> (ref extern))
+ (define-foreign audio-play
+ "audio" "play"
+ (ref extern) -> none)
+ (define-foreign audio-volume
+ "audio" "volume"
+ (ref extern) -> f64)
+ (define-foreign set-audio-volume!
+ "audio" "setVolume"
+ (ref extern) f64 -> none)
- (define-syntax-rule (define-type name
- constructor
- predicate
- (field getter setter) ...)
- (begin
- (define (constructor field ...)
- (vector 'name field ...))
- (define (predicate obj)
- (match obj
- (#('name field ...) #t)
- (_ #f)))
- (define (getter obj)
- (match obj
- (#('name field ...)
- field)))
- ...
- (define setter
- (let ((i (1+ (- (length '(field ...))
- (length (memq 'field '(field ...)))))))
- (lambda (obj val)
- (match obj
- (#('name field ...)
- (vector-set! obj i val))))))
- ...))
+ (define-foreign load-image
+ "image" "new"
+ (ref string) -> (ref extern))
- ;; TODO: Add basic fmod as inline wasm function
+ (define-syntax-rule (define-type name
+ constructor
+ predicate
+ (field getter setter) ...)
+ (begin
+ (define (constructor field ...)
+ (vector 'name field ...))
+ (define (predicate obj)
+ (match obj
+ (#('name field ...) #t)
+ (_ #f)))
+ (define (getter obj)
+ (match obj
+ (#('name field ...)
+ field)))
+ ...
+ (define setter
+ (let ((i (1+ (- (length '(field ...))
+ (length (memq 'field '(field ...)))))))
+ (lambda (obj val)
+ (match obj
+ (#('name field ...)
+ (vector-set! obj i val))))))
+ ...))
- ;; Hoot's exact and inexact aren't working right. These next two
- ;; procedures are alternatives for now.
- (define (trunc x)
- ;; rational? is also borked so can't use that here.
- (unless (and (number? x) (inexact? x))
- (error "expected inexact rational" x))
- (%inline-wasm
- '(func (param $x (ref eq)) (result (ref eq))
- (call $s64->scm
- (i64.trunc_f64_s
- (struct.get $flonum $val (ref.cast $flonum (local.get $x))))))
- x))
- (define (inexact x)
- (unless (exact-integer? x)
- (error "expected exact integer" x))
- (%inline-wasm
- '(func (param $x (ref eq)) (result (ref eq))
- (if (ref eq)
- (call $fixnum? (local.get $x))
- (then
- (struct.new $flonum
- (i32.const 0)
- (f64.convert_i32_s
- (call $fixnum->i32 (ref.cast i31 (local.get $x))))))
- (else
- (struct.new $flonum
- (i32.const 0)
- (f64.convert_i64_s
- (call $bignum-get-i64
- (struct.get $bignum $val
- (ref.cast $bignum (local.get $x)))))))))
- x))
+ ;; TODO: Add basic fmod as inline wasm function
- (define s32-ref bytevector-s32-native-ref)
- (define s32-set! bytevector-s32-native-set!)
- (define f64-ref bytevector-ieee-double-native-ref)
- (define f64-set! bytevector-ieee-double-native-set!)
+ ;; Hoot's exact and inexact aren't working right. These next two
+ ;; procedures are alternatives for now.
+ (define (trunc x)
+ ;; rational? is also borked so can't use that here.
+ (unless (and (number? x) (inexact? x))
+ (error "expected inexact rational" x))
+ (%inline-wasm
+ '(func (param $x (ref eq)) (result (ref eq))
+ (call $s64->scm
+ (i64.trunc_f64_s
+ (struct.get $flonum $val (ref.cast $flonum (local.get $x))))))
+ x))
+ (define (inexact x)
+ (unless (exact-integer? x)
+ (error "expected exact integer" x))
+ (%inline-wasm
+ '(func (param $x (ref eq)) (result (ref eq))
+ (if (ref eq)
+ (call $fixnum? (local.get $x))
+ (then
+ (struct.new $flonum
+ (i32.const 0)
+ (f64.convert_i32_s
+ (call $fixnum->i32 (ref.cast i31 (local.get $x))))))
+ (else
+ (struct.new $flonum
+ (i32.const 0)
+ (f64.convert_i64_s
+ (call $bignum-get-i64
+ (struct.get $bignum $val
+ (ref.cast $bignum (local.get $x)))))))))
+ x))
- (define pi (* 4.0 (atan 1.0)))
- (define pi/2 (/ pi 2.0))
- (define tau (* pi 2.0))
+ (define s32-ref bytevector-s32-native-ref)
+ (define s32-set! bytevector-s32-native-set!)
+ (define f64-ref bytevector-ieee-double-native-ref)
+ (define f64-set! bytevector-ieee-double-native-set!)
- (define (clamp x min max)
- (cond ((< x min) min)
- ((> x max) max)
- (else x)))
+ (define pi (* 4.0 (atan 1.0)))
+ (define pi/2 (/ pi 2.0))
+ (define tau (* pi 2.0))
- (define-type vec2
- make-vec2
- vec2?
- (bv vec2-bv set-vec2-bv!))
- (define (vec2 x y)
- (let ((v (make-vec2 (make-bytevector 16))))
- (set-vec2-x! v x)
- (set-vec2-y! v y)
- v))
- (define (vec2-x v)
- (f64-ref (vec2-bv v) 0))
- (define (vec2-y v)
- (f64-ref (vec2-bv v) 8))
- (define (set-vec2-x! v x)
- (f64-set! (vec2-bv v) 0 x))
- (define (set-vec2-y! v y)
- (f64-set! (vec2-bv v) 8 y))
- (define (vec2-add! v w)
- (set-vec2-x! v (+ (vec2-x v) (vec2-x w)))
- (set-vec2-y! v (+ (vec2-y v) (vec2-y w))))
- (define (vec2-sub! v w)
- (set-vec2-x! v (- (vec2-x v) (vec2-x w)))
- (set-vec2-y! v (- (vec2-y v) (vec2-y w))))
- (define (vec2-mul-scalar! v x)
- (set-vec2-x! v (* (vec2-x v) x))
- (set-vec2-y! v (* (vec2-y v) x)))
- (define (vec2-magnitude v)
- (sqrt (+ (* (vec2-x v) (vec2-x v)) (* (vec2-y v) (vec2-y v)))))
- (define (vec2-normalize! v)
- (unless (and (= (vec2-x v) 0.0) (= (vec2-y v) 0.0))
- (let ((m (vec2-magnitude v)))
- (set-vec2-x! v (/ (vec2-x v) m))
- (set-vec2-y! v (/ (vec2-y v) m)))))
- (define (vec2-clamp! v xmin ymin xmax ymax)
- (set-vec2-x! v (clamp (vec2-x v) xmin xmax))
- (set-vec2-y! v (clamp (vec2-y v) ymin ymax)))
+ (define (clamp x min max)
+ (cond ((< x min) min)
+ ((> x max) max)
+ (else x)))
- (define (make-rect x y w h)
- (let ((r (make-bytevector (* 8 4))))
- (f64-set! r 0 x)
- (f64-set! r 8 y)
- (f64-set! r 16 w)
- (f64-set! r 24 h)
- r))
- (define (rect-x r)
- (f64-ref r 0))
- (define (rect-y r)
- (f64-ref r 8))
- (define (rect-w r)
- (f64-ref r 16))
- (define (rect-h r)
- (f64-ref r 24))
+ (define-type vec2
+ make-vec2
+ vec2?
+ (bv vec2-bv set-vec2-bv!))
+ (define (vec2 x y)
+ (let ((v (make-vec2 (make-bytevector 16))))
+ (set-vec2-x! v x)
+ (set-vec2-y! v y)
+ v))
+ (define (vec2-x v)
+ (f64-ref (vec2-bv v) 0))
+ (define (vec2-y v)
+ (f64-ref (vec2-bv v) 8))
+ (define (set-vec2-x! v x)
+ (f64-set! (vec2-bv v) 0 x))
+ (define (set-vec2-y! v y)
+ (f64-set! (vec2-bv v) 8 y))
+ (define (vec2-add! v w)
+ (set-vec2-x! v (+ (vec2-x v) (vec2-x w)))
+ (set-vec2-y! v (+ (vec2-y v) (vec2-y w))))
+ (define (vec2-sub! v w)
+ (set-vec2-x! v (- (vec2-x v) (vec2-x w)))
+ (set-vec2-y! v (- (vec2-y v) (vec2-y w))))
+ (define (vec2-mul-scalar! v x)
+ (set-vec2-x! v (* (vec2-x v) x))
+ (set-vec2-y! v (* (vec2-y v) x)))
+ (define (vec2-magnitude v)
+ (sqrt (+ (* (vec2-x v) (vec2-x v)) (* (vec2-y v) (vec2-y v)))))
+ (define (vec2-normalize! v)
+ (unless (and (= (vec2-x v) 0.0) (= (vec2-y v) 0.0))
+ (let ((m (vec2-magnitude v)))
+ (set-vec2-x! v (/ (vec2-x v) m))
+ (set-vec2-y! v (/ (vec2-y v) m)))))
+ (define (vec2-clamp! v xmin ymin xmax ymax)
+ (set-vec2-x! v (clamp (vec2-x v) xmin xmax))
+ (set-vec2-y! v (clamp (vec2-y v) ymin ymax)))
- (define (within? x y rx ry rw rh)
- (and (>= x rx)
- (>= y ry)
- (< x (+ rx rw))
- (< y (+ ry rh))))
- (define (rect-within? ax ay aw ah bx by bw bh)
- (let ((ax* (+ ax aw))
- (ay* (+ ay ah)))
- (or (within? ax ay bx by bw bh)
- (within? ax* ay bx by bw bh)
- (within? ax* ay* bx by bw bh)
- (within? ax ay* bx by bw bh))))
+ (define (make-rect x y w h)
+ (let ((r (make-bytevector (* 8 4))))
+ (f64-set! r 0 x)
+ (f64-set! r 8 y)
+ (f64-set! r 16 w)
+ (f64-set! r 24 h)
+ r))
+ (define (rect-x r)
+ (f64-ref r 0))
+ (define (rect-y r)
+ (f64-ref r 8))
+ (define (rect-w r)
+ (f64-ref r 16))
+ (define (rect-h r)
+ (f64-ref r 24))
- ;; So we can play many overlapping audio samples at once.
- (define (load-sound-effect src)
- (let* ((k 32)
- (audio (load-audio src))
- (vec (make-vector k)))
- (do ((i 0 (+ i 1)))
- ((= i k))
- (vector-set! vec i (clone-element audio)))
- (vector 0 vec)))
- (define* (sound-effect-play sound #:optional (volume 1.0))
- (match sound
- (#(i vec)
- (let ((audio (vector-ref vec i)))
- (set-audio-volume! audio volume)
- (audio-play audio)
- (vector-set! sound 0 (modulo (+ i 1) (vector-length vec)))))))
+ (define (within? x y rx ry rw rh)
+ (and (>= x rx)
+ (>= y ry)
+ (< x (+ rx rw))
+ (< y (+ ry rh))))
+ (define (rect-within? ax ay aw ah bx by bw bh)
+ (let ((ax* (+ ax aw))
+ (ay* (+ ay ah)))
+ (or (within? ax ay bx by bw bh)
+ (within? ax* ay bx by bw bh)
+ (within? ax* ay* bx by bw bh)
+ (within? ax ay* bx by bw bh))))
- ;; Screen size stuff
- (define game-width 240.0)
- (define game-height 320.0)
+ ;; So we can play many overlapping audio samples at once.
+ (define (load-sound-effect src)
+ (let* ((k 32)
+ (audio (load-audio src))
+ (vec (make-vector k)))
+ (do ((i 0 (+ i 1)))
+ ((= i k))
+ (vector-set! vec i (clone-element audio)))
+ (vector 0 vec)))
+ (define* (sound-effect-play sound #:optional (volume 1.0))
+ (match sound
+ (#(i vec)
+ (let ((audio (vector-ref vec i)))
+ (set-audio-volume! audio volume)
+ (audio-play audio)
+ (vector-set! sound 0 (modulo (+ i 1) (vector-length vec)))))))
- ;; Elements
- (define canvas (get-element-by-id "canvas"))
- (define context (get-context canvas "2d"))
- (define image:background (load-image "images/background.png"))
- (define image:player (load-image "images/player.png"))
- (define image:player-bullet (load-image "images/player-bullet.png"))
- (define image:enemy-bullets (load-image "images/enemy-bullets.png"))
- (define image:map (load-image "images/map.png"))
- (define image:enemies (load-image "images/enemies.png"))
- (define sound:explosion (load-sound-effect "audio/explosion.wav"))
- (define sound:player-shoot (load-sound-effect "audio/player-shoot.wav"))
- (define sound:player-death (load-sound-effect "audio/player-death.wav"))
- (define sound:enemy-shoot (load-sound-effect "audio/enemy-shoot.wav"))
- (define sound:bullet-hit (load-sound-effect "audio/bullet-hit.wav"))
+ ;; intro, play, game-over, game-won
+ (define *game-state* 'play)
- ;; Scripting
- (define (make-scheduler max-tasks)
- (vector 0 0 max-tasks (make-vector max-tasks)))
- (define (scheduler-add! scheduler thunk delay)
- (match scheduler
- (#(ticks num-tasks max-tasks tasks)
- (unless (= num-tasks max-tasks)
- (vector-set! tasks num-tasks (cons (+ ticks delay) thunk))
- (vector-set! scheduler 1 (+ num-tasks 1))))))
- (define (scheduler-tick! scheduler)
- (define (run-thunks thunks)
- (for-each (lambda (thunk) (thunk)) thunks))
- (run-thunks
- (match scheduler
- (#(ticks num-tasks max-tasks tasks)
- (let ((t (+ ticks 1)))
- (let loop ((i 0) (k num-tasks) (to-run '()))
- (if (< i k)
- (match (vector-ref tasks i)
- ((t* . thunk)
- (if (<= t* t)
- (let ((k* (- k 1)))
- (vector-set! tasks i (vector-ref tasks k*))
- (vector-set! tasks k* #f)
- (loop i k* (cons thunk to-run)))
- (loop (+ i 1) k to-run))))
- (begin
- (vector-set! scheduler 0 t)
- (vector-set! scheduler 1 k)
- to-run))))))))
- (define (scheduler-reset! scheduler)
- (match scheduler
- (#(ticks num-tasks max-tasks tasks)
- (vector-set! scheduler 0 0)
- (vector-set! scheduler 1 0)
- (do ((i 0 (+ i 1)))
- ((= i num-tasks))
- (vector-set! tasks i #f)))))
- (define *scheduler* (make-scheduler 100))
- (define %script-tag (make-prompt-tag "script"))
- (define-type script
- %make-script
- script?
- (state script-state set-script-state!)
- (cont script-cont set-script-cont!))
- (define (make-script thunk)
- (%make-script 'pending thunk))
- (define (script-pending? script)
- (eq? (script-state script) 'pending))
- (define (script-running? script)
- (eq? (script-state script) 'running))
- (define (script-cancelled? script)
- (eq? (script-state script) 'cancelled))
- (define (script-cancel! script)
- (set-script-state! script 'cancelled))
- (define (script-run! script)
- (define (run thunk)
- (unless (script-cancelled? script)
- (call-with-prompt %script-tag thunk handler)))
- (define (handler k delay)
- (when delay
- (scheduler-add! *scheduler* (lambda () (run k)) delay)))
- (when (script-pending? script)
- (run
- (lambda ()
- (set-script-state! script 'running)
- ((script-cont script))
- ;; Nasty hack: For some reason, falling through the prompt
- ;; thunk messes up the Scheme stack, resulting in an invalid
- ;; ref.cast somewhere. So, we *never* fall through. Instead,
- ;; we create a continuation that gets thrown away.
- (abort-to-prompt %script-tag #f)))))
- (define (run-script thunk)
- (let ((script (make-script thunk)))
- (script-run! script)
- script))
- (define (wait delay)
- (abort-to-prompt %script-tag delay))
+ ;; Screen size stuff
+ (define game-width 240.0)
+ (define game-height 320.0)
- ;; Bullets:
- (define-type bullet-pool
- %make-bullet-pool
- bullet-pool?
- (length bullet-pool-length set-bullet-pool-length!)
- (capacity bullet-pool-capacity set-bullet-pool-capacity!)
- (bullets bullet-pool-bullets set-bullet-pool-bullets!))
- ;; per bullet: type, x, y, dx, dy
- (define %bullet-size (+ 4 8 8 8 8))
- (define (make-bullet-pool capacity)
- (let ((bullets (make-bytevector (* capacity %bullet-size))))
- (%make-bullet-pool 0 capacity bullets)))
- (define (bullet-pool-offset i)
- (* i %bullet-size))
- (define (bullet-pool-add! pool type x y dx dy)
- (match pool
- (#('bullet-pool length capacity bullets)
- (let ((offset (bullet-pool-offset length)))
- (s32-set! bullets offset type)
- (f64-set! bullets (+ offset 4) x)
- (f64-set! bullets (+ offset 12) y)
- (f64-set! bullets (+ offset 20) dx)
- (f64-set! bullets (+ offset 28) dy)
- (set-bullet-pool-length! pool (+ length 1))))))
- (define (bullet-pool-remove! pool i)
- (match pool
- (#('bullet-pool length capacity bullets)
- (when (and (>= i 0) (< i length))
- (let ((at (bullet-pool-offset i))
- (start (bullet-pool-offset (- length 1))))
- (bytevector-copy! bullets at bullets start (+ start %bullet-size))
- (set-bullet-pool-length! pool (- length 1)))))))
- (define (bullet-pool-reset! pool)
- (set-bullet-pool-length! pool 0))
- (define (bullet-pool-update! pool collide)
- (match pool
- (#('bullet-pool length capacity bullets)
- (let loop ((i 0) (k length))
- (when (< i k)
- (let* ((offset (bullet-pool-offset i))
- (x (f64-ref bullets (+ offset 4)))
- (y (f64-ref bullets (+ offset 12)))
- (dx (f64-ref bullets (+ offset 20)))
- (dy (f64-ref bullets (+ offset 28)))
- (x* (+ x dx))
- (y* (+ y dy)))
- (cond
- ;; TODO: different bullet hitbox sizes.
- ((collide x y 2.0 2.0)
- (bullet-pool-remove! pool i)
- (loop i (- k 1)))
- (else
- (f64-set! bullets (+ offset 4) x*)
- (f64-set! bullets (+ offset 12) y*)
- (loop (+ i 1) k)))))))))
- (define (draw-bullets pool image w h)
- (match pool
- (#('bullet-pool length capacity bullets)
- (do ((i 0 (+ i 1)))
- ((= i length))
- (let* ((offset (bullet-pool-offset i))
- (type (s32-ref bullets offset))
- (x (f64-ref bullets (+ offset 4)))
- (y (f64-ref bullets (+ offset 12))))
- (draw-image context image (* type w) (* type h) w h
- (- x (/ w 2.0)) (- y (/ w 2.0)) w h))))))
+ ;; Elements
+ (define canvas (get-element-by-id "canvas"))
+ (define context (get-context canvas "2d"))
+ (define image:background (load-image "images/background.png"))
+ (define image:player (load-image "images/player.png"))
+ (define image:player-bullet (load-image "images/player-bullet.png"))
+ (define image:enemy-bullets (load-image "images/enemy-bullets.png"))
+ (define image:map (load-image "images/map.png"))
+ (define image:enemies (load-image "images/enemies.png"))
+ (define sound:explosion (load-sound-effect "audio/explosion.wav"))
+ (define sound:player-shoot (load-sound-effect "audio/player-shoot.wav"))
+ (define sound:player-death (load-sound-effect "audio/player-death.wav"))
+ (define sound:enemy-shoot (load-sound-effect "audio/enemy-shoot.wav"))
+ (define sound:bullet-hit (load-sound-effect "audio/bullet-hit.wav"))
- (define player-bullets (make-bullet-pool 200))
- (define enemy-bullets (make-bullet-pool 400))
+ ;; Scripting
+ (define (make-scheduler max-tasks)
+ (vector 0 0 max-tasks (make-vector max-tasks)))
+ (define (scheduler-add! scheduler thunk delay)
+ (match scheduler
+ (#(ticks num-tasks max-tasks tasks)
+ (unless (= num-tasks max-tasks)
+ (vector-set! tasks num-tasks (cons (+ ticks delay) thunk))
+ (vector-set! scheduler 1 (+ num-tasks 1))))))
+ (define (scheduler-tick! scheduler)
+ (define (run-thunks thunks)
+ (for-each (lambda (thunk) (thunk)) thunks))
+ (run-thunks
+ (match scheduler
+ (#(ticks num-tasks max-tasks tasks)
+ (let ((t (+ ticks 1)))
+ (let loop ((i 0) (k num-tasks) (to-run '()))
+ (if (< i k)
+ (match (vector-ref tasks i)
+ ((t* . thunk)
+ (if (<= t* t)
+ (let ((k* (- k 1)))
+ (vector-set! tasks i (vector-ref tasks k*))
+ (vector-set! tasks k* #f)
+ (loop i k* (cons thunk to-run)))
+ (loop (+ i 1) k to-run))))
+ (begin
+ (vector-set! scheduler 0 t)
+ (vector-set! scheduler 1 k)
+ to-run))))))))
+ (define (scheduler-reset! scheduler)
+ (match scheduler
+ (#(ticks num-tasks max-tasks tasks)
+ (vector-set! scheduler 0 0)
+ (vector-set! scheduler 1 0)
+ (do ((i 0 (+ i 1)))
+ ((= i num-tasks))
+ (vector-set! tasks i #f)))))
+ (define *scheduler* (make-scheduler 100))
+ (define %script-tag (make-prompt-tag "script"))
+ (define-type script
+ %make-script
+ script?
+ (state script-state set-script-state!)
+ (cont script-cont set-script-cont!))
+ (define (make-script thunk)
+ (%make-script 'pending thunk))
+ (define (script-pending? script)
+ (eq? (script-state script) 'pending))
+ (define (script-running? script)
+ (eq? (script-state script) 'running))
+ (define (script-cancelled? script)
+ (eq? (script-state script) 'cancelled))
+ (define (script-cancel! script)
+ (set-script-state! script 'cancelled))
+ (define (script-run! script)
+ (define (run thunk)
+ (unless (script-cancelled? script)
+ (call-with-prompt %script-tag thunk handler)))
+ (define (handler k delay)
+ (when delay
+ (scheduler-add! *scheduler* (lambda () (run k)) delay)))
+ (when (script-pending? script)
+ (run
+ (lambda ()
+ (set-script-state! script 'running)
+ ((script-cont script))
+ ;; Nasty hack: For some reason, falling through the prompt
+ ;; thunk messes up the Scheme stack, resulting in an invalid
+ ;; ref.cast somewhere. So, we *never* fall through. Instead,
+ ;; we create a continuation that gets thrown away.
+ (abort-to-prompt %script-tag #f)))))
+ (define (run-script thunk)
+ (let ((script (make-script thunk)))
+ (script-run! script)
+ script))
+ (define (wait delay)
+ (abort-to-prompt %script-tag delay))
- ;; Scrolling level:
- (define *scroll* 0.0)
- (define *scroll-speed* 0.5)
- (define *last-row-scanned* 0)
- ;; action id, sprite sheet offset, x, y
- (define %tile-size (+ 4 8 8 8))
- (define tile-width 16.0)
- (define tile-height 16.0)
- (define level-width 15)
- (define-type level
- %make-level
- level?
- (height level-height set-level-height!)
- (tiles level-tiles set-level-tiles!))
- (define (make-level tiles)
- (let ((k (length tiles)))
- (unless (= (modulo k level-width) 0)
- (error "incomplete level data"))
- (let ((bv (make-bytevector (* k %tile-size))))
- (let y-loop ((tiles tiles) (y 0))
- (match tiles
- (() #t)
- (tiles
- (y-loop
- (let x-loop ((tiles tiles) (x 0))
- (if (< x level-width)
- (match tiles
- ((t . rest)
- (let ((n (match t
- ('X 0.0)
- ('\ 1.0)
- ('/ 2.0)
- (_ -1.0)))
- (action (match t
- ('A 1)
- (_ 0)))
- (offset (* (+ x (* y level-width)) %tile-size)))
- (s32-set! bv offset action)
- (f64-set! bv (+ offset 4) n)
- (f64-set! bv (+ offset 12)
- (* (inexact x) tile-width))
- (f64-set! bv (+ offset 20)
- (* (inexact y) tile-height)))
- (x-loop rest (+ x 1))))
- tiles))
- (+ y 1)))))
- (%make-level (/ k level-width) bv))))
- (define-syntax-rule (define-level name tile ...)
- (define name (make-level '(tile ...))))
- (define-level level
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X X X _ _ _ _ _ _ _ _ _ X X X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X X X _ _ _ _ _ _ _ _ _ X X X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X X X _ _ _ _ _ _ _ _ _ X X X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X X X A _ _ _ _ _ _ _ _ X X X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X X X _ _ _ _ _ _ _ _ A X X X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X X X A _ _ _ _ _ _ _ _ X X X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X X X _ _ _ _ _ _ _ _ A X X X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X X X A _ _ _ _ _ _ _ _ X X X
- X X _ _ _ _ _ _ _ _ _ _ _ X X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ X X X _ _ _ _ _ X
- X _ _ _ _ _ X X X _ _ _ _ _ X
- X _ _ _ _ _ X X X _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X _ _ _ _ _ _ _ _ _ _ _ _ _ X
- X \ _ _ _ _ _ _ _ _ _ _ _ / X
- X X \ _ _ _ _ _ _ _ _ _ / X X
- X X X \ _ _ _ _ _ _ _ / X X X)
- (define (level-offset x y)
- (* (+ (* level-width y) x) %tile-size))
- (define (point-collides-with-level? level x y)
- (match level
- (#('level height tiles)
- (let ((tx (trunc (/ x tile-width)))
- (ty (trunc (/ y tile-height))))
- (and (>= tx 0) (< tx level-width)
- (>= ty 0) (< tx height)
- (>= (f64-ref tiles (level-offset tx ty)) 0))))))
- (define (rect-collides-with-level? level x y w h)
- (match level
- (#('level height tiles)
- (let* ((y (+ y (- (* height tile-height) game-height *scroll*)))
- (tx0 (trunc (/ x tile-width)))
- (ty0 (trunc (/ y tile-height)))
- (tx1 (trunc (/ (+ x w) tile-width)))
- (ty1 (trunc (/ (+ y h) tile-height))))
- (define (occupied? x y)
- (and (>= x 0) (< x level-width)
- (>= y 0) (< x height)
- (>= (f64-ref tiles (+ (level-offset x y) 4)) 0.0)))
- (or (occupied? tx0 ty0)
- (occupied? tx1 ty0)
- (occupied? tx1 ty1)
- (occupied? tx0 ty1))))))
- (define (draw-tiles level)
- (match level
- (#('level height tiles)
- (let* ((tw tile-width)
- (th tile-height)
- (pixel-y-offset (- (* height th) *scroll* game-height))
- (scroll-y-offset (- height (trunc (/ *scroll* tile-height))))
- (y-start (clamp (- scroll-y-offset 21) 0 height))
- (y-end (clamp scroll-y-offset 0 height)))
- (do ((y y-start (+ y 1)))
- ((= y y-end))
- (do ((x 0 (+ x 1)))
- ((= x level-width))
- (let* ((offset (level-offset x y))
- (t (f64-ref tiles (+ offset 4)))
- (tx (f64-ref tiles (+ offset 12)))
- (ty (f64-ref tiles (+ offset 20))))
- (draw-image context image:map
- (* t tw) 0.0 tw th
- tx (- ty pixel-y-offset) tw th))))))))
- (define max-scroll (- (* (level-height level) tile-height) game-height))
- (define (level-update! level)
- (match level
- (#('level height tiles)
- (let ((scroll (min (+ *scroll* *scroll-speed*) max-scroll)))
- (set! *scroll* scroll)
- (let ((row (max (- (trunc
- (/ (- (* height tile-height)
- game-height scroll)
- tile-height)) 2)
- 0)))
- (do ((y row (+ y 1)))
- ((= y *last-row-scanned*))
- (do ((x 0 (+ x 1)))
- ((= x level-width))
- (case (s32-ref tiles (level-offset x y))
- ((0) #t)
- ((1)
- (spawn-enemy-a (+ (* x tile-width)
- (/ tile-width 2.0))
- (+ (* (- row y 3) tile-height)
- (/ tile-height 2.0)))))))
- (set! *last-row-scanned* row))))))
+ ;; Bullets:
+ (define-type bullet-pool
+ %make-bullet-pool
+ bullet-pool?
+ (length bullet-pool-length set-bullet-pool-length!)
+ (capacity bullet-pool-capacity set-bullet-pool-capacity!)
+ (bullets bullet-pool-bullets set-bullet-pool-bullets!))
+ ;; per bullet: type, x, y, dx, dy
+ (define %bullet-size (+ 4 8 8 8 8))
+ (define (make-bullet-pool capacity)
+ (let ((bullets (make-bytevector (* capacity %bullet-size))))
+ (%make-bullet-pool 0 capacity bullets)))
+ (define (bullet-pool-offset i)
+ (* i %bullet-size))
+ (define (bullet-pool-add! pool type x y dx dy)
+ (match pool
+ (#('bullet-pool length capacity bullets)
+ (let ((offset (bullet-pool-offset length)))
+ (s32-set! bullets offset type)
+ (f64-set! bullets (+ offset 4) x)
+ (f64-set! bullets (+ offset 12) y)
+ (f64-set! bullets (+ offset 20) dx)
+ (f64-set! bullets (+ offset 28) dy)
+ (set-bullet-pool-length! pool (+ length 1))))))
+ (define (bullet-pool-remove! pool i)
+ (match pool
+ (#('bullet-pool length capacity bullets)
+ (when (and (>= i 0) (< i length))
+ (let ((at (bullet-pool-offset i))
+ (start (bullet-pool-offset (- length 1))))
+ (bytevector-copy! bullets at bullets start (+ start %bullet-size))
+ (set-bullet-pool-length! pool (- length 1)))))))
+ (define (bullet-pool-reset! pool)
+ (set-bullet-pool-length! pool 0))
+ (define (bullet-pool-update! pool collide)
+ (match pool
+ (#('bullet-pool length capacity bullets)
+ (let loop ((i 0) (k length))
+ (when (< i k)
+ (let* ((offset (bullet-pool-offset i))
+ (x (f64-ref bullets (+ offset 4)))
+ (y (f64-ref bullets (+ offset 12)))
+ (dx (f64-ref bullets (+ offset 20)))
+ (dy (f64-ref bullets (+ offset 28)))
+ (x* (+ x dx))
+ (y* (+ y dy)))
+ (cond
+ ;; TODO: different bullet hitbox sizes.
+ ((collide x y 2.0 2.0)
+ (bullet-pool-remove! pool i)
+ (loop i (- k 1)))
+ (else
+ (f64-set! bullets (+ offset 4) x*)
+ (f64-set! bullets (+ offset 12) y*)
+ (loop (+ i 1) k)))))))))
+ (define (draw-bullets pool image w h)
+ (match pool
+ (#('bullet-pool length capacity bullets)
+ (do ((i 0 (+ i 1)))
+ ((= i length))
+ (let* ((offset (bullet-pool-offset i))
+ (type (s32-ref bullets offset))
+ (x (f64-ref bullets (+ offset 4)))
+ (y (f64-ref bullets (+ offset 12))))
+ (draw-image context image (* type w) (* type h) w h
+ (- x (/ w 2.0)) (- y (/ w 2.0)) w h))))))
- ;; Enemies
- (define-type enemy
- make-enemy
- enemy?
- (type enemy-type set-enemy-type!)
- (health enemy-health set-enemy-health!)
- (position enemy-position set-enemy-position!)
- (size enemy-size set-enemy-size!)
- (stationary? enemy-stationary? set-enemy-stationary!)
- (velocity enemy-velocity set-enemy-velocity!)
- (script enemy-script set-enemy-script!))
- (define (enemy-x enemy)
- (vec2-x (enemy-position enemy)))
- (define (enemy-y enemy)
- (vec2-y (enemy-position enemy)))
- (define (enemy-width enemy)
- (vec2-x (enemy-size enemy)))
- (define (enemy-height enemy)
- (vec2-y (enemy-size enemy)))
- (define (enemy-dx enemy)
- (vec2-x (enemy-velocity enemy)))
- (define (enemy-dy enemy)
- (vec2-y (enemy-velocity enemy)))
- (define (enemy-damage! enemy damage)
- (match enemy
- (#('enemy type health _ _ _ _ _)
- (set-enemy-health! enemy (- health damage)))))
- (define (enemy-dead? enemy)
- (<= (enemy-health enemy) 0))
- (define (enemy-out-of-bounds? enemy)
- (match enemy
- (#('enemy _ _ position size _ _ _)
- (out-of-bounds? (vec2-x position) (vec2-y position)
- (vec2-x size) (vec2-y size)))))
- (define (enemy-within-rect? enemy x y w h)
- (match enemy
- (#('enemy _ _ position size _ _ _)
- (let* ((w* (vec2-x size))
- (h* (vec2-y size))
- (x* (- (vec2-x position) (/ w* 2.0)))
- (y* (- (vec2-y position) (/ h* 2.0))))
- (rect-within? x y w h x* y* w* h*)))))
- (define (enemy-start! enemy)
- (let ((proc (enemy-script enemy)))
- (when (procedure? proc)
- (set-enemy-script! enemy (run-script (lambda () (proc enemy)))))))
- (define (enemy-stop! enemy)
- (let ((script (enemy-script enemy)))
- (when (script? script)
- (script-cancel! script))))
- (define (enemy-update! enemy)
- (match enemy
- (#('enemy _ _ position size stationary? velocity _)
- (if stationary?
- (set-vec2-y! position (+ (vec2-y position) *scroll-speed*))
- (begin
- (set-vec2-x! position (+ (vec2-x position) (vec2-x velocity)))
- (set-vec2-y! position (+ (vec2-y position) (vec2-y velocity))))))))
- (define (enemy-draw enemy)
- (match enemy
- (#('enemy type _ position size _ _ _)
- (let* ((t 0.0)
- (x (vec2-x position))
- (y (vec2-y position))
- (hbw (vec2-x size))
- (hbh (vec2-y size))
- (w 64.0)
- (h 64.0))
- (draw-image context image:enemies (* t w) (* t h) w h
- (- x (/ w 2.0)) (- y (/ h 2.0)) w h)
- (set-fill-color! context "#ff00ff80")
- (fill-rect context
- (- x (/ hbw 2.0))
- (- y (/ hbh 2.0))
- hbw hbh)))))
+ (define player-bullets (make-bullet-pool 200))
+ (define enemy-bullets (make-bullet-pool 400))
- (define-type enemy-pool
- %make-enemy-pool
- enemy-pool?
- (length enemy-pool-length set-enemy-pool-length!)
- (capacity enemy-pool-capacity set-enemy-pool-capacity!)
- (enemies enemy-pool-enemies set-enemy-pool-enemies!))
- (define (make-enemy-pool capacity)
- (%make-enemy-pool 0 capacity (make-vector capacity #f)))
- (define (enemy-pool-add! pool enemy)
- (match pool
- (#('enemy-pool length capacity enemies)
- (unless (= length capacity)
- (vector-set! enemies length enemy)
- (set-enemy-pool-length! pool (+ length 1))
- (enemy-start! enemy)))))
- (define (enemy-pool-remove! pool i)
- (match pool
- (#('enemy-pool length capacity enemies)
- (when (and (>= i 0) (< i length))
- (let ((j (- length 1))
- (enemy (vector-ref enemies i)))
- (vector-set! enemies i (vector-ref enemies j))
- (vector-set! enemies j #f)
- (enemy-stop! enemy)
- (set-enemy-pool-length! pool j))))))
- (define (enemy-pool-reset! pool)
- (match pool
- (#('enemy-pool length capacity enemies)
- (do ((i 0 (+ i 1)))
- ((= i length))
- (enemy-stop! (vector-ref enemies i))
- (vector-set! enemies i #f))
- (set-enemy-pool-length! pool 0))))
- (define (enemy-pool-update! pool)
- (match pool
- (#('enemy-pool length capacity enemies)
- (let ((padding 16.0))
- (let loop ((i 0) (k length))
- (unless (= i k)
- (let ((enemy (vector-ref enemies i)))
- (enemy-update! enemy)
- (cond
- ((or (enemy-dead? enemy)
- (enemy-out-of-bounds? enemy))
- (sound-effect-play sound:explosion)
- (enemy-pool-remove! pool i)
- (loop i (- k 1)))
- (else
- (loop (+ i 1) k))))))))))
- (define (draw-enemies pool)
- (match pool
- (#('enemy-pool length capacity enemies)
- (do ((i 0 (+ i 1)))
- ((= i length))
- (enemy-draw (vector-ref enemies i))))))
- (define (find-enemy pool x y w h)
- (match pool
- (#('enemy-pool length capacity enemies)
- (let loop ((i 0))
- (and (< i length)
- (let ((enemy (vector-ref enemies i)))
- (if (enemy-within-rect? enemy x y w h)
- enemy
- (loop (+ i 1)))))))))
+ ;; Scrolling level:
+ (define *scroll* 0.0)
+ (define *scroll-speed* 0.5)
+ (define *last-row-scanned* 0)
+ ;; action id, sprite sheet offset, x, y
+ (define %tile-size (+ 4 8 8 8))
+ (define tile-width 16.0)
+ (define tile-height 16.0)
+ (define level-width 15)
+ (define-type level-object
+ make-level-object
+ level-object?
+ (x level-object-x set-level-object-x!)
+ (type level-object-type set-level-object-type!))
+ (define-type level
+ make-level
+ level?
+ (height level-height set-level-height!)
+ (foreground level-foreground set-level-foreground!)
+ (background level-background set-level-background!)
+ (collision level-collision set-level-collision!)
+ (objects level-objects set-level-objects!))
+ (define level ,(call-with-input-file "level.scm" read))
+ (define (level-offset x y)
+ (* (+ (* level-width y) x)))
+ (define (point-collides-with-level? level x y)
+ (match level
+ (#('level height foreground background collision objects)
+ (let ((tx (trunc (/ x tile-width)))
+ (ty (trunc (/ y tile-height))))
+ (and (>= tx 0) (< tx level-width)
+ (>= ty 0) (< tx height)
+ (= (bytevector-u8-ref collision (level-offset tx ty)) 1))))))
+ (define (rect-collides-with-level? level x y w h)
+ (match level
+ (#('level height foreground background collision objects)
+ (let* ((y (+ y (- (* height tile-height) game-height *scroll*)))
+ (tx0 (trunc (/ x tile-width)))
+ (ty0 (trunc (/ y tile-height)))
+ (tx1 (trunc (/ (+ x w) tile-width)))
+ (ty1 (trunc (/ (+ y h) tile-height))))
+ (define (occupied? x y)
+ (and (>= x 0) (< x level-width)
+ (>= y 0) (< x height)
+ (= (bytevector-u8-ref collision (level-offset x y)) 1)))
+ (or (occupied? tx0 ty0)
+ (occupied? tx1 ty0)
+ (occupied? tx1 ty1)
+ (occupied? tx0 ty1))))))
+ (define (draw-level-layer level layer parallax)
+ (match level
+ (#('level height _ _ _ _)
+ (let* ((tw tile-width)
+ (th tile-height)
+ (scroll (* *scroll* parallax))
+ (pixel-y-offset (- (* height th) scroll game-height))
+ (scroll-y-offset (- height (trunc (/ scroll tile-height))))
+ (y-start (clamp (- scroll-y-offset 21) 0 height))
+ (y-end (clamp scroll-y-offset 0 height)))
+ (do ((y y-start (+ y 1)))
+ ((= y y-end))
+ (let* ((row (vector-ref layer y))
+ (k (/ (bytevector-length row) 16))
+ (ty (* y tile-height)))
+ (do ((x 0 (+ x 1)))
+ ((= x k))
+ (let* ((offset (* x 16))
+ (tx (f64-ref row offset))
+ (ix (f64-ref row (+ offset 8))))
+ (draw-image context image:map
+ ix 0.0 tw th
+ tx (- ty pixel-y-offset) tw th)))))))))
+ (define (draw-level-foreground level)
+ (match level
+ (#('level height foreground background collision objects)
+ (draw-level-layer level foreground 1.0))))
+ (define (draw-level-background level)
+ (match level
+ (#('level height foreground background collision objects)
+ (draw-level-layer level background 0.75))))
+ (define max-scroll (- (* (level-height level) tile-height) game-height))
+ (define (level-update! level)
+ (match level
+ (#('level height foreground background collision objects)
+ (let ((scroll (min (+ *scroll* *scroll-speed*) max-scroll)))
+ (set! *scroll* scroll)
+ (let ((row (max (- (trunc
+ (/ (- (* height tile-height)
+ game-height scroll)
+ tile-height)) 2)
+ 0)))
+ (do ((y row (+ y 1)))
+ ((= y *last-row-scanned*))
+ (for-each (lambda (obj)
+ (match obj
+ (#('level-object x type)
+ (match type
+ ('enemy-a
+ (spawn-enemy-a (+ (* x tile-width)
+ (/ tile-width 2.0))
+ (+ (* (- y row 3) tile-height)
+ (/ tile-height 2.0))))
+ (_ #t)))))
+ (vector-ref objects y)))
+ (set! *last-row-scanned* row))))))
- (define enemies (make-enemy-pool 64))
+ ;; Enemies
+ (define-type enemy
+ make-enemy
+ enemy?
+ (type enemy-type set-enemy-type!)
+ (health enemy-health set-enemy-health!)
+ (position enemy-position set-enemy-position!)
+ (size enemy-size set-enemy-size!)
+ (stationary? enemy-stationary? set-enemy-stationary!)
+ (velocity enemy-velocity set-enemy-velocity!)
+ (script enemy-script set-enemy-script!))
+ (define (enemy-x enemy)
+ (vec2-x (enemy-position enemy)))
+ (define (enemy-y enemy)
+ (vec2-y (enemy-position enemy)))
+ (define (enemy-width enemy)
+ (vec2-x (enemy-size enemy)))
+ (define (enemy-height enemy)
+ (vec2-y (enemy-size enemy)))
+ (define (enemy-dx enemy)
+ (vec2-x (enemy-velocity enemy)))
+ (define (enemy-dy enemy)
+ (vec2-y (enemy-velocity enemy)))
+ (define (enemy-damage! enemy damage)
+ (match enemy
+ (#('enemy type health _ _ _ _ _)
+ (set-enemy-health! enemy (- health damage)))))
+ (define (enemy-dead? enemy)
+ (<= (enemy-health enemy) 0))
+ (define (enemy-out-of-bounds? enemy)
+ (match enemy
+ (#('enemy _ _ position size _ _ _)
+ (out-of-bounds? (vec2-x position) (vec2-y position)
+ (vec2-x size) (vec2-y size)))))
+ (define (enemy-within-rect? enemy x y w h)
+ (match enemy
+ (#('enemy _ _ position size _ _ _)
+ (let* ((w* (vec2-x size))
+ (h* (vec2-y size))
+ (x* (- (vec2-x position) (/ w* 2.0)))
+ (y* (- (vec2-y position) (/ h* 2.0))))
+ (rect-within? x y w h x* y* w* h*)))))
+ (define (enemy-start! enemy)
+ (let ((proc (enemy-script enemy)))
+ (when (procedure? proc)
+ (set-enemy-script! enemy (run-script (lambda () (proc enemy)))))))
+ (define (enemy-stop! enemy)
+ (let ((script (enemy-script enemy)))
+ (when (script? script)
+ (script-cancel! script))))
+ (define (enemy-update! enemy)
+ (match enemy
+ (#('enemy _ _ position size stationary? velocity _)
+ (if stationary?
+ (set-vec2-y! position (+ (vec2-y position) *scroll-speed*))
+ (begin
+ (set-vec2-x! position (+ (vec2-x position) (vec2-x velocity)))
+ (set-vec2-y! position (+ (vec2-y position) (vec2-y velocity))))))))
+ (define (enemy-draw enemy)
+ (match enemy
+ (#('enemy type _ position size _ _ _)
+ (let* ((t 0.0)
+ (x (vec2-x position))
+ (y (vec2-y position))
+ (hbw (vec2-x size))
+ (hbh (vec2-y size))
+ (w 64.0)
+ (h 64.0))
+ (draw-image context image:enemies (* t w) (* t h) w h
+ (- x (/ w 2.0)) (- y (/ h 2.0)) w h)
+ (set-fill-color! context "#ff00ff80")
+ (fill-rect context
+ (- x (/ hbw 2.0))
+ (- y (/ hbh 2.0))
+ hbw hbh)))))
+
+ (define-type enemy-pool
+ %make-enemy-pool
+ enemy-pool?
+ (length enemy-pool-length set-enemy-pool-length!)
+ (capacity enemy-pool-capacity set-enemy-pool-capacity!)
+ (enemies enemy-pool-enemies set-enemy-pool-enemies!))
+ (define (make-enemy-pool capacity)
+ (%make-enemy-pool 0 capacity (make-vector capacity #f)))
+ (define (enemy-pool-add! pool enemy)
+ (match pool
+ (#('enemy-pool length capacity enemies)
+ (unless (= length capacity)
+ (vector-set! enemies length enemy)
+ (set-enemy-pool-length! pool (+ length 1))
+ (enemy-start! enemy)))))
+ (define (enemy-pool-remove! pool i)
+ (match pool
+ (#('enemy-pool length capacity enemies)
+ (when (and (>= i 0) (< i length))
+ (let ((j (- length 1))
+ (enemy (vector-ref enemies i)))
+ (vector-set! enemies i (vector-ref enemies j))
+ (vector-set! enemies j #f)
+ (enemy-stop! enemy)
+ (set-enemy-pool-length! pool j))))))
+ (define (enemy-pool-reset! pool)
+ (match pool
+ (#('enemy-pool length capacity enemies)
+ (do ((i 0 (+ i 1)))
+ ((= i length))
+ (enemy-stop! (vector-ref enemies i))
+ (vector-set! enemies i #f))
+ (set-enemy-pool-length! pool 0))))
+ (define (enemy-pool-update! pool)
+ (match pool
+ (#('enemy-pool length capacity enemies)
+ (let ((padding 16.0))
+ (let loop ((i 0) (k length))
+ (unless (= i k)
+ (let ((enemy (vector-ref enemies i)))
+ (enemy-update! enemy)
+ (cond
+ ((or (enemy-dead? enemy)
+ (enemy-out-of-bounds? enemy))
+ (sound-effect-play sound:explosion)
+ (enemy-pool-remove! pool i)
+ (loop i (- k 1)))
+ (else
+ (loop (+ i 1) k))))))))))
+ (define (draw-enemies pool)
+ (match pool
+ (#('enemy-pool length capacity enemies)
+ (do ((i 0 (+ i 1)))
+ ((= i length))
+ (enemy-draw (vector-ref enemies i))))))
+ (define (find-enemy pool x y w h)
+ (match pool
+ (#('enemy-pool length capacity enemies)
+ (let loop ((i 0))
+ (and (< i length)
+ (let ((enemy (vector-ref enemies i)))
+ (if (enemy-within-rect? enemy x y w h)
+ enemy
+ (loop (+ i 1)))))))))
- (define (spawn-enemy-a x y)
- (define (script enemy)
- (let ((speed 2.0))
- (let loop ((theta 0.0))
- (let ((dx (* (cos theta) speed))
- (dy (* (sin theta) speed))
- (v (direction-to-player (enemy-position enemy))))
- (bullet-pool-add! enemy-bullets 0
- (enemy-x enemy)
- (enemy-y enemy)
- (* (vec2-x v) speed)
- (* (vec2-y v) speed)))
- (wait 30)
- (loop (+ theta 0.2)))))
- (let ((enemy (make-enemy 'foo 20 (vec2 x y) (vec2 16.0 16.0)
- #t (vec2 0.0 0.0) script)))
- (enemy-pool-add! enemies enemy)))
+ (define enemies (make-enemy-pool 64))
- ;; Player state:
- (define player-position (vec2 (/ game-width 2.0) (- game-height 12.0)))
- (define player-velocity (vec2 0.0 0.0))
- (define player-speed 2.9)
- (define player-bullet-speed 12.0)
- (define player-width 24.0)
- (define player-height 24.0)
- (define *player-fire-counter* 0)
- (define player-fire-interval 3)
- (define player-hitbox-position (vec2 0.0 0.0))
- (define player-hitbox-width 2.0)
- (define player-hitbox-height 2.0)
- (define %default-lives 3)
- (define *player-lives* %default-lives)
- (define *player-visible?* #t)
- (define *player-invincible?* #f)
- ;; left, right, down, up, fire
- (define key-state (vector #f #f #f #f #f))
- (define (update-player-velocity!)
- (match key-state
- (#(left? right? down? up? _)
- (set-vec2-x! player-velocity
- (+ (if left? -1.0 0.0)
- (if right? 1.0 0.0)))
- (set-vec2-y! player-velocity
- (+ (if down? 1.0 0.0)
- (if up? -1.0 0.0)))
- (vec2-normalize! player-velocity)
- (vec2-mul-scalar! player-velocity player-speed))))
- (define (set-left! pressed?)
- (vector-set! key-state 0 pressed?)
- (update-player-velocity!))
- (define (set-right! pressed?)
- (vector-set! key-state 1 pressed?)
- (update-player-velocity!))
- (define (set-down! pressed?)
- (vector-set! key-state 2 pressed?)
- (update-player-velocity!))
- (define (set-up! pressed?)
- (vector-set! key-state 3 pressed?)
- (update-player-velocity!))
- (define (set-firing! pressed?)
- (let ((was-firing? (firing?)))
- (vector-set! key-state 4 pressed?)
- (when (and pressed? (not was-firing?))
- (set! *player-fire-counter* 0))))
- (define (firing?)
- (vector-ref key-state 4))
- (define (player-die!)
- (unless *player-invincible?*
- ;; (sound-effect-play sound:player-death)
- (set! *player-lives* (max (- *player-lives* 1) 0))
- (run-script
- (lambda ()
- (set! *player-invincible?* #t)
- (let ((t 5))
- (let loop ((i 0))
- (when (< i 10)
- (set! *player-visible?* #f)
- (wait t)
- (set! *player-visible?* #t)
- (wait t)
- (loop (+ i 1)))))
- (set! *player-invincible?* #f)))))
- (define (game-over?)
- (= *player-lives* 0))
- (define (player-update!)
- (vec2-add! player-position player-velocity)
- (vec2-clamp! player-position 0.0 0.0 game-width game-height)
- (set-vec2-x! player-hitbox-position
- (- (vec2-x player-position)
- (/ player-hitbox-width 2.0)))
- (set-vec2-y! player-hitbox-position
- (- (vec2-y player-position)
- (/ player-hitbox-height 2.0)))
- (when (and (let ((x (vec2-x player-hitbox-position))
- (y (vec2-y player-hitbox-position))
- (w player-hitbox-width)
- (h player-hitbox-height))
- (or (rect-collides-with-level? level x y w h)
- (find-enemy enemies x y w h))))
- (player-die!))
- (when (firing?)
- (set! *player-fire-counter*
- (modulo (+ *player-fire-counter* 1) player-fire-interval))
- (when (= *player-fire-counter* 0)
- (sound-effect-play sound:player-shoot 0.5)
- (bullet-pool-add! player-bullets 0
- (- (vec2-x player-position) 6.0)
- (vec2-y player-position)
- 0.0 (- player-bullet-speed))
- (bullet-pool-add! player-bullets 0
- (+ (vec2-x player-position) 8.0)
- (vec2-y player-position)
- 0.0 (- player-bullet-speed))
- (set! *player-fire-counter* 0))))
- (define (draw-player)
- (draw-image context image:player
- (if *player-visible?* 0.0 player-width) 0.0
- player-width player-height
- (- (vec2-x player-position)
- (/ player-width 2.0))
- (- (vec2-y player-position)
- (/ player-height 2.0))
- player-width player-height)
- (set-fill-color! context "#ff00ff80")
- (fill-rect context
- (vec2-x player-hitbox-position)
- (vec2-y player-hitbox-position)
- player-hitbox-width
- player-hitbox-height))
- (define (direction-to-player v)
- (let ((v* (vec2 (vec2-x player-position) (vec2-y player-position))))
- (vec2-sub! v* v)
- (vec2-normalize! v*)
- v*))
+ (define (spawn-enemy-a x y)
+ (define (script enemy)
+ (let ((speed 2.0))
+ (let loop ((theta 0.0))
+ (let ((dx (* (cos theta) speed))
+ (dy (* (sin theta) speed))
+ (v (direction-to-player (enemy-position enemy))))
+ (bullet-pool-add! enemy-bullets 0
+ (enemy-x enemy)
+ (enemy-y enemy)
+ (* (vec2-x v) speed)
+ (* (vec2-y v) speed)))
+ (wait 30)
+ (loop (+ theta 0.2)))))
+ (let ((enemy (make-enemy 'foo 20 (vec2 x y) (vec2 16.0 16.0)
+ #t (vec2 0.0 0.0) script)))
+ (enemy-pool-add! enemies enemy)))
- (define *canvas-scale* 0.0)
- (define *canvas-width* 0)
- (define *canvas-height* 0)
+ ;; Player state:
+ (define player-position (vec2 (/ game-width 2.0) (- game-height 12.0)))
+ (define player-velocity (vec2 0.0 0.0))
+ (define player-speed 2.9)
+ (define player-bullet-speed 12.0)
+ (define player-width 24.0)
+ (define player-height 24.0)
+ (define *player-fire-counter* 0)
+ (define player-fire-interval 3)
+ (define player-hitbox-position (vec2 0.0 0.0))
+ (define player-hitbox-width 2.0)
+ (define player-hitbox-height 2.0)
+ (define %default-lives 3)
+ (define *player-lives* %default-lives)
+ (define *player-visible?* #t)
+ (define *player-invincible?* #f)
+ ;; left, right, down, up, fire
+ (define key-state (vector #f #f #f #f #f))
+ (define (update-player-velocity!)
+ (match key-state
+ (#(left? right? down? up? _)
+ (set-vec2-x! player-velocity
+ (+ (if left? -1.0 0.0)
+ (if right? 1.0 0.0)))
+ (set-vec2-y! player-velocity
+ (+ (if down? 1.0 0.0)
+ (if up? -1.0 0.0)))
+ (vec2-normalize! player-velocity)
+ (vec2-mul-scalar! player-velocity player-speed))))
+ (define (set-left! pressed?)
+ (vector-set! key-state 0 pressed?)
+ (update-player-velocity!))
+ (define (set-right! pressed?)
+ (vector-set! key-state 1 pressed?)
+ (update-player-velocity!))
+ (define (set-down! pressed?)
+ (vector-set! key-state 2 pressed?)
+ (update-player-velocity!))
+ (define (set-up! pressed?)
+ (vector-set! key-state 3 pressed?)
+ (update-player-velocity!))
+ (define (set-firing! pressed?)
+ (let ((was-firing? (firing?)))
+ (vector-set! key-state 4 pressed?)
+ (when (and pressed? (not was-firing?))
+ (set! *player-fire-counter* 0))))
+ (define (firing?)
+ (vector-ref key-state 4))
+ (define (player-die!)
+ (unless *player-invincible?*
+ ;; (sound-effect-play sound:player-death)
+ (set! *player-lives* (max (- *player-lives* 1) 0))
+ (run-script
+ (lambda ()
+ (set! *player-invincible?* #t)
+ (let ((t 5))
+ (let loop ((i 0))
+ (when (< i 10)
+ (set! *player-visible?* #f)
+ (wait t)
+ (set! *player-visible?* #t)
+ (wait t)
+ (loop (+ i 1)))))
+ (set! *player-invincible?* #f)))))
+ (define (game-over?)
+ (= *player-lives* 0))
+ (define (player-update!)
+ (vec2-add! player-position player-velocity)
+ (vec2-clamp! player-position 0.0 0.0 game-width game-height)
+ (set-vec2-x! player-hitbox-position
+ (- (vec2-x player-position)
+ (/ player-hitbox-width 2.0)))
+ (set-vec2-y! player-hitbox-position
+ (- (vec2-y player-position)
+ (/ player-hitbox-height 2.0)))
+ (when (and (let ((x (vec2-x player-hitbox-position))
+ (y (vec2-y player-hitbox-position))
+ (w player-hitbox-width)
+ (h player-hitbox-height))
+ (or (rect-collides-with-level? level x y w h)
+ (find-enemy enemies x y w h))))
+ (player-die!))
+ (when (firing?)
+ (set! *player-fire-counter*
+ (modulo (+ *player-fire-counter* 1) player-fire-interval))
+ (when (= *player-fire-counter* 0)
+ (sound-effect-play sound:player-shoot 0.2)
+ (bullet-pool-add! player-bullets 0
+ (- (vec2-x player-position) 6.0)
+ (vec2-y player-position)
+ 0.0 (- player-bullet-speed))
+ (bullet-pool-add! player-bullets 0
+ (+ (vec2-x player-position) 8.0)
+ (vec2-y player-position)
+ 0.0 (- player-bullet-speed))
+ (set! *player-fire-counter* 0))))
+ (define (draw-player)
+ (draw-image context image:player
+ (if *player-visible?* 0.0 player-width) 0.0
+ player-width player-height
+ (- (vec2-x player-position)
+ (/ player-width 2.0))
+ (- (vec2-y player-position)
+ (/ player-height 2.0))
+ player-width player-height)
+ (set-fill-color! context "#ff00ff80")
+ (fill-rect context
+ (vec2-x player-hitbox-position)
+ (vec2-y player-hitbox-position)
+ player-hitbox-width
+ player-hitbox-height))
+ (define (direction-to-player v)
+ (let ((v* (vec2 (vec2-x player-position) (vec2-y player-position))))
+ (vec2-sub! v* v)
+ (vec2-normalize! v*)
+ v*))
- (define (resize-canvas)
- (let* ((win (current-window))
- (w (window-inner-width win))
- (h (window-inner-height win))
- (gw (trunc game-width))
- (gh (trunc game-height))
- (scale (max (min (quotient w gw) (quotient h gh)) 1))
- (cw (* gw scale))
- (ch (* gh scale)))
- (set-element-width! canvas cw)
- (set-element-height! canvas ch)
- (set-image-smoothing-enabled! context 0)
- (set! *canvas-scale* (inexact scale))
- (set! *canvas-width* (* game-width *canvas-scale*))
- (set! *canvas-height* (* game-height *canvas-scale*))))
+ ;; Canvas sizing/scaling.
+ (define *canvas-scale* 0.0)
+ (define *canvas-width* 0)
+ (define *canvas-height* 0)
+ (define (resize-canvas)
+ (let* ((win (current-window))
+ (w (window-inner-width win))
+ (h (window-inner-height win))
+ (gw (trunc game-width))
+ (gh (trunc game-height))
+ (scale (max (min (quotient w gw) (quotient h gh)) 1))
+ (cw (* gw scale))
+ (ch (* gh scale)))
+ (set-element-width! canvas cw)
+ (set-element-height! canvas ch)
+ (set-image-smoothing-enabled! context 0)
+ (set! *canvas-scale* (inexact scale))
+ (set! *canvas-width* (* game-width *canvas-scale*))
+ (set! *canvas-height* (* game-height *canvas-scale*))))
- (define (clear-screen)
- (clear-rect context 0.0 0.0 *canvas-width* *canvas-height*))
+ (define (clear-screen)
+ (clear-rect context 0.0 0.0 *canvas-width* *canvas-height*))
- (define (draw-player-bullets)
- (draw-bullets player-bullets image:player-bullet 8.0 8.0))
+ (define (draw-player-bullets)
+ (draw-bullets player-bullets image:player-bullet 8.0 8.0))
- (define (draw-enemy-bullets)
- (draw-bullets enemy-bullets image:enemy-bullets 16.0 16.0))
+ (define (draw-enemy-bullets)
+ (draw-bullets enemy-bullets image:enemy-bullets 16.0 16.0))
- (define (draw-background image parallax)
- (let ((scroll (remainder (* *scroll* parallax) game-height)))
- ;; Bottom
- (draw-image context image
- 0.0 0.0 game-width (- game-height scroll)
- 0.0 scroll game-width (- game-height scroll))
- ;; Top
- (draw-image context image
- 0.0 (- game-height scroll) game-width scroll
- 0.0 0.0 game-width scroll)))
+ (define (draw-background image parallax)
+ (let ((scroll (remainder (* *scroll* parallax) game-height)))
+ ;; Bottom
+ (draw-image context image
+ 0.0 0.0 game-width (- game-height scroll)
+ 0.0 scroll game-width (- game-height scroll))
+ ;; Top
+ (draw-image context image
+ 0.0 (- game-height scroll) game-width scroll
+ 0.0 0.0 game-width scroll)))
- (define (draw-hud)
- (let ((y (- game-height 8.0)))
- ;; TODO: Don't strings every frame when the UI values rarely
- ;; change.
- (set-fill-color! context "#ffffff")
- (set-font! context "bold 8px monospace")
- (fill-text context (string-append "x" (number->string *player-lives*))
- (- game-width 16.0) y)
- ;; TODO: Add scoring.
- (fill-text context (string-append "score " (number->string 0))
- 4.0 y)))
+ (define (draw-hud)
+ (let ((y (- game-height 8.0)))
+ ;; TODO: Don't strings every frame when the UI values rarely
+ ;; change.
+ (set-fill-color! context "#ffffff")
+ (set-font! context "bold 8px monospace")
+ (set-text-align! context "right")
+ (fill-text context (string-append "x" (number->string *player-lives*))
+ (- game-width 4.0) y)
+ ;; TODO: Add scoring.
+ (set-text-align! context "left")
+ (fill-text context (string-append "score " (number->string 0))
+ 4.0 y)))
- (define (draw time)
- (clear-screen)
- (set-transform! context 1.0 0.0 0.0 1.0 0.0 0.0)
- (set-scale! context *canvas-scale* *canvas-scale*)
- (set-fill-color! context "#3f2832")
- (fill-rect context 0.0 0.0 game-width game-height)
- (draw-background image:background 0.75)
- (draw-tiles level)
- (draw-player-bullets)
- (draw-enemies enemies)
- (draw-player)
- (draw-enemy-bullets)
- (draw-hud)
- (request-animation-frame draw))
+ (define (draw time)
+ (clear-screen)
+ (set-transform! context 1.0 0.0 0.0 1.0 0.0 0.0)
+ (set-scale! context *canvas-scale* *canvas-scale*)
+ (set-fill-color! context "#3f2832")
+ (fill-rect context 0.0 0.0 game-width game-height)
+ ;; (draw-level-background level)
+ (draw-background image:background 0.75)
+ (draw-level-foreground level)
+ (draw-player-bullets)
+ (draw-enemies enemies)
+ (draw-player)
+ (draw-enemy-bullets)
+ (draw-hud)
+ (match *game-state*
+ ('game-over
+ (set-fill-color! context "#ffffff")
+ (set-font! context "bold 24px monospace")
+ (set-text-align! context "center")
+ (fill-text context "GAME OVER" (/ game-width 2.0) (/ game-height 2.0)))
+ (_ #t))
+ (request-animation-frame draw))
- (define (reset!)
- (scheduler-reset! *scheduler*)
- (set! *scroll* 0.0)
- (set! *last-row-scanned* (level-height level))
- (bullet-pool-reset! player-bullets)
- (bullet-pool-reset! enemy-bullets)
- (enemy-pool-reset! enemies)
- (set-vec2-x! player-position (/ game-width 2.0))
- (set-vec2-y! player-position (- game-height 12.0))
- (set! *player-lives* %default-lives)
- (set! *player-invincible?* #f)
- (set! *player-visible?* #t)
- (set! *player-fire-counter* 0))
+ (define (reset!)
+ (set! *game-state* 'play)
+ (scheduler-reset! *scheduler*)
+ (set! *scroll* 0.0)
+ (set! *last-row-scanned* (level-height level))
+ (bullet-pool-reset! player-bullets)
+ (bullet-pool-reset! enemy-bullets)
+ (enemy-pool-reset! enemies)
+ (set-vec2-x! player-position (/ game-width 2.0))
+ (set-vec2-y! player-position (- game-height 12.0))
+ (set! *player-lives* %default-lives)
+ (set! *player-invincible?* #f)
+ (set! *player-visible?* #t)
+ (set! *player-fire-counter* 0))
- (define (on-key-down event)
- (let ((code (keyboard-event-code event)))
- (cond
- ((string-=? code "ArrowLeft")
- (set-left! #t)
- (prevent-default! event))
- ((string-=? code "ArrowRight")
- (set-right! #t)
- (prevent-default! event))
- ((string-=? code "ArrowDown")
- (set-down! #t)
- (prevent-default! event))
- ((string-=? code "ArrowUp")
- (set-up! #t)
- (prevent-default! event))
- ((string-=? code "KeyZ")
- (set-firing! #t)
- (prevent-default! event))
- ((string-=? code "KeyR")
- (reset!)
- (prevent-default! event)))))
+ (define (on-key-down event)
+ (let ((code (keyboard-event-code event)))
+ (cond
+ ((string-=? code "ArrowLeft")
+ (set-left! #t)
+ (prevent-default! event))
+ ((string-=? code "ArrowRight")
+ (set-right! #t)
+ (prevent-default! event))
+ ((string-=? code "ArrowDown")
+ (set-down! #t)
+ (prevent-default! event))
+ ((string-=? code "ArrowUp")
+ (set-up! #t)
+ (prevent-default! event))
+ ((string-=? code "KeyZ")
+ (set-firing! #t)
+ (prevent-default! event))
+ ((string-=? code "KeyR")
+ (reset!)
+ (prevent-default! event)))
+ (match *game-state*
+ ('game-over
+ (cond
+ ((string-=? code "Enter")
+ (reset!)
+ (prevent-default! event))))
+ (_ #t))))
- (define (on-key-up event)
- (let ((code (keyboard-event-code event)))
- (cond
- ((string-=? code "ArrowLeft")
- (set-left! #f)
- (prevent-default! event))
- ((string-=? code "ArrowRight")
- (set-right! #f)
- (prevent-default! event))
- ((string-=? code "ArrowDown")
- (set-down! #f)
- (prevent-default! event))
- ((string-=? code "ArrowUp")
- (set-up! #f)
- (prevent-default! event))
- ((string-=? code "KeyZ")
- (set-firing! #f)
- (prevent-default! event)))))
+ (define (on-key-up event)
+ (let ((code (keyboard-event-code event)))
+ (cond
+ ((string-=? code "ArrowLeft")
+ (set-left! #f)
+ (prevent-default! event))
+ ((string-=? code "ArrowRight")
+ (set-right! #f)
+ (prevent-default! event))
+ ((string-=? code "ArrowDown")
+ (set-down! #f)
+ (prevent-default! event))
+ ((string-=? code "ArrowUp")
+ (set-up! #f)
+ (prevent-default! event))
+ ((string-=? code "KeyZ")
+ (set-firing! #f)
+ (prevent-default! event)))))
- (define (out-of-bounds? x y w h)
- (let ((padding 32.0))
- (not (rect-within? x y w h (- padding) (- padding)
- (+ game-width padding) (+ game-height padding)))))
+ (define (out-of-bounds? x y w h)
+ (let ((padding 32.0))
+ (not (rect-within? x y w h (- padding) (- padding)
+ (+ game-width padding) (+ game-height padding)))))
- (define (player-bullet-collide x y w h)
- (let ((x* (- x (/ w 2.0)))
- (y* (- y(/ h 2.0))))
- (or (out-of-bounds? x* y* w h)
- (rect-collides-with-level? level x* y* w h)
- (let ((enemy (find-enemy enemies x y w h)))
- (and enemy
+ (define (player-bullet-collide x y w h)
+ (let ((x* (- x (/ w 2.0)))
+ (y* (- y(/ h 2.0))))
+ (or (out-of-bounds? x* y* w h)
+ (rect-collides-with-level? level x* y* w h)
+ (let ((enemy (find-enemy enemies x y w h)))
+ (and enemy
+ (begin
+ (enemy-damage! enemy 1)
+ #t))))))
+
+ (define (enemy-bullet-collide x y w h)
+ (let ((x* (- x (/ w 2.0)))
+ (y* (- y(/ h 2.0))))
+ (or (out-of-bounds? x* y* w h)
+ (rect-collides-with-level? level x* y* w h)
+ ;; (if (rect-collides-with-level? level x* y* w h)
+ ;; (begin
+ ;; (sound-effect-play sound:bullet-hit 0.1)
+ ;; #t)
+ ;; #f)
+ (if (rect-within? x y w h
+ (vec2-x player-hitbox-position)
+ (vec2-y player-hitbox-position)
+ player-hitbox-width
+ player-hitbox-height)
(begin
- (enemy-damage! enemy 1)
- #t))))))
+ (player-die!)
+ #t)
+ #f))))
- (define (enemy-bullet-collide x y w h)
- (let ((x* (- x (/ w 2.0)))
- (y* (- y(/ h 2.0))))
- (or (out-of-bounds? x* y* w h)
- (rect-collides-with-level? level x* y* w h)
- ;; (if (rect-collides-with-level? level x* y* w h)
- ;; (begin
- ;; (sound-effect-play sound:bullet-hit 0.1)
- ;; #t)
- ;; #f)
- (if (rect-within? x y w h
- (vec2-x player-hitbox-position)
- (vec2-y player-hitbox-position)
- player-hitbox-width
- player-hitbox-height)
- (begin
- (player-die!)
- #t)
- #f))))
+ (define dt (/ 1000.0 60.0))
+ (define (update)
+ (match *game-state*
+ ('play
+ (scheduler-tick! *scheduler*)
+ (level-update! level)
+ (player-update!)
+ (bullet-pool-update! player-bullets player-bullet-collide)
+ (bullet-pool-update! enemy-bullets enemy-bullet-collide)
+ (enemy-pool-update! enemies)
+ (when (game-over?)
+ (set! *game-state* 'game-over)))
+ (_ #t))
+ (timeout update dt))
- (define dt (/ 1000.0 60.0))
- (define (update)
- (scheduler-tick! *scheduler*)
- (level-update! level)
- (player-update!)
- (bullet-pool-update! player-bullets player-bullet-collide)
- (bullet-pool-update! enemy-bullets enemy-bullet-collide)
- (enemy-pool-update! enemies)
- (timeout update dt))
+ (add-event-listener! (current-window) "resize" (lambda (_) (resize-canvas)))
+ (add-event-listener! (current-document) "keydown" on-key-down)
+ (add-event-listener! (current-document) "keyup" on-key-up)
+ (resize-canvas)
+ (reset!)
+ (request-animation-frame draw)
+ (timeout update dt)))
- (add-event-listener! (current-window) "resize" (lambda (_) (resize-canvas)))
- (add-event-listener! (current-document) "keydown" on-key-down)
- (add-event-listener! (current-document) "keyup" on-key-up)
- (resize-canvas)
- (reset!)
- (request-animation-frame draw)
- (timeout update dt))
+(call-with-output-file "game.wasm"
+ (lambda (port)
+ (put-bytevector port (assemble-wasm (compile src)))))
diff --git a/images/map.ase b/images/map.ase
index 4177dd5..81fed57 100644
--- a/images/map.ase
+++ b/images/map.ase
Binary files differ
diff --git a/images/map.png b/images/map.png
index 14da796..5338adf 100644
--- a/images/map.png
+++ b/images/map.png
Binary files differ
diff --git a/js-runtime/reflect.js b/js-runtime/reflect.js
index 0662ea5..7ba19b7 100644
--- a/js-runtime/reflect.js
+++ b/js-runtime/reflect.js
@@ -360,6 +360,7 @@ class SchemeModule {
#io_handler;
#debug_handler;
static #rt = {
+ bignum_from_string(str) { return BigInt(str); },
bignum_from_i32(n) { return BigInt(n); },
bignum_from_i64(n) { return n; },
bignum_from_u64(n) { return n < 0n ? 0xffff_ffff_ffff_ffffn + (n + 1n) : n; },
diff --git a/js-runtime/reflect.wasm b/js-runtime/reflect.wasm
index d2b10d7..64d1156 100644
--- a/js-runtime/reflect.wasm
+++ b/js-runtime/reflect.wasm
Binary files differ
diff --git a/level.tmx b/level.tmx
new file mode 100644
index 0000000..2191ef4
--- /dev/null
+++ b/level.tmx
@@ -0,0 +1,141 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<map version="1.8" tiledversion="1.8.6" orientation="orthogonal" renderorder="right-down" width="15" height="40" tilewidth="16" tileheight="16" infinite="0" nextlayerid="6" nextobjectid="13">
+ <tileset firstgid="1" source="tiles.tsx"/>
+ <layer id="2" name="background" width="15" height="40">
+ <data encoding="csv">
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
+7,7,7,7,7,7,7,7,7,7,7,7,7,7,7
+</data>
+ </layer>
+ <layer id="1" name="foreground" width="15" height="40">
+ <data encoding="csv">
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,2,0,0,0,0,0,0,0,0,0,0,0,3,1,
+1,1,2,0,0,0,0,0,0,0,0,0,3,1,1,
+1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,
+1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,
+1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,
+1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,
+1,1,1,1,1,1,1,1,0,0,0,0,1,1,1,
+1,1,1,1,1,1,1,0,0,0,0,3,1,1,1,
+1,1,1,1,1,1,0,0,0,0,3,1,1,1,1,
+1,1,1,1,1,0,0,0,0,3,1,1,1,1,1,
+1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,
+1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,
+1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,
+1,1,1,1,1,0,0,0,0,1,1,1,1,1,1,
+1,1,1,1,0,0,0,0,0,0,1,1,1,1,1,
+1,1,1,0,0,0,0,0,0,0,0,1,1,1,1,
+1,1,0,0,0,0,0,0,0,0,0,0,1,1,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,1,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,0,0,0,0,0,0,0,0,0,0,0,0,0,1,
+1,2,0,0,0,0,0,0,0,0,0,0,0,3,1,
+1,1,2,0,0,0,0,0,0,0,0,0,3,1,1
+</data>
+ </layer>
+ <layer id="5" name="collision" width="15" height="40">
+ <data encoding="csv">
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,6,0,0,0,0,0,0,0,0,0,0,0,6,6,
+6,6,6,6,6,6,6,6,0,0,0,0,6,6,6,
+6,6,6,6,6,6,6,6,0,0,0,0,6,6,6,
+6,6,6,6,6,6,6,6,0,0,0,0,6,6,6,
+6,6,6,6,6,6,6,6,0,0,0,0,6,6,6,
+6,6,6,6,6,6,6,6,0,0,0,0,6,6,6,
+6,6,6,6,6,6,6,0,0,0,0,0,6,6,6,
+6,6,6,6,6,6,0,0,0,0,0,6,6,6,6,
+6,6,6,6,6,0,0,0,0,0,6,6,6,6,6,
+6,6,6,6,6,0,0,0,0,6,6,6,6,6,6,
+6,6,6,6,6,0,0,0,0,6,6,6,6,6,6,
+6,6,6,6,6,0,0,0,0,6,6,6,6,6,6,
+6,6,6,6,6,0,0,0,0,6,6,6,6,6,6,
+6,6,6,6,0,0,0,0,0,0,6,6,6,6,6,
+6,6,6,0,0,0,0,0,0,0,0,6,6,6,6,
+6,6,0,0,0,0,0,0,0,0,0,0,6,6,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,6,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,0,0,0,0,0,0,0,0,0,0,0,0,0,6,
+6,6,0,0,0,0,0,0,0,0,0,0,0,6,6
+</data>
+ </layer>
+ <objectgroup id="3" name="objects">
+ <object id="10" type="enemy-a" x="16" y="336" width="16" height="16"/>
+ <object id="11" type="enemy-a" x="192" y="336" width="16" height="16"/>
+ <object id="12" type="enemy-a" x="112" y="176" width="16" height="16"/>
+ </objectgroup>
+</map>
diff --git a/manifest.scm b/manifest.scm
new file mode 100644
index 0000000..e5c2491
--- /dev/null
+++ b/manifest.scm
@@ -0,0 +1,58 @@
+(use-modules (guix)
+ (guix build-system gnu)
+ (guix gexp)
+ (guix git)
+ (guix git-download)
+ ((guix licenses) #:prefix license:)
+ (guix packages)
+ (gnu packages autotools)
+ (gnu packages base)
+ (gnu packages compression)
+ (gnu packages guile)
+ (gnu packages pkg-config)
+ (gnu packages texinfo))
+
+(define guile-next-next
+ (let ((commit "79e836b8cc601a1259c934000a953a8d739ddd6f")
+ (revision "1"))
+ (package
+ (inherit guile-next)
+ (version (git-version "3.0.9" revision commit))
+ (source (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://git.savannah.gnu.org/git/guile.git")
+ (commit commit)))
+ (file-name (git-file-name "guile" version))
+ (sha256
+ (base32 "0s90khsdbvrkykp58izkvyxf8jciggdapm29dc3lzk3s1shajlgm")))))))
+
+(define guile-hoot
+ (let ((commit "89b936e198b158fc4a43b43977d783f4f798f45c")
+ (revision "1"))
+ (package
+ (name "guile-hoot")
+ (version (git-version "0.0.0" revision commit))
+ (source (origin
+ (method git-fetch)
+ (uri (git-reference
+ (url "https://gitlab.com/spritely/guile-hoot.git")
+ (commit commit)))
+ (file-name (git-file-name "guile-hoot" version))
+ (sha256
+ (base32 "0bzd2a840rzl3ymaaiqm7m4xsgb6hm24fjg3ssbiqq3n6qd88fnr"))))
+ (build-system gnu-build-system)
+ (arguments
+ '(#:make-flags '("GUILE_AUTO_COMPILE=0")
+ #:tests? #f))
+ (native-inputs
+ (list autoconf automake pkg-config texinfo))
+ (inputs
+ (list guile-next-next))
+ (synopsis "WASM compiler for Guile Scheme")
+ (description "Guile-hoot is an ahead-of-time WebAssembly compiler for GNU Guile.")
+ (home-page "https://gitlab.com/spritely/guile-hoot")
+ (license (list license:asl2.0 license:lgpl3+)))))
+
+(packages->manifest (list ;; guile-next-next guile-hoot
+ gnu-make zip))
diff --git a/tiles.tsx b/tiles.tsx
new file mode 100644
index 0000000..9adb30c
--- /dev/null
+++ b/tiles.tsx
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<tileset version="1.8" tiledversion="1.8.6" name="tiles" tilewidth="16" tileheight="16" tilecount="7" columns="7">
+ <image source="images/map.png" width="112" height="16"/>
+</tileset>