├── .gitmodules ├── README.md ├── build-desktop.sh ├── build-unix.janet ├── build-windows-desktop.janet ├── build-windows.janet ├── jaylib-logo.png ├── main.c ├── make-jaylib-janet-shim.janet ├── project.janet ├── resources ├── draw.janet ├── fall.janet ├── game.janet ├── lateral.janet ├── params.janet ├── state.janet ├── theme.ogg ├── turn.janet ├── update.janet └── utils.janet ├── shell.html └── test └── make-and-run-juat-tests.janet /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "jaylib"] 2 | path = jaylib 3 | url = https://github.com/sogaiu/jaylib 4 | [submodule "janet"] 5 | path = janet 6 | url = https://github.com/janet-lang/janet 7 | [submodule "janet-usages-as-tests"] 8 | path = janet-usages-as-tests 9 | url = https://github.com/sogaiu/janet-usages-as-tests 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jaylib-wasm-demo 2 | 3 | A demo of using [jaylib](https://github.com/janet-lang/jaylib) in a web browser 4 | 5 | ## Overview 6 | 7 | The current approach is to build a small program that embeds [janet](https://janet-lang.org) and is linked to [Raylib](https://www.raylib.com/). The program loads and executes an example game written in Janet that uses Raylib via jaylib. 8 | 9 | The program is made executable in a web browser by being compiled by [Emscripten](https://emscripten.org/). 10 | 11 | The goal of this demo is to produce appropriate `.wasm`, `.js`, `.html`, and related files and then to test their functionality via a web browser. Before compilation via Emscripten can take place, some pieces need to be prepared: 12 | 13 | * `main.c` - the aforementioned small program 14 | * `janet.c` + support files - for embedding janet 15 | * `libraylib.a` - "HTML5-ready" Raylib static library 16 | * `game.janet` - a small game written in Janet / jaylib 17 | 18 | ## Prerequisites 19 | 20 | * Emscripten 3.1.3 (other versions might work) 21 | * Usual build tools (e.g. to get Emscripten working on Windows Python is needed) 22 | * Janet 23 | 24 | ## Steps 25 | 26 | * Ensure repository has been cloned recursively: 27 | ``` 28 | git clone --recursive https://github.com/sogaiu/jaylib-wasm-demo 29 | ``` 30 | 31 | * For *nixen, with emsdk under `~/src/emsdk`: 32 | ``` 33 | source ~/src/emsdk/emsdk_env.sh 34 | janet build-unix.janet 35 | ``` 36 | 37 | * For Windows, with emsdk in a sibling directory of this repository (likely need to do via a Native Tools Command Prompt): 38 | ``` 39 | ..\emsdk\emsdk_env.bat 40 | janet build-windows.janet 41 | ``` 42 | 43 | * Start a web server to serve the built files: 44 | 45 | For a machine with python3, that might be: 46 | ``` 47 | python3 -m http.server --directory public 48 | ``` 49 | 50 | * Try out the results: 51 | 52 | Visit http://localhost:8000 and click on `main.html` 53 | 54 | ## Notes 55 | 56 | The "game loop" currently relies on invoking a modified version of 57 | `janet_pcall` named `janet_pcall_keep_env` to invoke Janet code. 58 | `janet_pcall` resets passed fibers (or creates completely new ones) 59 | which implies that dynamic variables are not preserved between 60 | invocations because dynamic variables are associated with specific 61 | fibers. If `janet_pcall_keep_env` is passed a non-NULL fiber, the 62 | associated environment (and hence dynamic variables) are reused. 63 | 64 | An alternative is to use `janet_continue`, which allows one to pass a 65 | fiber which doesn't get reset. Since the same fiber can be reused 66 | without resetting, dynamic variables can be preserved between 67 | invocations. However, Emscripten then produces somewhat broken code [1] 68 | unless `ASYNCIFY` is specified when compiling (this was discovered via 69 | a DevTools console message). Using `ASYNCIFY` does yield running 70 | code, but the resulting code is likely slower due to added 71 | instrumentation. Additionally, at present, audio hasn't been made to 72 | work for the `janet_continue` + `ASYNCIFY` combination (at least in 73 | this demo)... 74 | 75 | In summary, current advice is: 76 | 77 | * if audio is desired, either use `janet_pcall` and don't use dynamic 78 | variables, or use `janet_pcall_keep_env`. 79 | * if audio is unneeded, one can use `janet_continue` instead. 80 | 81 | [1] Currently, it's not clear why `ASYNCIFY` is necessary. bakpakin's 82 | `webrepl.c` for janet-lang.org's web REPL also uses 83 | `janet_continue` but `ASYNCIFY` does not appear to be needed 84 | there. One difference that might be relevant is that the demo 85 | would be triggering `janet_continue` via 86 | `emscripten_set_main_loop`, while `webrepl.c`'s `janet_continue` 87 | is user-triggered. 88 | 89 | ## Issues 90 | 91 | * On Windows if `janet build-windows.janet` fails in the final 92 | compilation step, it may be worth trying [this 93 | workaround](https://github.com/emscripten-core/emscripten/issues/10551#issuecomment-608566377). 94 | 95 | ## Thanks 96 | 97 | * bakpakin 98 | * MikeBeller 99 | * pyrmont 100 | * raysan5 101 | * saikyun 102 | * yumaikas 103 | * ZakharValaha 104 | 105 | -------------------------------------------------------------------------------- /build-desktop.sh: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | PRELOAD_DIR=resources 4 | 5 | # exit if some part fails 6 | set -e 7 | 8 | if [[ -z "${JAYLIB_WASM_DEMO_SKIP_DEPS}" ]]; then 9 | echo "[Preparing janet amalgamated bits]..." 10 | cd janet && \ 11 | make clean && \ 12 | make && \ 13 | cd .. 14 | 15 | echo "[Preparing libraylib.a]..." 16 | cd jaylib/raylib/src && \ 17 | make clean && \ 18 | make && 19 | cd ../../.. 20 | 21 | echo "[Preparing jaylib.janet shim]..." 22 | mkdir -p ${PRELOAD_DIR} && \ 23 | janet make-jaylib-janet-shim.janet \ 24 | jaylib/src ${PRELOAD_DIR}/jaylib.janet 25 | fi 26 | 27 | [ -e janet/build/c/janet.c ] || \ 28 | (echo "janet/build/c/janet.c not found, please build" && exit 1) 29 | 30 | [ -e jaylib/raylib/src/libraylib.a ] || \ 31 | (echo "jaylib/raylib/src/libraylib.a not found, please build" && exit 1) 32 | 33 | echo "[Compiling output]..." 34 | gcc \ 35 | -O0 -g3 \ 36 | -Wall \ 37 | -o main \ 38 | main.c \ 39 | janet/build/c/janet.c \ 40 | jaylib/raylib/src/libraylib.a \ 41 | -Ijanet/build \ 42 | -Ijaylib/src \ 43 | -Ijaylib/raylib/src \ 44 | -lm -lpthread -ldl 45 | 46 | -------------------------------------------------------------------------------- /build-unix.janet: -------------------------------------------------------------------------------- 1 | (def out-dir "public") 2 | 3 | (def port "8000") 4 | 5 | (def preload-dir "resources") 6 | 7 | ########################################################################### 8 | 9 | (def start (os/clock)) 10 | 11 | (unless (os/getenv "EMSDK") 12 | (eprintf "emsdk environment not detected: try source emsdk_env.sh?") 13 | (os/exit 1)) 14 | 15 | (prinf "\n[ensuring existence of directory: %p]..." out-dir) 16 | (try 17 | (os/mkdir out-dir) 18 | ([e] 19 | (eprintf "<>" out-dir) 20 | (os/exit 1))) 21 | 22 | (unless (os/getenv "JAYLIB_WASM_DEMO_SKIP_DEPS") 23 | # 24 | (printf "\n[preparing amalgamated janet.c and related]...") 25 | (let [old-dir (os/cwd)] 26 | (try 27 | (os/cd "janet") 28 | ([e] 29 | (eprintf "<>") 30 | (os/exit 1))) 31 | (try 32 | (os/execute ["make" "clean"] :px) 33 | ([e] 34 | (eprintf "<>") 35 | (os/exit 1))) 36 | (try 37 | (os/execute ["make"] :px) 38 | ([e] 39 | (eprintf "<>") 40 | (os/exit 1))) 41 | (try 42 | (os/cd old-dir) 43 | ([e] 44 | (eprintf "<>") 45 | (os/exit 1)))) 46 | # 47 | (printf "\n[preparing HTML5-aware libraylib.a]...") 48 | (let [old-dir (os/cwd)] 49 | (try 50 | (os/cd "jaylib/raylib/src") 51 | ([e] 52 | (eprintf "<>") 53 | (os/exit 1))) 54 | (try 55 | (os/execute ["make" "clean"] :px) 56 | ([e] 57 | (eprintf "<>") 58 | (os/exit 1))) 59 | (try 60 | (os/execute ["make" 61 | # XXX: causing emcc to fail at last step? 62 | #"CFLAGS=-gsource-map" 63 | "PLATFORM=PLATFORM_WEB" "-B" "-e"] :px) 64 | ([e] 65 | (eprintf "<>") 66 | (os/exit 1))) 67 | (try 68 | (os/cd old-dir) 69 | ([e] 70 | (eprintf "<>") 71 | (os/exit 1)))) 72 | # 73 | (printf "\n[preparing jaylib.janet shim]...") 74 | (try 75 | (os/execute ["janet" 76 | "make-jaylib-janet-shim.janet" 77 | "jaylib/src" 78 | (string preload-dir "/jaylib.janet")] :px) 79 | ([e] 80 | (eprintf "<>") 81 | (os/exit 1)))) 82 | 83 | (printf "\n[copying logo into place]...") 84 | (try 85 | (spit (string out-dir "/jaylib-logo.png") 86 | (slurp "jaylib-logo.png")) 87 | ([e] 88 | (eprintf "<>" 89 | (os/exit 1)))) 90 | 91 | (printf "\n[compiling with emcc]...") 92 | (try 93 | (os/execute ["emcc" 94 | #"-v" 95 | "-Wall" 96 | # debugging 97 | "-g3" 98 | #"-gsource-map" 99 | "-DPLATFORM_WEB" 100 | "-o" (string out-dir "/main.html") 101 | "main.c" 102 | "janet/build/c/janet.c" 103 | "jaylib/raylib/src/libraylib.a" 104 | "-Ijanet/build" 105 | "-Ijaylib/src" 106 | "-Ijaylib/raylib/src" 107 | "--preload-file" preload-dir 108 | "--source-map-base" (string "http://localhost:" port "/") 109 | "--shell-file" "shell.html" 110 | # -O0 for dev, -Os for non-ASYNCIFY, -O3 for ASYNCIFY 111 | "-O0" 112 | #"-Os" 113 | #"-O3" "-s" "ASYNCIFY" 114 | "-s" "ASSERTIONS=2" 115 | "-s" "ALLOW_MEMORY_GROWTH=1" 116 | "-s" "FORCE_FILESYSTEM=1" 117 | "-s" "USE_GLFW=3" 118 | "-s" `EXPORTED_RUNTIME_METHODS=['cwrap']` 119 | "-s" "AGGRESSIVE_VARIABLE_ELIMINATION=1" 120 | #"-s" "MINIFY_HTML=0" 121 | ] 122 | :px) 123 | ([e] 124 | (eprintf "<>") 125 | (os/exit 1))) 126 | (print) 127 | 128 | (def end (os/clock)) 129 | 130 | (printf "Completed in %p seconds" (- end start)) 131 | 132 | -------------------------------------------------------------------------------- /build-windows-desktop.janet: -------------------------------------------------------------------------------- 1 | (def build-dir "build") 2 | 3 | (def preload-dir "resources") 4 | 5 | ########################################################################### 6 | 7 | (def start (os/clock)) 8 | 9 | (prinf "\n[ensuring existence of build directory: %p]..." build-dir) 10 | (try 11 | (os/mkdir build-dir) 12 | ([e] 13 | (eprintf "<>" build-dir) 14 | (os/exit 1))) 15 | 16 | (printf "\n[preparing amalgamated janet.c and related]...") 17 | (let [old-dir (os/cwd)] 18 | (try 19 | (os/cd "janet") 20 | ([e] 21 | (eprintf "<>") 22 | (os/exit 1))) 23 | (try 24 | (os/execute ["build_win.bat" "clean"] :px) 25 | ([e] 26 | (eprintf "<>") 27 | (os/exit 1))) 28 | (try 29 | (os/execute ["build_win.bat"] :px) 30 | ([e] 31 | (eprintf "<>") 32 | (os/exit 1))) 33 | (try 34 | (os/cd old-dir) 35 | ([e] 36 | (eprintf "<>") 37 | (os/exit 1)))) 38 | # 39 | (printf "\n[preparing object files for raylib.lib]...") 40 | (def commands 41 | [["cl.exe" 42 | "-D_POSIX_C_SOURCE=200809L" 43 | "-DPLATFORM_DESKTOP" 44 | "/c" 45 | "/nologo" 46 | "/MD" 47 | "-Ijaylib/raylib/src" 48 | "-Ijaylib/raylib/src/external/glfw/include" 49 | "-O2" 50 | "/LD" 51 | "/Fobuild/rshapes.o" 52 | "jaylib/raylib/src/rshapes.c"] 53 | ["cl.exe" 54 | "-D_POSIX_C_SOURCE=200809L" 55 | "-DPLATFORM_DESKTOP" 56 | "/c" 57 | "/nologo" 58 | "/MD" 59 | "-Ijaylib/raylib/src" 60 | "-Ijaylib/raylib/src/external/glfw/include" 61 | "-O2" 62 | "/LD" 63 | "/Fobuild/rtextures.o" 64 | "jaylib/raylib/src/rtextures.c"] 65 | ["cl.exe" 66 | "-D_POSIX_C_SOURCE=200809L" 67 | "-DPLATFORM_DESKTOP" 68 | "/c" 69 | "/nologo" 70 | "/MD" 71 | "-Ijaylib/raylib/src" 72 | "-Ijaylib/raylib/src/external/glfw/include" 73 | "-O2" 74 | "/LD" 75 | "/Fobuild/raudio.o" 76 | "jaylib/raylib/src/raudio.c"] 77 | ["cl.exe" 78 | "-D_POSIX_C_SOURCE=200809L" 79 | "-DPLATFORM_DESKTOP" 80 | "/c" 81 | "/nologo" 82 | "/MD" 83 | "-Ijaylib/raylib/src" 84 | "-Ijaylib/raylib/src/external/glfw/include" 85 | "-O2" 86 | "/LD" 87 | "/Fobuild/rglfw.o" 88 | "jaylib/raylib/src/rglfw.c"] 89 | ["cl.exe" 90 | "-D_POSIX_C_SOURCE=200809L" 91 | "-DPLATFORM_DESKTOP" 92 | "/c" 93 | "/nologo" 94 | "/MD" 95 | "-Ijaylib/raylib/src" 96 | "-Ijaylib/raylib/src/external/glfw/include" 97 | "-O2" 98 | "/LD" 99 | "/Fobuild/rcore.o" 100 | "jaylib/raylib/src/rcore.c"] 101 | ["cl.exe" 102 | "-D_POSIX_C_SOURCE=200809L" 103 | "-DPLATFORM_DESKTOP" 104 | "/c" 105 | "/nologo" 106 | "/MD" 107 | "-Ijaylib/raylib/src" 108 | "-Ijaylib/raylib/src/external/glfw/include" 109 | "-O2" 110 | "/LD" 111 | "/Fobuild/rmodels.o" 112 | "jaylib/raylib/src/rmodels.c"] 113 | ["cl.exe" 114 | "-D_POSIX_C_SOURCE=200809L" 115 | "-DPLATFORM_DESKTOP" 116 | "/c" 117 | "/nologo" 118 | "/MD" 119 | "-Ijaylib/raylib/src" 120 | "-Ijaylib/raylib/src/external/glfw/include" 121 | "-O2" 122 | "/LD" 123 | "/Fobuild/rtext.o" 124 | "jaylib/raylib/src/rtext.c"] 125 | ["cl.exe" 126 | "-D_POSIX_C_SOURCE=200809L" 127 | "-DPLATFORM_DESKTOP" 128 | "/c" 129 | "/nologo" 130 | "/MD" 131 | "-Ijaylib/raylib/src" 132 | "-Ijaylib/raylib/src/external/glfw/include" 133 | "-O2" 134 | "/LD" 135 | "/Fobuild/utils.o" 136 | "jaylib/raylib/src/utils.c"]]) 137 | (each cmd commands 138 | (try 139 | (os/execute cmd :px) 140 | ([e] 141 | (eprintf "<>" cmd) 142 | (os/exit 1)))) 143 | 144 | (printf "\n[creating raylib.lib]...") 145 | (try 146 | (os/execute ["lib.exe" 147 | "/nologo" 148 | "/out:build/raylib.lib" 149 | "build/rcore.o" 150 | "build/rmodels.o" 151 | "build/raudio.o" 152 | "build/rglfw.o" 153 | "build/rshapes.o" 154 | "build/rtext.o" 155 | "build/rtextures.o" 156 | "build/utils.o" 157 | ] 158 | :px) 159 | ([e] 160 | (eprintf "<>" e) 161 | (os/exit 1))) 162 | 163 | (printf "\n[preparing jaylib.janet shim]...") 164 | (try 165 | (os/execute ["janet" 166 | "make-jaylib-janet-shim.janet" 167 | "jaylib/src" 168 | (string preload-dir "/jaylib.janet")] :px) 169 | ([e] 170 | (eprintf "<>") 171 | (os/exit 1))) 172 | 173 | (printf "\n[compiling final product]...") 174 | (try 175 | (os/execute ["cl.exe" 176 | "main.c" 177 | "janet/build/c/janet.c" 178 | "-Ijanet/src/include" 179 | "-Ijanet/src/conf" 180 | "-Ijaylib/raylib/src" 181 | "-Ijaylib/src" 182 | "/MD" 183 | #"/link" "/DEBUG" 184 | "build/raylib.lib" 185 | "user32.lib" 186 | "opengl32.lib" 187 | "gdi32.lib" 188 | "winmm.lib" 189 | "shell32.lib" 190 | ] 191 | :px) 192 | ([e] 193 | (eprintf "<>" e) 194 | (os/exit 1))) 195 | (print) 196 | 197 | (def end (os/clock)) 198 | 199 | (printf "Completed in %p seconds" (- end start)) 200 | 201 | -------------------------------------------------------------------------------- /build-windows.janet: -------------------------------------------------------------------------------- 1 | (def out-dir "public") 2 | 3 | (def port "8000") 4 | 5 | (def preload-dir "resources") 6 | 7 | ########################################################################### 8 | 9 | (def start (os/clock)) 10 | 11 | (unless (os/getenv "EMSDK") 12 | (eprintf "emsdk environment not detected: try emsdk_env.bat?") 13 | (os/exit 1)) 14 | 15 | (prinf "\n[ensuring existence of directory: %p]..." out-dir) 16 | (try 17 | (os/mkdir out-dir) 18 | ([e] 19 | (eprintf "<>" out-dir) 20 | (os/exit 1))) 21 | 22 | (unless (os/getenv "JAYLIB_WASM_DEMO_SKIP_DEPS") 23 | # 24 | (printf "\n[preparing amalgamated janet.c and related]...") 25 | (let [old-dir (os/cwd)] 26 | (try 27 | (os/cd "janet") 28 | ([e] 29 | (eprintf "<>") 30 | (os/exit 1))) 31 | (try 32 | (os/execute ["build_win.bat" "clean"] :px) 33 | ([e] 34 | (eprintf "<>") 35 | (os/exit 1))) 36 | (try 37 | (os/execute ["build_win.bat"] :px) 38 | ([e] 39 | (eprintf "<>") 40 | (os/exit 1))) 41 | # 42 | (try 43 | (os/cd old-dir) 44 | ([e] 45 | (eprintf "<>") 46 | (os/exit 1)))) 47 | # 48 | (printf "\n[preparing HTML5-aware libraylib.a]...") 49 | (let [old-dir (os/cwd)] 50 | (try 51 | (os/cd "jaylib/raylib/src") 52 | ([e] 53 | (eprintf "<>") 54 | (os/exit 1))) 55 | (def commands 56 | [["emcc.bat" 57 | "-c" "rcore.c" "-Os" "-Wall" "-gsource-map" 58 | "-DPLATFORM_WEB" "-DGRAPHICS_API_OPENGL_ES2"] 59 | ["emcc.bat" 60 | "-c" "rshapes.c" "-Os" "-Wall" "-gsource-map" 61 | "-DPLATFORM_WEB" "-DGRAPHICS_API_OPENGL_ES2"] 62 | ["emcc.bat" 63 | "-c" "rtextures.c" "-Os" "-Wall" "-gsource-map" 64 | "-DPLATFORM_WEB" "-DGRAPHICS_API_OPENGL_ES2"] 65 | ["emcc.bat" 66 | "-c" "rtext.c" "-Os" "-Wall" "-gsource-map" 67 | "-DPLATFORM_WEB" "-DGRAPHICS_API_OPENGL_ES2"] 68 | ["emcc.bat" 69 | "-c" "rmodels.c" "-Os" "-Wall" "-gsource-map" 70 | "-DPLATFORM_WEB" "-DGRAPHICS_API_OPENGL_ES2"] 71 | ["emcc.bat" 72 | "-c" "utils.c" "-Os" "-Wall" "-gsource-map" "-DPLATFORM_WEB"] 73 | ["emcc.bat" 74 | "-c" "raudio.c" "-Os" "-Wall" "-gsource-map" "-DPLATFORM_WEB"] 75 | ["emar.bat" 76 | "rcs" "libraylib.a" 77 | "rcore.o" "rshapes.o" "rtextures.o" "rtext.o" "rmodels.o" 78 | "utils.o" "raudio.o"]]) 79 | (each cmd commands 80 | (try 81 | (os/execute cmd :px) 82 | ([e] 83 | (eprintf "<>" cmd) 84 | (os/exit 1)))) 85 | (try 86 | (os/cd old-dir) 87 | ([e] 88 | (eprintf "<>") 89 | (os/exit 1)))) 90 | # 91 | (printf "\n[preparing jaylib.janet shim]...") 92 | (try 93 | (os/execute ["janet" 94 | "make-jaylib-janet-shim.janet" 95 | "jaylib/src" 96 | (string preload-dir "/jaylib.janet")] :px) 97 | ([e] 98 | (eprintf "<>") 99 | (os/exit 1)))) 100 | 101 | (printf "\n[copying logo into place]...") 102 | (try 103 | (spit (string out-dir "/jaylib-logo.png") 104 | (slurp "jaylib-logo.png")) 105 | ([e] 106 | (eprintf "<>" 107 | (os/exit 1)))) 108 | 109 | (printf "\n[compiling with emcc]...") 110 | (try 111 | (os/execute ["emcc.bat" 112 | #"-v" 113 | "-Wall" 114 | # debugging 115 | "-g3" 116 | #"-gsource-map" 117 | "-DPLATFORM_WEB" 118 | "-o" (string out-dir "/main.html") 119 | "main.c" 120 | "janet/build/c/janet.c" 121 | "jaylib/raylib/src/libraylib.a" 122 | "-Ijanet/build" 123 | "-Ijanet/src/conf" 124 | "-Ijanet/src/include" 125 | "-Ijaylib/src" 126 | "-Ijaylib/raylib/src" 127 | "--preload-file" preload-dir 128 | "--source-map-base" (string "http://localhost:" port "/") 129 | "--shell-file" "shell.html" 130 | # -O0 for dev, -Os for non-ASYNCIFY, -O3 for ASYNCIFY 131 | "-O0" 132 | #"-Os" 133 | #"-O3" "-s" "ASYNCIFY" 134 | "-s" "ASSERTIONS=2" 135 | "-s" "ALLOW_MEMORY_GROWTH=1" 136 | "-s" "FORCE_FILESYSTEM=1" 137 | "-s" "USE_GLFW=3" 138 | "-s" `EXPORTED_RUNTIME_METHODS=['cwrap']` 139 | "-s" "AGGRESSIVE_VARIABLE_ELIMINATION=1" 140 | #"-s" "MINIFY_HTML=0" 141 | ] 142 | :px) 143 | ([e] 144 | (eprintf "<>") 145 | (os/exit 1))) 146 | (print) 147 | 148 | (def end (os/clock)) 149 | 150 | (printf "Completed in %p seconds" (- end start)) 151 | 152 | -------------------------------------------------------------------------------- /jaylib-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogaiu/jaylib-wasm-demo/3e8f83cb31ae66156415853cc43911ea1acd369d/jaylib-logo.png -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #include "janet.h" 2 | #include "raylib.h" 3 | 4 | #include "types.h" 5 | 6 | #include "core.h" 7 | #include "shapes.h" 8 | #include "audio.h" 9 | #include "gestures.h" 10 | #include "text.h" 11 | #include "image.h" 12 | #include "3d.h" 13 | 14 | #if defined(PLATFORM_WEB) 15 | #include 16 | #endif 17 | 18 | static JanetTable* core_env = NULL; 19 | 20 | static JanetFiber* game_fiber = NULL; 21 | 22 | static JanetFunction* udf_fn = NULL; 23 | 24 | // janet_pcall, except if a non-NULL fiber is passed in, retain its env 25 | JanetSignal janet_pcall_keep_env( 26 | JanetFunction *fun, 27 | int32_t argc, 28 | const Janet *argv, 29 | Janet *out, 30 | JanetFiber **f) { 31 | JanetFiber *fiber; 32 | if (f && *f) { 33 | JanetTable* env = (*f)->env; 34 | fiber = janet_fiber_reset(*f, fun, argc, argv); 35 | fiber->env = env; 36 | } else { 37 | fiber = janet_fiber(fun, 64, argc, argv); 38 | } 39 | if (f) *f = fiber; 40 | if (!fiber) { 41 | *out = janet_cstringv("arity mismatch"); 42 | return JANET_SIGNAL_ERROR; 43 | } 44 | return janet_continue(fiber, janet_wrap_nil(), out); 45 | } 46 | 47 | void UpdateDrawFrame(void) { 48 | Janet ret; 49 | JanetSignal status = 50 | janet_pcall_keep_env(udf_fn, 0, NULL, &ret, &game_fiber); 51 | if (status == JANET_SIGNAL_ERROR) { 52 | janet_stacktrace(game_fiber, ret); 53 | janet_deinit(); 54 | game_fiber = NULL; 55 | udf_fn = NULL; 56 | // XXX 57 | abort(); 58 | } 59 | } 60 | 61 | int main(int argc, char** argv) { 62 | janet_init(); 63 | 64 | core_env = janet_core_env(NULL); 65 | 66 | // make jaylib functions available 67 | janet_cfuns(core_env, NULL, core_cfuns); 68 | janet_cfuns(core_env, NULL, shapes_cfuns); 69 | janet_cfuns(core_env, NULL, audio_cfuns); 70 | janet_cfuns(core_env, NULL, gesture_cfuns); 71 | janet_cfuns(core_env, NULL, text_cfuns); 72 | janet_cfuns(core_env, NULL, image_cfuns); 73 | janet_cfuns(core_env, NULL, threed_cfuns); 74 | 75 | Janet ret; 76 | 77 | int status = 78 | janet_dostring(core_env, 79 | "(setdyn :syspath \"./resources\")\n" 80 | "(import game :prefix \"\")\n" 81 | "(common-startup)\n" 82 | // want this in c anyway, so "returning" this 83 | "main-fiber", 84 | "game.janet", &ret); 85 | 86 | if (status != 0) { 87 | printf("error loading game\n"); 88 | janet_deinit(); 89 | game_fiber = NULL; 90 | return -1; 91 | } 92 | 93 | janet_gcroot(ret); 94 | game_fiber = janet_unwrap_fiber(ret); 95 | 96 | status = 97 | janet_dostring(core_env, 98 | "update-draw-frame", 99 | "JanetFunction", &ret); 100 | 101 | if (status != 0) { 102 | printf("error getting update-draw-frame\n"); 103 | janet_deinit(); 104 | game_fiber = NULL; 105 | return -1; 106 | } 107 | 108 | janet_gcroot(ret); 109 | udf_fn = janet_unwrap_function(ret); 110 | 111 | #if defined(PLATFORM_WEB) 112 | // XXX: chrome dev console suggests using framerate of 0 113 | //emscripten_set_main_loop(UpdateDrawFrame, 60, 1); 114 | emscripten_set_main_loop(UpdateDrawFrame, 0, 1); 115 | #else 116 | status = 117 | janet_dostring(core_env, "(desktop)", "source", &ret); 118 | 119 | if (status != 0) { 120 | printf("error during desktop-specific init\n"); 121 | janet_deinit(); 122 | game_fiber = NULL; 123 | udf_fn = NULL; 124 | return -1; 125 | } 126 | 127 | while (!WindowShouldClose()) 128 | { 129 | UpdateDrawFrame(); 130 | } 131 | #endif 132 | 133 | janet_deinit(); 134 | 135 | return 0; 136 | } 137 | -------------------------------------------------------------------------------- /make-jaylib-janet-shim.janet: -------------------------------------------------------------------------------- 1 | # create jaylib.janet, a janet file to enable `use` and `import` 2 | # forms for jaylib to work, hopefully reducing the number of 3 | # changes necessary to get one's jaylib-using code to work on the web 4 | 5 | # this code "scrapes" .h files from jaylib and then creates a file 6 | # named `jaylib.janet`. by placing this file in an appropriate 7 | # location (e.g. resources/jaylib.janet) for bundling by 8 | # emcc, use of `(import jaylib)` or `(use jaylib)` or similar in one's 9 | # janet code should (continue to) work in the context of wasm / 10 | # emscripten. note that use of this method is likely to add a bit of 11 | # overhead to startup -- but perhaps it is negligible. 12 | 13 | # sample invocation: 14 | # 15 | # janet make-jaylib-janet-shim.janet jaylib/src resources/jaylib.janet 16 | # 17 | # `jaylib/src` is an example of where jaylib's .h files live 18 | # `resources/jaylib.janet` is an example output file destination 19 | 20 | (defn main 21 | [& args] 22 | (def dir-root 23 | (when-let [dir (get args 1)] 24 | dir)) 25 | (def shim-file-path 26 | (when-let [filepath (get args 2)] 27 | filepath)) 28 | # 29 | (default dir-root "jaylib/src") 30 | (default shim-file-path "resources/jaylib.janet") 31 | # 32 | (def shim-file-dir 33 | (if-let [rev-path (string/reverse shim-file-path) 34 | slash-idx (string/find "/" rev-path)] 35 | (-> (string/slice rev-path (inc slash-idx)) 36 | (string/reverse)) 37 | ".")) 38 | (unless (= :directory 39 | (os/stat shim-file-dir :mode)) 40 | (eprintf "%p should exist and be a directory" shim-file-dir) 41 | (os/exit 1)) 42 | # 43 | (def cfuns @[]) 44 | # XXX: draw-grid is (was at one point?) duplicated in 3d.h 45 | (def dups @{}) 46 | # parse *.h files in jaylib, collecting names 47 | (each hf (os/dir dir-root) 48 | (def res 49 | (->> (slurp (string dir-root "/" hf)) 50 | (peg/match 51 | ~(sequence (thru "_cfuns[] = {") 52 | (thru "\n") 53 | (some (sequence (thru `"`) 54 | (capture (to `"`)) 55 | `"` 56 | (thru "\n"))))))) 57 | # XXX 58 | '(when res 59 | (printf "%p: %p" hf (length res)) 60 | (pp res)) 61 | (array/concat cfuns res)) 62 | # prepare jaylib.janet for writing 63 | (def jjf 64 | (try 65 | (file/open shim-file-path :w) 66 | ([e] 67 | (eprintf "problem opening file for writing: %p" e) 68 | (os/exit 1)))) 69 | # XXX 70 | '(print (length cfuns)) 71 | # populate jaylib.janet with def forms 72 | (each cf cfuns 73 | (when cf 74 | (when (nil? (get dups cf)) 75 | (file/write jjf 76 | (string "(def " cf " " cf ")\n")) 77 | (put dups cf true)))) 78 | # 79 | (file/close jjf)) 80 | 81 | -------------------------------------------------------------------------------- /project.janet: -------------------------------------------------------------------------------- 1 | (declare-project 2 | :name "jaylib-wasm-demo" 3 | :url "https://github.com/sogaiu/jaylib-wasm-demo" 4 | :repo "git+https://github.com/sogaiu/jaylib-wasm-demo" 5 | :dependencies ["https://github.com/janet-lang/jaylib"]) 6 | 7 | -------------------------------------------------------------------------------- /resources/draw.janet: -------------------------------------------------------------------------------- 1 | (import jaylib :as j) 2 | (import ./params :as p) 3 | 4 | (defn draw-grid 5 | [state] 6 | (var offset-x 7 | (- (/ p/screen-width 2) 8 | (* p/grid-x-size (/ p/square-size 2)) 9 | 50)) 10 | (var offset-y 11 | (- (/ p/screen-height 2) 12 | (+ (* (dec p/grid-y-size) (/ p/square-size 2)) 13 | (* p/square-size 2)) 14 | 50)) 15 | (var controller offset-x) 16 | # draw grid 17 | (for j 0 p/grid-y-size 18 | (for i 0 p/grid-x-size 19 | (case (get-in state [:grid i j]) 20 | :empty 21 | (do # outline of square 22 | (j/draw-line offset-x offset-y 23 | (+ offset-x p/square-size) offset-y 24 | :light-gray) 25 | (j/draw-line offset-x offset-y 26 | offset-x (+ offset-y p/square-size) 27 | :light-gray) 28 | (j/draw-line (+ offset-x p/square-size) offset-y 29 | (+ offset-x p/square-size) (+ offset-y p/square-size) 30 | :light-gray) 31 | (j/draw-line offset-x (+ offset-y p/square-size) 32 | (+ offset-x p/square-size) (+ offset-y p/square-size) 33 | :light-gray)) 34 | # 35 | :full 36 | (j/draw-rectangle offset-x offset-y 37 | p/square-size p/square-size :black) 38 | # 39 | :moving 40 | (j/draw-rectangle offset-x offset-y 41 | p/square-size p/square-size :dark-gray) 42 | # 43 | :block 44 | (j/draw-rectangle offset-x offset-y 45 | p/square-size p/square-size :light-gray) 46 | # 47 | :fading 48 | (j/draw-rectangle offset-x offset-y 49 | p/square-size p/square-size 50 | (state :fading-color)) 51 | # 52 | (eprintf "Unexpected value: %p at %p, %p" 53 | (get-in state [:grid i j]) i j)) 54 | (+= offset-x p/square-size)) 55 | (set offset-x controller) 56 | (+= offset-y p/square-size)) 57 | # 58 | state) 59 | 60 | (defn draw-info-box 61 | [state [x y]] 62 | (var offset-x x) 63 | (var offset-y y) 64 | (var controller offset-x) 65 | # draw future piece 66 | (for j 0 p/piece-dim 67 | (for i 0 p/piece-dim 68 | (case (get-in state [:future-piece i j]) 69 | :empty 70 | (do 71 | (j/draw-line offset-x offset-y 72 | (+ offset-x p/square-size) offset-y 73 | :light-gray) 74 | (j/draw-line offset-x offset-y 75 | offset-x (+ offset-y p/square-size) 76 | :light-gray) 77 | (j/draw-line (+ offset-x p/square-size) offset-y 78 | (+ offset-x p/square-size) (+ offset-y p/square-size) 79 | :light-gray) 80 | (j/draw-line offset-x (+ offset-y p/square-size) 81 | (+ offset-x p/square-size) (+ offset-y p/square-size) 82 | :light-gray) 83 | (+= offset-x p/square-size)) 84 | # 85 | :moving 86 | (do 87 | (j/draw-rectangle offset-x offset-y 88 | p/square-size p/square-size :gray) 89 | (+= offset-x p/square-size)))) 90 | (set offset-x controller) 91 | (+= offset-y p/square-size)) 92 | # label future piece box 93 | (j/draw-text "UPCOMING:" 94 | offset-x (- offset-y (* p/piece-dim p/square-size) 30) 95 | 10 :gray) 96 | # show how many lines completed so far 97 | (j/draw-text (string/format "LINES: %04i" (state :lines)) 98 | offset-x (+ offset-y 20) 99 | 10 :gray) 100 | # 101 | (put state :result [offset-x offset-y]) 102 | # 103 | state) 104 | 105 | (defn draw-pause-overlay 106 | [] 107 | (let [message "GAME PAUSED"] 108 | (j/draw-text message 109 | (- (/ p/screen-width 2) 110 | (/ (j/measure-text message 40) 111 | 2)) 112 | (- (/ p/screen-height 2) 113 | 40) 114 | 40 :gray))) 115 | 116 | (defn draw-play-again-overlay 117 | [] 118 | (let [message "PRESS [ENTER] TO PLAY AGAIN"] 119 | # XXX: why are get-screen-width and get-screen-height used here 120 | # when they are not in draw-grid and draw-pause-overlay? 121 | (j/draw-text message 122 | (- (/ (j/get-screen-width) 2) 123 | (/ (j/measure-text message 20) 124 | 2)) 125 | (- (/ (j/get-screen-height) 2) 126 | 50) 127 | 20 :gray))) 128 | 129 | (defn draw-game 130 | [state] 131 | (j/begin-drawing) 132 | # 133 | (j/clear-background :dark-green) 134 | # 135 | (if (state :game-over) 136 | (draw-play-again-overlay) 137 | (do 138 | (draw-grid state) 139 | (draw-info-box state [500 45]) # XXX: hard-coded 140 | # show pause overlay when appropriate 141 | (when (state :pause) 142 | (draw-pause-overlay)))) 143 | # 144 | (j/end-drawing) 145 | # 146 | state) 147 | 148 | -------------------------------------------------------------------------------- /resources/fall.janet: -------------------------------------------------------------------------------- 1 | (import ./params :as p) 2 | 3 | (defn resolve-falling-move! 4 | [state] 5 | (def grid-x-size 6 | (or (state :grid-x-size) p/grid-x-size)) 7 | (def grid-y-size 8 | (or (state :grid-y-size) p/grid-y-size)) 9 | (def grid (state :grid)) 10 | # 11 | (if (state :blocked-below) 12 | # stop the piece 13 | (loop [j :down-to [(- grid-y-size 2) 0] 14 | i :range [1 (dec grid-x-size)] 15 | :when (= :moving 16 | (get-in grid [i j]))] 17 | (put-in grid [i j] :full) 18 | (put state :blocked-below false) 19 | (put state :piece-active false)) 20 | # move the piece down 21 | (do 22 | (loop [j :down-to [(- grid-y-size 2) 0] 23 | i :range [1 (dec grid-x-size)] 24 | :when (= :moving 25 | (get-in grid [i j]))] 26 | (put-in grid [i (inc j)] :moving) 27 | (put-in grid [i j] :empty)) 28 | (++ (state :piece-pos-y)))) 29 | # 30 | state) 31 | 32 | (comment 33 | 34 | (import ./utils :as u) 35 | 36 | (let [t-grid [[:block :empty :empty :empty :empty :block] 37 | [:block :empty :moving :empty :empty :block] 38 | [:block :empty :moving :moving :empty :block] 39 | [:block :empty :moving :empty :empty :block] 40 | [:block :empty :empty :empty :empty :block] 41 | [:block :block :block :block :block :block]] 42 | grid (u/transpose t-grid) 43 | state @{:grid grid 44 | :grid-x-size (length grid) 45 | :grid-y-size (length (get grid 0)) 46 | :piece-pos-x 1 47 | :piece-pos-y 1}] 48 | (u/transpose ((resolve-falling-move! state) :grid))) 49 | # => 50 | @[@[:block :empty :empty :empty :empty :block] 51 | @[:block :empty :empty :empty :empty :block] 52 | @[:block :empty :moving :empty :empty :block] 53 | @[:block :empty :moving :moving :empty :block] 54 | @[:block :empty :moving :empty :empty :block] 55 | @[:block :block :block :block :block :block]] 56 | 57 | ) 58 | 59 | (defn check-blocked-below! 60 | [state] 61 | (def grid-x-size 62 | (or (state :grid-x-size) p/grid-x-size)) 63 | (def grid-y-size 64 | (or (state :grid-y-size) p/grid-y-size)) 65 | # check if there is even one spot below the current line that a piece 66 | # cannot be moved into (i.e. :full or :block) 67 | (loop [j :down-to [(- grid-y-size 2) 0] 68 | i :range [1 (dec grid-x-size)] 69 | :let [grid (state :grid)] 70 | :when (and (= :moving 71 | (get-in grid [i j])) 72 | (or (= :full 73 | (get-in grid [i (inc j)])) 74 | (= :block 75 | (get-in grid [i (inc j)]))))] 76 | (put state :blocked-below true)) 77 | # 78 | state) 79 | 80 | (comment 81 | 82 | (import ./utils :as u) 83 | 84 | (let [t-grid [[:block :moving :empty :block] 85 | [:block :moving :empty :block] 86 | [:block :moving :moving :block] 87 | [:block :block :block :block]] 88 | grid (u/transpose t-grid) 89 | state @{:grid grid 90 | :grid-x-size (length grid) 91 | :grid-y-size (length (get grid 0))}] 92 | ((check-blocked-below! state) :blocked-below)) 93 | # => 94 | true 95 | 96 | ) 97 | 98 | (defn check-completion! 99 | [state] 100 | (def grid-x-size 101 | (or (state :grid-x-size) p/grid-x-size)) 102 | (def grid-y-size 103 | (or (state :grid-y-size) p/grid-y-size)) 104 | (var calculator 0) 105 | # determine if any lines need to be deleted 106 | (loop [j :down-to [(- grid-y-size 2) 0] 107 | :let [grid (state :grid)] 108 | :before (set calculator 0)] 109 | # count spots that are occupied by stationary blocks (i.e. :full) 110 | (loop [i :range [1 (dec grid-x-size)]] 111 | (when (= :full 112 | (get-in grid [i j])) 113 | (++ calculator)) 114 | # if appropriate, mark spots that need to be deleted and remember 115 | # that at least one line needs to be deleted 116 | (when (= (- grid-x-size 2) 117 | calculator) 118 | (put state :line-to-delete true) 119 | (set calculator 0) 120 | (loop [z :range [1 (dec grid-x-size)]] 121 | (put-in grid [z j] :fading))))) 122 | state) 123 | 124 | (comment 125 | 126 | (import ./utils :as u) 127 | 128 | (let [t-grid [[:block :moving :empty :block] 129 | [:block :moving :empty :block] 130 | [:block :moving :moving :block] 131 | [:block :block :block :block]] 132 | grid (u/transpose t-grid) 133 | state @{:grid grid 134 | :grid-x-size (length grid) 135 | :grid-y-size (length (get grid 0))}] 136 | ((check-completion! state) :line-to-delete)) 137 | # => 138 | nil 139 | 140 | (let [t-grid [[:block :empty :empty :empty :block] 141 | [:block :full :full :full :block] 142 | [:block :full :full :full :block] 143 | [:block :block :block :block :block]] 144 | grid (u/transpose t-grid) 145 | state @{:grid grid 146 | :grid-x-size (length grid) 147 | :grid-y-size (length (get grid 0))} 148 | new-state (check-completion! state)] 149 | [(state :line-to-delete) 150 | (u/transpose (state :grid))]) 151 | # => 152 | [true 153 | @[@[:block :empty :empty :empty :block] 154 | @[:block :fading :fading :fading :block] 155 | @[:block :fading :fading :fading :block] 156 | @[:block :block :block :block :block]]] 157 | 158 | ) 159 | 160 | -------------------------------------------------------------------------------- /resources/game.janet: -------------------------------------------------------------------------------- 1 | # a port of: 2 | # 3 | # https://github.com/raysan5/raylib-games/blob/master/classics/src/tetris.c 4 | 5 | ########################################################################### 6 | 7 | (import jaylib :as j) 8 | (import ./params :as p) 9 | (import ./state :as s) 10 | (import ./draw :as d) 11 | (import ./update :as u) 12 | 13 | (defn update-draw-frame! 14 | [state] 15 | # XXX 16 | (when (zero? (mod (dyn :frame) 1000)) 17 | (let [d (os/date (os/time) true)] 18 | (printf "%02d:%02d:%02d - %p frames" 19 | (d :hours) (d :minutes) (d :seconds) (dyn :frame)))) 20 | (setdyn :frame (inc (dyn :frame))) 21 | # 22 | (when-let [bgm (state :bgm)] 23 | (j/update-music-stream bgm)) 24 | # 25 | (-> state 26 | u/update-game! 27 | d/draw-game)) 28 | 29 | (defn desktop 30 | [] 31 | (j/set-config-flags :msaa-4x-hint) 32 | (j/set-target-fps 60)) 33 | 34 | # filled in via common-startup 35 | (var update-draw-frame nil) 36 | 37 | # filled in via common-startup 38 | (var main-fiber nil) 39 | 40 | (defn common-startup 41 | [] 42 | (def state @{}) 43 | # 44 | (s/init! state) 45 | # 46 | (j/init-window p/screen-width p/screen-height "Jaylib Demo") 47 | # 48 | (j/init-audio-device) 49 | (put state :bgm (j/load-music-stream "resources/theme.ogg")) 50 | (j/play-music-stream (state :bgm)) 51 | (j/set-music-volume (state :bgm) (state :bgm-volume)) 52 | # to facilitate calling from main.c 53 | (set update-draw-frame 54 | |(update-draw-frame! state)) 55 | # XXX 56 | (setdyn :frame 0) 57 | # this fiber is used repeatedly by the c code, partly to maintain 58 | # dynamic variables (as those are per-fiber), but also because reusing 59 | # a fiber with a function is likely faster than parsing and compiling 60 | # code each time the game loop performs one iteration 61 | (set main-fiber 62 | (fiber/new 63 | (fn [] 64 | # XXX: this content only gets used when main.c uses janet_continue 65 | (while (not (window-should-close)) 66 | (update-draw-frame! state) 67 | (yield))) 68 | # important for inheriting existing dynamic variables 69 | :i))) 70 | 71 | -------------------------------------------------------------------------------- /resources/lateral.janet: -------------------------------------------------------------------------------- 1 | (import jaylib :as j) 2 | (import ./params :as p) 3 | 4 | (defn left-blocked? 5 | [state] 6 | (def grid-x-size 7 | (or (state :grid-x-size) p/grid-x-size)) 8 | (def grid-y-size 9 | (or (state :grid-y-size) p/grid-y-size)) 10 | (def collision 11 | (label result 12 | (loop [j :down-to [(- grid-y-size 2) 0] 13 | i :range [1 (dec grid-x-size)] 14 | :let [grid (state :grid)] 15 | :when (and (= :moving 16 | (get-in grid [i j])) 17 | (or (zero? (dec i)) 18 | (= :full 19 | (get-in grid [(dec i) j]))))] 20 | (return result true)) 21 | (return result false))) 22 | # 23 | (put state :result collision) 24 | # 25 | state) 26 | 27 | (comment 28 | 29 | (import ./utils :as u) 30 | 31 | (let [t-grid [[:block :moving :empty :block] 32 | [:block :moving :empty :block] 33 | [:block :moving :moving :block] 34 | [:block :block :block :block]] 35 | grid (u/transpose t-grid) 36 | state @{:grid grid 37 | :grid-x-size (length grid) 38 | :grid-y-size (length (get grid 0))}] 39 | ((left-blocked? state) :result)) 40 | # => 41 | true 42 | 43 | (let [t-grid [[:block :empty :moving :empty :block] 44 | [:block :empty :moving :moving :block] 45 | [:block :empty :moving :empty :block] 46 | [:block :block :block :block :block]] 47 | grid (u/transpose t-grid) 48 | state @{:grid grid 49 | :grid-x-size (length grid) 50 | :grid-y-size (length (get grid 0))}] 51 | ((left-blocked? state) :result)) 52 | # => 53 | false 54 | 55 | ) 56 | 57 | (defn move-left! 58 | [state] 59 | (def grid-x-size 60 | (or (state :grid-x-size) p/grid-x-size)) 61 | (def grid-y-size 62 | (or (state :grid-y-size) p/grid-y-size)) 63 | (loop [j :down-to [(- grid-y-size 2) 0] 64 | i :range [1 (dec grid-x-size)] 65 | :let [grid (state :grid)] 66 | :when (= :moving 67 | (get-in grid [i j]))] 68 | (-> grid 69 | (put-in [(dec i) j] :moving) 70 | (put-in [i j] :empty))) 71 | (-- (state :piece-pos-x)) 72 | # 73 | state) 74 | 75 | (comment 76 | 77 | (import ./utils :as u) 78 | 79 | (let [t-grid [[:block :empty :empty :empty :empty :block] 80 | [:block :empty :moving :empty :empty :block] 81 | [:block :empty :moving :moving :empty :block] 82 | [:block :empty :moving :empty :empty :block] 83 | [:block :block :block :block :block :block]] 84 | grid (u/transpose t-grid) 85 | state @{:grid grid 86 | :grid-x-size (length grid) 87 | :grid-y-size (length (get grid 0)) 88 | :piece-pos-x 1 89 | :piece-pos-y 1}] 90 | (u/transpose ((move-left! state) :grid))) 91 | # => 92 | @[@[:block :empty :empty :empty :empty :block] 93 | @[:block :moving :empty :empty :empty :block] 94 | @[:block :moving :moving :empty :empty :block] 95 | @[:block :moving :empty :empty :empty :block] 96 | @[:block :block :block :block :block :block]] 97 | 98 | ) 99 | 100 | (defn right-blocked? 101 | [state] 102 | (def grid-x-size 103 | (or (state :grid-x-size) p/grid-x-size)) 104 | (def grid-y-size 105 | (or (state :grid-y-size) p/grid-y-size)) 106 | (def collision 107 | (label result 108 | (loop [j :down-to [(- grid-y-size 2) 0] 109 | i :range [1 (dec grid-x-size)] 110 | :let [grid (state :grid)] 111 | :when (and (= :moving 112 | (get-in grid [i j])) 113 | (or (= (inc i) 114 | (dec grid-x-size)) 115 | (= :full 116 | (get-in grid [(inc i) j]))))] 117 | (return result true)) 118 | (return result false))) 119 | # 120 | (put state :result collision) 121 | # 122 | state) 123 | 124 | (comment 125 | 126 | (import ./utils :as u) 127 | 128 | (let [t-grid [[:block :moving :empty :block] 129 | [:block :moving :empty :block] 130 | [:block :moving :moving :block] 131 | [:block :block :block :block]] 132 | grid (u/transpose t-grid) 133 | state @{:grid grid 134 | :grid-x-size (length grid) 135 | :grid-y-size (length (get grid 0))}] 136 | ((right-blocked? state) :result)) 137 | # => 138 | true 139 | 140 | (let [t-grid [[:block :moving :empty :empty :block] 141 | [:block :moving :moving :empty :block] 142 | [:block :moving :empty :empty :block] 143 | [:block :block :block :block :block]] 144 | grid (u/transpose t-grid) 145 | state @{:grid grid 146 | :grid-x-size (length grid) 147 | :grid-y-size (length (get grid 0))}] 148 | ((right-blocked? state) :result)) 149 | # => 150 | false 151 | 152 | ) 153 | 154 | (defn move-right! 155 | [state] 156 | (def grid-x-size 157 | (or (state :grid-x-size) p/grid-x-size)) 158 | (def grid-y-size 159 | (or (state :grid-y-size) p/grid-y-size)) 160 | (loop [j :down-to [(- grid-y-size 2) 0] 161 | i :down-to [(dec grid-x-size) 1] 162 | :let [grid (state :grid)] 163 | :when (= :moving 164 | (get-in grid [i j]))] 165 | (-> grid 166 | (put-in [(inc i) j] :moving) 167 | (put-in [i j] :empty))) 168 | (++ (state :piece-pos-x)) 169 | # 170 | state) 171 | 172 | (comment 173 | 174 | (import ./utils :as u) 175 | 176 | (let [t-grid [[:block :empty :empty :empty :empty :block] 177 | [:block :empty :moving :empty :empty :block] 178 | [:block :empty :moving :moving :empty :block] 179 | [:block :empty :moving :empty :empty :block] 180 | [:block :block :block :block :block :block]] 181 | grid (u/transpose t-grid) 182 | state @{:grid grid 183 | :grid-x-size (length grid) 184 | :grid-y-size (length (get grid 0)) 185 | :piece-pos-x 1 186 | :piece-pos-y 1}] 187 | (u/transpose ((move-right! state) :grid))) 188 | # => 189 | @[@[:block :empty :empty :empty :empty :block] 190 | @[:block :empty :empty :moving :empty :block] 191 | @[:block :empty :empty :moving :moving :block] 192 | @[:block :empty :empty :moving :empty :block] 193 | @[:block :block :block :block :block :block]] 194 | 195 | ) 196 | 197 | (defn resolve-lateral-move! 198 | [state] 199 | (var collision true) 200 | # 201 | (cond 202 | (j/key-down? :a) 203 | (when (not ((left-blocked? state) :result)) 204 | (move-left! state) 205 | (set collision false)) 206 | # 207 | (j/key-down? :d) 208 | (when (not ((right-blocked? state) :result)) 209 | (move-right! state) 210 | (set collision false))) 211 | # 212 | (put state :result collision) 213 | # 214 | state) 215 | 216 | -------------------------------------------------------------------------------- /resources/params.janet: -------------------------------------------------------------------------------- 1 | (def screen-width 800) 2 | 3 | (def screen-height 450) 4 | 5 | (def square-size 20) 6 | 7 | (def grid-x-size 12) 8 | 9 | (def grid-y-size 20) 10 | 11 | (def piece-dim 4) 12 | 13 | (def lateral-speed 10) 14 | 15 | (def turning-speed 12) 16 | 17 | (def fast-fall-await-counter 30) 18 | 19 | (def fading-time 33) 20 | 21 | (def gravity-speed 30) 22 | -------------------------------------------------------------------------------- /resources/state.janet: -------------------------------------------------------------------------------- 1 | (import ./params :as p) 2 | 3 | (defn init-grid 4 | [] 5 | (def a-grid @[]) 6 | (loop [i :range [0 p/grid-x-size] 7 | :before (put a-grid i (array/new p/grid-y-size)) 8 | j :range [0 p/grid-y-size]] 9 | (if (or (= i 0) 10 | (= i (dec p/grid-x-size)) 11 | (= j (dec p/grid-y-size))) 12 | # pre-fill left, right, and bottom edges of the grid 13 | (put-in a-grid [i j] :block) 14 | # all other spots are :empty 15 | (put-in a-grid [i j] :empty))) 16 | a-grid) 17 | 18 | (defn init-piece 19 | [] 20 | (def a-piece @[]) 21 | # mark all spots in a-piece :empty 22 | (loop [i :range [0 p/piece-dim] 23 | :before (put a-piece i (array/new p/piece-dim)) 24 | j :range [0 p/grid-x-size]] 25 | (put-in a-piece [i j] :empty)) 26 | a-piece) 27 | 28 | (defn init! 29 | [state] 30 | # x-coordinate of top-left of "piece grid" 31 | # 32 | # "piece grid" is a piece-dim x piece-dim square of spots within the 33 | # game grid. the spots within the "piece grid" that represent the 34 | # piece have the value :moving, while the other spots within the 35 | # "piece grid" that are not occupied by the piece have the value 36 | # :empty. 37 | (put state :piece-pos-x 0) 38 | # y-coordinate of top-left of "piece grid" 39 | (put state :piece-pos-y 0) 40 | # 2-d array with dimensions grid-x-size x grid-y-size 41 | # 42 | # possibly values include: 43 | # 44 | # :empty - space unoccupied 45 | # :full - occupied (by what was part of past piece) 46 | # :moving - occupied by part of in-motion piece 47 | # :block - pre-filled space - left, right, or bottom edge 48 | # :fading - about to be deleted / cleared 49 | (put state :grid (init-grid)) 50 | # 2-d array with dimensions piece-dim x piece-dim 51 | # 52 | # possible values include: 53 | # 54 | # :empty - spot is empty 55 | # :moving - spot is part of piece 56 | (put state :piece @[]) 57 | # same structure and content as piece 58 | (put state :future-piece (init-piece)) 59 | (put state :game-over false) 60 | (put state :pause false) 61 | (put state :begin-play true) 62 | (put state :piece-active false) 63 | (put state :fading-color :gray) 64 | # whether any lines need to be deleted 65 | (put state :line-to-delete false) 66 | # number of lines deleted so far 67 | (put state :lines 0) 68 | (put state :blocked-below false) 69 | (put state :gravity-move-counter 0) 70 | (put state :lateral-move-counter 0) 71 | (put state :turn-move-counter 0) 72 | (put state :fast-fall-move-counter 0) 73 | (put state :fade-line-counter 0) 74 | # 75 | (put state :bgm nil) 76 | (put state :bgm-volume 0.5) 77 | # XXX: hack for storing and retrieving result of function invocation 78 | (put state :result nil) 79 | state) 80 | -------------------------------------------------------------------------------- /resources/theme.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sogaiu/jaylib-wasm-demo/3e8f83cb31ae66156415853cc43911ea1acd369d/resources/theme.ogg -------------------------------------------------------------------------------- /resources/turn.janet: -------------------------------------------------------------------------------- 1 | (import jaylib :as j) 2 | (import ./params :as p) 3 | 4 | # in rot-info below, each row can be thought of as representing a 5 | # counterclockwise rotation of n items that are components (units) of 6 | # a piece, e.g. the row: 7 | # 8 | # [[0 0] [3 0] [3 3] [0 3]] 9 | # 10 | # can indicate: 11 | # 12 | # * move the unit at 3,0 to 0,0 13 | # * move the unit at 3,3 to 3,0 14 | # * move the unit at 0,3 to 3,3 15 | # * move the unit at 0,0 to 0,3 16 | # 17 | # 0 1 2 3 18 | # +-----+-----+-----+-----+ 19 | # | | | | | 20 | # 0 | <- | --- | --- | -^ | 21 | # | | | | | | | 22 | # +-----------------------+ 23 | # | | | | | | | 24 | # 1 | | | | | | | 25 | # | | | | | | | 26 | # +-----------------------+ 27 | # | | | | | | | 28 | # 2 | | | | | | | 29 | # | | | | | | | 30 | # +-----------------------+ 31 | # | | | | | | | 32 | # 3 | V- | --- | --- | -> | 33 | # | | | | | 34 | # +-----+-----+-----+-----+ 35 | # 36 | # this is done in rotate-ccw! below. 37 | # 38 | # the info in all of the rows can also be used to check whether a 39 | # counterclockwise rotation is possible (i.e. not blocked), 40 | # e.g. looking at: 41 | # 42 | # [[0 0] [3 0] [3 3] [0 3]] 43 | # 44 | # and checking that: 45 | # 46 | # * the unit at 3,0 can be moved to 0,0 47 | # * the unit at 3,3 can be moved to 3,0 48 | # * the unit at 0,3 can be moved to 3,3 49 | # * the unit at 0,0 can be moved to 0,3 50 | # 51 | # it indicates that rotation might be possible. to fully answer 52 | # the question, the other remaining rows would need to be checked 53 | # in a similar manner. 54 | # 55 | # this is done in can-rotate? below. 56 | 57 | (def rot-info 58 | {3 59 | [[[0 0] [2 0] [2 2] [0 2]] 60 | [[1 0] [2 1] [1 2] [0 1]]] 61 | 4 62 | [[[0 0] [3 0] [3 3] [0 3]] 63 | [[1 0] [3 1] [2 3] [0 2]] 64 | [[2 0] [3 2] [1 3] [0 1]] 65 | [[1 1] [2 1] [2 2] [1 2]]] 66 | 5 67 | [[[0 0] [4 0] [4 4] [0 4]] 68 | [[1 0] [4 1] [3 4] [0 3]] 69 | [[2 0] [4 2] [2 4] [0 2]] 70 | [[3 0] [4 3] [1 4] [0 1]] 71 | [[1 1] [3 1] [3 3] [1 3]] 72 | [[2 1] [3 2] [2 3] [1 2]]] 73 | }) 74 | 75 | (defn can-rotate? 76 | [state] 77 | (def grid (state :grid)) 78 | (defn blocked? 79 | [src dst] 80 | (and (= :moving (get-in grid src)) 81 | (not= :empty (get-in grid dst)) 82 | (not= :moving (get-in grid dst)))) 83 | # 84 | (def piece-pos-x (state :piece-pos-x)) 85 | (def piece-pos-y (state :piece-pos-y)) 86 | # 87 | (def blocked 88 | (label result 89 | (loop [row :in (rot-info p/piece-dim) 90 | i :range [0 (length row)] 91 | :let [[x1 y1] (get row i) 92 | [x2 y2] (if (not= i (dec (length row))) 93 | (get row (inc i)) 94 | (get row 0))] 95 | :when (blocked? [(+ piece-pos-x x2) (+ piece-pos-y y2)] 96 | [(+ piece-pos-x x1) (+ piece-pos-y y1)])] 97 | (return result true)) 98 | (return result false))) 99 | # 100 | (put state :result (not blocked)) 101 | # 102 | state) 103 | 104 | (comment 105 | 106 | (import ./utils :as u) 107 | 108 | (let [t-grid [[:block :empty :empty :empty :empty :block] 109 | [:block :empty :moving :empty :empty :block] 110 | [:block :empty :moving :moving :empty :block] 111 | [:block :empty :moving :empty :empty :block] 112 | [:block :block :block :block :block :block]] 113 | grid (u/transpose t-grid) 114 | state @{:grid grid 115 | :grid-x-size (length grid) 116 | :grid-y-size (length (get grid 0)) 117 | :piece-pos-x 1 118 | :piece-pos-y 1}] 119 | ((can-rotate? state) :result)) 120 | # => 121 | true 122 | 123 | (let [t-grid [[:block :empty :empty :empty :empty :block] 124 | [:block :full :moving :empty :empty :block] 125 | [:block :full :moving :moving :empty :block] 126 | [:block :full :moving :empty :empty :block] 127 | [:block :block :block :block :block :block]] 128 | grid (u/transpose t-grid) 129 | state @{:grid grid 130 | :grid-x-size (length grid) 131 | :grid-y-size (length (get grid 0)) 132 | :piece-pos-x 1 133 | :piece-pos-y 1}] 134 | ((can-rotate? state) :result)) 135 | # => 136 | false 137 | 138 | ) 139 | 140 | (defn rotate-ccw! 141 | [state] 142 | (def piece (state :piece)) 143 | (defn left-rotate-units 144 | [positions] 145 | (var aux 146 | (get-in piece (first positions))) 147 | (loop [i :range [0 (dec (length positions))]] 148 | (put-in piece (get positions i) 149 | (get-in piece (get positions (inc i))))) 150 | (put-in piece (last positions) aux)) 151 | # 152 | (each row (rot-info p/piece-dim) 153 | (left-rotate-units row)) 154 | # 155 | state) 156 | 157 | (comment 158 | 159 | (import ./utils :as u) 160 | 161 | (let [t-piece [[:empty :empty :empty :empty] 162 | [:empty :moving :empty :empty] 163 | [:empty :moving :moving :empty] 164 | [:empty :moving :empty :empty]] 165 | piece (u/transpose t-piece) 166 | state @{:piece piece}] 167 | (u/transpose ((rotate-ccw! state) :piece))) 168 | # => 169 | @[@[:empty :empty :empty :empty] 170 | @[:empty :empty :moving :empty] 171 | @[:empty :moving :moving :moving] 172 | @[:empty :empty :empty :empty]] 173 | 174 | ) 175 | 176 | (defn resolve-turn-move! 177 | [state] 178 | (var result false) 179 | (def grid (state :grid)) 180 | # 181 | (when (j/key-down? :w) 182 | # rotate piece counterclockwise if appropriate 183 | (when ((can-rotate? state) :result) 184 | (rotate-ccw! state)) 185 | # clear grid spots occupied that were occupied by piece 186 | (loop [j :down-to [(- p/grid-y-size 2) 0] 187 | i :range [1 (dec p/grid-x-size)] 188 | :when (= :moving 189 | (get-in grid [i j]))] 190 | (put-in grid [i j] :empty)) 191 | # fill grid spots that the piece occupies 192 | (def piece-pos-x (state :piece-pos-x)) 193 | (def piece-pos-y (state :piece-pos-y)) 194 | (loop [i :range [piece-pos-x (+ piece-pos-x p/piece-dim)] 195 | j :range [piece-pos-y (+ piece-pos-y p/piece-dim)] 196 | :when (= :moving 197 | (get-in state 198 | [:piece (- i piece-pos-x) (- j piece-pos-y)]))] 199 | (put-in grid [i j] :moving)) 200 | # 201 | (set result true)) 202 | # 203 | (put state :result result) 204 | # 205 | state) 206 | 207 | -------------------------------------------------------------------------------- /resources/update.janet: -------------------------------------------------------------------------------- 1 | (import jaylib :as j) 2 | (import ./params :as p) 3 | (import ./state :as s) 4 | (import ./turn :as t) 5 | (import ./lateral :as l) 6 | (import ./fall :as f) 7 | 8 | (defn toggle-mute! 9 | [state] 10 | (if (zero? (state :bgm-volume)) 11 | (put state :bgm-volume 0.5) 12 | (put state :bgm-volume 0)) 13 | (j/set-music-volume (state :bgm) (state :bgm-volume)) 14 | # 15 | state) 16 | 17 | (defn toggle-pause! 18 | [state] 19 | (put state :pause (not (state :pause))) 20 | (if (state :pause) 21 | (j/pause-music-stream (state :bgm)) 22 | (j/resume-music-stream (state :bgm))) 23 | # 24 | state) 25 | 26 | (defn delete-complete-lines! 27 | [state] 28 | (var n-lines 0) 29 | # start at the bottom row (above the bottom :block row) and work way upward 30 | (loop [j :down-to [(- p/grid-y-size 2) 0] 31 | :let [grid (state :grid)]] 32 | # if left-most spot is :fading, whole row is 33 | (while (= :fading 34 | (get-in grid [1 j])) 35 | # delete the current row by marking all spots in it :empty 36 | (loop [i :range [1 (dec p/grid-x-size)]] 37 | (put-in grid [i j] :empty)) 38 | # count each deleted line 39 | (++ n-lines) 40 | # shift all rows above down by one appropriately 41 | (loop [j2 :down-to [(dec j) 0] 42 | i2 :range [1 (dec p/grid-x-size)]] 43 | (case (get-in grid [i2 j2]) 44 | :full 45 | (-> grid 46 | (put-in [i2 (inc j2)] :full) 47 | (put-in [i2 j2] :empty)) 48 | # 49 | :fading 50 | (-> grid 51 | (put-in [i2 (inc j2)] :fading) 52 | (put-in [i2 j2] :empty)))))) 53 | # 54 | (put state :result n-lines) 55 | # 56 | state) 57 | 58 | (defn handle-line-deletion! 59 | [state] 60 | (++ (state :fade-line-counter)) 61 | (if (< (% (state :fade-line-counter) 8) 4) 62 | (put state :fading-color :maroon) 63 | (put state :fading-color :gray)) 64 | (when (>= (state :fade-line-counter) p/fading-time) 65 | (def n-lines 66 | ((delete-complete-lines! state) :result)) 67 | (put state :fade-line-counter 0) 68 | (put state :line-to-delete false) 69 | (+= (state :lines) n-lines)) 70 | # 71 | state) 72 | 73 | (defn handle-active-piece! 74 | [state] 75 | (++ (state :fast-fall-move-counter)) 76 | (++ (state :gravity-move-counter)) 77 | (++ (state :lateral-move-counter)) 78 | (++ (state :turn-move-counter)) 79 | # arrange for move if necessary 80 | (when (or (j/key-pressed? :a) 81 | (j/key-pressed? :d)) 82 | (put state :lateral-move-counter p/lateral-speed)) 83 | (when (j/key-pressed? :w) 84 | (put state :turn-move-counter p/turning-speed)) 85 | # fall? 86 | (when (and (j/key-down? :s) 87 | (>= (state :fast-fall-move-counter) 88 | p/fast-fall-await-counter)) 89 | (+= (state :gravity-move-counter) p/gravity-speed)) 90 | (when (>= (state :gravity-move-counter) p/gravity-speed) 91 | # falling 92 | (f/check-blocked-below! state) 93 | # collision? 94 | (f/resolve-falling-move! state) 95 | # any lines completed? 96 | (f/check-completion! state) 97 | (put state :gravity-move-counter 0)) 98 | # sideways move 99 | (when (>= (state :lateral-move-counter) p/lateral-speed) 100 | (when (not ((l/resolve-lateral-move! state) :result)) 101 | (put state :lateral-move-counter 0))) 102 | # turning 103 | (when (>= (state :turn-move-counter) p/turning-speed) 104 | (when ((t/resolve-turn-move! state) :result) 105 | (put state :turn-move-counter 0))) 106 | # 107 | state) 108 | 109 | (def an-rng 110 | (math/rng) 111 | # XXX: wasn't playing well with wasm 112 | #'(math/rng (os/cryptorand 8)) 113 | ) 114 | 115 | (def pieces-for-dim 116 | {3 117 | [[[1 0] [1 1] [2 1]] 118 | [[0 1] [1 1] [2 1]]] 119 | 4 120 | [[[1 1] [2 1] [1 2] [2 2]] # O 121 | [[1 0] [1 1] [1 2] [2 2]] # L 122 | [[1 2] [2 0] [2 1] [2 2]] # J 123 | [[0 1] [1 1] [2 1] [3 1]] # I 124 | [[1 0] [1 1] [1 2] [2 1]] # T 125 | [[1 1] [2 1] [2 2] [3 2]] # Z 126 | [[1 2] [2 2] [2 1] [3 1]]] # S 127 | 5 128 | [[[0 2] [1 2] [2 2] [3 2] [4 2]] 129 | # 130 | [[3 1] [1 2] [2 2] [3 2] [3 3]] 131 | # 132 | [[2 1] [1 2] [2 2] [3 2] [2 3]] 133 | # 134 | [[1 1] [1 2] [2 2] [2 3] [3 3]] 135 | # 136 | [[1 1] [1 2] [2 1] [3 1] [3 2]] 137 | # 138 | [[1 1] [1 2] [1 3] [2 3] [3 3]] 139 | # 140 | [[0 2] [1 2] [2 2] [3 2] [3 3]] 141 | [[0 2] [1 2] [2 2] [3 2] [3 1]] 142 | # 143 | [[0 2] [1 2] [2 2] [3 2] [2 3]] 144 | [[0 2] [1 2] [2 2] [3 2] [2 1]] 145 | # 146 | [[1 1] [1 2] [2 2] [3 2] [3 3]] 147 | [[1 3] [1 2] [2 2] [3 2] [3 1]] 148 | # 149 | [[1 1] [1 2] [2 2] [3 2] [2 3]] 150 | [[1 3] [1 2] [2 2] [3 2] [2 1]] 151 | # 152 | [[2 1] [1 2] [2 2] [3 2] [3 1]] 153 | [[1 3] [1 2] [2 2] [3 2] [3 3]]] 154 | }) 155 | 156 | (defn get-random-piece! 157 | [state] 158 | (def future-piece 159 | (get state :future-piece)) 160 | # empty out future-piece 161 | (loop [i :range [0 p/piece-dim] 162 | j :range [0 p/piece-dim]] 163 | (put-in future-piece [i j] :empty)) 164 | # 165 | (def pieces 166 | (pieces-for-dim p/piece-dim)) 167 | # choose a random piece 168 | # XXX: docs say math/rng-int will return up through max, but only max - 1? 169 | (loop [a-unit :in (get pieces 170 | (math/rng-int an-rng 171 | (+ (dec (length pieces)) 1)))] 172 | (put-in future-piece a-unit :moving)) 173 | # 174 | state) 175 | 176 | (defn create-piece! 177 | [state] 178 | (put state :piece-pos-x 179 | (math/floor (/ (- p/grid-x-size p/piece-dim) 180 | 2))) 181 | (put state :piece-pos-y 0) 182 | # create extra piece this one time 183 | (when (state :begin-play) 184 | (get-random-piece! state) 185 | (put state :begin-play false)) 186 | # copy newly obtained future-piece to piece 187 | (loop [i :range [0 p/piece-dim] 188 | j :range [0 p/piece-dim]] 189 | (put-in state [:piece i j] 190 | (get-in state [:future-piece i j]))) 191 | # get another future piece 192 | (get-random-piece! state) 193 | # put the piece in the grid 194 | (def piece-pos-x (state :piece-pos-x)) 195 | (loop [i :range [piece-pos-x (+ piece-pos-x p/piece-dim)] 196 | j :range [0 p/piece-dim] 197 | :when (= :moving 198 | (get-in state [:piece (- i piece-pos-x) j]))] 199 | (put-in state [:grid i j] :moving)) 200 | # 201 | state) 202 | 203 | (defn init-active-piece! 204 | [state] 205 | (create-piece! state) 206 | (put state :piece-active true) 207 | (put state :fast-fall-move-counter 0) 208 | # 209 | state) 210 | 211 | (defn check-game-over! 212 | [state] 213 | (loop [j :range [0 2] # XXX: 2? 214 | i :range [1 (dec p/grid-x-size)] 215 | :when (= :full 216 | (get-in state [:grid i j]))] 217 | (put state :game-over true) 218 | (break)) 219 | # 220 | state) 221 | 222 | (defn update-game! 223 | [state] 224 | (when (state :game-over) 225 | (when (j/key-pressed? :enter) 226 | (s/init! state)) 227 | (break state)) 228 | # 229 | (when (j/key-pressed? :m) 230 | (toggle-mute! state)) 231 | # 232 | (when (j/key-pressed? :p) 233 | (toggle-pause! state)) 234 | # 235 | (when (state :pause) 236 | (break state)) 237 | # 238 | (when (state :line-to-delete) 239 | (handle-line-deletion! state) 240 | (break state)) 241 | # 242 | (if (state :piece-active) 243 | (handle-active-piece! state) 244 | (init-active-piece! state)) 245 | # 246 | (check-game-over! state) 247 | # 248 | state) 249 | 250 | -------------------------------------------------------------------------------- /resources/utils.janet: -------------------------------------------------------------------------------- 1 | (defn transpose 2 | [a-grid] 3 | (def new-grid @[]) 4 | (loop [j :range [0 (length (get a-grid 0))] 5 | :before (put new-grid j @[]) 6 | i :range [0 (length a-grid)]] 7 | (put-in new-grid [j i] 8 | (get-in a-grid [i j]))) 9 | new-grid) 10 | 11 | (comment 12 | 13 | (transpose [[1 2 3] 14 | [4 5 6] 15 | [7 8 9]]) 16 | # => 17 | @[@[1 4 7] 18 | @[2 5 8] 19 | @[3 6 9]] 20 | 21 | (transpose [[1 2 3 4] 22 | [5 6 7 8]]) 23 | # => 24 | @[@[1 5] 25 | @[2 6] 26 | @[3 7] 27 | @[4 8]] 28 | 29 | ) 30 | 31 | -------------------------------------------------------------------------------- /shell.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | raylib HTML5 GAME 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 167 | 168 | 169 | 184 | 185 |
186 | 187 |
188 | 189 | 190 | 191 | 192 | 210 | 295 | 296 | 297 | 326 | {{{ SCRIPT }}} 327 | 328 | 329 | -------------------------------------------------------------------------------- /test/make-and-run-juat-tests.janet: -------------------------------------------------------------------------------- 1 | (-> ["janet" 2 | "./janet-usages-as-tests/janet-usages-as-tests/make-and-run-tests.janet" 3 | # specify file and/or directory paths relative to project root 4 | "./resources/lateral.janet" 5 | "./resources/turn.janet" 6 | "./resources/fall.janet" 7 | ] 8 | (os/execute :p) 9 | os/exit) 10 | 11 | --------------------------------------------------------------------------------