Compare commits
No commits in common. "gh-pages" and "master" have entirely different histories.
7
.dockerignore
Normal file
7
.dockerignore
Normal file
@ -0,0 +1,7 @@
|
||||
.github
|
||||
.jenkins
|
||||
.git
|
||||
pkg
|
||||
target
|
||||
**/dist
|
||||
**/node_modules
|
72
.gitea/workflows/test.yml
Normal file
72
.gitea/workflows/test.yml
Normal file
@ -0,0 +1,72 @@
|
||||
name: test and deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
github-server-url: https://gitea.sheep-ghoul.ts.net
|
||||
|
||||
- uses: actions-rust-lang/setup-rust-toolchain@v1
|
||||
with:
|
||||
rustflags: ""
|
||||
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
|
||||
- name: Build Rust for WASM
|
||||
run: wasm-pack build
|
||||
|
||||
- name: Test WASM in-browser
|
||||
run: wasm-pack test --firefox --chrome --headless
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install Node Modules
|
||||
run: npm ci
|
||||
working-directory: ./www
|
||||
|
||||
- name: Build Js
|
||||
run: npm run build --if-present
|
||||
working-directory: ./www
|
||||
|
||||
package:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build] # for ignoring bad builds
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
github-server-url: https://gitea.sheep-ghoul.ts.net
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: gitea.sheep-ghoul.ts.net
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build & Push Container
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: gitea.sheep-ghoul.ts.net/sarsoo/game-of-life:latest
|
||||
context: .
|
73
.github/workflows/test.yml
vendored
Normal file
73
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
name: test and deploy
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
build:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
- name: Install wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
|
||||
- name: Build Rust for WASM
|
||||
run: wasm-pack build
|
||||
|
||||
- name: Test WASM in-browser
|
||||
run: wasm-pack test --firefox --headless
|
||||
|
||||
- name: Install Node
|
||||
uses: actions/setup-node@v2
|
||||
with:
|
||||
node-version: 18
|
||||
|
||||
- name: Install Node Modules
|
||||
run: npm ci
|
||||
working-directory: ./www
|
||||
|
||||
- name: Build Js
|
||||
run: npm run build --if-present
|
||||
working-directory: ./www
|
||||
|
||||
- name: Move CNAME file to Staging Directory
|
||||
run: mv CNAME www/dist
|
||||
|
||||
- name: Deploy To Pages
|
||||
uses: peaceiris/actions-gh-pages@v4
|
||||
with:
|
||||
github_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
publish_dir: ./www/dist
|
||||
|
||||
package:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build] # for ignoring bad builds
|
||||
if: github.event_name == 'push' && github.ref == 'refs/heads/master'
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
- name: Login to DockerHub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Build & Push Container
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
push: true
|
||||
tags: sarsoo/game-of-life:latest
|
6
.gitignore
vendored
Normal file
6
.gitignore
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
bin/
|
||||
pkg/
|
||||
wasm-pack.log
|
22
.jenkins/jenkinsfile
Normal file
22
.jenkins/jenkinsfile
Normal file
@ -0,0 +1,22 @@
|
||||
pipeline {
|
||||
agent any
|
||||
|
||||
stages {
|
||||
stage('Deploy') {
|
||||
when { branch 'master' }
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry('https://registry.sarsoo.xyz', 'git-registry-creds') {
|
||||
|
||||
docker.build("sarsoo/game-of-life:latest").push()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
cleanWs()
|
||||
}
|
||||
}
|
||||
}
|
48
Cargo.toml
Normal file
48
Cargo.toml
Normal file
@ -0,0 +1,48 @@
|
||||
[package]
|
||||
name = "gameoflife"
|
||||
version = "0.1.0"
|
||||
authors = ["aj <andrewjpack@gmail.com>"]
|
||||
edition = "2018"
|
||||
repository = "https://github.com/Sarsoo/game-of-life"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["console_error_panic_hook",
|
||||
"random_init"
|
||||
]
|
||||
|
||||
random_init = ["rand", "rand_pcg"]
|
||||
|
||||
[dependencies]
|
||||
wasm-bindgen = "0.2.74"
|
||||
rand = {version = "0.8.4", optional = true }
|
||||
rand_pcg = {version = "0.3.1", optional = true }
|
||||
|
||||
# The `console_error_panic_hook` crate provides better debugging of panics by
|
||||
# logging them with `console.error`. This is great for development, but requires
|
||||
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
|
||||
# code size when deploying.
|
||||
console_error_panic_hook = { version = "0.1.6", optional = true }
|
||||
|
||||
[dependencies.web-sys]
|
||||
version = "0.3.45"
|
||||
features = [
|
||||
"console",
|
||||
]
|
||||
|
||||
[dependencies.getrandom]
|
||||
version = "*"
|
||||
features = ["js"]
|
||||
|
||||
[dev-dependencies]
|
||||
wasm-bindgen-test = "0.3.24"
|
||||
|
||||
[profile.release]
|
||||
# Tell `rustc` to optimize for small code size.
|
||||
opt-level = "s"
|
||||
|
||||
[package.metadata.wasm-pack.profile.release]
|
||||
wasm-opt = false
|
||||
|
23
Dockerfile
Normal file
23
Dockerfile
Normal file
@ -0,0 +1,23 @@
|
||||
FROM rust:1.78 AS rust-build
|
||||
|
||||
RUN curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh
|
||||
|
||||
COPY . /gof
|
||||
WORKDIR /gof
|
||||
|
||||
RUN wasm-pack build --release
|
||||
RUN cargo doc --no-deps --document-private-items
|
||||
|
||||
FROM node:22-alpine AS js-build
|
||||
|
||||
COPY . /gof
|
||||
WORKDIR /gof
|
||||
|
||||
COPY --from=rust-build /gof/pkg /gof/pkg
|
||||
WORKDIR /gof/www
|
||||
RUN npm ci
|
||||
RUN npm run build --if-present
|
||||
COPY --from=rust-build /gof/target/doc /gof/www/dist/
|
||||
|
||||
FROM nginx:alpine-slim
|
||||
COPY --from=js-build /gof/www/dist /usr/share/nginx/html/
|
22
README.md
Normal file
22
README.md
Normal file
@ -0,0 +1,22 @@
|
||||
Game of Life
|
||||
===============
|
||||
|
||||
![gof-ci](https://github.com/sarsoo/game-of-life/actions/workflows/test.yml/badge.svg)
|
||||
|
||||
## [Try it Out!](https://sarsoo.github.io/game-of-life/)
|
||||
|
||||
WASM-based game of life following the [Rust WASM book](https://rustwasm.github.io/docs/book/introduction.html) tutorial.
|
||||
|
||||
Rust WASM module for game logic with a JS frontend for rendering and processing user input.
|
||||
|
||||
## Building
|
||||
|
||||
1. Setup a Rust + wasm-pack environment and a Node environment
|
||||
2. Build the Rust library into a WASM module
|
||||
- `wasm-pack build`
|
||||
3. Move to the Js workspace
|
||||
- `cd www`
|
||||
4. Install the Js dependencies
|
||||
- `npm install`
|
||||
5. Build the Js frontend with Rust WASM module
|
||||
- `npm run build`
|
393
bootstrap.js
vendored
393
bootstrap.js
vendored
@ -1,393 +0,0 @@
|
||||
/*
|
||||
* ATTENTION: The "eval" devtool has been used (maybe by default in mode: "development").
|
||||
* This devtool is neither made for production nor for readable output files.
|
||||
* It uses "eval()" calls to create a separate source file in the browser devtools.
|
||||
* If you are trying to read the output file, select a different devtool (https://webpack.js.org/configuration/devtool/)
|
||||
* or disable the default devtool with "devtool: false".
|
||||
* If you are looking for production-ready output files, see mode: "production" (https://webpack.js.org/configuration/mode/).
|
||||
*/
|
||||
/******/ (() => { // webpackBootstrap
|
||||
/******/ var __webpack_modules__ = ({
|
||||
|
||||
/***/ "./bootstrap.js":
|
||||
/*!**********************!*\
|
||||
!*** ./bootstrap.js ***!
|
||||
\**********************/
|
||||
/***/ ((__unused_webpack_module, __unused_webpack_exports, __webpack_require__) => {
|
||||
|
||||
eval("// A dependency graph that contains any wasm must all be imported\n// asynchronously. This `bootstrap.js` file does the single async import, so\n// that no one else needs to worry about it again.\n__webpack_require__.e(/*! import() */ \"index_js\").then(__webpack_require__.bind(__webpack_require__, /*! ./index.js */ \"./index.js\"))\n .catch(e => console.error(\"Error importing `index.js`:\", e));\n\n\n//# sourceURL=webpack://game-of-life-web/./bootstrap.js?");
|
||||
|
||||
/***/ })
|
||||
|
||||
/******/ });
|
||||
/************************************************************************/
|
||||
/******/ // The module cache
|
||||
/******/ var __webpack_module_cache__ = {};
|
||||
/******/
|
||||
/******/ // The require function
|
||||
/******/ function __webpack_require__(moduleId) {
|
||||
/******/ // Check if module is in cache
|
||||
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
||||
/******/ if (cachedModule !== undefined) {
|
||||
/******/ return cachedModule.exports;
|
||||
/******/ }
|
||||
/******/ // Create a new module (and put it into the cache)
|
||||
/******/ var module = __webpack_module_cache__[moduleId] = {
|
||||
/******/ id: moduleId,
|
||||
/******/ loaded: false,
|
||||
/******/ exports: {}
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // Execute the module function
|
||||
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
||||
/******/
|
||||
/******/ // Flag the module as loaded
|
||||
/******/ module.loaded = true;
|
||||
/******/
|
||||
/******/ // Return the exports of the module
|
||||
/******/ return module.exports;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ // expose the modules object (__webpack_modules__)
|
||||
/******/ __webpack_require__.m = __webpack_modules__;
|
||||
/******/
|
||||
/************************************************************************/
|
||||
/******/ /* webpack/runtime/async module */
|
||||
/******/ (() => {
|
||||
/******/ var webpackThen = typeof Symbol === "function" ? Symbol("webpack then") : "__webpack_then__";
|
||||
/******/ var webpackExports = typeof Symbol === "function" ? Symbol("webpack exports") : "__webpack_exports__";
|
||||
/******/ var webpackError = typeof Symbol === "function" ? Symbol("webpack error") : "__webpack_error__";
|
||||
/******/ var completeQueue = (queue) => {
|
||||
/******/ if(queue) {
|
||||
/******/ queue.forEach((fn) => (fn.r--));
|
||||
/******/ queue.forEach((fn) => (fn.r-- ? fn.r++ : fn()));
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ var completeFunction = (fn) => (!--fn.r && fn());
|
||||
/******/ var queueFunction = (queue, fn) => (queue ? queue.push(fn) : completeFunction(fn));
|
||||
/******/ var wrapDeps = (deps) => (deps.map((dep) => {
|
||||
/******/ if(dep !== null && typeof dep === "object") {
|
||||
/******/ if(dep[webpackThen]) return dep;
|
||||
/******/ if(dep.then) {
|
||||
/******/ var queue = [];
|
||||
/******/ dep.then((r) => {
|
||||
/******/ obj[webpackExports] = r;
|
||||
/******/ completeQueue(queue);
|
||||
/******/ queue = 0;
|
||||
/******/ }, (e) => {
|
||||
/******/ obj[webpackError] = e;
|
||||
/******/ completeQueue(queue);
|
||||
/******/ queue = 0;
|
||||
/******/ });
|
||||
/******/ var obj = {};
|
||||
/******/ obj[webpackThen] = (fn, reject) => (queueFunction(queue, fn), dep['catch'](reject));
|
||||
/******/ return obj;
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ var ret = {};
|
||||
/******/ ret[webpackThen] = (fn) => (completeFunction(fn));
|
||||
/******/ ret[webpackExports] = dep;
|
||||
/******/ return ret;
|
||||
/******/ }));
|
||||
/******/ __webpack_require__.a = (module, body, hasAwait) => {
|
||||
/******/ var queue = hasAwait && [];
|
||||
/******/ var exports = module.exports;
|
||||
/******/ var currentDeps;
|
||||
/******/ var outerResolve;
|
||||
/******/ var reject;
|
||||
/******/ var isEvaluating = true;
|
||||
/******/ var nested = false;
|
||||
/******/ var whenAll = (deps, onResolve, onReject) => {
|
||||
/******/ if (nested) return;
|
||||
/******/ nested = true;
|
||||
/******/ onResolve.r += deps.length;
|
||||
/******/ deps.map((dep, i) => (dep[webpackThen](onResolve, onReject)));
|
||||
/******/ nested = false;
|
||||
/******/ };
|
||||
/******/ var promise = new Promise((resolve, rej) => {
|
||||
/******/ reject = rej;
|
||||
/******/ outerResolve = () => (resolve(exports), completeQueue(queue), queue = 0);
|
||||
/******/ });
|
||||
/******/ promise[webpackExports] = exports;
|
||||
/******/ promise[webpackThen] = (fn, rejectFn) => {
|
||||
/******/ if (isEvaluating) { return completeFunction(fn); }
|
||||
/******/ if (currentDeps) whenAll(currentDeps, fn, rejectFn);
|
||||
/******/ queueFunction(queue, fn);
|
||||
/******/ promise['catch'](rejectFn);
|
||||
/******/ };
|
||||
/******/ module.exports = promise;
|
||||
/******/ body((deps) => {
|
||||
/******/ currentDeps = wrapDeps(deps);
|
||||
/******/ var fn;
|
||||
/******/ var getResult = () => (currentDeps.map((d) => {
|
||||
/******/ if(d[webpackError]) throw d[webpackError];
|
||||
/******/ return d[webpackExports];
|
||||
/******/ }))
|
||||
/******/ var promise = new Promise((resolve, reject) => {
|
||||
/******/ fn = () => (resolve(getResult));
|
||||
/******/ fn.r = 0;
|
||||
/******/ whenAll(currentDeps, fn, reject);
|
||||
/******/ });
|
||||
/******/ return fn.r ? promise : getResult();
|
||||
/******/ }, (err) => (err && reject(promise[webpackError] = err), outerResolve()));
|
||||
/******/ isEvaluating = false;
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/define property getters */
|
||||
/******/ (() => {
|
||||
/******/ // define getter functions for harmony exports
|
||||
/******/ __webpack_require__.d = (exports, definition) => {
|
||||
/******/ for(var key in definition) {
|
||||
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
||||
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/ensure chunk */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.f = {};
|
||||
/******/ // This file contains only the entry chunk.
|
||||
/******/ // The chunk loading function for additional chunks
|
||||
/******/ __webpack_require__.e = (chunkId) => {
|
||||
/******/ return Promise.all(Object.keys(__webpack_require__.f).reduce((promises, key) => {
|
||||
/******/ __webpack_require__.f[key](chunkId, promises);
|
||||
/******/ return promises;
|
||||
/******/ }, []));
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/get javascript chunk filename */
|
||||
/******/ (() => {
|
||||
/******/ // This function allow to reference async chunks
|
||||
/******/ __webpack_require__.u = (chunkId) => {
|
||||
/******/ // return url for filenames based on template
|
||||
/******/ return "" + chunkId + ".bootstrap.js";
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/global */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.g = (function() {
|
||||
/******/ if (typeof globalThis === 'object') return globalThis;
|
||||
/******/ try {
|
||||
/******/ return this || new Function('return this')();
|
||||
/******/ } catch (e) {
|
||||
/******/ if (typeof window === 'object') return window;
|
||||
/******/ }
|
||||
/******/ })();
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/harmony module decorator */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.hmd = (module) => {
|
||||
/******/ module = Object.create(module);
|
||||
/******/ if (!module.children) module.children = [];
|
||||
/******/ Object.defineProperty(module, 'exports', {
|
||||
/******/ enumerable: true,
|
||||
/******/ set: () => {
|
||||
/******/ throw new Error('ES Modules may not assign module.exports or exports.*, Use ESM export syntax, instead: ' + module.id);
|
||||
/******/ }
|
||||
/******/ });
|
||||
/******/ return module;
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/load script */
|
||||
/******/ (() => {
|
||||
/******/ var inProgress = {};
|
||||
/******/ var dataWebpackPrefix = "game-of-life-web:";
|
||||
/******/ // loadScript function to load a script via script tag
|
||||
/******/ __webpack_require__.l = (url, done, key, chunkId) => {
|
||||
/******/ if(inProgress[url]) { inProgress[url].push(done); return; }
|
||||
/******/ var script, needAttach;
|
||||
/******/ if(key !== undefined) {
|
||||
/******/ var scripts = document.getElementsByTagName("script");
|
||||
/******/ for(var i = 0; i < scripts.length; i++) {
|
||||
/******/ var s = scripts[i];
|
||||
/******/ if(s.getAttribute("src") == url || s.getAttribute("data-webpack") == dataWebpackPrefix + key) { script = s; break; }
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(!script) {
|
||||
/******/ needAttach = true;
|
||||
/******/ script = document.createElement('script');
|
||||
/******/
|
||||
/******/ script.charset = 'utf-8';
|
||||
/******/ script.timeout = 120;
|
||||
/******/ if (__webpack_require__.nc) {
|
||||
/******/ script.setAttribute("nonce", __webpack_require__.nc);
|
||||
/******/ }
|
||||
/******/ script.setAttribute("data-webpack", dataWebpackPrefix + key);
|
||||
/******/ script.src = url;
|
||||
/******/ }
|
||||
/******/ inProgress[url] = [done];
|
||||
/******/ var onScriptComplete = (prev, event) => {
|
||||
/******/ // avoid mem leaks in IE.
|
||||
/******/ script.onerror = script.onload = null;
|
||||
/******/ clearTimeout(timeout);
|
||||
/******/ var doneFns = inProgress[url];
|
||||
/******/ delete inProgress[url];
|
||||
/******/ script.parentNode && script.parentNode.removeChild(script);
|
||||
/******/ doneFns && doneFns.forEach((fn) => (fn(event)));
|
||||
/******/ if(prev) return prev(event);
|
||||
/******/ }
|
||||
/******/ ;
|
||||
/******/ var timeout = setTimeout(onScriptComplete.bind(null, undefined, { type: 'timeout', target: script }), 120000);
|
||||
/******/ script.onerror = onScriptComplete.bind(null, script.onerror);
|
||||
/******/ script.onload = onScriptComplete.bind(null, script.onload);
|
||||
/******/ needAttach && document.head.appendChild(script);
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/make namespace object */
|
||||
/******/ (() => {
|
||||
/******/ // define __esModule on exports
|
||||
/******/ __webpack_require__.r = (exports) => {
|
||||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||||
/******/ }
|
||||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/wasm loading */
|
||||
/******/ (() => {
|
||||
/******/ __webpack_require__.v = (exports, wasmModuleId, wasmModuleHash, importsObj) => {
|
||||
/******/ var req = fetch(__webpack_require__.p + "" + wasmModuleHash + ".module.wasm");
|
||||
/******/ if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||
/******/ return WebAssembly.instantiateStreaming(req, importsObj)
|
||||
/******/ .then((res) => (Object.assign(exports, res.instance.exports)));
|
||||
/******/ }
|
||||
/******/ return req
|
||||
/******/ .then((x) => (x.arrayBuffer()))
|
||||
/******/ .then((bytes) => (WebAssembly.instantiate(bytes, importsObj)))
|
||||
/******/ .then((res) => (Object.assign(exports, res.instance.exports)));
|
||||
/******/ };
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/publicPath */
|
||||
/******/ (() => {
|
||||
/******/ var scriptUrl;
|
||||
/******/ if (__webpack_require__.g.importScripts) scriptUrl = __webpack_require__.g.location + "";
|
||||
/******/ var document = __webpack_require__.g.document;
|
||||
/******/ if (!scriptUrl && document) {
|
||||
/******/ if (document.currentScript)
|
||||
/******/ scriptUrl = document.currentScript.src
|
||||
/******/ if (!scriptUrl) {
|
||||
/******/ var scripts = document.getElementsByTagName("script");
|
||||
/******/ if(scripts.length) scriptUrl = scripts[scripts.length - 1].src
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ // When supporting browsers where an automatic publicPath is not supported you must specify an output.publicPath manually via configuration
|
||||
/******/ // or pass an empty string ("") and set the __webpack_public_path__ variable from your code to use your own logic.
|
||||
/******/ if (!scriptUrl) throw new Error("Automatic publicPath is not supported in this browser");
|
||||
/******/ scriptUrl = scriptUrl.replace(/#.*$/, "").replace(/\?.*$/, "").replace(/\/[^\/]+$/, "/");
|
||||
/******/ __webpack_require__.p = scriptUrl;
|
||||
/******/ })();
|
||||
/******/
|
||||
/******/ /* webpack/runtime/jsonp chunk loading */
|
||||
/******/ (() => {
|
||||
/******/ // no baseURI
|
||||
/******/
|
||||
/******/ // object to store loaded and loading chunks
|
||||
/******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched
|
||||
/******/ // [resolve, reject, Promise] = chunk loading, 0 = chunk loaded
|
||||
/******/ var installedChunks = {
|
||||
/******/ "main": 0
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ __webpack_require__.f.j = (chunkId, promises) => {
|
||||
/******/ // JSONP chunk loading for javascript
|
||||
/******/ var installedChunkData = __webpack_require__.o(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;
|
||||
/******/ if(installedChunkData !== 0) { // 0 means "already installed".
|
||||
/******/
|
||||
/******/ // a Promise means "currently loading".
|
||||
/******/ if(installedChunkData) {
|
||||
/******/ promises.push(installedChunkData[2]);
|
||||
/******/ } else {
|
||||
/******/ if(true) { // all chunks have JS
|
||||
/******/ // setup Promise in chunk cache
|
||||
/******/ var promise = new Promise((resolve, reject) => (installedChunkData = installedChunks[chunkId] = [resolve, reject]));
|
||||
/******/ promises.push(installedChunkData[2] = promise);
|
||||
/******/
|
||||
/******/ // start chunk loading
|
||||
/******/ var url = __webpack_require__.p + __webpack_require__.u(chunkId);
|
||||
/******/ // create error before stack unwound to get useful stacktrace later
|
||||
/******/ var error = new Error();
|
||||
/******/ var loadingEnded = (event) => {
|
||||
/******/ if(__webpack_require__.o(installedChunks, chunkId)) {
|
||||
/******/ installedChunkData = installedChunks[chunkId];
|
||||
/******/ if(installedChunkData !== 0) installedChunks[chunkId] = undefined;
|
||||
/******/ if(installedChunkData) {
|
||||
/******/ var errorType = event && (event.type === 'load' ? 'missing' : event.type);
|
||||
/******/ var realSrc = event && event.target && event.target.src;
|
||||
/******/ error.message = 'Loading chunk ' + chunkId + ' failed.\n(' + errorType + ': ' + realSrc + ')';
|
||||
/******/ error.name = 'ChunkLoadError';
|
||||
/******/ error.type = errorType;
|
||||
/******/ error.request = realSrc;
|
||||
/******/ installedChunkData[1](error);
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/ __webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
|
||||
/******/ } else installedChunks[chunkId] = 0;
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ };
|
||||
/******/
|
||||
/******/ // no prefetching
|
||||
/******/
|
||||
/******/ // no preloaded
|
||||
/******/
|
||||
/******/ // no HMR
|
||||
/******/
|
||||
/******/ // no HMR manifest
|
||||
/******/
|
||||
/******/ // no on chunks loaded
|
||||
/******/
|
||||
/******/ // install a JSONP callback for chunk loading
|
||||
/******/ var webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
|
||||
/******/ var [chunkIds, moreModules, runtime] = data;
|
||||
/******/ // add "moreModules" to the modules object,
|
||||
/******/ // then flag all "chunkIds" as loaded and fire callback
|
||||
/******/ var moduleId, chunkId, i = 0;
|
||||
/******/ if(chunkIds.some((id) => (installedChunks[id] !== 0))) {
|
||||
/******/ for(moduleId in moreModules) {
|
||||
/******/ if(__webpack_require__.o(moreModules, moduleId)) {
|
||||
/******/ __webpack_require__.m[moduleId] = moreModules[moduleId];
|
||||
/******/ }
|
||||
/******/ }
|
||||
/******/ if(runtime) var result = runtime(__webpack_require__);
|
||||
/******/ }
|
||||
/******/ if(parentChunkLoadingFunction) parentChunkLoadingFunction(data);
|
||||
/******/ for(;i < chunkIds.length; i++) {
|
||||
/******/ chunkId = chunkIds[i];
|
||||
/******/ if(__webpack_require__.o(installedChunks, chunkId) && installedChunks[chunkId]) {
|
||||
/******/ installedChunks[chunkId][0]();
|
||||
/******/ }
|
||||
/******/ installedChunks[chunkId] = 0;
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ }
|
||||
/******/
|
||||
/******/ var chunkLoadingGlobal = self["webpackChunkgame_of_life_web"] = self["webpackChunkgame_of_life_web"] || [];
|
||||
/******/ chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
|
||||
/******/ chunkLoadingGlobal.push = webpackJsonpCallback.bind(null, chunkLoadingGlobal.push.bind(chunkLoadingGlobal));
|
||||
/******/ })();
|
||||
/******/
|
||||
/************************************************************************/
|
||||
/******/
|
||||
/******/ // startup
|
||||
/******/ // Load entry module and return exports
|
||||
/******/ // This entry module can't be inlined because the eval devtool is used.
|
||||
/******/ var __webpack_exports__ = __webpack_require__("./bootstrap.js");
|
||||
/******/
|
||||
/******/ })()
|
||||
;
|
Binary file not shown.
File diff suppressed because one or more lines are too long
188
src/lib.rs
Normal file
188
src/lib.rs
Normal file
@ -0,0 +1,188 @@
|
||||
mod utils;
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::fmt;
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
use rand::prelude::*;
|
||||
|
||||
use rand_pcg::Pcg64Mcg;
|
||||
|
||||
mod time;
|
||||
|
||||
macro_rules! log {
|
||||
( $( $t:tt )* ) => {
|
||||
web_sys::console::log_1(&format!( $( $t )* ).into());
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn init_game() {
|
||||
log!("initialising wasm");
|
||||
utils::set_panic_hook();
|
||||
|
||||
#[cfg(feature = "random_init")]
|
||||
log!("random layout enabled");
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
#[repr(u8)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Cell {
|
||||
Dead = 0,
|
||||
Alive = 1,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub struct Universe {
|
||||
width: u32,
|
||||
height: u32,
|
||||
cells: Vec<Cell>,
|
||||
|
||||
rng: Pcg64Mcg,
|
||||
rand_threshold: u32,
|
||||
}
|
||||
|
||||
impl Universe {
|
||||
fn get_index(&self, row: u32, column: u32) -> usize {
|
||||
(row * self.width + column) as usize
|
||||
}
|
||||
|
||||
fn live_neighbor_count(&self, row: u32, column: u32) -> u8 {
|
||||
let mut count = 0;
|
||||
for delta_row in [self.height - 1, 0, 1].iter().cloned() {
|
||||
for delta_col in [self.width - 1, 0, 1].iter().cloned() {
|
||||
if delta_row == 0 && delta_col == 0 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let neighbor_row = (row + delta_row) % self.height;
|
||||
let neighbor_col = (column + delta_col) % self.width;
|
||||
let idx = self.get_index(neighbor_row, neighbor_col);
|
||||
count += self.cells[idx] as u8;
|
||||
}
|
||||
}
|
||||
count
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "random_init"))]
|
||||
fn populate_cell(i: u32) -> Cell {
|
||||
if i % 2 == 0 || i % 7 == 0 {
|
||||
Cell::Alive
|
||||
} else {
|
||||
Cell::Dead
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "random_init")]
|
||||
fn populate_cell(i: u32, rng: &mut Pcg64Mcg, threshold: u32) -> Cell {
|
||||
match rng.gen_range(0..101).cmp(&threshold) {
|
||||
Ordering::Less => Cell::Alive,
|
||||
Ordering::Greater => Cell::Dead,
|
||||
Ordering::Equal => Cell::Dead,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen] // public methods exported to js
|
||||
impl Universe {
|
||||
pub fn tick(&mut self) {
|
||||
// log!("ticking");
|
||||
// let _timer = time::Timer::new("Universe::tick"); // will stop when dropped
|
||||
let mut next = self.cells.clone();
|
||||
|
||||
for row in 0..self.height {
|
||||
for col in 0..self.width {
|
||||
let idx = self.get_index(row, col);
|
||||
let cell = self.cells[idx];
|
||||
let live_neighbors = self.live_neighbor_count(row, col);
|
||||
|
||||
let next_cell = match (cell, live_neighbors) {
|
||||
// Rule 1: Any live cell with fewer than two live neighbours
|
||||
// dies, as if caused by underpopulation.
|
||||
(Cell::Alive, x) if x < 2 => Cell::Dead,
|
||||
// Rule 2: Any live cell with two or three live neighbours
|
||||
// lives on to the next generation.
|
||||
(Cell::Alive, 2) | (Cell::Alive, 3) => Cell::Alive,
|
||||
// Rule 3: Any live cell with more than three live
|
||||
// neighbours dies, as if by overpopulation.
|
||||
(Cell::Alive, x) if x > 3 => Cell::Dead,
|
||||
// Rule 4: Any dead cell with exactly three live neighbours
|
||||
// becomes a live cell, as if by reproduction.
|
||||
(Cell::Dead, 3) => Cell::Alive,
|
||||
// All other cells remain in the same state.
|
||||
(otherwise, _) => otherwise,
|
||||
};
|
||||
|
||||
next[idx] = next_cell;
|
||||
}
|
||||
}
|
||||
|
||||
self.cells = next;
|
||||
}
|
||||
|
||||
pub fn new(width: u32, height: u32, rand_threshold: u32, seed: f64) -> Universe {
|
||||
log!("Generating new board {}x{}", width, height);
|
||||
|
||||
let mut rng = Pcg64Mcg::seed_from_u64(seed as u64);
|
||||
|
||||
let cells = (0..width * height)
|
||||
.map(|i| {
|
||||
#[cfg(not(feature = "random_init"))]
|
||||
return Universe::populate_cell(i);
|
||||
#[cfg(feature = "random_init")]
|
||||
return Universe::populate_cell(i, &mut rng, rand_threshold);
|
||||
})
|
||||
.collect();
|
||||
|
||||
Universe {
|
||||
width,
|
||||
height,
|
||||
cells,
|
||||
|
||||
rng,
|
||||
rand_threshold,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn render(&self) -> String {
|
||||
self.to_string()
|
||||
}
|
||||
|
||||
pub fn width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn cells(&self) -> *const Cell {
|
||||
self.cells.as_ptr()
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.cells = (0..self.width * self.height)
|
||||
.map(|i| {
|
||||
#[cfg(not(feature = "random_init"))]
|
||||
return Universe::populate_cell(i);
|
||||
#[cfg(feature = "random_init")]
|
||||
return Universe::populate_cell(i, &mut self.rng, self.rand_threshold);
|
||||
})
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Universe {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
for line in self.cells.as_slice().chunks(self.width as usize) {
|
||||
for &cell in line {
|
||||
let symbol = if cell == Cell::Dead { '◻' } else { '◼' };
|
||||
write!(f, "{}", symbol)?;
|
||||
}
|
||||
write!(f, "\n")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
19
src/time.rs
Normal file
19
src/time.rs
Normal file
@ -0,0 +1,19 @@
|
||||
extern crate web_sys;
|
||||
use web_sys::console;
|
||||
|
||||
pub struct Timer<'a> {
|
||||
name: &'a str,
|
||||
}
|
||||
|
||||
impl<'a> Timer<'a> {
|
||||
pub fn new(name: &'a str) -> Timer<'a> {
|
||||
console::time_with_label(name);
|
||||
Timer { name }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for Timer<'a> {
|
||||
fn drop(&mut self) {
|
||||
console::time_end_with_label(self.name);
|
||||
}
|
||||
}
|
10
src/utils.rs
Normal file
10
src/utils.rs
Normal file
@ -0,0 +1,10 @@
|
||||
pub fn set_panic_hook() {
|
||||
// When the `console_error_panic_hook` feature is enabled, we can call the
|
||||
// `set_panic_hook` function at least once during initialization, and then
|
||||
// we will get better error messages if our code ever panics.
|
||||
//
|
||||
// For more details see
|
||||
// https://github.com/rustwasm/console_error_panic_hook#readme
|
||||
#[cfg(feature = "console_error_panic_hook")]
|
||||
console_error_panic_hook::set_once();
|
||||
}
|
32
tests/web.rs
Normal file
32
tests/web.rs
Normal file
@ -0,0 +1,32 @@
|
||||
//! Test suite for the Web and headless browsers.
|
||||
|
||||
#![cfg(target_arch = "wasm32")]
|
||||
|
||||
extern crate wasm_bindgen_test;
|
||||
// use wasm_bindgen_test::*;
|
||||
use wasm_bindgen_test::{wasm_bindgen_test, wasm_bindgen_test_configure};
|
||||
|
||||
wasm_bindgen_test_configure!(run_in_browser);
|
||||
|
||||
extern crate gameoflife;
|
||||
use gameoflife::Universe;
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn get_width() {
|
||||
let uni = Universe::new(10, 15, 1, 1.0);
|
||||
assert_eq!(uni.width(), 10);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn get_height() {
|
||||
let uni = Universe::new(10, 15, 1, 1.0);
|
||||
assert_eq!(uni.height(), 15);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn get_cells() {
|
||||
let uni = Universe::new(10, 15, 1, 1.0);
|
||||
uni.cells();
|
||||
}
|
||||
|
||||
|
2
www/.gitignore
vendored
Normal file
2
www/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
5
www/bootstrap.js
vendored
Normal file
5
www/bootstrap.js
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
// A dependency graph that contains any wasm must all be imported
|
||||
// asynchronously. This `bootstrap.js` file does the single async import, so
|
||||
// that no one else needs to worry about it again.
|
||||
import("./index.js")
|
||||
.catch(e => console.error("Error importing `index.js`:", e));
|
236
www/index.js
Normal file
236
www/index.js
Normal file
@ -0,0 +1,236 @@
|
||||
import { Universe, Cell, init_game } from "gameoflife";
|
||||
import { memory } from "gameoflife/gameoflife_bg.wasm";
|
||||
|
||||
// let PLAY = true;
|
||||
// let PLAY = false;
|
||||
init_game();
|
||||
const randSlider = document.getElementById("randThreshold");
|
||||
const randSliderLabel = document.getElementById("randThreshold-label");
|
||||
|
||||
const CELL_SIZE = 4; // px
|
||||
const GRID_COLOR = "#BBBBBB";
|
||||
const DEAD_COLOR = "#FFFFFF";
|
||||
const ALIVE_COLOR = "#FF55AA";
|
||||
|
||||
let universe = Universe.new(100, 100, randSlider.value, new Date().getTime() / 1000);
|
||||
let width = universe.width();
|
||||
let height = universe.height();
|
||||
let play = false;
|
||||
|
||||
const canvas = document.getElementById("game-of-life-canvas");
|
||||
canvas.height = (CELL_SIZE + 1) * height + 1;
|
||||
canvas.width = (CELL_SIZE + 1) * width + 1;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
/**
|
||||
* Draw grid onto canvas prior to painting cells
|
||||
*/
|
||||
const drawGrid = () => {
|
||||
ctx.beginPath();
|
||||
ctx.strokeStyle = GRID_COLOR;
|
||||
|
||||
// Vertical lines.
|
||||
for (let i = 0; i <= width; i++) {
|
||||
ctx.moveTo(i * (CELL_SIZE + 1) + 1, 0);
|
||||
ctx.lineTo(i * (CELL_SIZE + 1) + 1, (CELL_SIZE + 1) * height + 1);
|
||||
}
|
||||
|
||||
// Horizontal lines.
|
||||
for (let j = 0; j <= height; j++) {
|
||||
ctx.moveTo(0, j * (CELL_SIZE + 1) + 1);
|
||||
ctx.lineTo((CELL_SIZE + 1) * width + 1, j * (CELL_SIZE + 1) + 1);
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
/**
|
||||
* Get linear index from row and column indices
|
||||
* @param {*} row Row index
|
||||
* @param {*} column Column index
|
||||
* @returns Linear index
|
||||
*/
|
||||
const getIndex = (row, column) => {
|
||||
return row * width + column;
|
||||
};
|
||||
|
||||
/**
|
||||
* Paint alive cells onto grid
|
||||
*/
|
||||
const drawCells = () => {
|
||||
const cellsPtr = universe.cells();
|
||||
const cells = new Uint8Array(memory.buffer, cellsPtr, width * height);
|
||||
|
||||
ctx.beginPath();
|
||||
|
||||
for (let row = 0; row < height; row++) {
|
||||
for (let col = 0; col < width; col++) {
|
||||
const idx = getIndex(row, col);
|
||||
|
||||
ctx.fillStyle = cells[idx] === Cell.Dead
|
||||
? DEAD_COLOR
|
||||
: ALIVE_COLOR;
|
||||
|
||||
ctx.fillRect(
|
||||
col * (CELL_SIZE + 1) + 1,
|
||||
row * (CELL_SIZE + 1) + 1,
|
||||
CELL_SIZE,
|
||||
CELL_SIZE
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ctx.stroke();
|
||||
};
|
||||
|
||||
/**
|
||||
* Single frame/step of game, tick universe, refresh UI
|
||||
*/
|
||||
const renderSingle = () => {
|
||||
// fps.render(); //new
|
||||
universe.tick();
|
||||
|
||||
drawGrid();
|
||||
drawCells();
|
||||
}
|
||||
|
||||
/**
|
||||
* Start interval timer to periodically iterate frames
|
||||
*/
|
||||
const start = () => {
|
||||
if(loop != null) clearInterval(loop);
|
||||
loop = setInterval(renderSingle, frameInterval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear interval timer to stop animation loop
|
||||
*/
|
||||
const stop = () => {
|
||||
if(loop != null) clearInterval(loop);
|
||||
loop = null;
|
||||
}
|
||||
|
||||
var frameInterval = 50;
|
||||
// var loop = setInterval(renderSingle, frameInterval);
|
||||
var loop = null;
|
||||
|
||||
// SLIDERS
|
||||
|
||||
const frameSlider = document.getElementById("frameRate");
|
||||
const frameSliderLabel = document.getElementById("frameRate-label");
|
||||
/**
|
||||
* Handler for frame interval slider change, stop, change interval, start
|
||||
*/
|
||||
const onFrameSlider = () => {
|
||||
stop();
|
||||
|
||||
frameInterval = frameSlider.value;
|
||||
frameSliderLabel.innerHTML = `Frame Interval: ${frameSlider.value}ms`;
|
||||
|
||||
if(play) start();
|
||||
}
|
||||
frameSlider.onchange = onFrameSlider;
|
||||
frameSlider.value = 100;
|
||||
|
||||
/**
|
||||
* Handler for random threshold slider change, get a new universe with new threshold
|
||||
*/
|
||||
const onRandSlider = () => {
|
||||
stop();
|
||||
|
||||
universe = Universe.new(width, height, randSlider.value, new Date().getTime() / 1000);
|
||||
refreshCanvas();
|
||||
randSliderLabel.innerHTML = `Random Threshold: ${randSlider.value}%`;
|
||||
|
||||
if(play) start();
|
||||
}
|
||||
randSlider.onchange = onRandSlider;
|
||||
randSlider.value = 50;
|
||||
|
||||
/**
|
||||
* Refresh existing canvas, calculate dimensions and draw
|
||||
*/
|
||||
const refreshCanvas = () => {
|
||||
canvas.width = (CELL_SIZE + 1) * width + 1;
|
||||
canvas.height = (CELL_SIZE + 1) * height + 1;
|
||||
drawGrid();
|
||||
drawCells();
|
||||
}
|
||||
|
||||
// INPUT BOXES
|
||||
|
||||
const widthBox = document.getElementById("width");
|
||||
/**
|
||||
* Handler for width input box change, get a new universe of given size
|
||||
*/
|
||||
const onWidth = () => {
|
||||
// PLAY = false;
|
||||
width = widthBox.value;
|
||||
universe = Universe.new(width, height, randSlider.value, new Date().getTime() / 1000);
|
||||
refreshCanvas();
|
||||
// PLAY = true;
|
||||
// requestAnimationFrame(renderLoop);
|
||||
}
|
||||
widthBox.onchange = onWidth;
|
||||
widthBox.value = 100;
|
||||
|
||||
const heightBox = document.getElementById("height");
|
||||
/**
|
||||
* Handler for height input box change, get a new universe of given size
|
||||
*/
|
||||
const onHeight = () => {
|
||||
// PLAY = false;
|
||||
height = heightBox.value;
|
||||
universe = Universe.new(width, height, randSlider.value, new Date().getTime() / 1000);
|
||||
refreshCanvas();
|
||||
// PLAY = true;
|
||||
// requestAnimationFrame(renderLoop);
|
||||
}
|
||||
heightBox.onchange = onHeight;
|
||||
heightBox.value = 100;
|
||||
|
||||
// BUTTONS
|
||||
|
||||
/**
|
||||
* Click handler for step button, make single move
|
||||
*/
|
||||
const onPlay = () => {
|
||||
play = !play;
|
||||
|
||||
// console.log("play: " + play);
|
||||
if(play) {
|
||||
playButton.classList.remove("btn-success");
|
||||
playButton.classList.add("btn-danger");
|
||||
playButton.innerText = "Stop";
|
||||
start();
|
||||
}else {
|
||||
playButton.classList.add("btn-success");
|
||||
playButton.classList.remove("btn-danger");
|
||||
playButton.innerText = "Play";
|
||||
stop();
|
||||
}
|
||||
}
|
||||
const playButton = document.getElementById("play");
|
||||
playButton.onclick = onPlay;
|
||||
|
||||
/**
|
||||
* Click handler for step button, make single move
|
||||
*/
|
||||
const onStep = () => {
|
||||
console.log("stepping");
|
||||
renderSingle();
|
||||
}
|
||||
document.getElementById("step").onclick = onStep;
|
||||
|
||||
/**
|
||||
* Click handler for reset button, generate a new universe and refresh the canvas
|
||||
*/
|
||||
const onReset = () => {
|
||||
universe = Universe.new(width, height, randSlider.value, new Date().getTime() / 1000);
|
||||
refreshCanvas();
|
||||
}
|
||||
document.getElementById("reset").onclick = onReset;
|
||||
|
||||
drawGrid();
|
||||
drawCells();
|
6940
www/package-lock.json
generated
Normal file
6940
www/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
34
www/package.json
Normal file
34
www/package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "game-of-life-web",
|
||||
"version": "0.1.0",
|
||||
"description": "Rust wasm-based game-of-life",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"build": "webpack --config webpack.config.js",
|
||||
"start": "webpack serve --config webpack.config.js --progress"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/Sarsoo/game-of-life.git"
|
||||
},
|
||||
"keywords": [
|
||||
"webassembly",
|
||||
"wasm",
|
||||
"rust",
|
||||
"webpack"
|
||||
],
|
||||
"author": "sarsoo",
|
||||
"bugs": {
|
||||
"url": "https://github.com/Sarsoo/game-of-life/issues"
|
||||
},
|
||||
"homepage": "https://github.com/Sarsoo/game-of-life#readme",
|
||||
"dependencies": {
|
||||
"gameoflife": "file:../pkg"
|
||||
},
|
||||
"devDependencies": {
|
||||
"copy-webpack-plugin": "^9.0.0",
|
||||
"webpack": "^5.40.0",
|
||||
"webpack-cli": "^4.7.2",
|
||||
"webpack-dev-server": "^4.7.4"
|
||||
}
|
||||
}
|
23
www/webpack.config.js
Normal file
23
www/webpack.config.js
Normal file
@ -0,0 +1,23 @@
|
||||
const CopyWebpackPlugin = require("copy-webpack-plugin");
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: "./bootstrap.js",
|
||||
output: {
|
||||
path: path.resolve(__dirname, "dist"),
|
||||
filename: "bootstrap.js",
|
||||
},
|
||||
mode: "development",
|
||||
experiments: {
|
||||
asyncWebAssembly: true
|
||||
},
|
||||
plugins: [
|
||||
new CopyWebpackPlugin({
|
||||
patterns: [
|
||||
{
|
||||
from: path.resolve(__dirname, "index.html")
|
||||
}
|
||||
]
|
||||
})
|
||||
],
|
||||
};
|
Loading…
Reference in New Issue
Block a user