Memory-Enhanced Cellular Automata
Memory-Enhanced Cellular Automata
A probabilistic cellular automata implementation where each cell maintains a 3-state memory of its recent history. Cells use their own history plus neighbor influence to determine state transitions.
Key Features
- Memory Decay: Each cell remembers its last 3 states with exponential weight decay
- Energy System: Cells have energy levels that influence activation probability
- Neighbor Influence: History-weighted influence from surrounding cells
- Emergent Patterns: Evolves from sparse random initialization toward complex structures
Implementation
// Probabilistic cellular automata with memory decay
// Each cell remembers its last 3 states and uses them to influence neighbors
interface Cell {
current: boolean;
history: boolean[];
energy: number;
}
class MemoryAutomata {
private grid: Cell[][];
private width: number;
private height: number;
private generation: number = 0;
constructor(width: number, height: number) {
this.width = width;
this.height = height;
this.grid = this.initializeGrid();
}
private initializeGrid(): Cell[][] {
const grid: Cell[][] = [];
for (let y = 0; y < this.height; y++) {
grid[y] = [];
for (let x = 0; x < this.width; x++) {
grid[y][x] = {
current: Math.random() > 0.7,
history: [false, false, false],
energy: Math.random()
};
}
}
return grid;
}
private getNeighborInfluence(x: number, y: number): number {
let influence = 0;
let count = 0;
for (let dy = -1; dy <= 1; dy++) {
for (let dx = -1; dx <= 1; dx++) {
if (dx === 0 && dy === 0) continue;
const nx = (x + dx + this.width) % this.width;
const ny = (y + dy + this.height) % this.height;
const neighbor = this.grid[ny][nx];
// Weight recent history more heavily
const historyWeight = neighbor.history.reduce((sum, state, i) =>
sum + (state ? 1 : 0) * (0.8 ** i), 0);
influence += neighbor.current ? 1 : 0;
influence += historyWeight * 0.3;
influence += neighbor.energy * 0.1;
count++;
}
}
return count > 0 ? influence / count : 0;
}
step(): void {
const newGrid: Cell[][] = this.grid.map(row =>
row.map(cell => ({
current: cell.current,
history: [...cell.history],
energy: cell.energy
}))
);
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const cell = this.grid[y][x];
const influence = this.getNeighborInfluence(x, y);
// Update history (shift left, add current to front)
newGrid[y][x].history.unshift(cell.current);
newGrid[y][x].history = newGrid[y][x].history.slice(0, 3);
// Probabilistic rule based on influence and energy
const activationProbability = (influence * 0.6) + (cell.energy * 0.4);
const threshold = 0.3 + Math.sin(this.generation * 0.1) * 0.2;
newGrid[y][x].current = activationProbability > threshold;
// Energy decay and renewal
newGrid[y][x].energy *= 0.95;
if (newGrid[y][x].current) {
newGrid[y][x].energy = Math.min(1, newGrid[y][x].energy + 0.3);
}
}
}
this.grid = newGrid;
this.generation++;
}
render(): string {
let output = `Generation ${this.generation}\n`;
output += '┌' + '─'.repeat(this.width) + '┐\n';
for (let y = 0; y < this.height; y++) {
output += '│';
for (let x = 0; x < this.width; x++) {
const cell = this.grid[y][x];
if (cell.current) {
output += '█';
} else if (cell.energy > 0.5) {
output += '▓';
} else if (cell.history[0]) {
output += '░';
} else {
output += ' ';
}
}
output += '│\n';
}
output += '└' + '─'.repeat(this.width) + '┘\n';
return output;
}
getStats(): { alive: number; energy: number; memory: number } {
let alive = 0;
let totalEnergy = 0;
let memoryStates = 0;
for (let y = 0; y < this.height; y++) {
for (let x = 0; x < this.width; x++) {
const cell = this.grid[y][x];
if (cell.current) alive++;
totalEnergy += cell.energy;
memoryStates += cell.history.filter(Boolean).length;
}
}
return {
alive,
energy: totalEnergy / (this.width * this.height),
memory: memoryStates / (this.width * this.height * 3)
};
}
}
// Demo runner
async function runDemo() {
console.log("Memory-Enhanced Cellular Automata");
console.log("=================================");
const automata = new MemoryAutomata(20, 12);
for (let i = 0; i < 10; i++) {
console.clear();
console.log(automata.render());
const stats = automata.getStats();
console.log(`Alive: ${stats.alive} | Avg Energy: ${stats.energy.toFixed(2)} | Memory Density: ${stats.memory.toFixed(2)}`);
console.log("Press Ctrl+C to stop");
automata.step();
// Pause for visualization
await new Promise(resolve => setTimeout(resolve, 500));
}
}
// Export for use
export { MemoryAutomata, runDemo };
// Run if called directly
if (import.meta.main) {
runDemo().catch(console.error);
}
Observed Behavior
Starting from sparse random initialization (30% activation probability), the system exhibits interesting emergent properties:
- Rapid Filling: Memory-influenced activation leads to quick propagation
- Energy Saturation: Cells maintain high energy when consistently active
- Memory Density Growth: Historical states accumulate, creating stable patterns
- Steady State: Eventually reaches full activation with maximum memory
The interplay between memory, energy, and neighbor influence creates richer dynamics than traditional Conway's Game of Life rules.