pixelmanipulator
Version:
A super powerful Typescript library for cellular automation.
1 lines • 37.8 kB
Source Map (JSON)
{"mappings":"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","sources":["packages/pixelmanipulator/src/src/renderers.ts","packages/pixelmanipulator/src/src/neighborhoods.ts","packages/pixelmanipulator/src/src/pixelmanipulator.ts","packages/pixelmanipulator/src/pixelmanipulator.ts"],"sourcesContent":[null,null,null,"/** This is a cellular automata JavaScript library called PixelManipulator. For\n * information about how to use this script, see\n * https://github.com/Lazerbeak12345/pixelmanipulator\n *\n * Copyright (C) 2018-2024 Nathan Fritzler\n *\n * This program is free software: you can redistribute it and/or modify\n * it under the terms of the GNU General Public License as published by\n * the Free Software Foundation, either version 3 of the License, or\n * (at your option) any later version.\n *\n * This program is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n * GNU General Public License for more details.\n *\n * You should have received a copy of the GNU General Public License\n * along with this program. If not, see http://www.gnu.org/licenses.\n */\nimport * as package_json from '../package.json'\nimport type { Hitbox } from './neighborhoods'\nimport type { Renderer, Location } from './renderers'\nimport * as _renderers from './renderers'\n// export * as neighborhoods from './neighborhoods'\nimport * as _neighborhoods from './neighborhoods'\nexport { _neighborhoods as neighborhoods }\nexport * from './renderers'\nfunction startAnimation(callback: () => void): number | ReturnType<typeof setInterval> {\n if (typeof requestAnimationFrame === 'undefined') {\n const SMALLEST_INTERVAL_POSSIBLE = 1\n return setInterval(callback, SMALLEST_INTERVAL_POSSIBLE)\n } else {\n return requestAnimationFrame(callback)\n }\n}\nfunction resumeAnimation(num: ReturnType<typeof startAnimation>, callback: () => void): ReturnType<typeof startAnimation> {\n if (typeof requestAnimationFrame === 'undefined') {\n return num\n } else {\n return requestAnimationFrame(callback)\n }\n}\nfunction cancelAnimation(num: ReturnType<typeof startAnimation>): void {\n if (typeof cancelAnimationFrame === 'undefined') {\n clearInterval(num)\n } else if (typeof num === \"number\") {\n cancelAnimationFrame(num)\n }\n}\nfunction boolToNumber(bool: boolean): number {\n const TRUE = 1\n const FALSE = 0\n return bool ? TRUE : FALSE\n}\n/** The argument to {@link ElementDataUnknown.liveCell} and\n* {@link ElementDataUnknown.deadCell}\n*/\nexport interface Rel {\n /** The X location of this pixel. */\n x: number\n /** The Y location of this pixel. */\n y: number\n /** The ID number of the current pixel. Reccommended if performance profiling\n * shows string comparision is a bottleneck.\n */\n oldId: number\n /** The ID of the element for which this is being called. (in a\n * {@link ElementDataUnknown.liveCell} that's the same as {@link Rel.oldId}, but in a\n * {@link ElementDataUnknown.deadCell} it's the id that the deadCell belongs to.\n */\n thisId: number\n}\n/** Much like {@link ElementDataUnknown} but all fields except {@link ElementData.madeWithRule},\n* {@link ElementData.liveCell} and {@link ElementData.deadCell} are mandatory. */\nexport interface ElementData<T> extends ElementDataUnknownNameMandatory<T> {\n renderAs: T\n hitbox: Hitbox\n}\n/** Information about an element. */\nexport interface ElementDataUnknown<T> {\n /** The name of the element. */\n name?: string\n /** Information on how to render this element */\n renderAs?: T\n /** {@link ElementDataUnknown.deadCell} will only be called on empty\n * pixels within the hitbox of a live cell. Array of relative coordinate pairs.\n * Optional, defaults to the result of {@link neighborhoods.moore}\n * called with no arguments.\n */\n hitbox?: Hitbox\n /** Every frame of animation, pixelmanipulator iterates through each and every pixel on the screen. If this element is found, it calls this function.\n */\n liveCell?: (rel: Rel) => void\n /** Every frame of animation, pixelmanipulator iterates through each and every\n * pixel on the screen. If this element is found, it calls this function on\n * each of the locations defined in {@link ElementDataUnknown.hitbox} so long as\n * the pixel matches the value in {@link PixelManipulator.defaultId}, without\n * calling the same dead pixel twice.\n */\n deadCell?: (rel: Rel) => void\n /** If present, indicates that this element was auto-generated */\n madeWithRule?: true\n}\n/** Much like {@link ElementDataUnknown} but the name is mandatory. */\nexport interface ElementDataUnknownNameMandatory<T> extends ElementDataUnknown<T> {\n name: string\n}\nfunction _convertNumListToBf(nl: string): number {\n // While I used to use string with each digit in it, I found that since\n // there are 0-8, I could use a 9bit field (remember: off by one)\n let out = 0\n for (const item of nl) {\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- largest binary digit\n out |= 1 << parseInt(item)\n }\n return out\n}\n/** Template generators for your elements. */\nexport const rules = {\n /** Generates elements like conway's game of life.\n * @param p - `lifelike` needs to be able to call {@link PixelManipulator.mooreNearbyCounter}\n * @param pattern - The B/S syntax indicator of on how many cells of the same\n * type in the moore radius around each pixel should survive, and on how many\n * should be born.\n * @param loop - Should this loop around screen edges? (Passed to {@link renderers.Location.loop})\n */\n lifelike: function <T>(p: PixelManipulator<T>, pattern: string, loop?: boolean): ElementDataUnknown<T> {\n const numbers = pattern.split(/\\/?[a-z]/gi)// \"B\",born,die\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- after the pattern\n const bfdie = _convertNumListToBf(numbers[2])\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- before the pattern\n const bflive = _convertNumListToBf(numbers[1])\n const SMALLEST_BINARY_DIGIT = 1\n const PREV_FRAME = 1\n const NO_MATCH = 0\n return {\n madeWithRule: true,\n hitbox: _neighborhoods.moore(),\n liveCell: function llive({ x, y, thisId }) {\n // if any match (of how many moore are nearby) is found, it dies\n if ((bfdie & SMALLEST_BINARY_DIGIT << p.mooreNearbyCounter({ x, y, frame: PREV_FRAME, loop }, thisId)) === NO_MATCH) {\n p.setPixel({ x, y }, p.defaultId)\n }\n },\n deadCell: function ldead({ x, y, thisId }) {\n // if any match (of how many moore are nearby) is found, it lives\n if ((bflive & SMALLEST_BINARY_DIGIT << p.mooreNearbyCounter({ x, y, frame: PREV_FRAME, loop }, thisId)) !== NO_MATCH) {\n p.setPixel({ x, y }, thisId)\n }\n }\n }\n },\n /** Generates fundamental cellular automata\n * @param p - `wolfram` needs to be able to call {@link PixelManipulator.wolframNearbyCounter}\n * @param pattern - The Rule num syntax, where the 8-bit number is translated\n * into a binary list, each where the inverted 3-binary-digit index represents\n * the state of cells in the row above. On a match, the cell becomes the state\n * specified in the initial 8-bit number.\n * @param loop - Should this loop around screen edges? (Passed to {@link PixelManipulator.wolframNearby})\n */\n wolfram: function <T>(p: PixelManipulator<T>, pattern: string, loop?: boolean): ElementDataUnknown<T> {\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- after the pattern\n const binStates = parseInt(pattern.split(/Rule /gi)[1])\n const FIRST_ROW = 0\n const PREV_FRAME = 1\n return {\n madeWithRule: true,\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- default values\n hitbox: _neighborhoods.wolfram(1, 1),\n // The current state is used as the index in the binstates, as binstates is a bit array of every state\n liveCell: function wlive({ x, y, thisId }) {\n if (y === FIRST_ROW) return\n if (!p.wolframNewState({ x, y, frame: PREV_FRAME, loop }, binStates, thisId)) {\n p.setPixel({ x, y, loop }, p.defaultId)\n }\n },\n deadCell: function wdead({ x, y, thisId }) {\n if (p.wolframNewState({ x, y, frame: PREV_FRAME, loop }, binStates, thisId)) {\n p.setPixel({ x, y, loop }, thisId)\n }\n }\n }\n }\n}\n/** Sizes to set the canvases to. If a value below is absent, old value is used.\n*/\nexport interface CanvasSizes {\n /** width of the canvas */\n canvasW?: number\n /** height of the canvas */\n canvasH?: number\n}\n/** A cellular automata engine */\nexport class PixelManipulator<T> {\n /**\n * @param renderer - The target to render things to.\n * @param width - How wide should the initial target be?\n * @param height - How tall should the initial target be?\n */\n constructor(renderer: Renderer<T>, width: number, height: number) {\n if (typeof window !== 'undefined') console.log(licence)\n this.renderer = renderer\n this.defaultId = this.addElement({\n renderAs: this.renderer.defaultRenderAs,\n hitbox: [],\n name: 'blank'\n })\n this.reset({ canvasW: width, canvasH: height })\n }\n\n /** An instanace of the object that shows the state to the user. */\n renderer: Renderer<T>\n /**\n * This is the number that indicates what animation frame the iterate function\n * is being called with.\n *\n * You can use this to mannually stop the iterations like so:\n * `cancelAnimationFrame(this.loopint)` (not reccommended)\n */\n loopint: ReturnType<typeof startAnimation> = 0 // eslint-disable-line @typescript-eslint/no-magic-numbers -- default value\n /**\n * A low-level listing of the availiable elements.\n *\n * Format is much like the argument to\n * {@link PixelManipulator.addMultipleElements}, but is not sanitized.\n */\n readonly elements: Array<ElementData<T>> = []\n\n /**\n * A mapping from old names for elements to new names for elements.\n *\n * Allows a user to modify the name of an element at runtime.\n */\n nameAliases = new Map<string, string>()\n /**\n * A string indicating weather it is currently animating or not.\n *\n * It is `\"playing\"` if it is currently animating, or `\"paused\"` if not\n * currently animating.\n *\n * This has been around since early version 0, and once was the `innerText`\n * value of a pause/play button!\n */\n mode: 'playing' | 'paused' = 'paused'\n /**\n * The elm that pixelmanipulator will fill the screen with upon initialization,\n * and what elements should return to when they are \"dead\". Default value is\n * 0, an element with the color `#000F`\n *\n * If you update this, be sure to update {@link renderers.Renderer.defaultRenderAs} in {@link PixelManipulator.renderer}\n */\n defaultId: number\n /** Called before {@link PixelManipulator.iterate} does its work.\n * @returns false to postposne iteration.\n */\n onIterate: () => (boolean | undefined) = () => undefined // eslint-disable-line @typescript-eslint/class-methods-use-this -- event handler\n /** Called after {@link PixelManipulator.iterate} does its work. */\n onAfterIterate: () => undefined = () => undefined // eslint-disable-line @typescript-eslint/class-methods-use-this -- event handler\n\n /** Gets called after a call to {@link PixelManipulator.modifyElement}. The ID is\n * passed as the only argument.\n * @param id - The element that was modified.\n */\n onElementModified: (id: number) => void = () => undefined // eslint-disable-line @typescript-eslint/class-methods-use-this -- event handler\n /** @returns the width of the canvas */\n get_width(): number {\n return this.renderer.get_width()\n }\n\n /** @param value - The new width of the canvas */\n set_width(value: number): void {\n this.renderer.set_width(value)\n }\n\n /** @returns the height of the canvas */\n get_height(): number {\n return this.renderer.get_height()\n }\n\n /** @param value - The new height of the canvas */\n set_height(value: number): void {\n this.renderer.set_height(value)\n }\n\n /** fills the screen with value, at an optional given percent\n * @param value - The element to put on the screen\n * @param pr - The percent as a number from 1 to 100, defaulting at 15\n */\n randomlyFill(value: string | number, pr?: number): void {\n const DEFAULT_PERCENT = 15\n const MAX_PERCENT = 100\n pr ??= DEFAULT_PERCENT\n const w = this.get_width()\n const h = this.get_height()\n for (let x = 0; x < w; x++) {\n for (let y = 0; y < h; y++) { // iterate through x and y\n if (Math.random() * MAX_PERCENT < pr) { this.setPixel({ x, y }, value) }\n }\n }\n }\n\n /** Adds multiple elements.\n *\n * @param elements - Index is the element name, value is the element data (and\n * does not require the name). Value is passed to\n * {@link PixelManipulator.addElement}\n */\n addMultipleElements(elements: Record<string, ElementDataUnknown<T>>): void {\n Object.keys(elements).forEach(name =>\n this.addElement({ name, ...elements[name] })\n )\n }\n\n /** Add an element with the given element data\n * @param data - The details about the element.\n * @returns The generated {@link PixelManipulator.elements} index\n */\n addElement(\n data: ElementDataUnknownNameMandatory<T>\n ): number { // adds a single element\n const { name, renderAs } = data\n if (typeof name === 'undefined') throw new Error('Name is required for element')\n if (typeof data.name === 'undefined') data.name = name\n // @ts-expect-error renderAs might be undefined here, but it's fixed in the call to this.modifyElement below\n this.elements.push({ name, renderAs })\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- last item in list\n this.modifyElement(this.elements.length - 1, data)\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- last item in list (might not be the same as before modifyElement was called)\n return this.elements.length - 1\n }\n\n /**\n * @param id - How to identify what element to modify.\n * @param data - Values to apply to the pre-existing element.\n *\n * Automatically calls {@link PixelManipulator.aliasElements} if\n * {@link ElementDataUnknown.name} is present in `data`\n */\n modifyElement(id: number, data: ElementDataUnknown<T>): void {\n const oldData: Omit<ElementData<T>, \"hitbox\"> | ElementData<T> = this.elements[id]\n if (typeof data.name !== 'undefined' && data.name !== oldData.name) {\n this.aliasElements(oldData.name, data.name)\n }\n const newData = {\n hitbox: _neighborhoods.moore(),\n ...oldData,\n ...data,\n }\n newData.renderAs = this.renderer.modifyElement(id, newData.renderAs)\n this.elements[id] = newData\n this.onElementModified(id)\n }\n\n /**\n * @param oldName - The old {@link ElementData.name}\n * @param newName - The new {@link ElementData.name}\n *\n * Adds the name to {@link PixelManipulator.nameAliases}, and ensures no alias\n * loops are present.\n */\n aliasElements(oldName: string, newName: string): void {\n // Intentionally ignores aliases when checking for duplicate name.\n if (this.elements.find(elm => elm.name === newName) != null) {\n throw new Error('The name ' + newName + ' is already in use!')\n }\n this.nameAliases.delete(newName)\n this.nameAliases.set(oldName, newName)\n }\n\n /** Respecting aliases, convert an element name into its number.\n * @param name - name of element\n * @returns The number of the element\n */\n nameToId(name: string): number {\n let unaliased: string | undefined = name\n while (typeof unaliased !== 'undefined') {\n name = unaliased\n unaliased = this.nameAliases.get(name)\n }\n return this.elements.findIndex(elm => elm.name === name)\n }\n\n /**\n * @param name - Name of the (possibly aliased) element.\n * @returns The element from {@link PixelManipulator.elements}, respecting\n * aliases in {@link PixelManipulator.nameAliases}, or {@link undefined} if not found.\n */\n getElementByName(name: string): ElementData<T> | undefined {\n return this.elements[this.nameToId(name)]\n }\n\n /**\n * @param loc - Location of the element.\n * @returns Name of element at passed-in location. See {@link ElementData.name}\n */\n whatIs(loc: Location): string {\n return this.elements[this.getPixelId(loc)].name\n }\n\n /** Start iterations on all of the elements on the canvas.\n * Sets {@link PixelManipulator.mode} to `\"playing\"`, and requests a new animation\n * frame, saving it in {@link PixelManipulator.loopint}.\n *\n * @param canvasSizes - If {@link PixelManipulator.mode} is already `\"playing\"` then\n * canvasSizes is passed to {@link PixelManipulator.reset}. Otherwise reset is not\n * called.\n */\n play(canvasSizes?: CanvasSizes): void {\n if (this.mode === 'playing') this.reset(canvasSizes)\n this.mode = 'playing'\n this.loopint = startAnimation(() => {\n this.iterate()\n })\n }\n\n /** Reset, resize and initialize the canvas(es).\n * Calls {@link PixelManipulator.pause} then\n * {@link PixelManipulator.update}. Resets all internal state, excluding the\n * element definitions.\n *\n * @param canvasSizes - Allows one to change the size of the canvases during\n * the reset.\n */\n reset(canvasSizes?: CanvasSizes): void {\n const CURRENT_FRAME = 0\n const NEXT_FRAME = 1\n const MAX_PERCENT = 100\n if (typeof canvasSizes === 'undefined') { canvasSizes = {} }\n this.pause()\n const w = canvasSizes.canvasW ?? this.get_width()\n const h = canvasSizes.canvasH ?? this.get_height()\n this.set_width(w)\n this.set_height(h)\n this.frames[CURRENT_FRAME] = new Uint32Array(w * h)\n this.frames[NEXT_FRAME] = new Uint32Array(w * h)\n this.renderer.reset()\n this.randomlyFill(this.defaultId, MAX_PERCENT)\n this.update()\n }\n\n /** pause canvas iterations\n * Sets {@link PixelManipulator.mode} to `\"paused\"` and cancels the animation frame\n * referenced in {@link PixelManipulator.loopint}\n */\n pause(): void {\n this.mode = 'paused'\n cancelAnimation(this.loopint)\n }\n\n /**\n * @param loc - Location of the pixel (could be out of bounds).\n * @returns null if out-of-bounds when loop setting is false, or the location (loop set to false).\n */\n locationBoundsCheck(loc: Location): null | Location { // eslint-disable-line complexity -- close enough for me tbh\n const LEFTMOST = 0\n const TOPMOST = 0\n\n const w = this.get_width()\n const h = this.get_height()\n\n const overflowLeft = loc.x < LEFTMOST\n const overflowRight = loc.x >= w\n const overflowTop = loc.y < TOPMOST\n const overflowBottom = loc.y >= h\n\n if (loc.loop ?? true) {\n loc.x %= w\n loc.y %= h\n\n if (overflowLeft) loc.x += w\n if (overflowTop) loc.y += h\n\n loc.loop = false\n } else if (overflowLeft || overflowRight || overflowTop || overflowBottom) {\n return null\n }\n return loc\n }\n\n /**\n * @param loc - Location of the pixel\n * @returns the element id at a given location\n */\n getPixelId(loc: Location): number {\n const fixedLoc = this.locationBoundsCheck(loc)\n if (fixedLoc == null) return this.defaultId\n const w = this.get_width()\n const CURRENT_FRAME = 0 // TODO: dedupe this const\n return this.frames[fixedLoc.frame ?? CURRENT_FRAME][_renderers.location2Index(fixedLoc, w)]\n }\n\n /**\n * Applies any changes made with {@link renderers.Renderer.renderPixel} on {@link PixelManipulator.renderer} to the canvas\n */\n update(): void {\n this.renderer.update()\n }\n\n /**\n * @param loc - Where to confirm the element\n * @param id - The elm you expect it to be\n * @returns Does the cell at `loc` match `ident`?\n */\n confirmElm(loc: Location, id: number | string): boolean {\n return this.getPixelId(loc) === (typeof id === 'string' ? this.nameToId(id) : id)\n }\n\n /** Calculate the total number of elements within an area\n * @param area - The locations to total up.\n * @param search - The element to look for\n * @returns The total\n */\n totalWithin(area: Location[], search: number | string): number {\n return area\n .filter(loc => this.confirmElm(loc, search))\n .length\n }\n\n private static readonly _moore = _neighborhoods.moore()\n /** @param name - element to look for\n * @param center - location of the center of the moore area\n * @returns Number of matching elements in moore radius */\n mooreNearbyCounter(center: Location, search: number | string): number {\n return this.totalWithin(_renderers.transposeLocations(PixelManipulator._moore, center), search)\n }\n\n /** @param area - The Area to search within\n * @param ruleNum - A bitfield of what states a pixel should live or die on.\n * @param search - The element to search for\n * @see {@link PixelManipulator.wolframNewState} for higher-level tool\n * @see {@link PixelManipulator.fundamentalStatesWithin} for lower-level tool\n * @returns The state that the bitfied says this pixel should be in the next frame.\n */\n fundamentalNewState(area: Location[], ruleNum: number, search: number | string): boolean {\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 1 is largest binary digit, nonzero if matches\n return (ruleNum & 1 << this.fundamentalStatesWithin(area, search)) !== 0\n }\n\n /** @param area - Locations to look at.\n * @param search - Locations to mark as a true bit.\n * @see {@link PixelManipulator.fundamentalNewState} for higher-level tool\n * @returns number as a bitfied array, in order of the items in area, from left to right.\n *\n * That means that `(fundamentalStatesWithin([loc], search) & 1) === boolToNumber(confirmElm(loc, search))`\n *\n * You may want to see [this page](https://www.wolframscience.com/nks/notes-5-2--general-rules-for-multidimensional-cellular-automata/)\n * for more details on how this might be used.\n */\n fundamentalStatesWithin(area: Location[], search: number | string): number {\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- 1 is the SMALLEST_BINARY_DIGIT\n return area.map(loc => boolToNumber(this.confirmElm(loc, search))).reduce((res, x) => res << 1 | x)\n }\n\n private static readonly _wolfram = _neighborhoods.wolfram()\n\n /** @param loc - The pixel to change. (Defaults {@link renderers.Location.loop} to false)\n * @param ruleNum - A bitfield of what states a pixel should live or die on.\n * @param search - The element to search for\n * @see {@link PixelManipulator.fundamentalNewState} for more general tool.\n * @returns The state that the bitfied says this pixel should be in the next frame.\n */\n wolframNewState(loc: Location, ruleNum: number, search: number | string): boolean {\n // one-dimentional detectors by default don't loop around edges\n loc.loop ??= false\n return this.fundamentalNewState(\n _renderers.transposeLocations(PixelManipulator._wolfram, loc),\n ruleNum,\n search\n )\n }\n\n /**\n * @param current - \"Current\" pixel location. (Defaults {@link renderers.Location.loop} to false)\n * @param search - element to look for\n * @see {@link PixelManipulator.fundamentalStatesWithin} for lower-level tool\n * @returns Number used as bit area to indicate occupied cells\n */\n wolframNearby(current: Location, search: number | string): number {\n // one-dimentional detectors by default don't loop around edges\n current.loop ??= false\n return this.fundamentalStatesWithin(\n _renderers.transposeLocations(PixelManipulator._wolfram, current),\n search\n )\n }\n\n /** Counter tool used in slower wolfram algorithim.\n * @deprecated Replaced with {@link PixelManipulator.wolframNearby} for use in faster\n * algorithms\n * @param current - \"Current\" pixel location\n * @param name - element to look for\n * @param bindex - Either a string like `\"001\"` to match to, or the same\n * encoded as a number.\n * @returns Number of elements in wolfram radius */\n wolframNearbyCounter(current: Location, name: number | string, binDex: number | string): boolean {\n if (typeof binDex === 'string') {\n // Old format was a string of ones and zeros, three long.\n binDex = parseInt(binDex, 2)\n }\n return this.wolframNearby(current, name) === binDex\n }\n\n /** Set a pixel in a given location.\n *\n * @param x - X position.\n * @param y - Y position.\n * @param ident - Value to identify the element.\n *\n * - If a string, it assumes it's an element name.\n * - If a number, it assumes it's an element ID\n *\n * @param loop - Defaults to {@link true}. Wraps `x` and `y` around canvas borders.\n */\n setPixel(loc: Location, ident: string | number): void {\n const NOT_FOUND = -1\n let id = 0\n if (typeof ident === 'string') {\n id = this.nameToId(ident)\n if (id === NOT_FOUND) {\n throw new Error(`Element name ${ident} is invalid`)\n }\n } else {\n id = ident\n }\n const fixedLoc = this.locationBoundsCheck(loc)\n if (fixedLoc == null) return\n this.renderer.renderPixel(fixedLoc, id)\n const w = this.get_width()\n const CURRENT_FRAME = 0\n this.frames[CURRENT_FRAME][_renderers.location2Index(fixedLoc, w)] = id\n }\n\n /** Number of pixels per element in the last frame */\n pixelCounts: Record<number, number> = {}\n\n /** A single frame of animation. Media functions pass this into\n * {@link requestAnimationFrame}.\n *\n * Be careful! Calling this while {@link PixelManipulator.mode} is `\"playing\"`\n * might cause two concurrent calls to this function. If any of your automata\n * have \"hidden state\" - that is they don't represent every detail about\n * themselves as data within the pixels - it might cause conflicts.\n */\n iterate(): void { // eslint-disable-line complexity -- TODO: break this down a bit\n if (this.onIterate() ?? true) {\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- last item in list\n for (let frame = this.frames.length - 1; frame >= 0; frame--) {\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- nonzero\n if (frame > 0) {\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- next is minus one\n const nextFrame = frame - 1\n this.frames[frame].set(this.frames[nextFrame])\n }\n }\n const w = this.get_width()\n const h = this.get_height()\n const typedUpdatedDead = new Array<Uint8Array>(this.elements.length)\n this.pixelCounts = {}\n const NEXT_FRAME = 1\n for (let x = 0; x < w; x++) {\n for (let y = 0; y < h; y++) { // iterate through x and y\n const currentPixId = this.getPixelId({ x, y, frame: NEXT_FRAME })\n if (currentPixId === this.defaultId) continue\n const { elements: { [currentPixId]: elm } } = this\n if (typeof elm === \"undefined\") {\n throw new Error(\n 'This isn\\'t supposed to happen, but the internal pixel buffer was ' +\n 'currupted. This is likely a bug, or a symptom of improper direct ' +\n 'access to the current memory buffer'\n )\n }\n if (typeof elm.liveCell !== \"undefined\") {\n elm.liveCell({\n x,\n y,\n oldId: currentPixId,\n thisId: currentPixId\n })\n }\n if (typeof this.pixelCounts[currentPixId] === \"undefined\") {\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- starting at 1\n this.pixelCounts[currentPixId] = 1\n } else this.pixelCounts[currentPixId]++\n if (typeof elm.deadCell !== \"undefined\") {\n const UPDATED_SIZE = 8 // TODO: this is a guess. document this better, or rewrite this.\n typedUpdatedDead[currentPixId] ??= new Uint8Array(Math.ceil((w * h) / UPDATED_SIZE));\n elm.hitbox.forEach(pixel => {\n // We are looping, so it can't be null. Eslint doesn't like non-null assertions, so we must do this.\n const hblocStupidFallback: Location = {\n x: x + pixel.x,\n y: y + pixel.y\n }\n const hbLoc: Location = this.locationBoundsCheck(hblocStupidFallback) ?? hblocStupidFallback;\n const index = Math.floor(_renderers.location2Index(hbLoc, w) / UPDATED_SIZE)\n const { [currentPixId]: { [index]: oldValue } } = typedUpdatedDead\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- SMALLEST_BINARY_DIGIT\n const bitMask = 1 << (hbLoc.x % UPDATED_SIZE)\n // eslint-disable-next-line @typescript-eslint/no-magic-numbers -- compare to zero\n if ((oldValue & bitMask) !== 0) { return }\n // I timed it, and confirmOldElm is slower than all the math above.\n if (!this.confirmElm({ x: hbLoc.x, y: hbLoc.y, frame: NEXT_FRAME }, this.defaultId)) {\n return\n }\n if (typeof elm.deadCell !== \"undefined\") {\n elm.deadCell({\n x: hbLoc.x,\n y: hbLoc.y,\n oldId: this.defaultId,\n thisId: currentPixId\n })\n }\n typedUpdatedDead[currentPixId][index] = oldValue | bitMask\n })\n }\n }\n }\n this.update()\n this.onAfterIterate()\n }\n if (this.mode === 'playing') {\n this.loopint = resumeAnimation(this.loopint, () => {\n this.iterate()\n })\n }\n }\n\n /**\n * A List of {@link Uint32Array}s each the length of width times height of the\n * canvas. Frame 0 is the new frame, frame one is the prior, etc. Each item\n * holds the element id of each element on screen, from left to right, top to\n * bottom.\n */\n frames: Uint32Array[] = [new Uint32Array(0), new Uint32Array(0)] // eslint-disable-line @typescript-eslint/no-magic-numbers -- default values\n}// end class PixelManipulator\n/** Version of library **for logging purposes only**. Uses semver. */\nexport const { version } = package_json\n/** Licence disclaimer for PixelManipulator */\nexport const licence = 'PixelManipulator v' + version + ' Copyright (C) ' +\n '2018-2024 Nathan Fritzler\\nThis program comes with ABSOLUTELY NO ' +\n 'WARRANTY\\nThis is free software, and you are welcome to redistribute it\\n' +\n 'under certain conditions, as according to the GNU GENERAL PUBLIC LICENSE ' +\n 'version 3 or later.'\n// This is called a \"modeline\". It's a (n)vi(m)|ex thing.\n// vi: tabstop=2 shiftwidth=2 expandtab\n"],"names":[],"version":3,"file":"types.d.ts.map","sourceRoot":"../../../"}