pixelmanipulator
Version:
A super powerful Typescript library for cellular automation.
881 lines (869 loc) • 45 kB
JavaScript
function $parcel$export(e, n, v, s) {
Object.defineProperty(e, n, {get: v, set: s, enumerable: true, configurable: true});
}
/** 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 $779f9a72b2be71a3$exports = {};
$779f9a72b2be71a3$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 $3a20935030977292$exports = {};
$parcel$export($3a20935030977292$exports, "location2Index", function () { return $3a20935030977292$export$b6f174f59b684b34; });
$parcel$export($3a20935030977292$exports, "transposeLocations", function () { return $3a20935030977292$export$e27e751a5b1946e3; });
$parcel$export($3a20935030977292$exports, "Renderer", function () { return $3a20935030977292$export$88530751e3977073; });
$parcel$export($3a20935030977292$exports, "Ctx2dRenderer", function () { return $3a20935030977292$export$95ab700cf10a487; });
$parcel$export($3a20935030977292$exports, "StringRenderer", function () { return $3a20935030977292$export$446eec980ee5a157; });
$parcel$export($3a20935030977292$exports, "SplitRenderer", function () { return $3a20935030977292$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 $3a20935030977292$export$b6f174f59b684b34({ x: x, y: y }, width) {
return width * y + x;
}
function $3a20935030977292$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 $3a20935030977292$export$88530751e3977073 {
/** 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;
}
/** @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;
}
/** @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;
}
constructor(){
/** Ordered by ID, the {@link pixelmanipulator.ElementData.renderAs} info for each element. */ this.renderInfo = [];
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default value
this._width = 1;
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default value
this._height = 1;
}
}
const $3a20935030977292$var$NUMBER_OF_COLORS = 4;
class $3a20935030977292$export$95ab700cf10a487 extends $3a20935030977292$export$88530751e3977073 {
/** @param canvas - The canvas to render on, and to adjust the size of */ constructor(canvas){
super(), /** Default color is solid black */ this.defaultRenderAs = [
0,
0,
0,
255
] // eslint-disable-line @typescript-eslint/no-magic-numbers -- top-left corner
;
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());
}
/** 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 < $3a20935030977292$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 < $3a20935030977292$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 = $3a20935030977292$export$b6f174f59b684b34(loc, w) * $3a20935030977292$var$NUMBER_OF_COLORS;
for(let i = 0; i < $3a20935030977292$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 $3a20935030977292$export$446eec980ee5a157 extends $3a20935030977292$export$88530751e3977073 {
/** @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.defaultRenderAs = ' ', this._chars = [];
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 $3a20935030977292$export$5482513c57691790 extends $3a20935030977292$export$88530751e3977073 {
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 $09b4a56db8f03c3c$exports = {};
$parcel$export($09b4a56db8f03c3c$exports, "rect", function () { return $09b4a56db8f03c3c$export$4b409e53cf4df6e6; });
$parcel$export($09b4a56db8f03c3c$exports, "wolfram", function () { return $09b4a56db8f03c3c$export$570c5686df7a3a74; });
$parcel$export($09b4a56db8f03c3c$exports, "moore", function () { return $09b4a56db8f03c3c$export$dfd711383a0d1c21; });
$parcel$export($09b4a56db8f03c3c$exports, "vonNeumann", function () { return $09b4a56db8f03c3c$export$6d4d43aa0229d23f; });
$parcel$export($09b4a56db8f03c3c$exports, "euclidean", function () { return $09b4a56db8f03c3c$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 $09b4a56db8f03c3c$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 $09b4a56db8f03c3c$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 $09b4a56db8f03c3c$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 = $09b4a56db8f03c3c$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 $09b4a56db8f03c3c$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 $09b4a56db8f03c3c$export$dfd711383a0d1c21(radius, includeSelf).filter(({ x: x, y: y })=>Math.abs(x) + Math.abs(y) <= radius) // Taxicab distance
;
}
function $09b4a56db8f03c3c$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 $09b4a56db8f03c3c$export$dfd711383a0d1c21(radius, includeSelf).filter(({ x: x, y: y })=>// eslint-disable-next-line -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 $760307e299781208$var$startAnimation(callback) {
if (typeof requestAnimationFrame === 'undefined') {
const SMALLEST_INTERVAL_POSSIBLE = 1;
return setInterval(callback, SMALLEST_INTERVAL_POSSIBLE);
} else return requestAnimationFrame(callback);
}
function $760307e299781208$var$resumeAnimation(num, callback) {
if (typeof requestAnimationFrame === 'undefined') return num;
else return requestAnimationFrame(callback);
}
function $760307e299781208$var$cancelAnimation(num) {
if (typeof cancelAnimationFrame === 'undefined') clearInterval(num);
else if (typeof num === "number") cancelAnimationFrame(num);
}
function $760307e299781208$var$boolToNumber(bool) {
const TRUE = 1;
const FALSE = 0;
return bool ? TRUE : FALSE;
}
function $760307e299781208$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 $760307e299781208$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 = $760307e299781208$var$_convertNumListToBf(numbers[2]);
// eslint-disable-next-line @typescript-eslint/no-magic-numbers -- before the pattern
const bflive = $760307e299781208$var$_convertNumListToBf(numbers[1]);
const SMALLEST_BINARY_DIGIT = 1;
const PREV_FRAME = 1;
const NO_MATCH = 0;
return {
madeWithRule: true,
hitbox: $09b4a56db8f03c3c$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: $09b4a56db8f03c3c$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 $760307e299781208$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){
/**
* 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)
*/ this.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.
*/ this.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.
*/ this.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!
*/ this.mode = 'paused';
/** Called before {@link PixelManipulator.iterate} does its work.
* @returns false to postposne iteration.
*/ this.onIterate = ()=>undefined // eslint-disable-line @typescript-eslint/class-methods-use-this -- event handler
;
/** Called after {@link PixelManipulator.iterate} does its work. */ this.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.
*/ this.onElementModified = ()=>undefined // eslint-disable-line @typescript-eslint/class-methods-use-this -- event handler
;
/** Number of pixels per element in the last frame */ this.pixelCounts = {};
/**
* 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.
*/ this.frames = [
new Uint32Array(0),
new Uint32Array(0)
] // eslint-disable-line @typescript-eslint/no-magic-numbers -- default values
;
if (typeof window !== 'undefined') console.log($760307e299781208$export$b3b7c4718d5d9517);
this.renderer = renderer;
this.defaultId = this.addElement({
renderAs: this.renderer.defaultRenderAs,
hitbox: [],
name: 'blank'
});
this.reset({
canvasW: width,
canvasH: height
});
}
/** @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: $09b4a56db8f03c3c$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 = $760307e299781208$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';
$760307e299781208$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][$3a20935030977292$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 #_ = this._moore = $09b4a56db8f03c3c$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($3a20935030977292$export$e27e751a5b1946e3($760307e299781208$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)=>$760307e299781208$var$boolToNumber(this.confirmElm(loc, search))).reduce((res, x)=>res << 1 | x);
}
static #_2 = this._wolfram = $09b4a56db8f03c3c$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($3a20935030977292$export$e27e751a5b1946e3($760307e299781208$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($3a20935030977292$export$e27e751a5b1946e3($760307e299781208$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][$3a20935030977292$export$b6f174f59b684b34(fixedLoc, w)] = id;
}
/** 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($3a20935030977292$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 = $760307e299781208$var$resumeAnimation(this.loopint, ()=>{
this.iterate();
});
}
} // end class PixelManipulator
const { version: $760307e299781208$export$83d89fbfd8236492 } = $779f9a72b2be71a3$exports;
const $760307e299781208$export$b3b7c4718d5d9517 = 'PixelManipulator v' + $760307e299781208$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
;
export {$760307e299781208$export$354076178bdef094 as rules, $760307e299781208$export$b2b53dd543b26a90 as PixelManipulator, $760307e299781208$export$b3b7c4718d5d9517 as licence, $760307e299781208$export$83d89fbfd8236492 as version, $3a20935030977292$export$b6f174f59b684b34 as location2Index, $3a20935030977292$export$e27e751a5b1946e3 as transposeLocations, $3a20935030977292$export$88530751e3977073 as Renderer, $3a20935030977292$export$95ab700cf10a487 as Ctx2dRenderer, $3a20935030977292$export$446eec980ee5a157 as StringRenderer, $3a20935030977292$export$5482513c57691790 as SplitRenderer};
//# sourceMappingURL=module.js.map