Compare commits
No commits in common. "master" and "gh-pages" have entirely different histories.
@ -1,7 +0,0 @@
|
|||||||
.github
|
|
||||||
.jenkins
|
|
||||||
.git
|
|
||||||
pkg
|
|
||||||
target
|
|
||||||
**/dist
|
|
||||||
**/node_modules
|
|
@ -1,72 +0,0 @@
|
|||||||
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
73
.github/workflows/test.yml
vendored
@ -1,73 +0,0 @@
|
|||||||
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
6
.gitignore
vendored
@ -1,6 +0,0 @@
|
|||||||
/target
|
|
||||||
**/*.rs.bk
|
|
||||||
Cargo.lock
|
|
||||||
bin/
|
|
||||||
pkg/
|
|
||||||
wasm-pack.log
|
|
@ -1,22 +0,0 @@
|
|||||||
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
48
Cargo.toml
@ -1,48 +0,0 @@
|
|||||||
[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
23
Dockerfile
@ -1,23 +0,0 @@
|
|||||||
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
22
README.md
@ -1,22 +0,0 @@
|
|||||||
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
Normal file
393
bootstrap.js
vendored
Normal file
@ -0,0 +1,393 @@
|
|||||||
|
/*
|
||||||
|
* 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");
|
||||||
|
/******/
|
||||||
|
/******/ })()
|
||||||
|
;
|
BIN
f01ebc1bd0088213886b.module.wasm
Normal file
BIN
f01ebc1bd0088213886b.module.wasm
Normal file
Binary file not shown.
42
index_js.bootstrap.js
Normal file
42
index_js.bootstrap.js
Normal file
File diff suppressed because one or more lines are too long
188
src/lib.rs
188
src/lib.rs
@ -1,188 +0,0 @@
|
|||||||
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
19
src/time.rs
@ -1,19 +0,0 @@
|
|||||||
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
10
src/utils.rs
@ -1,10 +0,0 @@
|
|||||||
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
32
tests/web.rs
@ -1,32 +0,0 @@
|
|||||||
//! 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
2
www/.gitignore
vendored
@ -1,2 +0,0 @@
|
|||||||
node_modules
|
|
||||||
dist
|
|
5
www/bootstrap.js
vendored
5
www/bootstrap.js
vendored
@ -1,5 +0,0 @@
|
|||||||
// 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
236
www/index.js
@ -1,236 +0,0 @@
|
|||||||
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
6940
www/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,34 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
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