UNPKG

pixelmanipulator

Version:

A super powerful Typescript library for cellular automation.

482 lines (480 loc) 21.3 kB
/** 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 */ export interface Location { /** x position */ x: number; /** y position */ y: number; /** Should this location loop around screen borders? */ loop?: boolean; /** Should this location be treated to be on the current frame, previous, or older? * * Current frame is zero. Higher is older - but not guarenteed to be present */ frame?: number; } /** Convert a loction to a index to reduce need for 2D arrays * @param x - x location * @param y - y location * @param width - width of the canvas */ export function location2Index({ x, y }: Location, width: number): number; /** Transpose a list of locations, using a location. * @param locs - Locations to be transposed. If the frame or loop values are * absent, they are set to the value in [offset]. If absent from [offset] they * are not set. * @param offset - Amount to transpose the locations by, represented by a * location. */ export function transposeLocations(locs: Location[], offset: Location): Location[]; /** Abstract rendering type. Used by {@link pixelmanipulator.PixelManipulator} to enable rendering to * various targets. */ export abstract class Renderer<T> { /** Renders a pixel on a given location on the next call to {@link Renderer.update} * @param location - Where to render the pixel. * @param id - the pixel to render. */ abstract renderPixel(location: Location, id: number): void; /** Reset the render target. */ abstract reset(): void; /** Update the render target. Draws all changes queued up by {@link Renderer.renderPixel}. */ abstract update(): void; /** The {@link pixelmanipulator.ElementData.renderAs} value for the default element */ abstract defaultRenderAs: T; /** Ordered by ID, the {@link pixelmanipulator.ElementData.renderAs} info for each element. */ renderInfo: T[]; /** 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: number, newRenderAs: T): T; /** @param value - The new width of the canvas */ set_width(value: number): void; /** @returns the width of the canvas */ get_width(): number; /** @param value - The new height of the canvas */ set_height(value: number): void; /** @returns the height of the canvas */ get_height(): number; } /** The color of an element */ export type Color = [number, number, number, number] | [number, number, number] | [number, number] | [number] | []; /** Render onto an {@link HTMLCanvasElement} using a {@link CanvasRenderingContext2D} */ export class Ctx2dRenderer extends Renderer<Color> { /** @param canvas - The canvas to render on, and to adjust the size of */ constructor(canvas: HTMLCanvasElement); /** The last known image data from {@link Ctx2dRenderer.ctx} */ imageData: ImageData; /** The rendering context for the canvas */ ctx: CanvasRenderingContext2D; /** The canvas */ canvas: HTMLCanvasElement; /** Default color is solid black */ defaultRenderAs: Color; /** 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: number, newRenderAs: Color): Color; /** @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: Location, id: number): void; reset(): void; update(): void; set_width(value: number): void; set_height(value: number): void; } /** Render to a string */ export class StringRenderer extends Renderer<string> { defaultRenderAs: string; /** The callback function passed to the constructor. Called on {@link StringRenderer.update} */ readonly _callback: (string: string) => void; /** @param callback - A function called on {@link StringRenderer.update}. Passed a * string with the renderable state of the {@link pixelmanipulator.PixelManipulator} */ constructor(callback: (string: string) => void); /** @param newRenderAs - The proposed character to use. Must be 1 char long and unique */ modifyElement(id: number, newRenderAs: string): string; reset(): void; /** @param x - X location of pixel * @param y - y location of pixel * @param id - The id of the pixel */ renderPixel({ x, y }: Location, id: number): void; update(): void; } /** render on two different targets (which may also be {@link SplitRenderer}) */ export class SplitRenderer<A, B> extends Renderer<{ a: A; b: B; }> { defaultRenderAs: { a: A; b: B; }; a: Renderer<A>; b: Renderer<B>; constructor(a: Renderer<A>, b: Renderer<B>); renderPixel(loc: Location, id: number): void; reset(): void; update(): void; modifyElement(id: number, { a, b }: { a: A; b: B; }): { a: A; b: B; }; } /** A list of locations, usually relative around a pixel. */ type Hitbox = Location[]; declare function startAnimation(callback: () => void): number | ReturnType<typeof setInterval>; /** The argument to {@link ElementDataUnknown.liveCell} and * {@link ElementDataUnknown.deadCell} */ export interface Rel { /** The X location of this pixel. */ x: number; /** The Y location of this pixel. */ y: number; /** The ID number of the current pixel. Reccommended if performance profiling * shows string comparision is a bottleneck. */ oldId: number; /** The ID of the element for which this is being called. (in a * {@link ElementDataUnknown.liveCell} that's the same as {@link Rel.oldId}, but in a * {@link ElementDataUnknown.deadCell} it's the id that the deadCell belongs to. */ thisId: number; } /** Much like {@link ElementDataUnknown} but all fields except {@link ElementData.madeWithRule}, * {@link ElementData.liveCell} and {@link ElementData.deadCell} are mandatory. */ export interface ElementData<T> extends ElementDataUnknownNameMandatory<T> { renderAs: T; hitbox: Hitbox; } /** Information about an element. */ export interface ElementDataUnknown<T> { /** The name of the element. */ name?: string; /** Information on how to render this element */ renderAs?: T; /** {@link ElementDataUnknown.deadCell} will only be called on empty * pixels within the hitbox of a live cell. Array of relative coordinate pairs. * Optional, defaults to the result of {@link neighborhoods.moore} * called with no arguments. */ hitbox?: Hitbox; /** Every frame of animation, pixelmanipulator iterates through each and every pixel on the screen. If this element is found, it calls this function. */ liveCell?: (rel: Rel) => void; /** Every frame of animation, pixelmanipulator iterates through each and every * pixel on the screen. If this element is found, it calls this function on * each of the locations defined in {@link ElementDataUnknown.hitbox} so long as * the pixel matches the value in {@link PixelManipulator.defaultId}, without * calling the same dead pixel twice. */ deadCell?: (rel: Rel) => void; /** If present, indicates that this element was auto-generated */ madeWithRule?: true; } /** Much like {@link ElementDataUnknown} but the name is mandatory. */ export interface ElementDataUnknownNameMandatory<T> extends ElementDataUnknown<T> { name: string; } /** Template generators for your elements. */ export const rules: { /** 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: <T>(p: PixelManipulator<T>, pattern: string, loop?: boolean) => ElementDataUnknown<T>; /** 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: <T>(p: PixelManipulator<T>, pattern: string, loop?: boolean) => ElementDataUnknown<T>; }; /** Sizes to set the canvases to. If a value below is absent, old value is used. */ export interface CanvasSizes { /** width of the canvas */ canvasW?: number; /** height of the canvas */ canvasH?: number; } /** A cellular automata engine */ export class PixelManipulator<T> { /** * @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: Renderer<T>, width: number, height: number); /** An instanace of the object that shows the state to the user. */ renderer: Renderer<T>; /** * 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: ReturnType<typeof startAnimation>; /** * A low-level listing of the availiable elements. * * Format is much like the argument to * {@link PixelManipulator.addMultipleElements}, but is not sanitized. */ readonly elements: Array<ElementData<T>>; /** * 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: Map<string, string>; /** * 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: 'playing' | '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: number; /** Called before {@link PixelManipulator.iterate} does its work. * @returns false to postposne iteration. */ onIterate: () => (boolean | undefined); /** Called after {@link PixelManipulator.iterate} does its work. */ onAfterIterate: () => undefined; /** 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: (id: number) => void; /** @returns the width of the canvas */ get_width(): number; /** @param value - The new width of the canvas */ set_width(value: number): void; /** @returns the height of the canvas */ get_height(): number; /** @param value - The new height of the canvas */ set_height(value: number): void; /** 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: string | number, pr?: number): void; /** 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: Record<string, ElementDataUnknown<T>>): void; /** Add an element with the given element data * @param data - The details about the element. * @returns The generated {@link PixelManipulator.elements} index */ addElement(data: ElementDataUnknownNameMandatory<T>): number; /** * @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: number, data: ElementDataUnknown<T>): void; /** * @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: string, newName: string): void; /** Respecting aliases, convert an element name into its number. * @param name - name of element * @returns The number of the element */ nameToId(name: string): number; /** * @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: string): ElementData<T> | undefined; /** * @param loc - Location of the element. * @returns Name of element at passed-in location. See {@link ElementData.name} */ whatIs(loc: Location): string; /** 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?: CanvasSizes): void; /** 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?: CanvasSizes): void; /** pause canvas iterations * Sets {@link PixelManipulator.mode} to `"paused"` and cancels the animation frame * referenced in {@link PixelManipulator.loopint} */ pause(): void; /** * @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: Location): null | Location; /** * @param loc - Location of the pixel * @returns the element id at a given location */ getPixelId(loc: Location): number; /** * Applies any changes made with {@link renderers.Renderer.renderPixel} on {@link PixelManipulator.renderer} to the canvas */ update(): void; /** * @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: Location, id: number | string): boolean; /** 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: Location[], search: number | string): number; /** @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: Location, search: number | string): number; /** @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: Location[], ruleNum: number, search: number | string): boolean; /** @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: Location[], search: number | string): number; /** @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: Location, ruleNum: number, search: number | string): boolean; /** * @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: Location, search: number | string): number; /** 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: Location, name: number | string, binDex: number | string): boolean; /** 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: Location, ident: string | number): void; /** Number of pixels per element in the last frame */ pixelCounts: Record<number, number>; /** 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(): void; /** * 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: Uint32Array[]; } /** Version of library **for logging purposes only**. Uses semver. */ export const version: string; /** Licence disclaimer for PixelManipulator */ export const licence: string; //# sourceMappingURL=types.d.ts.map