From 2c824b40a9799dcd85069a0b50b92659861c30c7 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Fri, 17 May 2024 13:08:43 -0400 Subject: [PATCH] First commit! --- COPYING | 202 ++++++++++ Makefile | 24 ++ README.md | 35 ++ assets/images/AUTHORS | 5 + assets/images/ball.png | Bin 0 -> 583 bytes assets/images/brick-blue.png | Bin 0 -> 369 bytes assets/images/brick-green.png | Bin 0 -> 372 bytes assets/images/brick-red.png | Bin 0 -> 365 bytes assets/images/paddle.png | Bin 0 -> 994 bytes assets/sounds/AUTHORS | 3 + assets/sounds/brick.wav | Bin 0 -> 8672 bytes assets/sounds/paddle.wav | Bin 0 -> 8660 bytes game.css | 11 + game.js | 77 ++++ game.scm | 356 +++++++++++++++++ game.wasm | Bin 0 -> 236482 bytes index.html | 17 + js-runtime/reflect.js | 710 ++++++++++++++++++++++++++++++++++ js-runtime/reflect.wasm | Bin 0 -> 5196 bytes js-runtime/wtf8.wasm | Bin 0 -> 1071 bytes manifest.scm | 7 + modules/dom/canvas.scm | 72 ++++ modules/dom/document.scm | 45 +++ modules/dom/element.scm | 71 ++++ modules/dom/event.scm | 46 +++ modules/dom/image.scm | 29 ++ modules/dom/media.scm | 54 +++ modules/dom/window.scm | 45 +++ modules/math.scm | 49 +++ modules/math/rect.scm | 96 +++++ modules/math/vector.scm | 94 +++++ 31 files changed, 2048 insertions(+) create mode 100644 COPYING create mode 100644 Makefile create mode 100644 README.md create mode 100644 assets/images/AUTHORS create mode 100644 assets/images/ball.png create mode 100644 assets/images/brick-blue.png create mode 100644 assets/images/brick-green.png create mode 100644 assets/images/brick-red.png create mode 100644 assets/images/paddle.png create mode 100644 assets/sounds/AUTHORS create mode 100644 assets/sounds/brick.wav create mode 100644 assets/sounds/paddle.wav create mode 100644 game.css create mode 100644 game.js create mode 100644 game.scm create mode 100644 game.wasm create mode 100644 index.html create mode 100644 js-runtime/reflect.js create mode 100644 js-runtime/reflect.wasm create mode 100644 js-runtime/wtf8.wasm create mode 100644 manifest.scm create mode 100644 modules/dom/canvas.scm create mode 100644 modules/dom/document.scm create mode 100644 modules/dom/element.scm create mode 100644 modules/dom/event.scm create mode 100644 modules/dom/image.scm create mode 100644 modules/dom/media.scm create mode 100644 modules/dom/window.scm create mode 100644 modules/math.scm create mode 100644 modules/math/rect.scm create mode 100644 modules/math/vector.scm diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..f0d24dd --- /dev/null +++ b/Makefile @@ -0,0 +1,24 @@ +modules = \ + modules/dom/canvas.scm \ + modules/dom/document.scm \ + modules/dom/element.scm \ + modules/dom/event.scm \ + modules/dom/image.scm \ + modules/dom/media.scm \ + modules/dom/window.scm \ + modules/math.scm \ + modules/math/rect.scm \ + modules/math/vector.scm + +game.wasm: game.scm $(modules) + guild compile-wasm -L modules -o $@ $< + +serve: game.wasm + guile -c '((@ (hoot web-server) serve))' + +bundle: game.wasm + rm game.zip || true + zip game.zip -r assets/ js-runtime/ game.js game.css game.wasm index.html + +clean: + rm -f game.wasm game.zip diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6dd6bc --- /dev/null +++ b/README.md @@ -0,0 +1,35 @@ +# Cirkoban + +One of Spritely's entries into the 2024 Spring Lisp Game Jam! + +This game combines the Wireworld cellular automaton with Sokoban-style +gameplay! + +## Building + +The fastest way to get everything you need is to use [GNU +Guix](https://guix.gnu.org), a wonderful package manager written in +Scheme. + +Once you have Guix, the development environment with all necessary +dependencies can be created: + +``` +guix shell +``` + +To build the game, run: + +``` +make +``` + +To launch a development web server, run: + +``` +make serve +``` + +To check if the program works, visit https://localhost:8088 in your +web browser. We recommend using Mozilla Firefox or Google Chrome. +Hoot is not supported on Safari at this time. diff --git a/assets/images/AUTHORS b/assets/images/AUTHORS new file mode 100644 index 0000000..ad20298 --- /dev/null +++ b/assets/images/AUTHORS @@ -0,0 +1,5 @@ +All images by Kenney + +Licensed under CC0 + +https://kenney.nl/assets/puzzle-pack diff --git a/assets/images/ball.png b/assets/images/ball.png new file mode 100644 index 0000000000000000000000000000000000000000..d6db2be34bef4b95a4ab79bcea4081f86bbea69f GIT binary patch literal 583 zcmV-N0=WH&P)Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D0o+MMK~zXfrIkC1 z0znW!y}=&ABkYm(2p(aNu;3>!5D~<{z)8#}FcV=C69pacgP_6){=(jV%~Uhv&Zl=* zD0=!+ud1uc>PjW&*K9VcjYi``kDeC)MZ9>gny3F`z1?oVwOXyN21R;QMG~LsqEl#~ zUa!AvPeOr>$7ALDzM4*_YCfME;hVMLaA=L9gKj1qs{-{rPlaKqj>lu6V5r~k8w2QO zdagGE4&Y50@O3}oeUS26$LbR(2V9_06sha=YUFe}soid8J%?rBxG#xvx&GyHQP1bo z3UHiRE|(@GuuMqwq{ae?a`E=@c%;A>5j_}j;%gP^lSvQ+PT>3fo&qx$FpSu1AF!!c zSMd3KR=3;Dz!{lmfFnX-tSlA_wcT!=yv_>QX0tI2;ibTAkih+Z?`(jISglqDngUBG zD0bOoT(8&W28YAphY{A;O9Ih>A(dg1HNn{1`ISV`@iy0XB4ude`@%$AjKn-l3E{-7?&TnTL3N;xBxLP`K@jqmI^Kx&C z`v!N*zQqZS>Dj4g1?OzsRG4y%xn)hVNTFk!-tPT3^jP1kzPazIG&yaf=chwUs#mX? zcP3J1=GSc@toQC@9G9#7adhP-&z}K-M_%o`&y?0a`>H~tMCVjdh1jO{_o51+PQw4z zGh_$nt>Yfw%)d$4>qQoJy<$%`=z{I1FnU zCO>$c_Tsfui?x=byF#sXl7L}{gp7RiDxp(%%qT7xvrY)s4Ha3$~e@e7DYz$Nn{1`ISV`@iy0XB4ude`@%$AjKnXO3(v3%j_xe?qu<{{`hEb_Pq2U60!JbZ>n{fLB{x{H5>hM|Rvf7d~_E zn$HujhaB;Fv#d(Pq|rRG^KOgy@}!6B&oAv#x^^Iz+v3jhxMr3lm9Vge5>Cc_!3`mt zzCZReE%EGlSj{;>sj>OJZUdW~{N^Ry5<9YTn?5FR9N;>j-yk=kNZvu%g-d~LBmV)n zj+e|gIP3Zt_Vc;ySURbQ<qtFW9E(f8!K6V`>6br Q1cnrYr>mdKI;Vst02$eko&W#< literal 0 HcmV?d00001 diff --git a/assets/images/brick-red.png b/assets/images/brick-red.png new file mode 100644 index 0000000000000000000000000000000000000000..f0e1303d5d738179d57e155a41caa7a3782f3e43 GIT binary patch literal 365 zcmeAS@N?(olHy`uVBq!ia0vp^4nVBH!3HE3&8=$zQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1`ISV`@iy0XB4ude`@%$AjKn+ZuE{-7?&TnTr@--OAA zH9ghc?}4562JP<;Zf*N^u~dgELiW4LgP7~nnpqN9!a^8IIr-|s7(zMi{=_q0QEBDu3$C62`-;B$hC|x06##G-J~|cwEJWHK66V$^s^h z!|g(KECmhURehMMlJ#Qh`Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D19wS8K~!i%?V8VP z6G0Tm^CygnAb9Wxcn~}k1U(5J1P>nl2fTRm;LSt3B_JMpvWO_66#Rj;*)+8(h#(pv z*tEsiAkh-5rN*iirS(4;=R3P^vRRYO&ZwuC!cfZ2?#%ms_PxG+Z!(#LA2Bk$TZ|Tt z3eV4rVgJ7H?k$PYqU<*gkNMK~c@5ayPZxb18hVAY9?(?jo5Y~&wE#*w7~M8C0M!2_ z?$AwZof4EHd)C~XpOM=xR%G^CUGBNHEDx2|Hf!NJQ2o09;=uR&92 z3yq<5Ur1u4cuG%FL~|3%`o{e;$!|rk4OQQ+umc2}Z2-|+Xbi2Px$!2&qQjGfR~iz- zQ(FL(y;?J#bNtzs^`fUMKiI*ca1PsTAwgr~NlJU|Bw(SK7)Uy!P!|vrL377s%Q~#l z#>N)EumuF$Z2@6E!W}e+_WJ*l&h`pr4M=+rg|^$PdeYFt?@kM0vFZ96TR^bg1rY9m z<~r0V1}4_7`N;#Zm%mKXb$Z=y{I^@00rSKccY;%?k&2xDK>Vl zy1~|a2oyAMg#_)j7bzAd%u#wr^!_HiU9Wv+4+t8#0-`Vn7Q`j#PSfh=1dx)iOG-rw7?SZ0A(lr6(3Xu!e(|6B-a8Xk)7<&2IyW zB?&GMq*9Rga-BUOXh7dKE?WMXB!p>p_R^d)?Sqek@1~P4HrWG$2G9bUxB>!;Ad5qq zjn)GGIRC);f%{5djaxr{^^Pqh*iPXMnz#Z2iy*{>EePuUPzBre!6;ME#;(k-vIPX& zNe3$3*lKAi+p0&@uEmiT;e$1-WZOQNdPHX`8*Bl=b^wAFw)&Zhw&_^M8hE;`9oFQi zqHVLtf!X#KboKQcc938*c@MO((GGK=dAkPZ=exJM!-PS%9m0lP+J#UurwvXnzGnvr zHk19-K1ib-ildzkJCztD9~222cVXfHvbmcpson@GvR;^Lu>%B~0VrDZOacT=wbz)0 zKqUq7MqMvZgF_?sCz4X1>gjeW%I45W6q;^>>!U$-r&I=~dQ$2IZToo5Z+z1$RXD#B Qg#Z8m07*qoM6N<$f{D1o5C8xG literal 0 HcmV?d00001 diff --git a/assets/sounds/AUTHORS b/assets/sounds/AUTHORS new file mode 100644 index 0000000..5dca0e2 --- /dev/null +++ b/assets/sounds/AUTHORS @@ -0,0 +1,3 @@ +Sound effects hastily generated by David Thompson using sfxr + +Licensed under CC0 diff --git a/assets/sounds/brick.wav b/assets/sounds/brick.wav new file mode 100644 index 0000000000000000000000000000000000000000..9212400305007f94fb9f91168927d351ac59c740 GIT binary patch literal 8672 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 NMOG9={riUI{{jD`&l3Ou literal 0 HcmV?d00001 diff --git a/game.css b/game.css new file mode 100644 index 0000000..5ff196f --- /dev/null +++ b/game.css @@ -0,0 +1,11 @@ +body { + /* background-color: #000; */ + margin: 0; + width: 100vw; + height: 100vh; +} + +canvas { + display: block; + margin: 0 auto; +} diff --git a/game.js b/game.js new file mode 100644 index 0000000..89f0b12 --- /dev/null +++ b/game.js @@ -0,0 +1,77 @@ +window.addEventListener("load", async () => { + try { + await Scheme.load_main("game.wasm", {}, { + window: { + get: () => window, + innerWidth: () => window.innerWidth, + innerHeight: () => window.innerHeight, + requestAnimationFrame: (f) => window.requestAnimationFrame(f), + setTimeout: (f, delay) => window.setTimeout(f, delay) + }, + document: { + get: () => document, + body: () => document.body, + getElementById: (id) => document.getElementById(id), + createTextNode: (text) => document.createTextNode(text), + createElement: (tag) => document.createElement(tag) + }, + element: { + value: (elem) => elem.value, + setValue: (elem, value) => elem.value = value, + width: (elem) => elem.width, + height: (elem) => elem.height, + setWidth: (elem, width) => elem.width = width, + setHeight: (elem, height) => elem.height = height, + appendChild: (parent, child) => parent.appendChild(child), + setAttribute: (elem, name, value) => elem.setAttribute(name, value), + removeAttribute: (elem, name) => elem.removeAttribute(name), + remove: (elem) => elem.remove(), + replaceWith: (oldElem, newElem) => oldElem.replaceWith(newElem), + clone: (elem) => elem.cloneNode() + }, + event: { + addEventListener: (target, type, listener) => target.addEventListener(type, listener), + removeEventListener: (target, type, listener) => target.removeEventListener(type, listener), + preventDefault: (event) => event.preventDefault(), + keyboardCode: (event) => event.code + }, + image: { + new: (src) => { + const img = new Image(); + img.src = src; + return img; + } + }, + media: { + newAudio: (src) => new Audio(src), + play: (media) => media.play(), + pause: (media) => media.pause(), + volume: (media) => media.volume, + setVolume: (media, volume) => media.volume = volume, + setLoop: (media, loop) => media.loop = (loop == 1), + seek: (media, time) => media.currentTime = time + }, + canvas: { + getContext: (elem, type) => elem.getContext(type), + setFillColor: (ctx, color) => ctx.fillStyle = color, + setFont: (ctx, font) => ctx.font = font, + setTextAlign: (ctx, align) => ctx.textAlign = align, + clearRect: (ctx, x, y, w, h) => ctx.clearRect(x, y, w, h), + fillRect: (ctx, x, y, w, h) => ctx.fillRect(x, y, w, h), + fillText: (ctx, text, x, y) => ctx.fillText(text, x, y), + drawImage: (ctx, image, sx, sy, sw, sh, dx, dy, dw, dh) => ctx.drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh), + setScale: (ctx, sx, sy) => ctx.scale(sx, sy), + setTransform: (ctx, a, b, c, d, e, f) => ctx.setTransform(a, b, c, d, e, f), + setImageSmoothingEnabled: (ctx, enabled) => ctx.imageSmoothingEnabled = (enabled == 1) + }, + math: { + random: () => Math.random() + } + }); + } catch(e) { + if(e instanceof WebAssembly.CompileError) { + document.getElementById("wasm-error").hidden = false; + } + throw e; + } +}); diff --git a/game.scm b/game.scm new file mode 100644 index 0000000..4c1042c --- /dev/null +++ b/game.scm @@ -0,0 +1,356 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; Example game showing off several common game programming things. +;;; +;;; Code: + +(import (scheme base) + (scheme inexact) + (hoot debug) + (hoot ffi) + (hoot hashtables) + (hoot match) + (dom canvas) + (dom document) + (dom element) + (dom event) + (dom image) + (dom media) + (dom window) + (math) + (math rect) + (math vector)) + +;; Data types +(define-record-type + (make-brick-type image points) + brick-type? + (image brick-type-image) + (points brick-type-points)) + +(define-record-type + (make-brick type hitbox) + brick? + (type brick-type) + (hitbox brick-hitbox) + (broken? brick-broken? set-brick-broken!)) + +(define-record-type + (make-ball velocity hitbox) + ball? + (velocity ball-velocity) + (hitbox ball-hitbox)) + +(define-record-type + (make-paddle velocity hitbox) + paddle? + (velocity paddle-velocity) + (hitbox paddle-hitbox)) + +(define-record-type + (make-level state bricks ball paddle score move-left? move-right?) + level? + (state level-state set-level-state!) ; play, win, lose + (bricks level-bricks) + (ball level-ball) + (paddle level-paddle) + (score level-score set-level-score!) + (move-left? level-move-left? set-level-move-left!) + (move-right? level-move-right? set-level-move-right!)) + +;; Assets +(define image:paddle (make-image "assets/images/paddle.png")) +(define image:ball (make-image "assets/images/ball.png")) +(define image:brick-red (make-image "assets/images/brick-red.png")) +(define image:brick-green (make-image "assets/images/brick-green.png")) +(define image:brick-blue (make-image "assets/images/brick-blue.png")) +(define audio:brick (make-audio "assets/sounds/brick.wav")) +(define audio:paddle (make-audio "assets/sounds/paddle.wav")) + +;; Game data +(define game-width 640.0) +(define game-height 480.0) +(define brick-width 64.0) +(define brick-height 32.0) +(define ball-width 22.0) +(define ball-height 22.0) +(define paddle-width 104.0) +(define paddle-height 24.0) +(define paddle-speed 6.0) +(define brick:red (make-brick-type image:brick-red 10)) +(define brick:green (make-brick-type image:brick-green 20)) +(define brick:blue (make-brick-type image:brick-blue 30)) + +(define (make-brick* type x y) + (make-brick type (make-rect x y brick-width brick-height))) + +(define (make-brick-grid types) + (let* ((h (vector-length types)) + (w (vector-length (vector-ref types 0))) + (offset-x (/ (- game-width (* w brick-width)) 2.0)) + (offset-y 48.0) + (bricks (make-vector (* w h)))) + (do ((y 0 (+ y 1))) + ((= y h)) + (let ((row (vector-ref types y))) + (do ((x 0 (+ x 1))) + ((= x w)) + (vector-set! bricks (+ (* y w) x) + (make-brick* (vector-ref row x) + (+ offset-x (* x brick-width)) + (+ offset-y (* y brick-height))))))) + bricks)) + +(define (make-level-1) + (make-level 'play + (make-brick-grid + (vector + (vector brick:red brick:green brick:blue brick:red brick:green brick:blue brick:red brick:green) + (vector brick:green brick:blue brick:red brick:green brick:blue brick:red brick:green brick:blue) + (vector brick:blue brick:red brick:green brick:blue brick:red brick:green brick:blue brick:red) + (vector brick:red brick:green brick:blue brick:red brick:green brick:blue brick:red brick:green) + (vector brick:green brick:blue brick:red brick:green brick:blue brick:red brick:green brick:blue) + (vector brick:blue brick:red brick:green brick:blue brick:red brick:green brick:blue brick:red))) + (make-ball (vec2 1.0 3.0) + (make-rect (/ game-width 2.0) (/ game-height 2.0) + ball-width ball-height)) + (make-paddle (vec2 0.0 0.0) + (make-rect (- (/ game-width 2.0) + (/ paddle-width 2.0)) + (- game-height paddle-height 8.0) + paddle-width paddle-height)) + 0 #f #f)) + +;; Game state +(define *level* (make-level-1)) + +(define (level-clear? level) + (let ((bricks (level-bricks level))) + (let loop ((i 0)) + (if (< i (vector-length bricks)) + (if (brick-broken? (vector-ref bricks i)) + (loop (+ i 1)) + #f) + #t)))) + +(define (win! level) + (set-level-state! level 'win)) + +(define (lose! level) + (set-level-state! level 'lose)) + +(define (update-paddle-velocity! level) + (let ((speed (* paddle-speed + (+ (if (level-move-left? level) -1.0 0.0) + (if (level-move-right? level) 1.0 0.0))))) + (set-vec2-x! (paddle-velocity (level-paddle level)) speed))) + +(define (speed-up-ball! ball) + (let* ((v (ball-velocity ball)) + (speed (+ (vec2-magnitude v) (* (random) 0.1))) + ;; Also change its angle slightly. Not the proper Breakout + ;; behavior but I don't want to write the code for that. :) + (dir (+ (atan (vec2-y v) (vec2-x v)) + (- (* (random) 0.5) 0.25)))) + (set-vec2-x! v (* (cos dir) speed)) + (set-vec2-y! v (* (sin dir) speed)))) + +(define (reflect-ball! ball x? y?) + (let ((v (ball-velocity ball))) + (when x? (set-vec2-x! v (- (vec2-x v)))) + (when y? (set-vec2-y! v (- (vec2-y v)))))) + +(define (collide-ball! ball hitbox) + (let ((b-hitbox (ball-hitbox ball))) + (and (rect-intersects? b-hitbox hitbox) + (let ((overlap (rect-clip b-hitbox hitbox))) + ;; Resolve collision by adjusting the ball's position the + ;; minimum amount along the X or Y axis. + (if (< (rect-width overlap) (rect-height overlap)) + (begin + (reflect-ball! ball #t #f) + (if (= (rect-x b-hitbox) (rect-x overlap)) + (set-rect-x! b-hitbox (+ (rect-x b-hitbox) (rect-width overlap))) + (set-rect-x! b-hitbox (- (rect-x b-hitbox) (rect-width overlap))))) + (begin + (reflect-ball! ball #f #t) + (if (= (rect-y b-hitbox) (rect-y overlap)) + (set-rect-y! b-hitbox (+ (rect-y b-hitbox) (rect-height overlap))) + (set-rect-y! b-hitbox (- (rect-y b-hitbox) (rect-height overlap)))))))))) + +(define dt (/ 1000.0 60.0)) ; aim for updating at 60Hz +(define (update) + (match (level-state *level*) + ('play + (let* ((bricks (level-bricks *level*)) + (ball (level-ball *level*)) + (b-velocity (ball-velocity ball)) + (b-hitbox (ball-hitbox ball)) + (paddle (level-paddle *level*)) + (p-velocity (paddle-velocity paddle)) + (p-hitbox (paddle-hitbox paddle)) + (score (level-score *level*))) + ;; Move ball and paddle + (set-rect-x! b-hitbox (+ (rect-x b-hitbox) (vec2-x b-velocity))) + (set-rect-y! b-hitbox (+ (rect-y b-hitbox) (vec2-y b-velocity))) + ;; We only move the paddle along the x-axis. + (set-rect-x! p-hitbox + (clamp (+ (rect-x p-hitbox) (vec2-x p-velocity)) + 0.0 + (- game-width paddle-width))) + ;; Collide ball against walls, bricks, and paddle. + (cond + ((< (rect-x b-hitbox) 0.0) ; left wall + (set-rect-x! b-hitbox 0.0) + (reflect-ball! ball #t #f)) + ((> (+ (rect-x b-hitbox) (rect-width b-hitbox)) game-width) ; right wall + (set-rect-x! b-hitbox (- game-width (rect-width b-hitbox))) + (reflect-ball! ball #t #f)) + ((< (rect-y b-hitbox) 0.0) ; top wall + (set-rect-y! b-hitbox 0.0) + (reflect-ball! ball #f #t)) + ((> (+ (rect-y b-hitbox) (rect-height b-hitbox)) game-height) ; bottom wall + (lose! *level*)) + ((collide-ball! ball (paddle-hitbox paddle)) + (media-play audio:paddle) + (speed-up-ball! ball)) + (else + (let loop ((i 0) (hit? #f)) + (if (< i (vector-length bricks)) + (let ((brick (vector-ref bricks i))) + (if (and (not (brick-broken? brick)) + (collide-ball! ball (brick-hitbox brick))) + (begin + (media-play audio:brick) + (speed-up-ball! ball) + (set-brick-broken! brick #t) + (set-level-score! *level* + (+ (level-score *level*) + (brick-type-points (brick-type brick)))) + (loop (+ i 1) #t)) + (loop (+ i 1) hit?))) + ;; Maybe change to win state if all bricks are broken. + (when (and hit? (level-clear? *level*)) + (win! *level*)))))))) + (_ #t)) + (timeout update-callback dt)) +(define update-callback (procedure->external update)) + +;; Rendering +(define number->string* + (let ((cache (make-eq-hashtable))) ; assuming fixnums only + (lambda (x) + (or (hashtable-ref cache x) + (let ((str (number->string x))) + (hashtable-set! cache x str) + str))))) + +(define (draw prev-time) + (let ((bricks (level-bricks *level*)) + (ball (level-ball *level*)) + (paddle (level-paddle *level*)) + (score (level-score *level*))) + ;; Draw background + (set-fill-color! context "#140c1c") + (fill-rect context 0.0 0.0 game-width game-height) + ;; Draw bricks + (do ((i 0 (+ i 1))) + ((= i (vector-length bricks))) + (let* ((brick (vector-ref bricks i)) + (type (brick-type brick)) + (hitbox (brick-hitbox brick))) + (unless (brick-broken? brick) + (draw-image context (brick-type-image type) + 0.0 0.0 + brick-width brick-height + (rect-x hitbox) (rect-y hitbox) + brick-width brick-height)))) + ;; Draw paddle + (let ((w 104.0) + (h 24.0) + (hitbox (paddle-hitbox paddle))) + (draw-image context image:paddle + 0.0 0.0 w h + (rect-x hitbox) (rect-y hitbox) w h)) + ;; Draw ball + (let ((w 22.0) + (h 22.0) + (hitbox (ball-hitbox ball))) + (draw-image context image:ball + 0.0 0.0 w h + (rect-x hitbox) (rect-y hitbox) w h)) + ;; Print score + (set-fill-color! context "#ffffff") + (set-font! context "bold 24px monospace") + (set-text-align! context "left") + (fill-text context "SCORE:" 16.0 36.0) + (fill-text context (number->string* score) 108.0 36.0) + (match (level-state *level*) + ('win + (set-text-align! context "center") + (fill-text context "YAY YOU DID IT!!!" (/ game-width 2.0) (/ game-height 2.0))) + ('lose + (set-text-align! context "center") + (fill-text context "OH NO, GAME OVER :(" (/ game-width 2.0) (/ game-height 2.0))) + (_ #t))) + (request-animation-frame draw-callback)) +(define draw-callback (procedure->external draw)) + +;; Input +(define key:left "ArrowLeft") +(define key:right "ArrowRight") +(define key:confirm "Enter") + +(define (on-key-down event) + (let ((key (keyboard-event-code event))) + (match (level-state *level*) + ('play + (cond + ((string=? key key:left) + (set-level-move-left! *level* #t) + (update-paddle-velocity! *level*)) + ((string=? key key:right) + (set-level-move-right! *level* #t) + (update-paddle-velocity! *level*)))) + ((or 'win 'lose) + (when (string=? key key:confirm) + (set! *level* (make-level-1))))))) + +(define (on-key-up event) + (let ((key (keyboard-event-code event))) + (match (level-state *level*) + ('play + (cond + ((string=? key key:left) + (set-level-move-left! *level* #f) + (update-paddle-velocity! *level*)) + ((string=? key key:right) + (set-level-move-right! *level* #f) + (update-paddle-velocity! *level*)))) + (_ #t)))) + +;; Canvas and event loop setup +(define canvas (get-element-by-id "canvas")) +(define context (get-context canvas "2d")) +(set-element-width! canvas (exact game-width)) +(set-element-height! canvas (exact game-height)) +(add-event-listener! (current-document) "keydown" + (procedure->external on-key-down)) +(add-event-listener! (current-document) "keyup" + (procedure->external on-key-up)) +(request-animation-frame draw-callback) +(timeout update-callback dt) diff --git a/game.wasm b/game.wasm new file mode 100644 index 0000000000000000000000000000000000000000..0e4ad3a32fbc9a3ecb722edf0733ff0ec72fc0ae GIT binary patch literal 236482 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

74_%z}_yZP)AI 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 F{|myfp$7l} literal 0 HcmV?d00001 diff --git a/index.html b/index.html new file mode 100644 index 0000000..1c4ea28 --- /dev/null +++ b/index.html @@ -0,0 +1,17 @@ + + + + + + + + + + +

+ + diff --git a/js-runtime/reflect.js b/js-runtime/reflect.js new file mode 100644 index 0000000..1e66c50 --- /dev/null +++ b/js-runtime/reflect.js @@ -0,0 +1,710 @@ +// -*- js2-basic-offset: 4 -*- +class Char { + constructor(codepoint) { + this.codepoint = codepoint; + } + toString() { + let ch = String.fromCodePoint(this.codepoint); + if (ch.match(/[a-zA-Z0-9$[\]().]/)) return `#\\${ch}`; + return `#\\x${this.codepoint.toString(16)}`; + } +} +class Eof { toString() { return "#"; } } +class Nil { toString() { return "#nil"; } } +class Null { toString() { return "()"; } } +class Unspecified { toString() { return "#"; } } + +class Complex { + constructor(real, imag) { + this.real = real; + this.imag = imag; + } + toString() { + const sign = this.imag >= 0 && Number.isFinite(this.imag) ? "+": ""; + return `${flonum_to_string(this.real)}${sign}${flonum_to_string(this.imag)}i`; + } +} +class Fraction { + constructor(num, denom) { + this.num = num; + this.denom = denom; + } + toString() { + return `${this.num}/${this.denom}`; + } +} + +class HeapObject { + constructor(reflector, obj) { + this.reflector = reflector; + this.obj = obj; + } + repr() { return this.toString(); } // Default implementation. +} + +class Pair extends HeapObject { + toString() { return "#"; } + repr() { + let car_repr = repr(this.reflector.car(this)); + let cdr_repr = repr(this.reflector.cdr(this)); + if (cdr_repr == '()') + return `(${car_repr})`; + if (cdr_repr.charAt(0) == '(') + return `(${car_repr} ${cdr_repr.substring(1)}`; + return `(${car_repr} . ${cdr_repr})`; + } +} +class MutablePair extends Pair { toString() { return "#"; } } + +class Vector extends HeapObject { + toString() { return "#"; } + repr() { + let len = this.reflector.vector_length(this); + let out = '#('; + for (let i = 0; i < len; i++) { + if (i) out += ' '; + out += repr(this.reflector.vector_ref(this, i)); + } + out += ')'; + return out; + } +} +class MutableVector extends Vector { + toString() { return "#"; } +} + +class Bytevector extends HeapObject { + toString() { return "#"; } + repr() { + let len = this.reflector.bytevector_length(this); + let out = '#vu8('; + for (let i = 0; i < len; i++) { + if (i) out += ' '; + out += this.reflector.bytevector_ref(this, i); + } + out += ')'; + return out; + } +} +class MutableBytevector extends Bytevector { + toString() { return "#"; } +} + +class Bitvector extends HeapObject { + toString() { return "#"; } + repr() { + let len = this.reflector.bitvector_length(this); + let out = '#*'; + for (let i = 0; i < len; i++) { + out += this.reflector.bitvector_ref(this, i) ? '1' : '0'; + } + return out; + } +} +class MutableBitvector extends Bitvector { + toString() { return "#"; } +} + +class MutableString extends HeapObject { + toString() { return "#"; } + repr() { return string_repr(this.reflector.string_value(this)); } +} + +class Procedure extends HeapObject { + toString() { return "#"; } + call(...arg) { + return this.reflector.call(this, ...arg); + } +} + +class Sym extends HeapObject { + toString() { return "#"; } + repr() { return this.reflector.symbol_name(this); } +} + +class Keyword extends HeapObject { + toString() { return "#"; } + repr() { return `#:${this.reflector.keyword_name(this)}`; } +} + +class Variable extends HeapObject { toString() { return "#"; } } +class AtomicBox extends HeapObject { toString() { return "#"; } } +class HashTable extends HeapObject { toString() { return "#"; } } +class WeakTable extends HeapObject { toString() { return "#"; } } +class Fluid extends HeapObject { toString() { return "#"; } } +class DynamicState extends HeapObject { toString() { return "#"; } } +class Syntax extends HeapObject { toString() { return "#"; } } +class Port extends HeapObject { toString() { return "#"; } } +class Struct extends HeapObject { toString() { return "#"; } } + +function instantiate_streaming(path, imports) { + if (typeof fetch !== 'undefined') + return WebAssembly.instantiateStreaming(fetch(path), imports); + let bytes; + if (typeof read !== 'undefined') { + bytes = read(path, 'binary'); + } else if (typeof readFile !== 'undefined') { + bytes = readFile(path); + } else { + let fs = require('fs'); + bytes = fs.readFileSync(path); + } + return WebAssembly.instantiate(bytes, imports); +} + +class Scheme { + #instance; + #abi; + constructor(instance, abi) { + this.#instance = instance; + this.#abi = abi; + } + + static async reflect(abi) { + let debug = { + debug_str(x) { console.log(`reflect debug: ${x}`); }, + debug_str_i32(x, y) { console.log(`reflect debug: ${x}: ${y}`); }, + debug_str_scm: (x, y) => { + console.log(`reflect debug: ${x}: #`); + }, + }; + let { module, instance } = + await instantiate_streaming('js-runtime/reflect.wasm', { + abi, + debug, + rt: { + die(tag, data) { throw new SchemeTrapError(tag, data); }, + wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); }, + string_to_wtf8(str) { return string_to_wtf8(str); }, + } + }); + return new Scheme(instance, abi); + } + + #init_module(mod) { + mod.set_debug_handler({ + debug_str(x) { console.log(`debug: ${x}`); }, + debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); }, + debug_str_scm: (x, y) => { + console.log(`debug: ${x}: ${repr(this.#to_js(y))}`); + }, + }); + mod.set_ffi_handler({ + procedure_to_extern: (obj) => { + const proc = this.#to_js(obj); + return (...args) => { + return proc.call(...args); + }; + } + }); + let proc = new Procedure(this, mod.get_export('$load').value); + return proc.call(); + } + static async load_main(path, abi, user_imports = {}) { + let mod = await SchemeModule.fetch_and_instantiate(path, abi, user_imports); + let reflect = await mod.reflect(); + return reflect.#init_module(mod); + } + async load_extension(path, user_imports = {}) { + let mod = await SchemeModule.fetch_and_instantiate(path, this.#abi, user_imports); + return this.#init_module(mod); + } + + #to_scm(js) { + let api = this.#instance.exports; + if (typeof(js) == 'number') { + return api.scm_from_f64(js); + } else if (typeof(js) == 'bigint') { + if (BigInt(api.scm_most_negative_fixnum()) <= js + && js <= BigInt(api.scm_most_positive_fixnum())) + return api.scm_from_fixnum(Number(js)); + return api.scm_from_bignum(js); + } else if (typeof(js) == 'boolean') { + return js ? api.scm_true() : api.scm_false(); + } else if (typeof(js) == 'string') { + return api.scm_from_string(js); + } else if (typeof(js) == 'object') { + if (js instanceof Eof) return api.scm_eof(); + if (js instanceof Nil) return api.scm_nil(); + if (js instanceof Null) return api.scm_null(); + if (js instanceof Unspecified) return api.scm_unspecified(); + if (js instanceof Char) return api.scm_from_char(js.codepoint); + if (js instanceof HeapObject) return js.obj; + if (js instanceof Fraction) + return api.scm_from_fraction(this.#to_scm(js.num), + this.#to_scm(js.denom)); + if (js instanceof Complex) + return api.scm_from_complex(js.real, js.imag); + return api.scm_from_extern(js); + } else { + throw new Error(`unexpected; ${typeof(js)}`); + } + } + + #to_js(scm) { + let api = this.#instance.exports; + let descr = api.describe(scm); + let handlers = { + fixnum: () => BigInt(api.fixnum_value(scm)), + char: () => new Char(api.char_value(scm)), + true: () => true, + false: () => false, + eof: () => new Eof, + nil: () => new Nil, + null: () => new Null, + unspecified: () => new Unspecified, + flonum: () => api.flonum_value(scm), + bignum: () => api.bignum_value(scm), + complex: () => new Complex(api.complex_real(scm), + api.complex_imag(scm)), + fraction: () => new Fraction(this.#to_js(api.fraction_num(scm)), + this.#to_js(api.fraction_denom(scm))), + pair: () => new Pair(this, scm), + 'mutable-pair': () => new MutablePair(this, scm), + vector: () => new Vector(this, scm), + 'mutable-vector': () => new MutableVector(this, scm), + bytevector: () => new Bytevector(this, scm), + 'mutable-bytevector': () => new MutableBytevector(this, scm), + bitvector: () => new Bitvector(this, scm), + 'mutable-bitvector': () => new MutableBitvector(this, scm), + string: () => api.string_value(scm), + 'mutable-string': () => new MutableString(this, scm), + procedure: () => new Procedure(this, scm), + symbol: () => new Sym(this, scm), + keyword: () => new Keyword(this, scm), + variable: () => new Variable(this, scm), + 'atomic-box': () => new AtomicBox(this, scm), + 'hash-table': () => new HashTable(this, scm), + 'weak-table': () => new WeakTable(this, scm), + fluid: () => new Fluid(this, scm), + 'dynamic-state': () => new DynamicState(this, scm), + syntax: () => new Syntax(this, scm), + port: () => new Port(this, scm), + struct: () => new Struct(this, scm), + 'extern-ref': () => api.extern_value(scm) + }; + let handler = handlers[descr]; + return handler ? handler() : scm; + } + + call(func, ...args) { + let api = this.#instance.exports; + let argv = api.make_vector(args.length + 1, api.scm_false()); + func = this.#to_scm(func); + api.vector_set(argv, 0, func); + for (let [idx, arg] of args.entries()) + api.vector_set(argv, idx + 1, this.#to_scm(arg)); + argv = api.call(func, argv); + let results = []; + for (let idx = 0; idx < api.vector_length(argv); idx++) + results.push(this.#to_js(api.vector_ref(argv, idx))) + return results; + } + + car(x) { return this.#to_js(this.#instance.exports.car(x.obj)); } + cdr(x) { return this.#to_js(this.#instance.exports.cdr(x.obj)); } + + vector_length(x) { return this.#instance.exports.vector_length(x.obj); } + vector_ref(x, i) { + return this.#to_js(this.#instance.exports.vector_ref(x.obj, i)); + } + + bytevector_length(x) { + return this.#instance.exports.bytevector_length(x.obj); + } + bytevector_ref(x, i) { + return this.#instance.exports.bytevector_ref(x.obj, i); + } + + bitvector_length(x) { + return this.#instance.exports.bitvector_length(x.obj); + } + bitvector_ref(x, i) { + return this.#instance.exports.bitvector_ref(x.obj, i) == 1; + } + + string_value(x) { return this.#instance.exports.string_value(x.obj); } + symbol_name(x) { return this.#instance.exports.symbol_name(x.obj); } + keyword_name(x) { return this.#instance.exports.keyword_name(x.obj); } +} + +class SchemeTrapError extends Error { + constructor(tag, data) { super(); this.tag = tag; this.data = data; } + // FIXME: data is raw Scheme object; would need to be reflected to + // have a toString. + toString() { return `SchemeTrap(${this.tag}, )`; } +} + +function string_repr(str) { + // FIXME: Improve to match Scheme. + return '"' + str.replace(/(["\\])/g, '\\$1').replace(/\n/g, '\\n') + '"'; +} + +function flonum_to_string(f64) { + if (Object.is(f64, -0)) { + return '-0.0'; + } else if (Number.isFinite(f64)) { + let repr = f64 + ''; + return /^-?[0-9]+$/.test(repr) ? repr + '.0' : repr; + } else if (Number.isNaN(f64)) { + return '+nan.0'; + } else { + return f64 < 0 ? '-inf.0' : '+inf.0'; + } +} + +let wtf8_helper; + +function wtf8_to_string(wtf8) { + let { as_iter, iter_next } = wtf8_helper.exports; + let codepoints = []; + let iter = as_iter(wtf8); + for (let cp = iter_next(iter); cp != -1; cp = iter_next(iter)) + codepoints.push(cp); + + // Passing too many codepoints can overflow the stack. + let maxcp = 100000; + if (codepoints.length <= maxcp) { + return String.fromCodePoint(...codepoints); + } + + // For converting large strings, concatenate several smaller + // strings. + let substrings = []; + let end = 0; + for (let start = 0; start != codepoints.length; start = end) { + end = Math.min(start + maxcp, codepoints.length); + substrings.push(String.fromCodePoint(...codepoints.slice(start, end))); + } + return substrings.join(''); +} + +function string_to_wtf8(str) { + let { string_builder, builder_push_codepoint, finish_builder } = + wtf8_helper.exports; + let builder = string_builder() + for (let cp of str) + builder_push_codepoint(builder, cp.codePointAt(0)); + return finish_builder(builder); +} + +async function load_wtf8_helper_module() { + if (wtf8_helper) return; + let { module, instance } = await instantiate_streaming("js-runtime/wtf8.wasm"); + wtf8_helper = instance; +} + +class SchemeModule { + #instance; + #io_handler; + #debug_handler; + #ffi_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; }, + bignum_is_i64(n) { + return -0x8000_0000_0000_0000n <= n && n <= 0x7FFF_FFFF_FFFF_FFFFn; + }, + bignum_is_u64(n) { + return 0n <= n && n <= 0xFFFF_FFFF_FFFF_FFFFn; + }, + // This truncates; see https://tc39.es/ecma262/#sec-tobigint64. + bignum_get_i64(n) { return n; }, + + bignum_add(a, b) { return BigInt(a) + BigInt(b) }, + bignum_sub(a, b) { return BigInt(a) - BigInt(b) }, + bignum_mul(a, b) { return BigInt(a) * BigInt(b) }, + bignum_lsh(a, b) { return BigInt(a) << BigInt(b) }, + bignum_rsh(a, b) { return BigInt(a) >> BigInt(b) }, + bignum_quo(a, b) { return BigInt(a) / BigInt(b) }, + bignum_rem(a, b) { return BigInt(a) % BigInt(b) }, + bignum_mod(a, b) { + let r = BigInt(a) % BigInt(b); + if ((b > 0n && r < 0n) || (b < 0n && r > 0n)) { + return b + r; + } else { + return r; + } + }, + bignum_gcd(a, b) { + a = BigInt(a); + b = BigInt(b); + if (a < 0n) { a = -a; } + if (b < 0n) { b = -b; } + if (a == 0n) { return b; } + if (b == 0n) { return a; } + + let r; + while (b != 0n) { + r = a % b; + a = b; + b = r; + } + return a; + }, + + bignum_logand(a, b) { return BigInt(a) & BigInt(b); }, + bignum_logior(a, b) { return BigInt(a) | BigInt(b); }, + bignum_logxor(a, b) { return BigInt(a) ^ BigInt(b); }, + bignum_logsub(a, b) { return BigInt(a) & (~ BigInt(b)); }, + + bignum_lt(a, b) { return a < b; }, + bignum_le(a, b) { return a <= b; }, + bignum_eq(a, b) { return a == b; }, + + bignum_to_f64(n) { return Number(n); }, + + f64_is_nan(n) { return Number.isNaN(n); }, + f64_is_infinite(n) { return !Number.isFinite(n); }, + + flonum_to_string, + + string_upcase: Function.call.bind(String.prototype.toUpperCase), + string_downcase: Function.call.bind(String.prototype.toLowerCase), + + make_weak_map() { return new WeakMap; }, + weak_map_get(map, k, fail) { + const val = map.get(k); + return val === undefined ? fail: val; + }, + weak_map_set(map, k, v) { return map.set(k, v); }, + weak_map_delete(map, k) { return map.delete(k); }, + + fsqrt: Math.sqrt, + fsin: Math.sin, + fcos: Math.cos, + ftan: Math.tan, + fasin: Math.asin, + facos: Math.acos, + fatan: Math.atan, + fatan2: Math.atan2, + flog: Math.log, + fexp: Math.exp, + + jiffies_per_second() { return 1000000; }, + current_jiffy() { return performance.now() * 1000; }, + current_second() { return Date.now() / 1000; }, + + // Wrap in functions to allow for lazy loading of the wtf8 + // module. + wtf8_to_string(wtf8) { return wtf8_to_string(wtf8); }, + string_to_wtf8(str) { return string_to_wtf8(str); }, + + die(tag, data) { throw new SchemeTrapError(tag, data); } + }; + + constructor(instance) { + this.#instance = instance; + if (typeof printErr === 'function') { // v8/sm dev console + // On the console, try to use 'write' (v8) or 'putstr' (sm), + // as these don't add an extraneous newline. Unfortunately + // JSC doesn't have a printer that doesn't add a newline. + let write_no_newline = + typeof write === 'function' ? write + : typeof putstr === 'function' ? putstr : print; + // Use readline when available. v8 strips newlines so + // we need to add them back. + let read_stdin = + typeof readline == 'function' ? () => { + let line = readline(); + if (line) { + return `${line}\n`; + } else { + return '\n'; + } + }: () => ''; + let delete_file = (filename) => false; + this.#io_handler = { + write_stdout: write_no_newline, + write_stderr: printErr, + read_stdin, + file_exists: (filename) => false, + open_input_file: (filename) => {}, + open_output_file: (filename) => {}, + close_file: () => undefined, + read_file: (handle, length) => 0, + write_file: (handle, length) => 0, + seek_file: (handle, offset, whence) => -1, + file_random_access: (handle) => false, + file_buffer_size: (handle) => 0, + file_buffer_ref: (handle, i) => 0, + file_buffer_set: (handle, i, x) => undefined, + delete_file: (filename) => undefined + }; + } else if (typeof window !== 'undefined') { // web browser + this.#io_handler = { + write_stdout: console.log, + write_stderr: console.error, + read_stdin: () => '', + file_exists: (filename) => false, + open_input_file: (filename) => {}, + open_output_file: (filename) => {}, + close_file: () => undefined, + read_file: (handle, length) => 0, + write_file: (handle, length) => 0, + seek_file: (handle, offset, whence) => -1, + file_random_access: (handle) => false, + file_buffer_size: (handle) => 0, + file_buffer_ref: (handle, i) => 0, + file_buffer_set: (handle, i, x) => undefined, + delete_file: (filename) => undefined + }; + } else { // nodejs + const fs = require('fs'); + const process = require('process'); + const bufLength = 1024; + const stdinBuf = Buffer.alloc(bufLength); + const SEEK_SET = 0, SEEK_CUR = 1, SEEK_END = 2; + this.#io_handler = { + write_stdout: console.log, + write_stderr: console.error, + read_stdin: () => { + let n = fs.readSync(process.stdin.fd, stdinBuf, 0, stdinBuf.length); + return stdinBuf.toString('utf8', 0, n); + }, + file_exists: fs.existsSync.bind(fs), + open_input_file: (filename) => { + let fd = fs.openSync(filename, 'r'); + return { + fd, + buf: Buffer.alloc(bufLength), + pos: 0 + }; + }, + open_output_file: (filename) => { + let fd = fs.openSync(filename, 'w'); + return { + fd, + buf: Buffer.alloc(bufLength), + pos: 0 + }; + }, + close_file: (handle) => { + fs.closeSync(handle.fd); + }, + read_file: (handle, count) => { + const n = fs.readSync(handle.fd, handle.buf, 0, count, handle.pos); + handle.pos += n; + return n; + }, + write_file: (handle, count) => { + const n = fs.writeSync(handle.fd, handle.buf, 0, count, handle.pos); + handle.pos += n; + return n; + }, + seek_file: (handle, offset, whence) => { + // There doesn't seem to be a way to ask NodeJS if + // a position is valid or not. + if (whence == SEEK_SET) { + handle.pos = offset; + return handle.pos; + } else if (whence == SEEK_CUR) { + handle.pos += offset; + return handle.pos; + } + + // SEEK_END not supported. + return -1; + }, + file_random_access: (handle) => { + return true; + }, + file_buffer_size: (handle) => { + return handle.buf.length; + }, + file_buffer_ref: (handle, i) => { + return handle.buf[i]; + }, + file_buffer_set: (handle, i, x) => { + handle.buf[i] = x; + }, + delete_file: fs.rmSync.bind(fs) + }; + } + this.#debug_handler = { + debug_str(x) { console.log(`debug: ${x}`); }, + debug_str_i32(x, y) { console.log(`debug: ${x}: ${y}`); }, + debug_str_scm(x, y) { console.log(`debug: ${x}: #`); }, + }; + } + static async fetch_and_instantiate(path, imported_abi, user_imports = {}) { + await load_wtf8_helper_module(); + let io = { + write_stdout(str) { mod.#io_handler.write_stdout(str); }, + write_stderr(str) { mod.#io_handler.write_stderr(str); }, + read_stdin() { return mod.#io_handler.read_stdin(); }, + file_exists(filename) { return mod.#io_handler.file_exists(filename); }, + open_input_file(filename) { return mod.#io_handler.open_input_file(filename); }, + open_output_file(filename) { return mod.#io_handler.open_output_file(filename); }, + close_file(handle) { mod.#io_handler.close_file(handle); }, + read_file(handle, length) { return mod.#io_handler.read_file(handle, length); }, + write_file(handle, length) { return mod.#io_handler.write_file(handle, length); }, + seek_file(handle, offset, whence) { return mod.#io_handler.seek_file(handle, offset, whence); }, + file_random_access(handle) { return mod.#io_handler.file_random_access(handle); }, + file_buffer_size(handle) { return mod.#io_handler.file_buffer_size(handle); }, + file_buffer_ref(handle, i) { return mod.#io_handler.file_buffer_ref(handle, i); }, + file_buffer_set(handle, i, x) { return mod.#io_handler.file_buffer_set(handle, i, x); }, + delete_file(filename) { mod.#io_handler.delete_file(filename); } + }; + let debug = { + debug_str(x) { mod.#debug_handler.debug_str(x); }, + debug_str_i32(x, y) { mod.#debug_handler.debug_str_i32(x, y); }, + debug_str_scm(x, y) { mod.#debug_handler.debug_str_scm(x, y); }, + } + let ffi = { + procedure_to_extern(proc) { + return mod.#ffi_handler.procedure_to_extern(proc); + } + }; + let imports = { + rt: SchemeModule.#rt, + abi: imported_abi, + debug, io, ffi, ...user_imports + }; + let { module, instance } = await instantiate_streaming(path, imports); + let mod = new SchemeModule(instance); + return mod; + } + set_io_handler(h) { this.#io_handler = h; } + set_debug_handler(h) { this.#debug_handler = h; } + set_ffi_handler(h) { this.#ffi_handler = h; } + all_exports() { return this.#instance.exports; } + exported_abi() { + let abi = {} + for (let [k, v] of Object.entries(this.all_exports())) { + if (k.startsWith("$")) + abi[k] = v; + } + return abi; + } + exports() { + let ret = {} + for (let [k, v] of Object.entries(this.all_exports())) { + if (!k.startsWith("$")) + ret[k] = v; + } + return ret; + } + get_export(name) { + if (name in this.all_exports()) + return this.all_exports()[name]; + throw new Error(`unknown export: ${name}`) + } + async reflect() { + return await Scheme.reflect(this.exported_abi()); + } +} + +function repr(obj) { + if (obj instanceof HeapObject) + return obj.repr(); + if (typeof obj === 'boolean') + return obj ? '#t' : '#f'; + if (typeof obj === 'number') + return flonum_to_string(obj); + if (typeof obj === 'string') + return string_repr(obj); + return obj + ''; +} diff --git a/js-runtime/reflect.wasm b/js-runtime/reflect.wasm new file mode 100644 index 0000000000000000000000000000000000000000..770c94c16c0500eca9e63e50db1e656495e0355c GIT binary patch literal 5196 zcmds4S##Ug6}}f6mkW>}MNzv92#~j!@e;?5vkH!_CTW_w`yPrSAej~yg@j~###8ps z^eNLm_O1O5u7hd+LtpyRmku@4e&+(DXydR)U-n7k0c-Lxf57#oGUn|8BP0bm5`-j>=$QoSm_K7P2nYD16_CR( z{vL4iEF-;!Dr`#99iddF0NGMTNO~BvkONoAL55RX2Dde7ODL6TGf%Y{T9_dUB(+Ns zdE_#VRGQ{V38wKAT9K=DDN8fUlob+@<(L0zknqrwzcPDiaK>l*GU9M4A0Szsjixqb zB=0q`VXBmSz}{~fFN>t%W;6E{sZ^YA=D$uMbr+h2Z_r8g#b)uF)zae9w_05OR*O2N z9BM3FV_}_P-caX8QvoIx&=@oNK9TjMzLFNk)Vf9!jo~|t?HJk2hT53zDB%FL-r;SA z5d|6W5F8Y;Q*4LvF<3AVKnL=ShXshku9SD-0rSs8v7z z5vil1WNLD&a3Lj&DKS$E4Ur*C9+r31una_zma;fXaIA)vu?*kFK*F<)UnQ)jOjU{u zWRqsp+tFt^lcRcA;$aD^)@V01TuD>tK?TU$CR9E0k-F2Ry9U(+ES&Jz9^?vZHqIl%4a#Xs>nDjT?uZr^_6&Yd^kdix#udH21o;O^b`@7=$@z5SgJKK$sT z?|%HfPrm=)2S5DLkAL!0P1A1t?B~Du<*$By_cxz5Z`{~v?Y5E34dWmDLYwjDO#I*e zs{FYs#ynsm_fPFoK8!}~VP`jD!6KU5>g|{!fhyr;^>yJ}obrr#824GEtCht2>IAnIqiqbeWZx=&0LeB?;r;I=xJ|v&~5fxy@=g252B}!2g5LB@2hR3=&J2-$Tmc;br3a=qjo$PGQXID z<|vBU=9C#md+fZGX}VE=KYqwAly;xS(HZNDX9cw4Qss_#%S7_l5|N^{NJLl*L@aBb zh-uY{l&v`;C96iHVpWLPR+Y${RVGrkY$EfPMWkkxh}12U$buz^ELue(OICr%vXv*Y zV&#aeS_Y9dODD2!X++LhNPJRrEQN?`aUup)#XFyvE5ch$%w^$uiMb@a)x=yB-g06t z2=7W_)`fR5G3SN1l$bT)T~5q7;k}ZWRpHeWvm(55V%oxU6SFM5jl?VouauaU@T|l% zg;z;TA-q=;vnV_}F$=D)%w@OI`fQxANYocF*@*(FqO|yJ+{f`HL=9(8-W4=#vchOlX`uk z)JfwoFX!d4ig|@e{mMkSqS&v$xIST5?YCcCvpsP;?V)bpP`3;uoceXNnwDdZ{>Wn= zX@6%0uDhD6x{AwPhQ{ObZ?4h-EmZj!aiV`NBA%?eiu_gOulBVy)hFdISJl9a+JPKJ z{ZK=JCuLg`sI&nA%57jW&NFZ*wBZ<}pOqRapUYAcQV8jUWcf8hD1siSgfN+Upa8j2 zMg!+cJ4Q$mFRt1iIj71}KdFZHYu>fQ)t{^WHFQLuuo4E^!~2)iYuvsi);NyrR5<&# zt2*jmwXo+XIvT1_rS>#AXgJX;A*?3KiUKQczX5~tp-VfZ5g}a-|0nKiThGlhMr=|y zDOq^o1a<5U=sduY)=9wF^CX$+*U#w5{@j;%C*%L6;Hd}- zlLBMD05X8MZvs_-6yM5Zs$a^G=9htr(CWSoL~9Md0;B=u{3?(D%KLLbXy%4rKZZnVsb?0+HvEzXW6fmHlO)GLY@BWOfyQ6^M4L`fETc(44=X$!h*N zpb}*Bz5_(@)qOXUE%+Om-J3V5<7DX7uHs<)27>T9PzThKO8)6$Xl$uO(Kqd+%vD@qc=2)6I>>@D(hCJIw&DTB hwmW#j^T(}WM~1A+yD!gWcLQFXMslW9=2S+p{ulE8lm-9* literal 0 HcmV?d00001 diff --git a/js-runtime/wtf8.wasm b/js-runtime/wtf8.wasm new file mode 100644 index 0000000000000000000000000000000000000000..ca1079dcdb787237c463525b72e85fd7fe3824f9 GIT binary patch literal 1071 zcmc&zy>in)5Z>K8Np{Y$ND9@bhJqnALxnB{8Hx-uJOFY)1|+~qY(3{)&9`^o?zcZD!P(+70suZ8zP`qiDgYY*Elz~BmaLs%1y~^iqMfiy z6ROApUl3CwiG_^fWDp4iOkPFF*`lo5=^SwAp`1;xTcGixo!7JX<(sSeQ^lmnetG4^ z)#5|>_Pm;2oY%7!manifest (list guile-next guile-hoot gnu-make zip)) diff --git a/modules/dom/canvas.scm b/modules/dom/canvas.scm new file mode 100644 index 0000000..57bd7fc --- /dev/null +++ b/modules/dom/canvas.scm @@ -0,0 +1,72 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; HTMLCanvasElement and CanvasRenderingContext2D bindings. +;;; +;;; Code: + +(define-module (dom canvas) + #:pure + #:use-module (scheme base) + #:use-module (hoot ffi) + #:export (get-context + set-fill-color! + set-font! + set-text-align! + clear-rect + fill-rect + fill-text + draw-image + set-scale! + set-transform! + set-image-smoothing-enabled!)) + +;; HTMLCanvasElement +(define-foreign get-context + "canvas" "getContext" + (ref extern) (ref string) -> (ref extern)) + +;; CanvasRenderingContext2D +(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) diff --git a/modules/dom/document.scm b/modules/dom/document.scm new file mode 100644 index 0000000..6fbbc1b --- /dev/null +++ b/modules/dom/document.scm @@ -0,0 +1,45 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; Document bindings. +;;; +;;; Code: + +(define-module (dom document) + #:pure + #:use-module (scheme base) + #:use-module (hoot ffi) + #:export (current-document + document-body + get-element-by-id + make-text-node + make-element)) + +(define-foreign current-document + "document" "get" + -> (ref extern)) +(define-foreign document-body + "document" "body" + -> (ref null 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)) diff --git a/modules/dom/element.scm b/modules/dom/element.scm new file mode 100644 index 0000000..539b5d5 --- /dev/null +++ b/modules/dom/element.scm @@ -0,0 +1,71 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; Element bindings. +;;; +;;; Code: + +(define-module (dom element) + #:pure + #:use-module (scheme base) + #:use-module (hoot ffi) + #:export (element-value + set-element-value! + element-width set-element-width! + element-height set-element-height! + append-child! + remove! + replace-with! + set-attribute! + remove-attribute! + clone-element)) + +(define-foreign element-value + "element" "value" + (ref extern) -> (ref string)) +(define-foreign set-element-value! + "element" "setValue" + (ref extern) (ref string) -> none) +(define-foreign element-width + "element" "width" + (ref extern) -> i32) +(define-foreign element-height + "element" "height" + (ref extern) -> i32) +(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 clone-element + "element" "clone" + (ref extern) -> (ref extern)) diff --git a/modules/dom/event.scm b/modules/dom/event.scm new file mode 100644 index 0000000..65594fb --- /dev/null +++ b/modules/dom/event.scm @@ -0,0 +1,46 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; EventTarget and Event bindings. +;;; +;;; Code: + +(define-module (dom event) + #:pure + #:use-module (scheme base) + #:use-module (hoot ffi) + #:export (add-event-listener! + remove-event-listener! + prevent-default! + keyboard-event-code)) + +;; EventTarget +(define-foreign add-event-listener! + "event" "addEventListener" + (ref extern) (ref string) (ref extern) -> none) +(define-foreign remove-event-listener! + "event" "removeEventListener" + (ref extern) (ref string) (ref extern) -> none) + +;; Event +(define-foreign prevent-default! + "event" "preventDefault" + (ref extern) -> none) + +;; KeyboardEvent +(define-foreign keyboard-event-code + "event" "keyboardCode" + (ref extern) -> (ref string)) diff --git a/modules/dom/image.scm b/modules/dom/image.scm new file mode 100644 index 0000000..c85f205 --- /dev/null +++ b/modules/dom/image.scm @@ -0,0 +1,29 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; HTMLImageElement bindings. +;;; +;;; Code: + +(define-module (dom image) + #:pure + #:use-module (scheme base) + #:use-module (hoot ffi) + #:export (make-image)) + +(define-foreign make-image + "image" "new" + (ref string) -> (ref extern)) diff --git a/modules/dom/media.scm b/modules/dom/media.scm new file mode 100644 index 0000000..a0acd2a --- /dev/null +++ b/modules/dom/media.scm @@ -0,0 +1,54 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; HTMLMediaElement bindings. +;;; +;;; Code: + +(library (dom media) + (export make-audio + media-play + media-pause + media-volume + set-media-volume! + set-media-loop! + media-seek) + (import (scheme base) + (hoot ffi) + (hoot match) + (only (hoot syntax) define*)) + + (define-foreign make-audio + "media" "newAudio" + (ref string) -> (ref extern)) + (define-foreign media-play + "media" "play" + (ref extern) -> none) + (define-foreign media-pause + "media" "pause" + (ref extern) -> none) + (define-foreign media-volume + "media" "volume" + (ref extern) -> f64) + (define-foreign set-media-volume! + "media" "setVolume" + (ref extern) f64 -> none) + (define-foreign set-media-loop! + "media" "setLoop" + (ref extern) i32 -> none) + (define-foreign media-seek + "media" "seek" + (ref extern) f64 -> none)) diff --git a/modules/dom/window.scm b/modules/dom/window.scm new file mode 100644 index 0000000..edb2b04 --- /dev/null +++ b/modules/dom/window.scm @@ -0,0 +1,45 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; Window bindings. +;;; +;;; Code: + +(define-module (dom window) + #:pure + #:use-module (scheme base) + #:use-module (hoot ffi) + #:export (current-window + window-inner-width + window-inner-height + request-animation-frame + timeout)) + +(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 extern) -> none) +(define-foreign timeout + "window" "setTimeout" + (ref extern) f64 -> i32) diff --git a/modules/math.scm b/modules/math.scm new file mode 100644 index 0000000..2f8c4f9 --- /dev/null +++ b/modules/math.scm @@ -0,0 +1,49 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; Helpful math things. +;;; +;;; Code: + +;; (library (math) +;; (export random +;; clamp) +;; (import (scheme base) +;; (hoot ffi)) + +;; (define-foreign random +;; "math" "random" +;; -> f64) + +;; (define (clamp x min max) +;; (cond ((< x min) min) +;; ((> x max) max) +;; (else x)))) + +(define-module (math) + #:pure + #:use-module (scheme base) + #:use-module (hoot ffi) + #:export (random clamp)) + +(define-foreign random + "math" "random" + -> f64) + +(define (clamp x min max) + (cond ((< x min) min) + ((> x max) max) + (else x))) diff --git a/modules/math/rect.scm b/modules/math/rect.scm new file mode 100644 index 0000000..35b846f --- /dev/null +++ b/modules/math/rect.scm @@ -0,0 +1,96 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; Rectangle data type. +;;; +;;; Code: + +(define-module (math rect) + #:pure + #:use-module (scheme base) + #:use-module ((hoot bytevectors) + #:select + (bytevector-ieee-double-native-ref + bytevector-ieee-double-native-set!)) + #:export (make-rect + rect? + rect-x + rect-y + rect-width + rect-height + set-rect-x! + set-rect-y! + set-rect-width! + set-rect-height! + rect-intersects? + rect-clip)) + +;; For speed, a rect is a wrapper around a bytevector so that we can +;; use unboxed floats. +(define-record-type + (%make-rect bv) + rect? + (bv rect-bv)) + +(define f64-ref bytevector-ieee-double-native-ref) +(define f64-set! bytevector-ieee-double-native-set!) + +(define (make-rect x y w h) + (let ((bv (make-bytevector (* 8 4)))) + (f64-set! bv 0 x) + (f64-set! bv 8 y) + (f64-set! bv 16 w) + (f64-set! bv 24 h) + (%make-rect bv))) + +(define (rect-x r) + (f64-ref (rect-bv r) 0)) + +(define (rect-y r) + (f64-ref (rect-bv r) 8)) + +(define (rect-width r) + (f64-ref (rect-bv r) 16)) + +(define (rect-height r) + (f64-ref (rect-bv r) 24)) + +(define (set-rect-x! r x) + (f64-set! (rect-bv r) 0 x)) + +(define (set-rect-y! r y) + (f64-set! (rect-bv r) 8 y)) + +(define (set-rect-width! r width) + (f64-set! (rect-bv r) 16 width)) + +(define (set-rect-height! r height) + (f64-set! (rect-bv r) 24 height)) + +(define (rect-intersects? a b) + (and (< (rect-x a) (+ (rect-x b) (rect-width b))) + (< (rect-y a) (+ (rect-y b) (rect-height b))) + (> (+ (rect-x a) (rect-width a)) (rect-x b)) + (> (+ (rect-y a) (rect-height a)) (rect-y b)))) + +(define (rect-clip a b) + (let* ((x1 (max (rect-x a) (rect-x b))) + (x2 (min (+ (rect-x a) (rect-width a)) + (+ (rect-x b) (rect-width b)))) + (y1 (max (rect-y a) (rect-y b))) + (y2 (min (+ (rect-y a) (rect-height a)) + (+ (rect-y b) (rect-height b))))) + (make-rect x1 y1 (- x2 x1) (- y2 y1)))) diff --git a/modules/math/vector.scm b/modules/math/vector.scm new file mode 100644 index 0000000..6dcf2f2 --- /dev/null +++ b/modules/math/vector.scm @@ -0,0 +1,94 @@ +;;; Copyright (C) 2024 David Thompson +;;; +;;; 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. + +;;; Commentary: +;;; +;;; Vectors, in the linear algebra sense. +;;; +;;; Code: + +(define-module (math vector) + #:pure + #:use-module (scheme base) + #:use-module (scheme inexact) + #:use-module ((hoot bytevectors) + #:select + (bytevector-ieee-double-native-ref + bytevector-ieee-double-native-set!)) + #:use-module (math) + #:export (vec2 + vec2? + vec2-x + vec2-y + set-vec2-x! + set-vec2-y! + vec2-add! + vec2-sub! + vec2-mul-scalar! + vec2-magnitude + vec2-normalize! + vec2-clamp!)) + +;; For speed, a vec2 is a wrapper around a bytevector so that we can +;; use unboxed floats. +(define-record-type + (make-vec2 bv) + vec2? + (bv vec2-bv)) + +(define f64-ref bytevector-ieee-double-native-ref) +(define f64-set! bytevector-ieee-double-native-set!) + +(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)))