UNPKG

pixelmanipulator

Version:

A super powerful Typescript library for cellular automation.

919 lines (903 loc) 45.6 kB
function $parcel$exportWildcard(dest, source) { Object.keys(source).forEach(function(key) { if (key === 'default' || key === '__esModule' || Object.prototype.hasOwnProperty.call(dest, key)) { return; } Object.defineProperty(dest, key, { enumerable: true, get: function get() { return source[key]; } }); }); return dest; } function $parcel$export(e, n, v, s) { Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true}); } $parcel$export(module.exports, "rules", () => $c64fb279e7084826$export$354076178bdef094); $parcel$export(module.exports, "PixelManipulator", () => $c64fb279e7084826$export$b2b53dd543b26a90); $parcel$export(module.exports, "licence", () => $c64fb279e7084826$export$b3b7c4718d5d9517); $parcel$export(module.exports, "version", () => $c64fb279e7084826$export$83d89fbfd8236492); $parcel$export(module.exports, "neighborhoods", () => $857f30a500ae4087$exports); /** This is a cellular automata JavaScript library called PixelManipulator. For * information about how to use this script, see * https://github.com/Lazerbeak12345/pixelmanipulator * * Copyright (C) 2018-2024 Nathan Fritzler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses. */ var $93a93d09ee95f008$exports = {}; $93a93d09ee95f008$exports = JSON.parse("{\"name\":\"pixelmanipulator\",\"version\":\"5.5.10\",\"description\":\"A super powerful Typescript library for cellular automation.\",\"main\":\"dist/main.js\",\"browser\":\"dist/browser.js\",\"module\":\"dist/module.js\",\"types\":\"dist/types.d.ts\",\"unpkg\":\"dist/bundle.js\",\"targets\":{\"main\":{\"source\":\"./src/pixelmanipulator.ts\"},\"module\":{\"source\":\"src/pixelmanipulator.ts\"},\"browser\":{\"source\":\"src/pixelmanipulator.ts\",\"outputFormat\":\"commonjs\"},\"types\":{\"source\":\"src/pixelmanipulator.ts\"}},\"repository\":\"git@github.com:Lazerbeak12345/pixelmanipulator.git\",\"homepage\":\"https://lazerbeak12345.github.io/pixelmanipulator\",\"author\":\"Nathan Fritzler <nfblaster@live.com>\",\"license\":\"GPL-3.0-or-later\",\"browserslist\":\"defaults\",\"keywords\":[\"cellular automata\",\"game of life\",\"highlife\",\"brians-brain\",\"wireworld\",\"wolfram\",\"rule-30\",\"rule-90\",\"rule-110\",\"rule-184\",\"conway\",\"pixel\",\"game\",\"life\",\"cellular\",\"automata\",\"automaton\",\"gameoflife\",\"gol\",\"brian's-brain\",\"particle\",\"simulation\",\"engine\",\"grid\",\"canvas\"],\"devDependencies\":{\"@fast-check/ava\":\"catalog:\",\"@parcel/packager-ts\":\"catalog:\",\"@parcel/transformer-typescript-types\":\"catalog:\",\"@rollup/plugin-commonjs\":\"catalog:\",\"@types/eslint\":\"catalog:\",\"@types/node\":\"catalog:\",\"@typescript-eslint/eslint-plugin\":\"catalog:\",\"@typescript-eslint/parser\":\"catalog:\",\"ava\":\"catalog:\",\"c8\":\"catalog:\",\"eslint\":\"catalog:\",\"eslint-config-love\":\"catalog:\",\"eslint-plugin-node\":\"catalog:\",\"eslint-plugin-promise\":\"catalog:\",\"eslint-plugin-tsdoc\":\"catalog:\",\"fast-check\":\"catalog:\",\"gh-pages\":\"^6.2.0\",\"parcel\":\"catalog:\",\"rollup\":\"catalog:\",\"typedoc\":\"catalog:\",\"typedoc-plugin-mdn-links\":\"catalog:\",\"typescript\":\"catalog:\"},\"scripts\":{\"test\":\"pnpm run test:types && pnpm run lint && pnpm run test:test\",\"test:types\":\"rm -r build; tsc --outDir build\",\"lint\":\"eslint . --ignore-pattern dist --ignore-pattern docs --ignore-pattern coverage --ignore-pattern build\",\"test:test\":\"c8 -r text -r text-summary -r lcov -r html --all --include build/src ava\",\"build\":\"pnpm t && pnpm run build:docs && pnpm run build:parcel && pnpm build:readme && pnpm run build:bundle && pnpm run build:prepare-ci\",\"build:docs\":\"typedoc --name PixelManipulator --out docs --includeVersion src/*.ts\",\"build:parcel\":\"parcel build\",\"build:readme\":\"cp ../../README.md .\",\"build:bundle\":\"rollup -c\",\"build:prepare-ci\":\"cp -r ../../.circleci docs && cp -r ../../media/* docs\",\"watch\":\"parcel watch --no-hmr\",\"updatedemo\":\"gh-pages -d docs -m \\\"Update $npm_package_version\\\" -tf\"},\"ava\":{\"timeout\":\"20s\"},\"packageManager\":\"pnpm@9.15.1\"}"); var $41fdf26e2a5539f6$exports = {}; $parcel$export($41fdf26e2a5539f6$exports, "location2Index", () => $41fdf26e2a5539f6$export$b6f174f59b684b34); $parcel$export($41fdf26e2a5539f6$exports, "transposeLocations", () => $41fdf26e2a5539f6$export$e27e751a5b1946e3); $parcel$export($41fdf26e2a5539f6$exports, "Renderer", () => $41fdf26e2a5539f6$export$88530751e3977073); $parcel$export($41fdf26e2a5539f6$exports, "Ctx2dRenderer", () => $41fdf26e2a5539f6$export$95ab700cf10a487); $parcel$export($41fdf26e2a5539f6$exports, "StringRenderer", () => $41fdf26e2a5539f6$export$446eec980ee5a157); $parcel$export($41fdf26e2a5539f6$exports, "SplitRenderer", () => $41fdf26e2a5539f6$export$5482513c57691790); /** Various rendering targets * * Copyright (C) 2018-2024 Nathan Fritzler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/ */ /** The location of a pixel */ function $41fdf26e2a5539f6$export$b6f174f59b684b34({ x: x, y: y }, width) { return width * y + x; } function $41fdf26e2a5539f6$export$e27e751a5b1946e3(locs, offset) { const { x: x, y: y, frame: frame, loop: loop } = offset; return locs.map((loc)=>{ const newLoc = { x: loc.x + x, y: loc.y + y, frame: loc.frame ?? frame, loop: loc.loop ?? loop }; if (newLoc.frame == null) delete newLoc.frame; if (newLoc.loop == null) delete newLoc.loop; return newLoc; }); } class $41fdf26e2a5539f6$export$88530751e3977073 { /** Ordered by ID, the {@link pixelmanipulator.ElementData.renderAs} info for each element. */ renderInfo = []; /** Intentionally overridable, called when an element is modified. * @param id - The id of the element to modify. * @param newRenderAs - The new {@link pixelmanipulator.ElementData.renderAs} info. * @returns The value passed upstream to be stored as the actual renderAs info, * allowing for sanitation in this function, or one overriding it. */ modifyElement(id, newRenderAs) { if (this.renderInfo.length === id) this.renderInfo.push(newRenderAs); else if (this.renderInfo.length > id) this.renderInfo[id] = newRenderAs; else throw new Error('Renderer received elements out of order!'); return newRenderAs; } // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default value _width = 1; /** @param value - The new width of the canvas */ set_width(value) { this._width = value; } /** @returns the width of the canvas */ get_width() { return this._width; } // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default value _height = 1; /** @param value - The new height of the canvas */ set_height(value) { this._height = value; } /** @returns the height of the canvas */ get_height() { return this._height; } } const $41fdf26e2a5539f6$var$NUMBER_OF_COLORS = 4; class $41fdf26e2a5539f6$export$95ab700cf10a487 extends $41fdf26e2a5539f6$export$88530751e3977073 { /** @param canvas - The canvas to render on, and to adjust the size of */ constructor(canvas){ super(); this.canvas = canvas; const ctx = canvas.getContext('2d'); if (ctx == null) throw new Error('CanvasRenderingContext2D not supported in enviroment'); this.ctx = ctx; // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- top-left corner this.imageData = this.ctx.getImageData(0, 0, this.get_width(), this.get_height()); } /** The last known image data from {@link Ctx2dRenderer.ctx} */ imageData; /** The rendering context for the canvas */ ctx; /** The canvas */ canvas; /** Default color is solid black */ defaultRenderAs = [ 0, 0, 0, 255 ]; /** In addition to calling {@link Renderer.modifyElement}, this leftpads colors * with `255` and checks for dupicates. * @param id - Id of element * @param newRenderAs - The proposed color of the element. * @returns the actual color of the element. Always 4 long. */ modifyElement(id, newRenderAs) { // allows for arrays that are too small while(newRenderAs.length < $41fdf26e2a5539f6$var$NUMBER_OF_COLORS)// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default values to 255 newRenderAs.push(255); const NOT_FOUND = -1; const indexOfColor = this.renderInfo.indexOf(newRenderAs); if (!(indexOfColor === id || indexOfColor === NOT_FOUND)) throw new Error(`The color ${JSON.stringify(newRenderAs)} is already in use!`); return super.modifyElement(id, newRenderAs); } /** @param loc - location of the pixel to render. Ignores {@link Location.frame} and {@link Location.loop} * @param id - The id of the pixel to render. */ renderPixel(loc, id) { const { renderInfo: { [id]: color } } = this; if (typeof color === "undefined") throw new Error(`Invalid ID ${id}`); // allows for arrays that are too small // TODO: unify this code (duplicate in above function) while(color.length < $41fdf26e2a5539f6$var$NUMBER_OF_COLORS)// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default values to 255 color.push(255); const w = this.get_width(); const pixelOffset = $41fdf26e2a5539f6$export$b6f174f59b684b34(loc, w) * $41fdf26e2a5539f6$var$NUMBER_OF_COLORS; for(let i = 0; i < $41fdf26e2a5539f6$var$NUMBER_OF_COLORS; ++i)// eslint-disable-next-line @typescript-eslint/prefer-destructuring -- destructuring is more messy here this.imageData.data[pixelOffset + i] = color[i]; } reset() { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- top left corner this.imageData = this.ctx.getImageData(0, 0, this.get_width(), this.get_height()); this.ctx.imageSmoothingEnabled = false; } update() { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- top left corner this.ctx.putImageData(this.imageData, 0, 0); } set_width(value) { this.canvas.width = value; super.set_width(value); } set_height(value) { this.canvas.height = value; super.set_height(value); } } class $41fdf26e2a5539f6$export$446eec980ee5a157 extends $41fdf26e2a5539f6$export$88530751e3977073 { defaultRenderAs = ' '; _chars = []; /** The callback function passed to the constructor. Called on {@link StringRenderer.update} */ _callback; /** @param callback - A function called on {@link StringRenderer.update}. Passed a * string with the renderable state of the {@link pixelmanipulator.PixelManipulator} */ constructor(callback){ super(); this._callback = callback; } /** @param newRenderAs - The proposed character to use. Must be 1 char long and unique */ modifyElement(id, newRenderAs) { if (this.renderInfo.includes(newRenderAs)) throw new Error(`Element ${id} must have a unique renderAs`); return super.modifyElement(id, newRenderAs); } reset() { const w = this.get_width(); const h = this.get_height(); this._chars = new Array(h).fill([]).map(()=>new Array(w).fill(this.defaultRenderAs)); } /** @param x - X location of pixel * @param y - y location of pixel * @param id - The id of the pixel */ renderPixel({ x: x, y: y }, id) { const { renderInfo: { [id]: char } } = this; this._chars[y][x] = char; } update() { this._callback(this._chars.map((l)=>l.join('')).join('\n')); } } class $41fdf26e2a5539f6$export$5482513c57691790 extends $41fdf26e2a5539f6$export$88530751e3977073 { defaultRenderAs; a; b; constructor(a, b){ super(); this.a = a; this.b = b; this.defaultRenderAs = { a: a.defaultRenderAs, b: b.defaultRenderAs }; } renderPixel(loc, id) { this.a.renderPixel(loc, id); this.b.renderPixel(loc, id); } reset() { this.a.reset(); this.b.reset(); } update() { this.a.update(); this.b.update(); } modifyElement(id, { a: a, b: b }) { return super.modifyElement(id, { a: this.a.modifyElement(id, a), b: this.b.modifyElement(id, b) }); } } // This is called a "modeline". It's a (n)vi(m)|ex thing. // vi: tabstop=2 shiftwidth=2 expandtab var $857f30a500ae4087$exports = {}; $parcel$export($857f30a500ae4087$exports, "rect", () => $857f30a500ae4087$export$4b409e53cf4df6e6); $parcel$export($857f30a500ae4087$exports, "wolfram", () => $857f30a500ae4087$export$570c5686df7a3a74); $parcel$export($857f30a500ae4087$exports, "moore", () => $857f30a500ae4087$export$dfd711383a0d1c21); $parcel$export($857f30a500ae4087$exports, "vonNeumann", () => $857f30a500ae4087$export$6d4d43aa0229d23f); $parcel$export($857f30a500ae4087$exports, "euclidean", () => $857f30a500ae4087$export$d7ea5b7fe202bfa1); /** Several functions to generate lists of relative positions * for a neighborhood hitbox. * * Copyright (C) 2018-2024 Nathan Fritzler * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/ */ function $857f30a500ae4087$export$4b409e53cf4df6e6(topLeft, bottomRight) { const output = []; for(let { x: x } = topLeft; x <= bottomRight.x; x++)for(let { y: y } = topLeft; y <= bottomRight.y; y++)output.push({ x: x, y: y }); return output; } function $857f30a500ae4087$export$570c5686df7a3a74(radius, y, includeSelf) { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default value radius ??= 1; // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default value y ??= -1; const output = []; // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- reverse iterated for(let i = radius; i > 0; i--)output.push({ x: -i, y: y }); if (includeSelf == null || includeSelf) // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- center output.push({ x: 0, y: y }); // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- reverse iterated for(let i = radius; i > 0; i--)output.push({ x: i, y: y }); return output; } function $857f30a500ae4087$export$dfd711383a0d1c21(radius, includeSelf) { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default value radius ??= 1; includeSelf ??= false; // Note: no need to calculate the Chebyshev distance. All pixels in this // range are "magically" within. const r = $857f30a500ae4087$export$4b409e53cf4df6e6({ x: -radius, y: -radius }, { x: radius, y: radius }); if (includeSelf) return r; return r.filter(({ x: x, y: y })=>!(x === 0 && y === 0)) // eslint-disable-line @typescript-eslint/no-magic-numbers -- center is zeros ; // And to think that this used to be hard... Perhaps they had a different // goal? Or just weren't using higher-order algorithims? } function $857f30a500ae4087$export$6d4d43aa0229d23f(radius, includeSelf) { // A Von Neumann neighborhood of a given distance always fits inside of a // Moore neighborhood of the same. (This is a bit brute-force) const DEFAULT_RADIUS = 1; radius ??= DEFAULT_RADIUS; return $857f30a500ae4087$export$dfd711383a0d1c21(radius, includeSelf).filter(({ x: x, y: y })=>Math.abs(x) + Math.abs(y) <= radius) // Taxicab distance ; } function $857f30a500ae4087$export$d7ea5b7fe202bfa1(radius, includeSelf) { // A circle of a given diameter always fits inside of a square of the same // side-length. (This is a bit brute-force) const DEFAULT_RADIUS = 1; return $857f30a500ae4087$export$dfd711383a0d1c21(radius, includeSelf).filter(({ x: x, y: y })=>// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- pythagorean theorum Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= (radius ?? DEFAULT_RADIUS)) // Euclidean distance ; } // TODO https://www.npmjs.com/package/compute-minkowski-distance ? // TODO Non-Euclidean distance algorithim? // This is called a "modeline". It's a (n)vi(m)|ex thing. // vi: tabstop=2 shiftwidth=2 expandtab function $c64fb279e7084826$var$startAnimation(callback) { if (typeof requestAnimationFrame === 'undefined') { const SMALLEST_INTERVAL_POSSIBLE = 1; return setInterval(callback, SMALLEST_INTERVAL_POSSIBLE); } else return requestAnimationFrame(callback); } function $c64fb279e7084826$var$resumeAnimation(num, callback) { if (typeof requestAnimationFrame === 'undefined') return num; else return requestAnimationFrame(callback); } function $c64fb279e7084826$var$cancelAnimation(num) { if (typeof cancelAnimationFrame === 'undefined') clearInterval(num); else if (typeof num === "number") cancelAnimationFrame(num); } function $c64fb279e7084826$var$boolToNumber(bool) { const TRUE = 1; const FALSE = 0; return bool ? TRUE : FALSE; } function $c64fb279e7084826$var$_convertNumListToBf(nl) { // While I used to use string with each digit in it, I found that since // there are 0-8, I could use a 9bit field (remember: off by one) let out = 0; for (const item of nl)// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- largest binary digit out |= 1 << parseInt(item); return out; } const $c64fb279e7084826$export$354076178bdef094 = { /** Generates elements like conway's game of life. * @param p - `lifelike` needs to be able to call {@link PixelManipulator.mooreNearbyCounter} * @param pattern - The B/S syntax indicator of on how many cells of the same * type in the moore radius around each pixel should survive, and on how many * should be born. * @param loop - Should this loop around screen edges? (Passed to {@link renderers.Location.loop}) */ lifelike: function(p, pattern, loop) { const numbers = pattern.split(/\/?[a-z]/gi) // "B",born,die ; // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- after the pattern const bfdie = $c64fb279e7084826$var$_convertNumListToBf(numbers[2]); // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- before the pattern const bflive = $c64fb279e7084826$var$_convertNumListToBf(numbers[1]); const SMALLEST_BINARY_DIGIT = 1; const PREV_FRAME = 1; const NO_MATCH = 0; return { madeWithRule: true, hitbox: $857f30a500ae4087$export$dfd711383a0d1c21(), liveCell: function llive({ x: x, y: y, thisId: thisId }) { // if any match (of how many moore are nearby) is found, it dies if ((bfdie & SMALLEST_BINARY_DIGIT << p.mooreNearbyCounter({ x: x, y: y, frame: PREV_FRAME, loop: loop }, thisId)) === NO_MATCH) p.setPixel({ x: x, y: y }, p.defaultId); }, deadCell: function ldead({ x: x, y: y, thisId: thisId }) { // if any match (of how many moore are nearby) is found, it lives if ((bflive & SMALLEST_BINARY_DIGIT << p.mooreNearbyCounter({ x: x, y: y, frame: PREV_FRAME, loop: loop }, thisId)) !== NO_MATCH) p.setPixel({ x: x, y: y }, thisId); } }; }, /** Generates fundamental cellular automata * @param p - `wolfram` needs to be able to call {@link PixelManipulator.wolframNearbyCounter} * @param pattern - The Rule num syntax, where the 8-bit number is translated * into a binary list, each where the inverted 3-binary-digit index represents * the state of cells in the row above. On a match, the cell becomes the state * specified in the initial 8-bit number. * @param loop - Should this loop around screen edges? (Passed to {@link PixelManipulator.wolframNearby}) */ wolfram: function(p, pattern, loop) { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- after the pattern const binStates = parseInt(pattern.split(/Rule /gi)[1]); const FIRST_ROW = 0; const PREV_FRAME = 1; return { madeWithRule: true, // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default values hitbox: $857f30a500ae4087$export$570c5686df7a3a74(1, 1), // The current state is used as the index in the binstates, as binstates is a bit array of every state liveCell: function wlive({ x: x, y: y, thisId: thisId }) { if (y === FIRST_ROW) return; if (!p.wolframNewState({ x: x, y: y, frame: PREV_FRAME, loop: loop }, binStates, thisId)) p.setPixel({ x: x, y: y, loop: loop }, p.defaultId); }, deadCell: function wdead({ x: x, y: y, thisId: thisId }) { if (p.wolframNewState({ x: x, y: y, frame: PREV_FRAME, loop: loop }, binStates, thisId)) p.setPixel({ x: x, y: y, loop: loop }, thisId); } }; } }; class $c64fb279e7084826$export$b2b53dd543b26a90 { /** * @param renderer - The target to render things to. * @param width - How wide should the initial target be? * @param height - How tall should the initial target be? */ constructor(renderer, width, height){ if (typeof window !== 'undefined') console.log($c64fb279e7084826$export$b3b7c4718d5d9517); this.renderer = renderer; this.defaultId = this.addElement({ renderAs: this.renderer.defaultRenderAs, hitbox: [], name: 'blank' }); this.reset({ canvasW: width, canvasH: height }); } /** An instanace of the object that shows the state to the user. */ renderer; /** * This is the number that indicates what animation frame the iterate function * is being called with. * * You can use this to mannually stop the iterations like so: * `cancelAnimationFrame(this.loopint)` (not reccommended) */ loopint = 0 // eslint-disable-line @typescript-eslint/no-magic-numbers -- default value ; /** * A low-level listing of the availiable elements. * * Format is much like the argument to * {@link PixelManipulator.addMultipleElements}, but is not sanitized. */ elements = []; /** * A mapping from old names for elements to new names for elements. * * Allows a user to modify the name of an element at runtime. */ nameAliases = new Map(); /** * A string indicating weather it is currently animating or not. * * It is `"playing"` if it is currently animating, or `"paused"` if not * currently animating. * * This has been around since early version 0, and once was the `innerText` * value of a pause/play button! */ mode = 'paused'; /** * The elm that pixelmanipulator will fill the screen with upon initialization, * and what elements should return to when they are "dead". Default value is * 0, an element with the color `#000F` * * If you update this, be sure to update {@link renderers.Renderer.defaultRenderAs} in {@link PixelManipulator.renderer} */ defaultId; /** Called before {@link PixelManipulator.iterate} does its work. * @returns false to postposne iteration. */ onIterate = ()=>undefined // eslint-disable-line @typescript-eslint/class-methods-use-this -- event handler ; /** Called after {@link PixelManipulator.iterate} does its work. */ onAfterIterate = ()=>undefined // eslint-disable-line @typescript-eslint/class-methods-use-this -- event handler ; /** Gets called after a call to {@link PixelManipulator.modifyElement}. The ID is * passed as the only argument. * @param id - The element that was modified. */ onElementModified = ()=>undefined // eslint-disable-line @typescript-eslint/class-methods-use-this -- event handler ; /** @returns the width of the canvas */ get_width() { return this.renderer.get_width(); } /** @param value - The new width of the canvas */ set_width(value) { this.renderer.set_width(value); } /** @returns the height of the canvas */ get_height() { return this.renderer.get_height(); } /** @param value - The new height of the canvas */ set_height(value) { this.renderer.set_height(value); } /** fills the screen with value, at an optional given percent * @param value - The element to put on the screen * @param pr - The percent as a number from 1 to 100, defaulting at 15 */ randomlyFill(value, pr) { const DEFAULT_PERCENT = 15; const MAX_PERCENT = 100; pr ??= DEFAULT_PERCENT; const w = this.get_width(); const h = this.get_height(); for(let x = 0; x < w; x++){ for(let y = 0; y < h; y++)if (Math.random() * MAX_PERCENT < pr) this.setPixel({ x: x, y: y }, value); } } /** Adds multiple elements. * * @param elements - Index is the element name, value is the element data (and * does not require the name). Value is passed to * {@link PixelManipulator.addElement} */ addMultipleElements(elements) { Object.keys(elements).forEach((name)=>this.addElement({ name: name, ...elements[name] })); } /** Add an element with the given element data * @param data - The details about the element. * @returns The generated {@link PixelManipulator.elements} index */ addElement(data) { const { name: name, renderAs: renderAs } = data; if (typeof name === 'undefined') throw new Error('Name is required for element'); if (typeof data.name === 'undefined') data.name = name; // @ts-expect-error renderAs might be undefined here, but it's fixed in the call to this.modifyElement below this.elements.push({ name: name, renderAs: renderAs }); // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- last item in list this.modifyElement(this.elements.length - 1, data); // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- last item in list (might not be the same as before modifyElement was called) return this.elements.length - 1; } /** * @param id - How to identify what element to modify. * @param data - Values to apply to the pre-existing element. * * Automatically calls {@link PixelManipulator.aliasElements} if * {@link ElementDataUnknown.name} is present in `data` */ modifyElement(id, data) { const oldData = this.elements[id]; if (typeof data.name !== 'undefined' && data.name !== oldData.name) this.aliasElements(oldData.name, data.name); const newData = { hitbox: $857f30a500ae4087$export$dfd711383a0d1c21(), ...oldData, ...data }; newData.renderAs = this.renderer.modifyElement(id, newData.renderAs); this.elements[id] = newData; this.onElementModified(id); } /** * @param oldName - The old {@link ElementData.name} * @param newName - The new {@link ElementData.name} * * Adds the name to {@link PixelManipulator.nameAliases}, and ensures no alias * loops are present. */ aliasElements(oldName, newName) { // Intentionally ignores aliases when checking for duplicate name. if (this.elements.find((elm)=>elm.name === newName) != null) throw new Error('The name ' + newName + ' is already in use!'); this.nameAliases.delete(newName); this.nameAliases.set(oldName, newName); } /** Respecting aliases, convert an element name into its number. * @param name - name of element * @returns The number of the element */ nameToId(name) { let unaliased = name; while(typeof unaliased !== 'undefined'){ name = unaliased; unaliased = this.nameAliases.get(name); } return this.elements.findIndex((elm)=>elm.name === name); } /** * @param name - Name of the (possibly aliased) element. * @returns The element from {@link PixelManipulator.elements}, respecting * aliases in {@link PixelManipulator.nameAliases}, or {@link undefined} if not found. */ getElementByName(name) { return this.elements[this.nameToId(name)]; } /** * @param loc - Location of the element. * @returns Name of element at passed-in location. See {@link ElementData.name} */ whatIs(loc) { return this.elements[this.getPixelId(loc)].name; } /** Start iterations on all of the elements on the canvas. * Sets {@link PixelManipulator.mode} to `"playing"`, and requests a new animation * frame, saving it in {@link PixelManipulator.loopint}. * * @param canvasSizes - If {@link PixelManipulator.mode} is already `"playing"` then * canvasSizes is passed to {@link PixelManipulator.reset}. Otherwise reset is not * called. */ play(canvasSizes) { if (this.mode === 'playing') this.reset(canvasSizes); this.mode = 'playing'; this.loopint = $c64fb279e7084826$var$startAnimation(()=>{ this.iterate(); }); } /** Reset, resize and initialize the canvas(es). * Calls {@link PixelManipulator.pause} then * {@link PixelManipulator.update}. Resets all internal state, excluding the * element definitions. * * @param canvasSizes - Allows one to change the size of the canvases during * the reset. */ reset(canvasSizes) { const CURRENT_FRAME = 0; const NEXT_FRAME = 1; const MAX_PERCENT = 100; if (typeof canvasSizes === 'undefined') canvasSizes = {}; this.pause(); const w = canvasSizes.canvasW ?? this.get_width(); const h = canvasSizes.canvasH ?? this.get_height(); this.set_width(w); this.set_height(h); this.frames[CURRENT_FRAME] = new Uint32Array(w * h); this.frames[NEXT_FRAME] = new Uint32Array(w * h); this.renderer.reset(); this.randomlyFill(this.defaultId, MAX_PERCENT); this.update(); } /** pause canvas iterations * Sets {@link PixelManipulator.mode} to `"paused"` and cancels the animation frame * referenced in {@link PixelManipulator.loopint} */ pause() { this.mode = 'paused'; $c64fb279e7084826$var$cancelAnimation(this.loopint); } /** * @param loc - Location of the pixel (could be out of bounds). * @returns null if out-of-bounds when loop setting is false, or the location (loop set to false). */ locationBoundsCheck(loc) { const LEFTMOST = 0; const TOPMOST = 0; const w = this.get_width(); const h = this.get_height(); const overflowLeft = loc.x < LEFTMOST; const overflowRight = loc.x >= w; const overflowTop = loc.y < TOPMOST; const overflowBottom = loc.y >= h; if (loc.loop ?? true) { loc.x %= w; loc.y %= h; if (overflowLeft) loc.x += w; if (overflowTop) loc.y += h; loc.loop = false; } else if (overflowLeft || overflowRight || overflowTop || overflowBottom) return null; return loc; } /** * @param loc - Location of the pixel * @returns the element id at a given location */ getPixelId(loc) { const fixedLoc = this.locationBoundsCheck(loc); if (fixedLoc == null) return this.defaultId; const w = this.get_width(); const CURRENT_FRAME = 0 // TODO: dedupe this const ; return this.frames[fixedLoc.frame ?? CURRENT_FRAME][$41fdf26e2a5539f6$export$b6f174f59b684b34(fixedLoc, w)]; } /** * Applies any changes made with {@link renderers.Renderer.renderPixel} on {@link PixelManipulator.renderer} to the canvas */ update() { this.renderer.update(); } /** * @param loc - Where to confirm the element * @param id - The elm you expect it to be * @returns Does the cell at `loc` match `ident`? */ confirmElm(loc, id) { return this.getPixelId(loc) === (typeof id === 'string' ? this.nameToId(id) : id); } /** Calculate the total number of elements within an area * @param area - The locations to total up. * @param search - The element to look for * @returns The total */ totalWithin(area, search) { return area.filter((loc)=>this.confirmElm(loc, search)).length; } static _moore = $857f30a500ae4087$export$dfd711383a0d1c21(); /** @param name - element to look for * @param center - location of the center of the moore area * @returns Number of matching elements in moore radius */ mooreNearbyCounter(center, search) { return this.totalWithin($41fdf26e2a5539f6$export$e27e751a5b1946e3($c64fb279e7084826$export$b2b53dd543b26a90._moore, center), search); } /** @param area - The Area to search within * @param ruleNum - A bitfield of what states a pixel should live or die on. * @param search - The element to search for * @see {@link PixelManipulator.wolframNewState} for higher-level tool * @see {@link PixelManipulator.fundamentalStatesWithin} for lower-level tool * @returns The state that the bitfied says this pixel should be in the next frame. */ fundamentalNewState(area, ruleNum, search) { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 1 is largest binary digit, nonzero if matches return (ruleNum & 1 << this.fundamentalStatesWithin(area, search)) !== 0; } /** @param area - Locations to look at. * @param search - Locations to mark as a true bit. * @see {@link PixelManipulator.fundamentalNewState} for higher-level tool * @returns number as a bitfied array, in order of the items in area, from left to right. * * That means that `(fundamentalStatesWithin([loc], search) & 1) === boolToNumber(confirmElm(loc, search))` * * You may want to see [this page](https://www.wolframscience.com/nks/notes-5-2--general-rules-for-multidimensional-cellular-automata/) * for more details on how this might be used. */ fundamentalStatesWithin(area, search) { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 1 is the SMALLEST_BINARY_DIGIT return area.map((loc)=>$c64fb279e7084826$var$boolToNumber(this.confirmElm(loc, search))).reduce((res, x)=>res << 1 | x); } static _wolfram = $857f30a500ae4087$export$570c5686df7a3a74(); /** @param loc - The pixel to change. (Defaults {@link renderers.Location.loop} to false) * @param ruleNum - A bitfield of what states a pixel should live or die on. * @param search - The element to search for * @see {@link PixelManipulator.fundamentalNewState} for more general tool. * @returns The state that the bitfied says this pixel should be in the next frame. */ wolframNewState(loc, ruleNum, search) { // one-dimentional detectors by default don't loop around edges loc.loop ??= false; return this.fundamentalNewState($41fdf26e2a5539f6$export$e27e751a5b1946e3($c64fb279e7084826$export$b2b53dd543b26a90._wolfram, loc), ruleNum, search); } /** * @param current - "Current" pixel location. (Defaults {@link renderers.Location.loop} to false) * @param search - element to look for * @see {@link PixelManipulator.fundamentalStatesWithin} for lower-level tool * @returns Number used as bit area to indicate occupied cells */ wolframNearby(current, search) { // one-dimentional detectors by default don't loop around edges current.loop ??= false; return this.fundamentalStatesWithin($41fdf26e2a5539f6$export$e27e751a5b1946e3($c64fb279e7084826$export$b2b53dd543b26a90._wolfram, current), search); } /** Counter tool used in slower wolfram algorithim. * @deprecated Replaced with {@link PixelManipulator.wolframNearby} for use in faster * algorithms * @param current - "Current" pixel location * @param name - element to look for * @param bindex - Either a string like `"001"` to match to, or the same * encoded as a number. * @returns Number of elements in wolfram radius */ wolframNearbyCounter(current, name, binDex) { if (typeof binDex === 'string') // Old format was a string of ones and zeros, three long. binDex = parseInt(binDex, 2); return this.wolframNearby(current, name) === binDex; } /** Set a pixel in a given location. * * @param x - X position. * @param y - Y position. * @param ident - Value to identify the element. * * - If a string, it assumes it's an element name. * - If a number, it assumes it's an element ID * * @param loop - Defaults to {@link true}. Wraps `x` and `y` around canvas borders. */ setPixel(loc, ident) { const NOT_FOUND = -1; let id = 0; if (typeof ident === 'string') { id = this.nameToId(ident); if (id === NOT_FOUND) throw new Error(`Element name ${ident} is invalid`); } else id = ident; const fixedLoc = this.locationBoundsCheck(loc); if (fixedLoc == null) return; this.renderer.renderPixel(fixedLoc, id); const w = this.get_width(); const CURRENT_FRAME = 0; this.frames[CURRENT_FRAME][$41fdf26e2a5539f6$export$b6f174f59b684b34(fixedLoc, w)] = id; } /** Number of pixels per element in the last frame */ pixelCounts = {}; /** A single frame of animation. Media functions pass this into * {@link requestAnimationFrame}. * * Be careful! Calling this while {@link PixelManipulator.mode} is `"playing"` * might cause two concurrent calls to this function. If any of your automata * have "hidden state" - that is they don't represent every detail about * themselves as data within the pixels - it might cause conflicts. */ iterate() { if (this.onIterate() ?? true) { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- last item in list for(let frame = this.frames.length - 1; frame >= 0; frame--)// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- nonzero if (frame > 0) { // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- next is minus one const nextFrame = frame - 1; this.frames[frame].set(this.frames[nextFrame]); } const w = this.get_width(); const h = this.get_height(); const typedUpdatedDead = new Array(this.elements.length); this.pixelCounts = {}; const NEXT_FRAME = 1; for(let x = 0; x < w; x++)for(let y = 0; y < h; y++){ const currentPixId = this.getPixelId({ x: x, y: y, frame: NEXT_FRAME }); if (currentPixId === this.defaultId) continue; const { elements: { [currentPixId]: elm } } = this; if (typeof elm === "undefined") throw new Error("This isn't supposed to happen, but the internal pixel buffer was currupted. This is likely a bug, or a symptom of improper direct access to the current memory buffer"); if (typeof elm.liveCell !== "undefined") elm.liveCell({ x: x, y: y, oldId: currentPixId, thisId: currentPixId }); if (typeof this.pixelCounts[currentPixId] === "undefined") // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- starting at 1 this.pixelCounts[currentPixId] = 1; else this.pixelCounts[currentPixId]++; if (typeof elm.deadCell !== "undefined") { const UPDATED_SIZE = 8 // TODO: this is a guess. document this better, or rewrite this. ; typedUpdatedDead[currentPixId] ??= new Uint8Array(Math.ceil(w * h / UPDATED_SIZE)); elm.hitbox.forEach((pixel)=>{ // We are looping, so it can't be null. Eslint doesn't like non-null assertions, so we must do this. const hblocStupidFallback = { x: x + pixel.x, y: y + pixel.y }; const hbLoc = this.locationBoundsCheck(hblocStupidFallback) ?? hblocStupidFallback; const index = Math.floor($41fdf26e2a5539f6$export$b6f174f59b684b34(hbLoc, w) / UPDATED_SIZE); const { [currentPixId]: { [index]: oldValue } } = typedUpdatedDead; // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- SMALLEST_BINARY_DIGIT const bitMask = 1 << hbLoc.x % UPDATED_SIZE; // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- compare to zero if ((oldValue & bitMask) !== 0) return; // I timed it, and confirmOldElm is slower than all the math above. if (!this.confirmElm({ x: hbLoc.x, y: hbLoc.y, frame: NEXT_FRAME }, this.defaultId)) return; if (typeof elm.deadCell !== "undefined") elm.deadCell({ x: hbLoc.x, y: hbLoc.y, oldId: this.defaultId, thisId: currentPixId }); typedUpdatedDead[currentPixId][index] = oldValue | bitMask; }); } } this.update(); this.onAfterIterate(); } if (this.mode === 'playing') this.loopint = $c64fb279e7084826$var$resumeAnimation(this.loopint, ()=>{ this.iterate(); }); } /** * A List of {@link Uint32Array}s each the length of width times height of the * canvas. Frame 0 is the new frame, frame one is the prior, etc. Each item * holds the element id of each element on screen, from left to right, top to * bottom. */ frames = [ new Uint32Array(0), new Uint32Array(0) ] // eslint-disable-line @typescript-eslint/no-magic-numbers -- default values ; } // end class PixelManipulator const { version: $c64fb279e7084826$export$83d89fbfd8236492 } = $93a93d09ee95f008$exports; const $c64fb279e7084826$export$b3b7c4718d5d9517 = 'PixelManipulator v' + $c64fb279e7084826$export$83d89fbfd8236492 + ' Copyright (C) ' + '2018-2024 Nathan Fritzler\nThis program comes with ABSOLUTELY NO ' + 'WARRANTY\nThis is free software, and you are welcome to redistribute it\n' + 'under certain conditions, as according to the GNU GENERAL PUBLIC LICENSE ' + 'version 3 or later.' // This is called a "modeline". It's a (n)vi(m)|ex thing. // vi: tabstop=2 shiftwidth=2 expandtab ; $parcel$exportWildcard(module.exports, $41fdf26e2a5539f6$exports); //# sourceMappingURL=main.js.map