javascripthtmlreactjsconways-game-of-life

trying to change color of square in grid on html canvas but not working and no clue as to why, double checked a hundred times at this point


game of life project in react, trying to change cell color onClick but it just won't work; I literally have no clue why not. stack overflow seems to want me to be more specific but I literally have no clue what the issue could be; it is referencing my array coordinates just fine(console.log confirmed) but it tells me that theres a type error on the y axis grid[x][y] and I dont know whether the type error is referring to the value at that index or whether it is trying to use the index itself; ive just been staring at it forever and I cant figure it out.

My canvas component(havent begun to split it down just yet; not commented too well yet)

import { useRef, useEffect } from "react";

export default function GameOfLifeCanvas() {
  //! component wide variables
  //! create reference to canvas
  const canvasRef = useRef(null);
  const resolution = 20;
  const width = 400;
  const height = 400;
  const cellTypes = [
    {
      name: "standardCell",
      liveRepresentation: 1,
      color: "",
      behavior: {},
    },
  ];
  //! cellTypeCount is the number of cell types; alive, x, y, z = 4;; + 1 for dead cell
  const cellTypeCount = cellTypes.length + 1;

  //! run when component mounts
  useEffect(() => {
    const columns = width / resolution;
    const rows = height / resolution;
    const canvas = canvasRef.current;
    const context = canvas.getContext("2d");

    let grid = () => {
      //! start with empty array
      const grd = [];
      //! for every row
      for (let i = 0; i < rows; i++) {
        const row = [];
        //! for every column
        for (let ii = 0; ii < columns; ii++) {
          //! push 0 to row
          row.push(Math.floor(Math.random() * cellTypeCount));
        }
        //! push row to columns for every column
        grd.push(row);
      }
      return grd;
    };

    function mouseClick(e) {
      let mouseX, mouseY;

      if (e.offsetX) {
        mouseX = e.offsetX;
        mouseY = e.offsetY;
      } else if (e.layerX) {
        mouseX = e.layerX;
        mouseY = e.layerY;
      }
      let gridX = Math.floor(mouseX / resolution);
      let gridY = Math.floor(mouseY / resolution);
      console.log(gridX, gridY);

      let xy = grid[gridX][gridY];
      if (xy == 0) {
        grid[gridX][gridY] = 1;
        console.log("white");
      } else if (xy === 1) {
        grid[gridX][gridY] = 0;
        console.log("black");
      }

      render(grid);
    }

    canvas.addEventListener("mousedown", mouseClick, false);

    render(grid());
    function render(grid) {
      for (let col = 0; col < grid.length; col++) {
        for (let row = 0; row < grid[col].length; row++) {
          const cell = grid[col][row];

          context.beginPath();
          //   context.rect(
          //     col * resolution,
          //     row * resolution,
          //     resolution,
          //     resolution
          //   );
          //! truthy is black falsy is white
          context.fillStyle = cell ? "black" : "white";
          context.fillRect(
            col * resolution,
            row * resolution,
            resolution,
            resolution
          );
          context.stroke();
        }
      }
      console.log(grid);
    }
  }, []);
  return (
    <>
      <canvas ref={canvasRef} width={width} height={height}></canvas>
    </>
  );
}


Solution

  • In order to initialise the grid matrix to its initial state, you have created a function called grid. In the first render, this function is being called and its return value passed to the render function.

    However, any time user clicks on the canvas and the grid needs to be updated, you seem to be modifying the function instead of its return value as

    grid[gridX][gridY] = 1;
    

    Instead, you should be storing the matrix of the grid in a variable, and operating on it.

    In the code snippet below, I have placed your grid function inside a global create_empty_grid function. During the first render of the component, this function is called and the return value is stored in a variable. And on mousedown events, we modify the variable and re-render the canvas using it.

    import { useRef, useEffect } from "react";
    
    const resolution = 20;
    const width = 400;
    const height = 400;
    const cellTypes = [
      {
        name: "standardCell",
        liveRepresentation: 1,
        color: "",
        behavior: {},
      },
    ];
    
    const create_empty_grid = () => {
      const columns = width / resolution;
      const rows = height / resolution;
    
      //! cellTypeCount is the number of cell types; alive, x, y, z = 4;; + 1 for dead cell
      const cellTypeCount = cellTypes.length + 1;
    
      //! start with empty array
      const grd = [];
      //! for every row
      for (let i = 0; i < rows; i++) {
        const row = [];
        //! for every column
        for (let ii = 0; ii < columns; ii++) {
          //! push 0 to row
          row.push(Math.floor(Math.random() * cellTypeCount));
        }
        //! push row to columns for every column
        grd.push(row);
      }
      return grd;
    };
    
    export default function GameOfLifeCanvas() {
      //! component wide variables
      //! create reference to canvas
      const canvasRef = useRef(null);
    
      //! run when component mounts
      useEffect(() => {
        const canvas = canvasRef.current;
        const context = canvas.getContext("2d");
    
        let grid = create_empty_grid();
    
        function mouseClick(e) {
          let mouseX, mouseY;
    
          if (e.offsetX) {
            mouseX = e.offsetX;
            mouseY = e.offsetY;
          } else if (e.layerX) {
            mouseX = e.layerX;
            mouseY = e.layerY;
          }
          let gridX = Math.floor(mouseX / resolution);
          let gridY = Math.floor(mouseY / resolution);
          console.log(gridX, gridY);
    
          let xy = grid[gridX][gridY];
          if (xy == 0) {
            grid[gridX][gridY] = 1;
            console.log("white");
          } else if (xy === 1) {
            grid[gridX][gridY] = 0;
            console.log("black");
          }
    
          render(grid);
        }
    
        canvas.addEventListener("mousedown", mouseClick, false);
    
        render(grid);
        function render(grid) {
          for (let col = 0; col < grid.length; col++) {
            for (let row = 0; row < grid[col].length; row++) {
              const cell = grid[col][row];
    
              context.beginPath();
              //! truthy is black falsy is white
              context.fillStyle = cell ? "black" : "white";
              context.fillRect(
                col * resolution,
                row * resolution,
                resolution,
                resolution
              );
              context.stroke();
            }
          }
          console.log(grid);
        }
      }, []);
      return <canvas ref={canvasRef} width={width} height={height}></canvas>;
    }