after making 1-d automata.


james helped me figure out the idea of a ‘diff-matrix’ to capture changes, and then to update the main matrix.

/*
advanced 2-d automata; october 17th, 2026.
 
given a set of cells & their initial state, form a temporary change set & then update the cells. 
*/
 
//definitions:
//a cell is an object with properties. currently: state.
class Cell {
  constructor(s) {
    this.state = s;
  }
}
 
//given the number of rows & columns, make a collection of cells.
function make_space(_r, _c) {
  let _sp = [];
  for (let i = 0; i < _r; i++) {
    const row = [];
    for (let j = 0; j < _c; j++) {
      //row.push(new Cell());
      row.push(
        new Cell(
          i === Math.floor(_r / 2) && j === Math.floor(_c / 2) ? 255 : 0,
        ),
      );
    }
    _sp.push(row);
  }
  return _sp;
}
 
//for everything below, we assume the space is global.
 
//given a space & position of cell in space, return all its neighbors:
function get_neighbors(_r, _c) {
  //wrapping:
  const at = (i, j) => [((i % r) + r) % r, ((j % c) + c) % c];
 
  return [
    //up_left: at(_r - 1, _c - 1),
    at(_r - 1, _c), //up.
    //up_right: at(_r - 1, _c + 1),
    at(_r, _c - 1), //left.
    at(_r, _c + 1), //right.
    //down_left: at(_r + 1, _c - 1),
    at(_r + 1, _c), //down.
    //down_right: at(_r + 1, _c + 1),
  ];
}
 
//render the global space.
function render() {
  for (let i = 0; i < space.length; i++) {
    let line = "";
    for (let j = 0; j < space[0].length; j++) {
      line += space[i][j].state + " ";
    }
    console.log(line);
  }
  console.log("");
}
 
//given a space, return a matrix of the same shape filled with zeroes.
function make_diff(_sp) {
  return _sp.map((row) => row.map(() => 0));
}
 
function automaton() {
  let diff_matrix = make_diff(space);
 
  const displacement = 1;
 
  //for every cell in the matrix, look at its neighbors & get the change:
  for (let i = 0; i < r; i++) {
    for (let j = 0; j < c; j++) {
      let cell = space[i][j];
      let neighbors = get_neighbors(i, j);
 
      //a cell can't give away more than it has:
      let has = cell.state;
 
      for (let n = 0; n < neighbors.length; n++) {
        //can't give more:
        if (has <= 0) break;
 
        //for each neighbor, if we have more than the neighbor, give to the neighbor at displacement-rate. else don't do anything.
        let neighbor = space[neighbors[n][0]][neighbors[n][1]];
 
        if (cell.state > neighbor.state) {
          diff_matrix[i][j] -= displacement;
          diff_matrix[neighbors[n][0]][neighbors[n][1]] += displacement;
          has -= displacement;
        }
      }
    }
  }
 
  //once all of that is done, we update the space.
  for (let i = 0; i < r; i++) {
    for (let j = 0; j < c; j++) {
      space[i][j].state += diff_matrix[i][j];
    }
  }
}
 
//setup:
const r = 5;
const c = 5;
 
let space = make_space(r, c);
 
//show the starting state.
render();
 
//main loop:
let n = 50;
let counter = 0;
while (counter <= n) {
  automaton();
  render();
  counter++;
}
 

100x100 automaton behavior, when center cell has 100, and has to displace to all 8 neighbors:

more complex behavior on a bigger canvas:

same ruleset:

/*
advanced 2-d automata; october 17th, 2026.
 
given a set of cells & their initial state, form a temporary change set & then update the cells. 
*/
 
//definitions:
//a cell is an object with properties. currently: state.
class Cell {
  constructor(s) {
    this.state = s;
  }
}
 
//given the number of rows & columns, make a collection of cells.
function make_space(_r, _c) {
  let _sp = [];
  for (let i = 0; i < _r; i++) {
    const row = [];
    for (let j = 0; j < _c; j++) {
      let r = Math.random();
 
      if (r < 0.5) {
        row.push(new Cell(1000));
      } else {
        row.push(new Cell(0));
      }
 
      //row.push(
      //  new Cell(
      //    i === Math.floor(_r / 2) && j === Math.floor(_c / 2) ? 5000 : 0,
      //  ),
      //);
    }
    _sp.push(row);
  }
  return _sp;
}
 
//for everything below, we assume the space is global.
 
//given a space & position of cell in space, return all its neighbors:
function get_neighbors(_r, _c) {
  //wrapping:
  const at = (i, j) => [((i % r) + r) % r, ((j % c) + c) % c];
 
  return [
    at(_r - 1, _c - 1), //up-left.
    at(_r - 1, _c), //up.
    at(_r - 1, _c + 1), //up_right.
    at(_r, _c - 1), //left.
    at(_r, _c + 1), //right.
    at(_r + 1, _c - 1), //down_left:
    at(_r + 1, _c), //down.
    at(_r + 1, _c + 1), //down_right:
  ];
}
 
//render the global space.
function render() {
  for (let i = 0; i < space.length; i++) {
    let line = "";
    for (let j = 0; j < space[0].length; j++) {
      line += space[i][j].state + " ";
    }
    console.log(line);
  }
  console.log("");
}
 
//given a space, return a matrix of the same shape filled with zeroes.
function make_diff(_sp) {
  return _sp.map((row) => row.map(() => 0));
}
 
function automaton() {
  let diff_matrix = make_diff(space);
 
  const displacement = 2;
 
  //for every cell in the matrix, look at its neighbors & get the change:
  for (let i = 0; i < r; i++) {
    for (let j = 0; j < c; j++) {
      let cell = space[i][j];
      let neighbors = get_neighbors(i, j);
 
      //a cell can't give away more than it has:
      let has = cell.state;
 
      for (let n = 0; n < neighbors.length; n++) {
        //can't give more:
        if (has <= 0) break;
 
        //for each neighbor, if we have more than the neighbor, give to the neighbor at displacement-rate. else don't do anything.
        let neighbor = space[neighbors[n][0]][neighbors[n][1]];
 
        if (cell.state > neighbor.state) {
          const give = Math.min(displacement, has, cell.state - neighbor.state);
          diff_matrix[i][j] -= give;
          diff_matrix[neighbors[n][0]][neighbors[n][1]] += give;
          has -= give;
        }
      }
    }
  }
 
  //once all of that is done, we update the space.
  for (let i = 0; i < r; i++) {
    for (let j = 0; j < c; j++) {
      space[i][j].state += diff_matrix[i][j];
    }
  }
}
 
//setup:
const r = 100;
const c = 100;
 
let space = make_space(r, c);
 
if (typeof window === "undefined") {
  //show the starting state.
  render();
 
  //main loop:
  let n = 10;
  let counter = 0;
 
  while (counter <= n) {
    automaton();
    render();
    counter++;
  }
}
 

achieved 2-d automata in cpp.

#include <array>
#include <cmath>
#include <iostream>
using namespace std;
 
constexpr int r = 10;
constexpr int c = 10;
constexpr int max_run = 2;
 
class cell {
 public:
  int state;
  cell(int s = 0) : state(s) {}
};
 
struct pos {
  int x;
  int y;
};
 
cell space[r][c];
 
// change matrix; we only store integer values.
 
// helper to render:
void render() {
  // to render the space, we go through each item in the space.
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      cout << space[x][y].state << " ";
    }
    // after a row:
    cout << "\n";
  }
}
 
// given # of rows & columns, make a space.
void make_space() {
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      // int val = (x + y) % 2;
      int val = (x == floor(r / 2) && y == floor(c / 2)) ? 255 : 0;
      space[x][y].state = val;
    }
  }
}
 
// for updation:
array<pos, 8> get_neighbors(int x, int y) {
  int up = (x - 1 + r) % r;
  int down = (x + 1) % r;
  int left = (y - 1 + c) % c;
  int right = (y + 1) % c;
 
  return {
      pos{up, left},     // up-left.
      pos{up, y},        // up.
      pos{up, right},    // up-right.
      pos{x, left},      // left.
      pos{x, right},     // right.
      pos{down, left},   // down-left.
      pos{down, y},      // down.
      pos{down, right},  // down-right.
  };
}
 
void update_space() {
  int diff[r][c] = {};
  const int displacement = 2;
 
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      cell& self = space[x][y];
      array<pos, 8> neighbors = get_neighbors(x, y);
 
      int has = self.state;
 
      for (int n = 0; n < 8; n++) {
        if (has <= 0) break;
 
        pos n_p = neighbors[n];
        cell& neighbor = space[n_p.x][n_p.y];
 
        if (self.state > neighbor.state) {
          int give = min({displacement, has, self.state - neighbor.state});
          // arduino has its own min function; hence the change:
          //int give = min(displacement, min(has, self.state - neighbor.state));
          diff[x][y] -= give;
          diff[n_p.x][n_p.y] += give;
          has -= give;
        }
      }
    }
  }
 
  // apply the change set to the space.
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      space[x][y].state += diff[x][y];
    }
  }
}
 
// automaton function:
int main() {
  int counter = 0;
 
  // make space; show it once:
  make_space();
 
  cout << "generation: " << counter << "\n";
  render();
  cout << "\n";
  while (counter < max_run) {
    update_space();
    counter++;
 
    cout << "generation: " << counter << "\n";
    render();
    cout << "\n";
  }
 
  return 0;
}
 
 

made it work with multi-actuator build.


261023:

need to have a way to have a bounded automata vs an infinite plane. i should be able to specify it when i make the space.

first, added debug lines:

void update_space() {
  int diff[r][c] = {};
  const int displacement = 2;
 
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      cout << "cell: " << x << ", " << y << "\n";
 
      cell& self = space[x][y];
      array<pos, 8> neighbors = get_neighbors(x, y);
 
      int has = self.state;
 
      for (int n = 0; n < 8; n++) {
        //if (has <= 0) break;
 
        pos n_p = neighbors[n];
 
        cout << "neighbor n: " << n << "|" << "pos:" << n_p.x << ", " << n_p.y << "\n";
 
        // skip the ones off the plane:
        if (n_p.x < 0 || n_p.x >= r || n_p.y < 0 || n_p.y >= c) continue;
 
        cell& neighbor = space[n_p.x][n_p.y];
 
        if (self.state > neighbor.state) {
          int give = min({displacement, has, self.state - neighbor.state});
          // arduino has its own min function; hence the change:
          // int give = min(displacement, min(has, self.state -
          // neighbor.state));
          diff[x][y] -= give;
          diff[n_p.x][n_p.y] += give;
          has -= give;
        }
      }
    }
  }
 
  // apply the change set to the space.
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      space[x][y].state += diff[x][y];
    }
  }
}

finally reached a stable state automation machine; added parameters to control neighborhood type & whether it’s an infinite plane or not.

/*
2-d automata; advanced; with the option of being on a finite or infinite plane.
*/
 
#include <algorithm>
#include <array>
#include <cmath>
#include <iostream>
using namespace std;
 
constexpr int r = 10;
constexpr int c = 10;
constexpr int max_run = 50;
 
// neighbors stuff:
constexpr int neighborhood = 8;
constexpr bool infinite_plane = false;
 
class cell {
 public:
  int state;
  cell(int s = 0) : state(s) {}
};
 
struct pos {
  int x;
  int y;
};
 
cell space[r][c];
 
// change matrix; we only store integer values.
 
// helper to render:
void render() {
  // to render the space, we go through each item in the space.
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      cout << space[x][y].state << " ";
    }
    // after a row:
    cout << "\n";
  }
}
 
// given # of rows & columns, make a space. this function also defines the seed.
void make_space() {
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      //int val = 1;
       int val = (x == floor(r / 2) && y == floor(c / 2)) ? 50 : 0;
      space[x][y].state = val;
    }
  }
}
 
// neighbors; wrapping on an infinite plane, not wrapping on a finite one:
array<pos, neighborhood> get_neighbors(int x, int y) {
  int up = 0;
  int down = 0;
  int left = 0;
  int right = 0;
 
  if (infinite_plane == true) {
    // infinite plane:
    up = (x - 1 + r) % r;
    down = (x + 1) % r;
    left = (y - 1 + c) % c;
    right = (y + 1) % c;
  } else {
    // finite plane:
    up = x - 1;
    down = x + 1;
    left = y - 1;
    right = y + 1;
  }
 
  if (neighborhood == 4) {
    // return von-neumann neighborhood.
    return {
        pos{up, y},     // up.
        pos{x, left},   // left.
        pos{x, right},  // right.
        pos{down, y},   // down.
    };
  } else {
    // return moore neighborhood.
    return {
        pos{up, left},     // up-left.
        pos{up, y},        // up.
        pos{up, right},    // up-right.
        pos{x, left},      // left.
        pos{x, right},     // right.
        pos{down, left},   // down-left.
        pos{down, y},      // down.
        pos{down, right},  // down-right.
    };
  }
}
 
// mutate the global space.
void update_space() {
  int diff[r][c] = {};  // temporary difference-matrix; starts at zero.
 
  const int displacement = 5;  // rate at which things are disseminated.
  const int capacity = 10;      // a cell can store a certain amount of energy.
 
  // go over each cell:
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      cell& self = space[x][y];
 
      //cout << "cell(" << x << ", " << y << ")" << "\n";
 
      int has = self.state;
 
      if (has > capacity) {
        // we have stuff to give:
 
        // for each neighbor, go over them & decide displacement in diff_matrix.
        array<pos, neighborhood> neighbors = get_neighbors(x, y);
 
        for (int n = 0; n < neighborhood; n++) {
          pos n_p = neighbors[n];
 
          //cout << "neighbor being checked: " << n_p.x << ", " << n_p.y;
 
          // finite plane logic: skip neighbors off the edge.
          if (!infinite_plane &&
              (n_p.x < 0 || n_p.x >= r || n_p.y < 0 || n_p.y >= c)) {
            //cout << " -> " << "out of bounds" << "\n";
            continue;
          }
 
          // in bounds:
          cell& neighbor = space[n_p.x][n_p.y];
 
          //cout << " -> " << "valid neighbor: " << n_p.x << ", " << n_p.y << "\n";
 
          // cout << "neighbor(" << x << ", " << y << ")";
 
          if (self.state > neighbor.state) {
            // i have stuff to give:
            // arduino has its own two-value min; hence the nesting.
            int give = min(displacement, min(has, self.state - neighbor.state));
            diff[x][y] -= give;
            diff[n_p.x][n_p.y] += give;
            has -= give;
          }
        }
      }else{
        //have enough to keep.
        continue;
      }
    }
  }
 
  // apply the change, and mutate the space:
  for (int x = 0; x < r; x++) {
    for (int y = 0; y < c; y++) {
      space[x][y].state += diff[x][y];
    }
  }
}
 
// automaton function:
int main() {
  int counter = 0;
 
  // make space; show it once:
  make_space();
 
  // for debug; single run:
  //update_space();
 
   cout << "generation: " << counter << "\n";
   render();
   cout << "\n";
   while (counter < max_run) {
     update_space();
     counter++;
 
    cout << "generation: " << counter << "\n";
    render();
    cout << "\n";
  }
 
  return 0;
}

expressed via multi-actuator build.