Search code examples
javascriptreactjsreact-hookscomponentsgame-development

Grid Game - Should I try and break this React component down?


I'm having a go at creating a top down grid game as a way to learn react and test out my newly learned skills.

I have created a version that has some basic mechanics; -click and dragging on cells within the grid to change their color. -a player represented by a colored grid cell whose movement is controlled using the directional keys -an automated bot represented by a colored grid cell that moves in random directions.

Now that my application is getting bigger I've noticed the code is getting harder to follow, before things get out of hand I want to break things down into smaller chunks but I'm encountering problems as many of the functions and useEffects require access to the same useState variables which makes putting them into separate js files impossible.

Key questions?

  • Should I be trying to use useContext for useState variables?
  • Would this even allow functions/useEffects kept in a separate .js utilities file to access said variables or is does it only work with components?
  • Do I even need to break the code up and move functions/effects to separate js files? or is this some concept that I've miss understood when really is actually about components.

All efforts to modularise my code have failed due to the spaghetti use of variables.

Any pointers or help would be appreciated, cheers

  function BotTech() {

  const numRows = 20;
  const numCols = 20;

  const gridStyles = {
    display: "grid",
    gridTemplateRows: `repeat(${numRows}, 1fr)`,
    gridTemplateColumns: `repeat(${numCols}, 1fr)`,
    gap: "0px", // Adjust the gap between cells if needed
    border: "1px solid black", // Optional border for each cell
    width: "800px",
  };

  const sectionStyle = {
    width: "100vw",
    height: "91vh",
    display: "flex",
    justifyContent: "center",
    alignItems: "center",
  };

  const [mousePressed, setMousePressed] = useState(false);

  useEffect(() => {

    const handleGlobalMouseUp = () => {
      setMousePressed(false);
    };
    
    document.addEventListener("mouseup", handleGlobalMouseUp);

    return () => {
      document.removeEventListener("mouseup", handleGlobalMouseUp);
    };

  }, [])

  const [grid, setGrid] = useState([]);

  useEffect(() => {

    const newGrid = [];
    const cellStyles = {
      width: "40px", // Adjust cell size if needed
      height: "40px",
      backgroundColor: "grey", // Default cell background color
      border: "1px solid black",
    };

    for (let row = 0; row < numRows; row++) {
      for (let col = 0; col < numCols; col++) {
        newGrid.push({
          key: `${row}-${col}`,
          style: { ...cellStyles },
        });
      }
    }
    setGrid(newGrid);

  }, []);

  const [position, setPosition] = useState(`0-0`);

  const changeCellStyle = (key, color) => {
    setGrid((prevGrid) =>
      prevGrid.map((cell) =>
        cell.key === key
          ? { ...cell, style: { ...cell.style, backgroundColor: color } }
          : cell
      )
    );
  };

  const movePlayerUp = () => {
  const [first, second] = position.split("-").map(Number);
  const newPosition = `${first - 1}-${second}`;
  setPosition(newPosition);
  changeCellStyle(newPosition, "green");
  changeCellStyle(position, "grey");
  
  };

  const movePlayerDown = () => {
  const [first, second] = position.split("-").map(Number);
  const newPosition = `${first + 1}-${second}`;
  setPosition(newPosition);
  changeCellStyle(newPosition, "green");
  changeCellStyle(position, "grey");
  };

  const movePlayerLeft = () => {
  const [first, second] = position.split("-").map(Number);
  const newPosition = `${first}-${second - 1}`;
  setPosition(newPosition);
  changeCellStyle(newPosition, "green");
  changeCellStyle(position, "grey");
  };

  const movePlayerRight = () => {
  const [first, second] = position.split("-").map(Number);
  const newPosition = `${first}-${second + 1}`;
  setPosition(newPosition);
  changeCellStyle(newPosition, "green");
  changeCellStyle(position, "grey");
  };

  useEffect(() => {
    const handleKeyDown = (event) => {
      switch (event.key) {
        case "ArrowUp":
          movePlayerUp();
          break;
        case "ArrowDown":
          movePlayerDown();
          break;
        case "ArrowLeft":
          movePlayerLeft();
          break;
        case "ArrowRight":
          movePlayerRight();
          break;
        default:
          // Handle other key presses if needed
          break;
      }
    };

    window.addEventListener("keydown", handleKeyDown);

    // Cleanup the event listener when the component unmounts
    return () => {
      window.removeEventListener("keydown", handleKeyDown);
    };
  }, );

  const [botPosition, setBotPosition] = useState(`10-10`);

  const moveBotUp = (botPosition) => {
    const [first, second] = botPosition.split("-").map(Number);
    const newBotPosition = `${first - 1}-${second}`;
    setBotPosition(newBotPosition);
    changeCellStyle(newBotPosition, "red");
    changeCellStyle(botPosition, "grey");
  };
  
  const moveBotDown = (botPosition) => {
    const [first, second] = botPosition.split("-").map(Number);
    const newBotPosition = `${first + 1}-${second}`;
    setBotPosition(newBotPosition);
    changeCellStyle(newBotPosition, "red");
    changeCellStyle(botPosition, "grey");
  };
  
  const moveBotLeft = (botPosition) => {
    const [first, second] = botPosition.split("-").map(Number);
    const newBotPosition = `${first}-${second - 1}`;
    setBotPosition(newBotPosition);
    changeCellStyle(newBotPosition, "red");
    changeCellStyle(botPosition, "grey");
  };
  
  const moveBotRight = (botPosition) => {
    const [first, second] = botPosition.split("-").map(Number);
    const newBotPosition = `${first}-${second + 1}`;
    setBotPosition(newBotPosition);
    changeCellStyle(newBotPosition, "red");
    changeCellStyle(botPosition, "grey");
  };

  const moveFunctions = [
    moveBotUp,
    moveBotDown,
    moveBotLeft,
    moveBotRight
  ];

  const randomMove = (position) => {
    const randomIndex = Math.floor(Math.random() * moveFunctions.length);
    const selectedMoveFunction = moveFunctions[randomIndex];
    selectedMoveFunction(position);
  };

  useEffect(() => {
    const randomMoveInterval = setInterval(() => {
      randomMove(botPosition);
    }, 100); // Call a random move every 1000ms (1 second)

    return () => {
      clearInterval(randomMoveInterval);
    };
  }, [botPosition]);

  const handleMouseDown = (key) => {
    changeCellStyle(key, "blue");
    setMousePressed(true);
  };

  const handleMouseOver = (key) => {
    if (mousePressed) {
      changeCellStyle(key, "blue");
    }
  };

  return (
    <div style={sectionStyle}>
      <h1>{position}</h1>
      <div style={gridStyles}>
        {grid.map((cell) => (
          <div
            key={cell.key}
            style={cell.style}
            onMouseDown={() => handleMouseDown(cell.key)}
            onMouseOver={() => handleMouseOver(cell.key)}
          ></div>
        ))}
      </div>
    </div>
  );
  }

  export default BotTech

Solution

  • Here is my take

    State:

    When it comes to state there are two maybe three options

    1. Pass it down through props
    2. useContext hook
    3. Rare case but... Build a separate component to manage the state

    How do I know when to use what?

    Generally, it's up to you some people won't prop drill(passing props down multiple components) more than one component. And that might be the correct answer however my rule is I won't go more than two levels down. So if you are passing a value down more than two components I would switch to use context before things get messy.

    Functions

    Your functions can definitely be put in a different folder. I would name it utils or maybe name the folder specifical for the board like board-utils. Once you export the functions using export syntax you will be cleaning up the code a lot.

    Can I make it a function?

    A function can take in any value(s) and return a value so if you keep this in mind you will find that lots of things can be reduced to functions. For example, there is nothing stopping you from passing a state variable into a function and altering it, and returning another value.

    • Hope this helps give you a start