export default function Play()

in infrastructure/load-balancing-for-inference/components/Play.tsx [152:582]


export default function Play() {

  // New GPU positions for each player.
const player1GpuPositions = [-4.90, -3.69, -2.52, -1.31];
const player2GpuPositions = [1.19, 2.43, 3.64, 4.86];


  const totalGameTime = 60;


  const [robotTargetX, setRobotTargetX] = useState(3.2);
  const [isGripping, setIsGripping] = useState(false);


  // TODO: Add pseudonyms for users
  const [playerOneEnd, setPlayerOneEnd] = useState<[number, number, number]>(
    playerEndPositions[0]
  );
  const [timeElapsed, setTimeElapsed] = useState(-5);
  const [quarterSecondCounter, setQuarterSecondCounter] = useState(0);
  const [playerName, setPlayerName] = useState("You are the...");
  const [failBlocks, setFailBlocks] = useState<FailResultBlock[]>([]);
  const [successBlocks, setSuccessBlocks] = useState<ResultBlock[]>([]);
  const [vmStats, setVmStats] = useState<VmStats>(defaultVmStats);
  const [playerTwoTotal, setTotalPlayerTwoTotal] = useState(0);
  const [gameStarted, setGameStarted] = useState(false);
  const [showResults, setShowResults] = useState(false);

  const [blocks, setBlocks] = useState<
    {
      uuid: string;
      gpuIndex: number;
      stackPosition: number; // NEW: Tracks stacking inside GPU
    }[]
  >([]);

  const userBoxTexture = useLoader(TextureLoader, "/assets/user-box.png");
  const userFunnelTexture = useLoader(TextureLoader, "/assets/user-funnel.png");
  const yellowDataBlockTexture = useLoader(
    TextureLoader,
    "/assets/yellow-datablock.png"
  );

  // ✅ Define Robot Position Variables

  const robotGripperPosition: [number, number, number] = [3.0, -0.2, 0]; // Gripper at the end of Arm 2

  // ✅ Load Textures
  const baseTexture = useLoader(TextureLoader, "/assets/base.png");
  const arm1Texture = useLoader(TextureLoader, "/assets/arm1.png");
  const arm2Texture = useLoader(TextureLoader, "/assets/arm2.png");
  const railTexture = useLoader(TextureLoader, "/assets/rail.png");
  const gripperTexture = useLoader(TextureLoader, "/assets/gripper.png");

  // Blue side assets

const player2FunnelXPositions = [1.2, 2.4, 3.6, 4.8];
const [player2FunnelX, setPlayer2FunnelX] = useState(player2FunnelXPositions[0]);
const playerTwoActiveRef = useRef<number>(0);



useEffect(() => {
  const interval = setInterval(() => {
    const randomIndex = Math.floor(Math.random() * player2FunnelXPositions.length);
    setPlayer2FunnelX(player2FunnelXPositions[randomIndex]);
  }, 500); // update every 0.5 seconds for a faster funnel movement
  return () => clearInterval(interval);
}, []);


  // ✅ Define Robot Base Position
  const robotBasePosition: [number, number, number] = [.0, 1.0, 0];

  // ✅ Define Arm Positions Relative to Base
  const robotArm1Position: [number, number, number] = [
    robotBasePosition[0],
    robotBasePosition[1] - 0.5,
    0,
  ];
  const robotArm2Position: [number, number, number] = [
    robotArm1Position[0],
    robotArm1Position[1] - 0.5,
    0,
  ];

  // ✅ Define Rail Position
  const railPosition: [number, number, number] = [3.0, 0.5, 0];

  const userFunnelBaseY = 0.09; // ✅ Base Y-position for funnel (adjust as needed)

  // ✅ Define Constants for UserBox & UserFunnel Positioning

  const userFunnelBasePosition: [number, number, number] = [
    player1VmXPositions[0],
    userFunnelBaseY,
    0,
  ]; // ✅ Moves based on keypress

  // calculated values based on variables
  const [activeGpuIndex, setActiveGpuIndex] = useState(0);

  const playerOneMid: [number, number, number] = [
    playerOneEnd[0],
    playerMidYPosition,
    0,
  ];
  const player2NextVmIndex = playerTwoTotal % 4;
  const player2NextXPosition = player2VmXPositions[player2NextVmIndex];
  const playerTwoEnd: [number, number, number] = [
    player2NextXPosition,
    vmYPositionBottom,
    0,
  ];
  const playerTwoMid: [number, number, number] = [
    playerTwoEnd[0],
    playerMidYPosition,
    0,
  ];
  const playerOneActiveVmIndex = activeGpuIndex; // Use activeGpuIndex instead of finding position
  const playerOneAtMaxCapacity =
    vmStats.statusArray[playerOneActiveVmIndex].atMaxCapacity;
  const playerOneActiveVmId = playerOneActiveVmIndex + 1;

  const playerTwoActiveVmIndex = player2VmXPositions.reduce((bestIndex, pos, i) => {
    return Math.abs(pos - player2FunnelX) < Math.abs(player2VmXPositions[bestIndex] - player2FunnelX)
      ? i
      : bestIndex;
  }, 0);
  
  
  const playerTwoAtMaxCapacity =
    vmStats.statusArray[playerTwoActiveVmIndex + 4].atMaxCapacity;
  const playerOneScore = timeElapsed < 1 ? 0 : vmStats.playerOneScore;
  const playerTwoScore = timeElapsed < 1 ? 0 : vmStats.playerTwoScore;
  const timeRemaining = Math.min(totalGameTime - timeElapsed, totalGameTime);

  useEffect(() => {
    // add success block to any vm that has more than 0% in the queue
    const newSuccessXPosition = [];
    // add player one success block
    if (vmStats.statusArray[player2NextVmIndex].queue > 0) {
      newSuccessXPosition.push(player1VmXPositions[player2NextVmIndex]);
    }
    // add player two success block
    if (vmStats.statusArray[player2NextVmIndex + 4].queue > 0) {
      newSuccessXPosition.push(player2VmXPositions[player2NextVmIndex]);
    }
    const newSuccessBlocks = newSuccessXPosition.map((xPosition) => {
      const startingPosition: [number, number, number] = [
        xPosition,
        vmYPosition + 0.42,
        -0.5,
      ];
      const uid = crypto.randomUUID();
      return { uid, startingPosition };
    });
    // limit to 100 blocks to prevent the screen from freezing up
    setSuccessBlocks([...newSuccessBlocks, ...successBlocks].slice(0, 20));
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [player2NextVmIndex]);

  useEffect(() => {
    const getStartLoader = async (playerName: string) => {
      try {
        setPlayerName(playerName);
      } catch (error) {
        console.error("Failed to start");
      }
    };
    if (!gameStarted && timeElapsed > -2) {
      setGameStarted(true);
      getStartLoader(playerName);
    }
  }, [gameStarted, playerName, timeElapsed]);

  useEffect(() => {
    const getVMStatus = async () => {
      if (timeElapsed < totalGameTime + 5) {
        // During GET READY (timeElapsed < 0), force GPU utilizations to 0.
        if (timeElapsed < 0) {
          const resetStats = {
            ...vmStats,
            statusArray: vmStats.statusArray.map((vm) => ({ ...vm, utilization: 0 })),
            playerOneScore: 0,
            playerTwoScore: 0,
          };
          setVmStats(resetStats);
          return;
        }
        
        // Compute active right GPU index based on player2FunnelX:
        const playerTwoActiveVmIndex = player2GpuPositions.reduce((bestIndex, pos, i) => {
          return Math.abs(pos - player2FunnelX) < Math.abs(player2GpuPositions[bestIndex] - player2FunnelX)
            ? i
            : bestIndex;
        }, 0);
        
        // Update rates:
        const leftSideIncreaseRate = 0.035;
        const leftSideDecreaseRate = 0.01;
        const rightSideIncreaseRate = 0.08; // Increased active fill rate for right side
        const rightSideDecreaseRate = 0.02; // Decreased drain rate for inactive right GPUs
  
        const updatedVmStats = {
          ...vmStats,
          statusArray: vmStats.statusArray.map((vmDetails, index) => {
            if (index < 4) {
              // Left side GPUs.
              if (index === playerOneActiveVmIndex) {
                return {
                  ...vmDetails,
                  utilization: Math.min(
                    vmDetails.utilization + leftSideIncreaseRate,
                    1
                  ),
                };
              } else {
                return {
                  ...vmDetails,
                  utilization: Math.max(vmDetails.utilization - leftSideDecreaseRate, 0),
                };
              }
            } else {
              // Right side GPUs (indices 4–7): compare (index - 4) with playerTwoActiveVmIndex.
              if ((index - 4) === playerTwoActiveVmIndex) {
                return {
                  ...vmDetails,
                  utilization: Math.min(vmDetails.utilization + rightSideIncreaseRate, 1),
                };
              } else {
                return {
                  ...vmDetails,
                  utilization: Math.max(vmDetails.utilization - rightSideDecreaseRate, 0),
                };
              }
            }
          }),
          playerOneScore:
            vmStats.playerOneScore +
            vmStats.statusArray.slice(0, 4).reduce((agg, vm) => agg + (vm.utilization > 0 ? 1 : 0), 0),
          playerTwoScore:
            vmStats.playerTwoScore +
            vmStats.statusArray.slice(4).reduce((agg, vm) => agg + (vm.utilization > 0 ? 1 : 0), 0),
          playerOneAtMaxCapacity: vmStats.statusArray[playerOneActiveVmIndex].utilization >= 1,
          playerTwoAtMaxCapacity:
            vmStats.statusArray[playerTwoActiveVmIndex + 4].utilization >= 1,
        };
  
        setVmStats(updatedVmStats);
      }
    };
    getVMStatus();
  }, [quarterSecondCounter, timeElapsed, player2FunnelX]);
  
  

  

  // ✅ Define Constants for UserBox & UserFunnel Positioning
  // const userBoxPosition: [number, number, number] = [-2.9, 1.2, 0]; // ✅ Fixed position

  // ✅ State to Track Funnel Movement (Moves Horizontally)
  const [userFunnelPosition, setUserFunnelPosition] = useState<
    [number, number, number]
  >([
    player1VmXPositions[0], // Starts at GPU 1
    userFunnelBaseY, // Using the new base Y position
    0,
  ]);

  // Add activeGpuIndex state to track which GPU is currently selected

  // Update the handleKeyDown function in your useEffect
  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      let newXPosition: number | null = null;
      let newGpuIndex: number = activeGpuIndex;

      switch (event.code) {
        case "Digit1":
          newXPosition = player1VmXPositions[0];
          newGpuIndex = 0;
          break;
        case "Digit2":
          newXPosition = player1VmXPositions[1];
          newGpuIndex = 1;
          break;
        case "Digit3":
          newXPosition = player1VmXPositions[2];
          newGpuIndex = 2;
          break;
        case "Digit4":
          newXPosition = player1VmXPositions[3];
          newGpuIndex = 3;
          break;
        default:
          break;
      }
      if (newXPosition !== null) {
        setUserFunnelPosition([newXPosition, userFunnelBaseY, 0]);
        setActiveGpuIndex(newGpuIndex);
      }
    };
    window.addEventListener("keydown", handleKeyDown);
    return () => window.removeEventListener("keydown", handleKeyDown);
  }, []);

  useEffect(() => {
    const playerName = generatePlayerName();
    setPlayerName(playerName);
  }, []);

  useEffect(() => {
    //Implementing the setInterval method
    const interval = setInterval(() => {
      setTimeElapsed(timeElapsed + 1);
    }, 1000);

    //Clearing the interval
    return () => clearInterval(interval);
  }, [timeElapsed]);

  useEffect(() => {
    //Implementing the setInterval method
    const fastInterval = setInterval(() => {
      setQuarterSecondCounter(quarterSecondCounter + 1);
    }, 250);

    //Clearing the interval
    return () => clearInterval(fastInterval);
  }, [quarterSecondCounter]);

  useEffect(() => {
    if (timeElapsed >= totalGameTime && !showResults) {
      setShowResults(true);
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [timeElapsed]);

  const addFailBlock = (
    failBlockStartingPosition: [number, number, number]
  ) => {
    const [x, y, z] = failBlockStartingPosition;
    if (x < 0) {
      const audio = new Audio("/beep.wav");
      audio.volume = 0.1;
      // audio.play();
    } else {
      setTotalPlayerTwoTotal(playerTwoTotal + 1);
    }
    const startingPosition: [number, number, number] = [
      x + Math.random() / 4 - 0.1,
      y,
      z,
    ];
    const uid = crypto.randomUUID();
    const side: "LEFT" | "RIGHT" = ["LEFT", "RIGHT"][
      Math.floor(Math.random() * 2)
    ] as "LEFT" | "RIGHT";
    const newBlock = { uid, startingPosition, side };
    // limit to 100 blocks to prevent the screen from freezing up
    setFailBlocks([newBlock, ...failBlocks].slice(0, 10));
  };


  return (
    <main className="flex h-screen flex-col items-center justify-between">
      <div className="flex w-full overflow-clip justify-center text-7xl font-mono">
        <div
          className="flex flex-row transition-all duration-1000 justify-end px-2 text-[#1a212c]"
          style={{
            height: "80px",
            width: `${Math.max(playerOneScore, 50)}rem`,
            backgroundColor: colors.player1,
          }}
        >
          <h3 className={`text-7xl font-mono ${jersey15.className}`}>
            {playerOneScore.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
          </h3>
        </div>
        <div
          className="flex flex-row transition-all duration-1000 justify-start px-2 text-white"
          style={{
            height: "80px",
            width: `${Math.max(playerTwoScore, 50)}rem`,
            backgroundColor: colors.player2,
          }}
        >
          <h3 className={`text-7xl font-mono ${jersey15.className}`}>
            {playerTwoScore.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ",")}
          </h3>
        </div>
      </div>

      <Canvas style={{ background: "white" }}>
        <ambientLight intensity={Math.PI / 2} />
        <pointLight position={[-10, -10, -10]} decay={0} intensity={2} />
        <pointLight position={[5, 5, 5]} decay={0} intensity={3} />
  
        <Player1 showResults={false} />
        <Player2 showResults={showResults} funnelX={player2FunnelX} />
  
        <GpuGroup
          positions={player1GpuPositions}
          stats={vmStats.statusArray.slice(0, 4)}
        />
  
        {/* Right side GPUs */}
        <GpuGroupRight
          positions={player2GpuPositions}
          stats={vmStats.statusArray.slice(4, 8)}
        />
      </Canvas>
  
      <CountdownOverlay
        timeElapsed={timeElapsed}
        timeRemaining={timeRemaining}
        playerOneScore={playerOneScore}
        playerTwoScore={playerTwoScore}
      />
  
      <ResultsOverlay
        showResults={showResults}
        playerOneScore={playerOneScore}
        playerTwoScore={playerTwoScore}
        playerName={playerName}
      />
    </main>
  );
}