Quantcast
Channel: Active questions tagged conways-game-of-life - Stack Overflow
Viewing all articles
Browse latest Browse all 55

Why isn't this Conway Game of Life Cellular Automaton in TypeScript evolving after ~5 generations? [closed]

$
0
0

I have a 2D cellular automaton open source project here, where the automaton is basically this:

export interface CA {  i: Uint8ClampedArray;  load: Uint8ClampedArray;  max: number;  move: Array<RuleType> | Array<(n: Uint8ClampedArray) => number>;  n: Uint8ClampedArray;  save: Uint8ClampedArray;  size: number;}export type RuleType = {  make: number;  test: Array<(r: number) => boolean>;};type CA2DPropsType = {  h: number;  max: number;  move: Array<RuleType> | Array<(n: Uint8ClampedArray) => number>;  w: number;};export class CA2D implements CA {  load: Uint8ClampedArray;  save: Uint8ClampedArray;  i: Uint8ClampedArray;  n: Uint8ClampedArray;  w: number;  h: number;  size: number;  // 0 means it doesn't care  move: Array<RuleType> | Array<(n: Uint8ClampedArray) => number>;  max: number;  constructor({ w, h, move, max }: CA2DPropsType) {    this.w = w;    this.h = h;    this.max = max;    this.size = w * h;    this.i = new Uint8ClampedArray(9);    this.n = new Uint8ClampedArray(9);    this.load = new Uint8ClampedArray(this.size);    this.save = new Uint8ClampedArray(this.size);    this.move = move;  }  update() {    update2d(this);  }  seed() {    seed(this);  }}function loadNeighborhood2d(ca: CA2D, x: number, y: number) {  let x1;  let x2;  let x3;  if (x === 0) {    x1 = ca.w - 1;    x2 = x;    x3 = x + 1;  } else if (x === ca.w - 1) {    x1 = x - 1;    x2 = x;    x3 = 0;  } else {    x1 = x - 1;    x2 = x;    x3 = x + 1;  }  let y1;  let y2;  let y3;  if (y === 0) {    y1 = ca.h - 1;    y2 = y;    y3 = y + 1;  } else if (y === ca.h - 1) {    y1 = y - 1;    y2 = y;    y3 = 0;  } else {    y1 = y - 1;    y2 = y;    y3 = y + 1;  }  let i00 = y1 * ca.h + x1;  let i01 = y1 * ca.h + x2;  let i02 = y1 * ca.h + x3;  let i10 = y2 * ca.h + x1;  let i11 = y2 * ca.h + x2;  let i12 = y2 * ca.h + x3;  let i20 = y3 * ca.h + x1;  let i21 = y3 * ca.h + x2;  let i22 = y3 * ca.h + x3;  // indexes  ca.i[0] = i00; // upper left  ca.i[1] = i01;  ca.i[2] = i02;  ca.i[3] = i10;  ca.i[4] = i11; // middle  ca.i[5] = i12;  ca.i[6] = i20;  ca.i[7] = i21;  ca.i[8] = i22; // lower right  // neighborhoods  ca.n[0] = ca.save[i00]; // upper left  ca.n[1] = ca.save[i01];  ca.n[2] = ca.save[i02];  ca.n[3] = ca.save[i10];  ca.n[4] = ca.save[i11]; // middle  ca.n[5] = ca.save[i12];  ca.n[6] = ca.save[i20];  ca.n[7] = ca.save[i21];  ca.n[8] = ca.save[i22]; // lower right}export function update2d(ca: CA2D) {  let y = 0;  while (y < ca.h) {    let x = 0;    while (x < ca.w) {      loadNeighborhood2d(ca, x, y);      loadUpdate(y * ca.h + x, ca);      x++;    }    y++;  }  saveUpdates(ca);}export function loadUpdate(p: number, ca: CA): void {  let k = 0;  ruleLoop: while (k < ca.move.length) {    let rule = ca.move[k++];    if (typeof rule === "function") {      const make = rule(ca.n);      if (make >= 0) {        ca.load[p] = make;        break ruleLoop;      }    } else {      let i = 0;      const { test, make } = rule;      while (i < ca.n.length) {        const v = ca.n[i];        const t = test[i++];        if (!t(v)) {          continue ruleLoop;        }      }      ca.load[p] = make;      break ruleLoop;    }  }}export function randomIntBetween(min: number, max: number) {  // min and max included  return Math.floor(Math.random() * (max - min + 1) + min);}export function saveUpdates(ca: CA) {  let i = 0;  while (i < ca.load.length) {    ca.save[i] = ca.load[i];    i++;  }}export function seed(ca: CA) {  let i = 0;  while (i < ca.save.length) {    const rand = randomIntBetween(0, ca.max);    ca.save[i++] = rand;  }}

I am generating a Gif from it in the terminal like this:

import { any, CA2D, CA2DRenderer, eq, wait } from './src/index.js'import CanvasGifEncoder from '@pencil.js/canvas-gif-encoder'import GIFEncoder from 'gif-encoder-2'import fs from 'fs'import { createCanvas } from 'canvas'import { writeFile } from 'fs'import _ from 'lodash'const COLOR = {  black: 'rgba(40, 40, 40, 0)',  blue: 'rgba(56, 201, 247)',  green: 'hsl(165, 92%, 44%)',  greenLight: 'hsl(165, 92%, 79%)',  purple: 'rgba(121, 85, 243, 0.8)',  purpleLight: 'hsl(254, 87%, 70%)',  red: 'rgba(238, 56, 96)',  white: 'rgba(255, 255, 255)',  white2: 'rgba(244, 244, 244)',  white3: 'rgba(222, 222, 222)',  yellow: 'rgba(246, 223, 104)',}export const caProps = {  h: 60,  max: 1,  move: [    (n: Uint8ClampedArray) => {      const sum = _.sum(n)      const isLive = n[4] === 1      if (isLive && sum >= 3 && sum <= 4) {        return 1      } else {        return -1      }    },    (n: Uint8ClampedArray) => {      const sum = _.sum(n)      const isDead = n[4] === 0      if (isDead && sum === 3) {        return 0      } else {        return -1      }    },    (n: Uint8ClampedArray) => {      const isLive = n[4] === 1      return isLive ? 0 : -1    },  ],  w: 60,}const ca = new CA2D(caProps)ca.seed()// const saveByteArray = (function () {//   var a = document.createElement('a')//   document.body.appendChild(a)//   a.style.display = 'none'//   return function (data: Array<BlobPart>, name: string) {//     var blob = new Blob(data, { type: 'octet/stream' }),//       url = window.URL.createObjectURL(blob)//     a.href = url//     a.download = name//     a.click()//     window.URL.revokeObjectURL(url)//   }// })()start()async function start() {  const canvas = createCanvas(500, 500)  const renderer = new CA2DRenderer(canvas)  renderer.setDimensions({    gap: 1,    width: 500,    rowSize: ca.h,    columnSize: ca.w,  })  const encoder = new GIFEncoder(500, 500)  encoder.setDelay(100)  encoder.start()  // document.body.appendChild(renderer.canvas)  const colors = [COLOR.white2, COLOR.green, COLOR.purple, COLOR.red]  renderer.draw(ca, colors)  if (renderer.context) encoder.addFrame(renderer.context)  let i = 0  while (i < 5) {    console.log(i)    await wait(20)    ca.update()    renderer.draw(ca, colors)    if (renderer.context) encoder.addFrame(renderer.context)    i++  }  encoder.finish()  const buffer = encoder.out.getData()  fs.writeFileSync('example.gif', buffer)  // saveByteArray([buffer], 'example.gif')}

You can run the project like this:

git clone git@github.com:lancejpollard/ca.js.gitnpm installnpm run testnode dist.test/test

Notice the rules in the second code snippet:

[  (n: Uint8ClampedArray) => {    const sum = _.sum(n)    const isLive = n[4] === 1    if (isLive && sum >= 3 && sum <= 4) {      return 1    } else {      return -1    }  },  (n: Uint8ClampedArray) => {    const sum = _.sum(n)    const isDead = n[4] === 0    if (isDead && sum === 3) {      return 0    } else {      return -1    }  },  (n: Uint8ClampedArray) => {    const isLive = n[4] === 1    return isLive ? 0 : -1  },]

It gets a neighborhood array (including self), and then implements the 3 rules on the Wiki page:

  1. Any live cell with two or three live neighbours survives.
  2. Any dead cell with three live neighbours becomes a live cell.
  3. All other live cells die in the next generation. Similarly, all other dead cells stay dead.

I am getting it stabilizing after 5 generations though, what did I do wrong?

enter image description here

What do I need to change in the rules or the CA2D implementation to get it working? I don't think the CA2D implementation needs to change, I think I got that right, but maybe I misinterpreted the rules?


Viewing all articles
Browse latest Browse all 55

Latest Images

Trending Articles



Latest Images

<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>