Skip to content

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

bitgrid(width,height,layers)

  • width height layers of single bit pixels
  • 32 bit data words contained in single Uint32Array
  • drawShape stepConwayLife cellular automata

methods

  • getPixel(x,y,layer)
  • setPixel(x,y,layer,state)
  • cool(falloff)
  • heat(layer,value)
  • drawMask(strings,maskChar,x,y,layer)
  • rect(x,y,width,height,layer=0)
  • drawGrid(skipx=20,skipy=10,layer=0)
  • writePixels(pixels,x,y,layer)
  • stepConwayLife(readLayer,writeLayer)
  • copyLayer(readLayer,writeLayer)
  • countNeighbors(x,y,layer)

Repository

Cellular Automata and friends are welcome to visit conway life on bitgrid

annotated source

bitgrid

// bitgrid.js
// (c) 2026 nitrlogic
// All rights reserved

//  - width height layers of single bit pixels
//  - 32 bit data words contained in single Uint32Array
//	- drawShape stepConwayLife cellular automata functions incoming

class BitGrid {

	constructor(width,height,layers) {
		this.width = width;
		this.height = height;
		this.layers = layers;
		this.span=(width+31)>>5;
		this.data=new Uint32Array(this.span*height*layers);
		this.heatmap=new Uint16Array(width*height);
	}

	// bit pixel

	getPixel(x,y,layer){
		// x,y toroidal wrap around getter
		x = (x + this.width) % this.width;
		y = (y + this.height) % this.height;
		const offset = layer*this.height*this.span;
		const wordIndex = y*this.span+(x>>5);
		const bitIndex = x&31;
		const word=this.data[offset+wordIndex];
		return (word&(1<<bitIndex))!=0;
	}

	setPixel(x,y,layer,state){
		const offset=layer*this.height*this.span+y*this.span+(x>>5);
		const mask=1<<(x&31);
		let word=this.data[offset];
		if(state){
			word|=mask;
		}else{
			word&=~mask;
		}
		this.data[offset]=word
	}

	// heatmap methods

	cool(falloff){
		let index=0;
		for(let y=0;y<this.height;y++){
			for(let x=0;x<this.width;x++){
				this.heatmap[index++]*=falloff;
			}
		}
	}

	heat(layer,value){
		let offset=layer*this.height*this.span;
		let index=0;
		let word=0;
		for(let y=0;y<this.height;y++){
			for(let x=0;x<this.width;x++){
				if((x&31)==0){
					word=this.data[offset++];
				}
				const mask=1<<(x&31);
				if(word&mask) this.heatmap[index]+=value;
				index++;
			}
		}
	}

	drawMask(strings,maskChar,x,y,layer){
		for(const text of strings){
			for(let i=0;i<text.length;i++){
				const char=text[i];
				const state=(char==maskChar);//(char=="O");
				this.setPixel(x+i,y,layer,state);
			}
			y++;
		}
	}

	rect(x,y,width,height,layer=0){
		const offset=layer*this.height*this.span;
		for (let row = y; row < y + height; row++) {
			for (let col = x; col < x + width; col++) {
				const wordIndex = row * this.span + (col >> 5);
				const bitIndex = col & 31;
				this.data[offset+wordIndex] |= (1 << bitIndex);
			}
		}    
	}

	drawGrid(skipx=20,skipy=10,layer=0){
		let w=this.width;
		let h=this.height;
		for(let x=0;x<w;x+=skipx){
			this.rect(x,2,1,h-2,layer);
		}
		this.rect(w-3,3,2,h-4,layer);
		for(let y=0;y<h;y+=skipy){
			this.rect(0,y,w,1,layer);
		}
		this.rect(3,h-3,w-4,2,layer);
	}

	writePixels(pixels,x,y,layer){
		let offset=layer*this.height*this.span+y*this.span+(x>>5);
		let word=this.data[offset];
		for(let i=0;i<pixels.length;i++){
			const mask=1<<(x&31);
			const state=pixels[i];
			if(state){
				word|=mask;
			}else{
				word&=~mask;
			}
			x++;
			if(i<pixels.length-1 && ((x&31)==0)){
				this.data[offset++]=word;
				word=this.data[offset];
			}
		}
		this.data[offset]=word
	}

	stepConwayLife(readLayer, writeLayer) {
		let entropy=0;
		const w = this.width;
		const h = this.height;
		const pixels = new Array(w);//.fill(false);
		for (let y = 0; y < h; y++) {
			for (let x = 0; x < w; x++) {
				const alive = this.getPixel(x, y, readLayer);
				const neighbors = this.countNeighbors(x, y, readLayer);
				const next = (alive && (neighbors === 2 || neighbors === 3)) || (!alive && neighbors === 3);
				if(next!=alive) entropy++;
				pixels[x]=next;

			}
			this.writePixels(pixels, 0, y, writeLayer);
		}
		return entropy;
	}

	copyLayer(readLayer, writeLayer) {
		const wordsPerLayer = this.height * this.span;
		const readOffset = readLayer * wordsPerLayer;
		const writeOffset = writeLayer * wordsPerLayer;
		this.data.copyWithin(writeOffset, readOffset, readOffset + wordsPerLayer);
	}

	countNeighbors(x, y, layer) {
		let count=0;
		if (this.getPixel(x-1, y-1, layer)) count++;
		if (this.getPixel(x, y-1, layer)) count++;
		if (this.getPixel(x+1, y-1, layer)) count++;
		if (this.getPixel(x-1, y, layer)) count++;
		if (this.getPixel(x+1, y, layer)) count++;
		if (this.getPixel(x-1, y+1, layer)) count++;
		if (this.getPixel(x, y+1, layer)) count++;
		if (this.getPixel(x+1, y+1, layer)) count++;
		return count;
	}
};

gridlife

// gridlife.js 
// (c) 2026 nitrlogic
// All rights reserved

// requestAnimationFrame

"use strict"

const gridTitle="gridlife 0.2.3";
const help="cursor to pan , 0-9 speed";
function echo(...args){
	const lines=[];
	for(const arg of args){
		const line=arg?.toString();
		lines.push(line);
	}
	const text=lines.join(" ");
	console.log("[GRIDLIFE]",text);
}

let dotBlocks=["⚫","🟠","🟡","🟢","🔴","🔵","🟣","🟤","🟧","🟨","🟩","🟥","🟦","🟪","🟫","🧡","💛","💚","💙","💜","🤎"];
const dotBlockWide=2;

const friendEmoji="🐨🐼🐸🐰🐭🐯🐱🐶🐵🐥🐷🦧🐺🦊🦝🦁🦉";
const friends=[...friendEmoji];

let displayDirty=true;
let gridTick=0;

function setSpeed(digit){
	let speed=(digit);
	gridTick=5*(speed*speed);
}

let gridWidth=22*8*4;
let gridHeight=23*8;

let vidWidth=72*2;
let vidHeight=22;

// todo - clear display daggs

function resizeTerminal(){
	const w=terminal.clientWidth;
	const h=terminal.clientHeight;
	let vw=((w/8)|0)-12;	//10
	let vh=((h/15)|0)-2;
	if((vidWidth!=vw)||(vidHeight!=vh)){
		vidWidth=vw;
		vidHeight=vh;
		displayDirty=true;
	}
}

const UPDOWN=0;
const LEFTRIGHT=1;

const pump=[0,0];

let terminal;
let startTime = performance.now();
let previousMillis = startTime;
let totalElapsed = 0;
let gridTime = 0;

function updateGrid(requestAnimationFrame_timestamp) {	//was tick
	resizeTerminal();
	const millis=performance.now();
	const elapsed = millis - previousMillis;
	previousMillis=millis;
	totalElapsed+=elapsed;
	if(gridTime<totalElapsed){
		stepFrame();
		gridTime+=gridTick;
	}

	terminal.value=gridTitle+" "+help+"\n"+frameBlocks();

	const keys=
		(pressedKeys["ArrowUp"]?1:0)|
		(pressedKeys["ArrowDown"]?2:0)|
		(pressedKeys["ArrowRight"]?8:0)|
		(pressedKeys["ArrowLeft"]?4:0);
	updatePumps(keys,millis);
	updateCursor();
	requestAnimationFrame(updateGrid);
}

const pressedKeys={};

function onKeyUp(e){
	const key = e.key;
	const code = e.code;
	pressedKeys[key]=false;
}

function onKeyDown(e){
	const key = e.key;
	const code = e.code;
	pressedKeys[key]=true;
	if (key === "Enter") {
		e.preventDefault(); // Stop newline creation
		echo("Execute terminal command!");
	}
	else if (key === "Tab") {
		e.preventDefault(); // Stop focus from leaving the textarea
		echo("Trigger auto-complete!");
	}
	else if ((key>="0")&&(key<="9")) {
//		echo("Number speed",key|0);
		setSpeed(key|0);
	}
	else{
		echo("Key Down",{key,code});
	}
}

let mouseLock=false;
let mouseSensitivity=1.0;
function inGutter(e){
	const gutter=50;
	const x=e.clientX;
	const y=e.clientY;
	const mx=0;
	const my=0;
	if(x<gutter) mx-=mouseSensitivity;
	if(y<gutter) my-=mouseSensitivity;
	return false;
}
function onMouseDown(e){
	e.preventDefault();
	mouseLock=!mouseLock;
//	echo({e});
}
function onMouseUp(e){
//	echo({e});
}
function onMouseMove(e){
	e.preventDefault();
//	return inGutter(e);
	if(mouseLock){
		const x=e.movementX*mouseSensitivity;
		const y=e.movementY*mouseSensitivity;
		pump[1]+=x;
		pump[0]+=y;
//		echo({x,y});
	}
}


let previous=[];
let touchSensitivity=5.0;

function onTouchStart(e){
	e.preventDefault();
	previous=[];
}

function onTouchEnd(e){
	previous=[];
}

function onTouchMove(e){
	e.preventDefault();
	const t = e.touches[0];
	const tx=t.clientX;
	const ty=t.clientY;
	if(previous.length){
		const dx=tx-previous[0];
		const dy=ty-previous[1];
		pump[0]+=touchSensitivity*dy;
		pump[1]+=touchSensitivity*dx;
	}
	previous=[tx,ty];
}

function mirror(shape){
	let result=[];
	for(let line of shape){
		result.push(line.split("").reverse().join(""));
	}
	return result;
}

// four directions of shape using x y symmetry flips
function axis(glider){
	return [
		glider,
		glider.toReversed(),
		mirror(glider),
		mirror(glider).toReversed()
	];
}

const bitgrid = new BitGrid(gridWidth,gridHeight,4);

//bitgrid.rect(4,2,2,20);
//bitgrid.rect(gridWidth/4-10,4,8,20);

let blinker=conway.shapes.oscillators.blinker;
let beacon=conway.shapes.oscillators.beacon;
let pent=conway.shapes.methuselahs.rPentomino;

let pulsar=conway.shapes.oscillators.pulsar;

const glider=axis(conway.shapes.spaceships.glider);

function draw(shape,x,y,layer){
	bitgrid.drawMask(shape,"O",x,y,layer);
}

/*
let keys=Object.keys(conway.shapes.still);
let x=10;
for(let index of keys){
	const still=conway.shapes.still[index];
	draw(still,x,80,2);
	x+=12;
}
*/

let keys1=Object.keys(conway.shapes.oscillators);
let x1=10;
for(let index of keys1){
	const shape=conway.shapes.oscillators[index];
	draw(shape,x1,100,2);
	x1+=12;
}

//draw(beacon,10,10,2);
//draw(pent,100,14,2);

draw(glider[0],20,35,2);
draw(glider[1],20,30,2);
draw(glider[2],10,30,2);
draw(glider[3],10,20,2);

for(let i=0;i<12;i++){
	for(let j=0;j<5;j++){
		draw(pulsar,62+i*25,14+j*17,2);
	}
}

bitgrid.stepConwayLife(2,3);

let cursorX=0;
let cursorVX=0;
let cursorY=0;
let cursorVY=0;

function updateCursor(){
	cursorVX+=(pump[LEFTRIGHT])/400;
	cursorVY+=(pump[UPDOWN])/400;

	cursorX+=cursorVX;
	if(cursorX<0){
		cursorX=0;cursorVX=0;
	}
	let w=bitgrid.width-vidWidth;
	if(w<10) w=10;
	if(cursorX>=w){
		cursorX=w;
		cursorVX=0;
	}

	let h=bitgrid.height*4-vidHeight*4;
	cursorY+=cursorVY;
	if(cursorY<0){
		cursorY=0;
		cursorVY=0;
	}
	if(cursorY>h){
		cursorY=h;
		cursorVY=0;
	}

	cursorVX *= 0.9;
	cursorVY *= 0.9;
}

function resetGrid(){	
}

function gridHeatmap12(){
	const result=[];
	for(let i=0;i<4096;i++){
		const r=((i>>8)&15)*12;
		const g=((i>>4)&15)*12;
		const b=((i>>0)&15)*12;
		const line=""+r+";"+g+";"+b;
		result[i]=line;
	}
	return result;
}

function gridHeatmap(){
	const result=[];
	for(let i=0;i<512;i++){
		const r=((i>>6)&7)*18;
		const g=((i>>3)&7)*18;
		const b=((i>>0)&7)*18;
		const line=""+r+";"+g+";"+b;
		result[i]=line;
	}
	return result;
}

const heatRGBColors=gridHeatmap();
function heatRGB(heat){
	const n=heatRGBColors.length-1;
	let h=heat|0;
	if(h<0) h=0;
	if(h>n) h=n;
	return heatRGBColors[h];
}

function gridDotWindowLayer(grid,dots,wx,wy,ww,wh){
	const n=dots.length;
	const w=grid.width;
	const heat=grid.heatmap;
	const result=[];
	for(let y=0;y<wh;y++){
		let offset=(wy+y)*w+wx;
		let line=""
		for(let x=0;x<ww;x++){  
			const h=(heat[offset])|0;
			const index=h?(1+(h%(n-1))):0;
			line+=dots[index];
			offset++;
		}
		result.push(line);
	}
	return result;
}

function fadePumps(){
	const previous = [...pump];
	for(let index=0;index<pump.length;index++){
		let integral=pump[index]|0;
		let fade=(integral>>3);
		integral=(fade)?integral-fade:0;
		pump[index]=integral;
	}
	return previous;
}

function updatePumps(keys,millis){
	const m=(millis|0)*0.00004;
	if(keys&1) pump[UPDOWN]-=100*m;
	if(keys&2) pump[UPDOWN]+=100*m;
	if(keys&4) pump[LEFTRIGHT]-=72*m;
	if(keys&8) pump[LEFTRIGHT]+=72*m;
	fadePumps();
}

let mainMenu=false;//true;

function menuWall(blocks){
	let result=[];
	for(let row of blocks){
		result.push("*****"+row);
	}
	return result.join("\n")
}

function backSpace(){
//	mainMenu=!mainMenu;
	draw(glider[0],20,35,2);
}

function flattenChunks(chunks) {
	const count = chunks.reduce((acc, chunk) => acc + chunk.length, 0);
	const result = new Uint8Array(count);
	let offset = 0;
	for (const chunk of chunks) {
		result.set(chunk, offset);
		offset += chunk.length;
	}
	return result;
}

let layer=0;
let count=0;
let entropy=0;

function stepFrame(){
	count++;
	if(true){//((count++)&7)==5){
		layer=1-layer;
		entropy=bitgrid.stepConwayLife(2+layer,3-layer);
		bitgrid.heat(3-layer,25);
	}
	bitgrid.cool(0.95);
}
function frameBlocks(){
	let panx=cursorX>>1;
	let pany=cursorY>>2;
	let blocks=gridDotWindowLayer(bitgrid,dotBlocks,panx,pany,vidWidth/dotBlockWide,vidHeight);
	return blocks.join("\n");
}

function testFrame(){
	count++;
	if(true){//((count++)&7)==5){
		layer=1-layer;
		entropy=bitgrid.stepConwayLife(2+layer,3-layer);
		bitgrid.heat(3-layer,25);
	}
	bitgrid.cool(0.95);
	let panx=cursorX>>1;
	let pany=cursorY>>2;
	let blocks=gridDotWindowLayer(bitgrid,dotBlocks,panx,pany,vidWidth/dotBlockWide,vidHeight);
	return blocks.join("\n");
}

function initGridLife(){
	terminal=document.getElementById("gridlife");
//	terminal.value+="\n123\n"+friends[1]+"\n";
	echo("[GRIDLIFE] initGridLife");
	setSpeed(5);
	resizeTerminal();
	requestAnimationFrame(updateGrid);
	terminal.addEventListener("mousedown",onMouseDown);
	terminal.addEventListener("mouseup",onMouseUp);
	terminal.addEventListener("mousemove",onMouseMove);
	terminal.addEventListener("keydown",onKeyDown);
	terminal.addEventListener("keyup",onKeyUp);
	terminal.addEventListener("touchmove",onTouchMove);
	terminal.addEventListener("touchstart",onTouchStart);
	terminal.addEventListener("touchend",onTouchEnd);
}

About

width height layers of single bit pixels

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages