Adding the lab project
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import type { BoundaryCondition, CaClass, Cells, JsonObject, SceneParams } from './types.js'
|
||||
|
||||
export const DEFAULT_BOUNDARY_CONDITION: BoundaryCondition = 'wrap'
|
||||
export const DEFAULT_GRID_SIZE = 24
|
||||
|
||||
export function isPlainObject(value: unknown): value is JsonObject {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value)
|
||||
}
|
||||
|
||||
export function statesEqual(left: CaClass['states'] | undefined, right: CaClass['states'] | undefined) {
|
||||
if (left === undefined || right === undefined) return false
|
||||
if (Array.isArray(left) || Array.isArray(right)) {
|
||||
if (!Array.isArray(left) || !Array.isArray(right)) return false
|
||||
return JSON.stringify([...left].sort()) === JSON.stringify([...right].sort())
|
||||
}
|
||||
return left === right
|
||||
}
|
||||
|
||||
export function supportsCaClass(runtime: { supported_classes?: CaClass[]; supportedClasses?: CaClass[] }, caClass: CaClass | undefined) {
|
||||
if (!caClass) return false
|
||||
const supportedClasses = runtime.supported_classes ?? runtime.supportedClasses ?? []
|
||||
return supportedClasses.some(
|
||||
(supportedClass) =>
|
||||
supportedClass.dimensions === caClass.dimensions &&
|
||||
statesEqual(supportedClass.states, caClass.states) &&
|
||||
neighborhoodsCompatible(supportedClass, caClass)
|
||||
)
|
||||
}
|
||||
|
||||
function neighborhoodsCompatible(supportedClass: CaClass, caClass: CaClass) {
|
||||
if (!supportedClass.neighborhoodId || !caClass.neighborhoodId) return true
|
||||
return supportedClass.neighborhoodId === caClass.neighborhoodId
|
||||
}
|
||||
|
||||
export function getCaClassFromParams(params: JsonObject | undefined): CaClass | undefined {
|
||||
const caClass = params?.caClass
|
||||
if (!isPlainObject(caClass)) return undefined
|
||||
|
||||
const dimensions = typeof caClass.dimensions === 'number' ? caClass.dimensions : undefined
|
||||
const states = caClass.states
|
||||
|
||||
if (!dimensions || (typeof states !== 'number' && !Array.isArray(states))) return undefined
|
||||
return {
|
||||
neighborhoodId: typeof caClass.neighborhoodId === 'string' ? caClass.neighborhoodId : undefined,
|
||||
dimensions,
|
||||
states
|
||||
}
|
||||
}
|
||||
|
||||
export function numericGridSize(value: unknown, dimensions: number, fallbackSize = DEFAULT_GRID_SIZE) {
|
||||
const grid = isPlainObject(value) ? value : {}
|
||||
const size = Array.isArray(grid.size) ? grid.size : []
|
||||
const x = typeof size[0] === 'number' ? size[0] : fallbackSize
|
||||
const y = dimensions >= 2 && typeof size[1] === 'number' ? size[1] : 1
|
||||
const z = dimensions >= 3 && typeof size[2] === 'number' ? size[2] : 1
|
||||
return [x, y, z] as const
|
||||
}
|
||||
|
||||
export function seededRandom(seed: string) {
|
||||
let state = 2166136261
|
||||
for (let index = 0; index < seed.length; index += 1) {
|
||||
state ^= seed.charCodeAt(index)
|
||||
state = Math.imul(state, 16777619)
|
||||
}
|
||||
|
||||
return () => {
|
||||
state += 0x6d2b79f5
|
||||
let t = state
|
||||
t = Math.imul(t ^ (t >>> 15), t | 1)
|
||||
t ^= t + Math.imul(t ^ (t >>> 7), t | 61)
|
||||
return ((t ^ (t >>> 14)) >>> 0) / 4294967296
|
||||
}
|
||||
}
|
||||
|
||||
export function isBoundaryCondition(value: unknown): value is BoundaryCondition {
|
||||
return value === 'wrap' || value === 'mirror' || value === 'fixed'
|
||||
}
|
||||
|
||||
export function getBoundaryCondition(params: SceneParams | undefined): BoundaryCondition {
|
||||
return resolveBoundaryCondition(params?.simulation?.grid)
|
||||
}
|
||||
|
||||
export function resolveBoundaryCondition(grid: JsonObject | undefined): BoundaryCondition {
|
||||
if (isBoundaryCondition(grid?.boundary)) return grid.boundary
|
||||
if (grid?.wrap === false) return 'fixed'
|
||||
return DEFAULT_BOUNDARY_CONDITION
|
||||
}
|
||||
|
||||
export function wrapIndex(index: number, size: number) {
|
||||
return ((index % size) + size) % size
|
||||
}
|
||||
|
||||
export function mirrorIndex(index: number, size: number) {
|
||||
if (size <= 1) return 0
|
||||
const period = (size - 1) * 2
|
||||
const wrapped = wrapIndex(index, period)
|
||||
return wrapped <= size - 1 ? wrapped : period - wrapped
|
||||
}
|
||||
|
||||
export function resolveBoundaryIndex(index: number, size: number, boundary: BoundaryCondition) {
|
||||
if (size <= 0) return null
|
||||
if (boundary === 'wrap') return wrapIndex(index, size)
|
||||
if (boundary === 'mirror') return mirrorIndex(index, size)
|
||||
return index >= 0 && index < size ? index : null
|
||||
}
|
||||
|
||||
export function cloneCells(cells: Cells): Cells {
|
||||
return cells.map((row) => [...row])
|
||||
}
|
||||
|
||||
export function assertCellsShape(cells: Cells, label = 'cells') {
|
||||
const width = cells[0]?.length ?? 0
|
||||
for (const row of cells) {
|
||||
if (row.length !== width) throw new Error(`${label} must be rectangular`)
|
||||
for (const value of row) {
|
||||
if (typeof value !== 'boolean') throw new Error(`${label} must contain boolean cell states`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function assertSameCellsShape(reference: Cells, next: Cells, label = 'engine output') {
|
||||
assertCellsShape(reference, 'input cells')
|
||||
assertCellsShape(next, label)
|
||||
|
||||
if (next.length !== reference.length || (next[0]?.length ?? 0) !== (reference[0]?.length ?? 0)) {
|
||||
throw new Error(`${label} must preserve the input grid dimensions`)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user