diff --git a/README.md b/README.md index 221bfa9d..92032646 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Start with this html:
- + diff --git a/package-lock.json b/package-lock.json index 7ff7503b..8e0846c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "pixelmanipulator", - "version": "5.0.0", + "version": "5.3.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -1372,9 +1372,9 @@ "dev": true }, "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "version": "2.6.4", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", + "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", "dev": true, "requires": { "lodash": "^4.17.14" diff --git a/package.json b/package.json index 2c410c72..ba8ab744 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pixelmanipulator", - "version": "5.2.0", + "version": "5.3.0", "description": "Run any cellular automata on an html5 canvas.", "main": "dist/main.js", "browser": "dist/browser.js", @@ -92,7 +92,7 @@ "build:bundle": "browserify dist/browser.js -o dist/bundle.js --standalone pixelmanipulator", "build": "npm run check && npm run build:docs && npm run build:parcel && npm run build:bundle", "updatedemo": "npm run build && gh-pages -d docs -m \"Update $npm_package_version\" -tf", - "node-demo": "ts-node src/node-demo", + "node-demo": "ts-node src/node-demo", "coverage": "npm run test" }, "dependencies": { diff --git a/src/lib/neighborhoods.ts b/src/lib/neighborhoods.ts index 85fef594..266a9585 100644 --- a/src/lib/neighborhoods.ts +++ b/src/lib/neighborhoods.ts @@ -19,6 +19,20 @@ import { Location } from './renderers' /** A list of locations, usually relative around a pixel. */ export type Hitbox=Location[] +/** A rect between two points +* @param topLeft - The top left corner +* @param bottomRight - The bottom right corner +* @returns A hitbox shaped like a rectangle between the corners. +*/ +export function rect (topLeft: Location, bottomRight: Location): Location[] { + const output: Hitbox = [] + for (let x = topLeft.x; x <= bottomRight.y; x++) { + for (let y = topLeft.y; y <= bottomRight.y; y++) { + output.push({ x, y }) + } + } + return output +} /** * Makes a wolfram neighborhood. * @@ -75,20 +89,15 @@ export function moore (radius?: number, includeSelf?: boolean): Hitbox { if (radius == null) { radius = 1 } - if (includeSelf == null) { - includeSelf = false - } - const output: Hitbox = [] // Note: no need to calculate the Chebyshev distance. All pixels in this // range are "magically" within. - for (let x = -1 * radius; x <= radius; x++) { - for (let y = -1 * radius; y <= radius; y++) { - if (includeSelf || !(x === 0 && y === 0)) { - output.push({ x, y }) - } - } - } - return output + return rect({ + x: -1 * radius, + y: -1 * radius + }, { + x: radius, + y: radius + }).filter(({ x, y }) => (includeSelf ?? false) || !(x === 0 && y === 0)) // And to think that this used to be hard... Perhaps they had a different // goal? Or just weren't using higher-order algorithims? } @@ -119,26 +128,21 @@ export function moore (radius?: number, includeSelf?: boolean): Hitbox { * ``` */ export function vonNeumann (radius?: number, includeSelf?: boolean): Hitbox { - if (typeof radius === 'undefined') { + if (radius == null) { radius = 1 } - if (typeof includeSelf === 'undefined') { - includeSelf = false - } - const output: Hitbox = [] // A Von Neumann neighborhood of a given distance always fits inside of a // Moore neighborhood of the same. (This is a bit brute-force) - for (let x = -1 * radius; x <= radius; x++) { - for (let y = -1 * radius; y <= radius; y++) { - if ( - (includeSelf || !(x === 0 && y === 0)) && - (Math.abs(x) + Math.abs(y) <= radius) // Taxicab distance - ) { - output.push({ x, y }) - } - } - } - return output + return rect({ + x: -1 * radius, + y: -1 * radius + }, { + x: radius, + y: radius + }).filter(({ x, y }) => + ((includeSelf ?? false) || !(x === 0 && y === 0)) && + (Math.abs(x) + Math.abs(y) <= (radius ?? 1)) // Taxicab distance + ) } /** * Makes a euclidean neighborhood. @@ -155,23 +159,18 @@ export function euclidean (radius?: number, includeSelf?: boolean): Hitbox { if (radius == null) { radius = 1 } - if (includeSelf == null) { - includeSelf = false - } - const output: Hitbox = [] // A circle of a given diameter always fits inside of a square of the same // side-length. (This is a bit brute-force) - for (let x = -1 * radius; x <= radius; x++) { - for (let y = -1 * radius; y <= radius; y++) { - if ( - (includeSelf || !(x === 0 && y === 0)) && - (Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= radius) // Euclidean distance - ) { - output.push({ x, y }) - } - } - } - return output + return rect({ + x: -1 * radius, + y: -1 * radius + }, { + x: radius, + y: radius + }).filter(({ x, y }) => + ((includeSelf ?? false) || !(x === 0 && y === 0)) && + (Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)) <= (radius ?? 1)) // Euclidean distance + ) } // TODO https://www.npmjs.com/package/compute-minkowski-distance ? // TODO Non-Euclidean distance algorithim? diff --git a/src/lib/pixelmanipulator.ts b/src/lib/pixelmanipulator.ts index 14991666..3fc7f823 100644 --- a/src/lib/pixelmanipulator.ts +++ b/src/lib/pixelmanipulator.ts @@ -20,7 +20,7 @@ */ import { version as _version } from '../../package.json' import { Hitbox, moore, wolfram } from './neighborhoods' -import { Renderer, Location, location2Index } from './renderers' +import { Renderer, Location, location2Index, transposeLocations } from './renderers' // export * as neighborhoods from './neighborhoods' import * as _neighborhoods from './neighborhoods' export { _neighborhoods as neighborhoods } @@ -154,33 +154,20 @@ export const rules = { return { madeWithRule: true, hitbox: wolfram(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, y, thisId }) { if (y === 0) return - // for every possible state - for (let binDex = 0; binDex < 8; binDex++) { - if ( - // if the state is "off". Use a bit mask and shift it - (binStates & 1 << binDex) === 0 && - // if there is a wolfram match (wolfram code goes from 111 to 000) - p.wolframNearbyCounter({ x, y, frame: 1, loop }, thisId, binDex) - ) { - p.setPixel({ x, y, loop }, p.defaultId) - return// No more logic needed, it is done. - } + const currentState = p.wolframNearby({ x, y, frame: 1, loop }, thisId) + // if the state is "off". Use a bit mask and shift it + if ((binStates & 1 << currentState) === 0) { + p.setPixel({ x, y, loop }, p.defaultId) } }, deadCell: function wdead ({ x, y, thisId }) { - // for every possible state - for (let binDex = 0; binDex < 8; binDex++) { - if ( - // if the state is "on". Use a bit mask and shift it - (binStates & 1 << binDex) > 0 && - // if there is a wolfram match (wolfram code goes from 111 to 000) - p.wolframNearbyCounter({ x, y, frame: 1, loop }, thisId, binDex) - ) { - p.setPixel({ x, y, loop }, thisId) - return// No more logic needed, it is done. - } + const currentState = p.wolframNearby({ x, y, frame: 1, loop }, thisId) + // if the state is "on". Use a bit mask and shift it + if ((binStates & 1 << currentState) > 0) { + p.setPixel({ x, y, loop }, thisId) } } } @@ -509,40 +496,53 @@ export class PixelManipulator