React Conway's Game of Life

April 11, 2021

Alt Text


Table of Content

  • What is Conway’s Game of Life
  • Rules of the Game.
  • Coding out the simulation using React
  • CodeSandBox Playground

What is Conway’s Game of Life

The Game of Life, also known simply as Life, is a cellular automaton devised by the British mathematician John Horton Conway in 1970. It is a zero-player game, meaning that its evolution is determined by its initial state, requiring no further input. One interacts with the Game of Life by creating an initial configuration and observing how it evolves.

View full details for the Game here

Rule of the Game

  • Any live cell with fewer than two live neighbors dies, as if by underpopulation.
  • Any live cell with two or three live neighbors lives on to the next generation.
  • Any live cell with more than three live neighbors dies, as if by overpopulation.
  • Any dead cell with exactly three live neighbors becomes a live cell, as if by reproduction.

Coding out simulator using React

Generating Empty Grid (our first task)

  • The total number of Row and columns for the grid is to be set initially.

Note: Totally depends as per the requirement. The best is to use 30/30.

const numRows = 30;
const numCols = 30;
const generateEmptyGrid = () => {
    const rows = [];
    for (let i = 0; i < numRows; i++) {
      rows.push(Array.from(Array(numCols), () => 0));
    }
    return rows;
  };

Explanation:

  • we used Array rows [] length of numRows: 30
  • For every row index we are pushing numCols: 30 columns.
  • This function will be later used as a clear function to clear, to set the grid to empty.
[   {1, 2, 3, ...., 30},
    {1, 2, 3, ...., 30},
    .
    .
    30th row    ]

Putting Random stuff on the grid

Requirement: Button and funtion

  • Creating Function generateRandomStuff()
 const generateRandomStuff = () => {
    const rows = [];
    for (let i = 0; i < numRows; i++) {
      rows.push(
        Array.from(Array(numCols), 
       () => (Math.random() > 0.5 ? 1 : 0))
      );
    }
    return rows;
  };
  • In this function, we are actually randomizing the column’s number and choosing random columns in each row and if the Math.Random() value for the columns is greater than 0.5 we put that 1: black else 0:cleared;

State management for setting Random Stuff and clearing the stuff from the grid

const [grid, setGrid] = useState(() => {
    return generateEmptyGrid();
  });
  • using use State: we can do the state management for the grid.
  • Initially: The grid is set to empty.

Generate Random stuff: TO do this we will call for the function

const generateRandomStuff = () =>

and set it in grid : setGrid(generateRandomStuff())

 <button
    onClick={() => {
       setGrid(generateRandomStuff());
      }}>
      Random Stuff
</button>

Alt Text

Generate Empty Grid: To do this we will call for the function

const generateEmptyGrid = () =>

and set it in Empty the grid : setGrid(generateEmptyGrid())

 <button
    onClick={() => {
      setGrid(generateEmptyGrid());
       }}>
    Clear
</button>

Alt Text

Running Simulation (Logic) :)

  • For the simulation we need some preprocessing.
const redundant = [
  [0.1],
  [0, -1],
  [1, -1],
  [-1, 1],
  [1, 1],
  [-1, -1],
  [1, 0],
  [-1, 0]
];

an array is taken with all steps, where we can move

  • We can move in all eight directions in the grid.
 const [Simulation, setSimulation] = useState(false);

  const runningRef = useRef(Simulation);
  runningRef.current = Simulation;

  const runSimulation = useCallback(() => {
    if (!runningRef.current) {
      return;
    }
    setGrid((g) => {
      return produce(g, (gridCopy) => {
        for (let i = 0; i < numRows; i++) {
          for (let k = 0; k < numCols; k++) {
            let neighbors = 0;
            redundant.forEach(([x, y]) => {
              const newI = i + x;
              const newK = k + y;
              if (newI >= 0 && newK >= 0 && newI < numRows && newK < numCols) {
                neighbors += g[newI][newK];
              }
            });
            if (neighbors < 2 || neighbors > 3) {
              gridCopy[i][k] = 0;
            } else if (g[i][k] === 0 && neighbors === 3) {
              gridCopy[i][k] = 1;
            }
          }
        }
      });
    });
    setTimeout(runSimulation, 100);
  }, []);
  • we will make a state simulation and setStimulation which will be initially false. and will be triggered to true using the button.
  • const runSimulation = useCallback(() =>{}: here we will be using callback function.
  • Logic:

    • we will traverse the grid from index {0,0} to {numRows,numCols}
    • Take a counter for the neigbours.

What we exactly want is:

  1. if there is a cell in the grid which is set with exactly 2 or 3 neighbors in any of the direction.
  2. if there is a cell in the grid that is not set and has three set or live neighbors become set or live.
  3. All other cells that are set or live are now set to dead or unset, whereas all unset will remain unset.
 redundant.forEach(([x, y]) => {
              const newI = i + x;
              const newK = k + y;
              if (newI >= 0 && newK >= 0 && newI < numRows && newK < numCols) {
                neighbors += g[newI][newK];
              }
            });
  • we will move in 8 directions from redundant array
  • following the above rule we have written, three cases.

After completion of the simulation, we run the function once after the interval of time.

For this we use setTimeout(runSimulation, 100);

  • Button for the simulation.

    <button onClick={() => {
     setSimulation(!Simulation);
       if (!Simulation) {
           runningRef.current = true;
           runSimulation();
         }
       }} >
    {Simulation ? "Stop" : "start"} Simulation
    </button>

    Alt Text

Note: Using immer for mutating the state.

If you like the content. kindly let me know.

Happy Coding.

{% codesandbox silly-hill-3gtgh %}


© 2021, Utkarsh Yadav . All Rights Reserved