Nooble / SnakeOnPipeline

// ==UserScript==
// @name         SnakeOnPipeline
// @namespace    Slay
// @version      2024-05-03
// @description  snake on pipeline lololol
// @author       Nooble
// @match        *://*/**/pipelines/*
// @grant        none
// @copyright 2024, Nooble (https://openuserjs.org/users/Nooble)
// @license MIT
// ==/UserScript==

(function () {
  "use strict";

  const css = '#snakeBackdrop,#snakeGameArea{position:fixed;top:0;left:0;width:100vw;height:100vh}body,html{min-height:100vh;min-width:100vw;margin:0;position:relative}#snakeOpenGameBtn{position:fixed;bottom:1rem;right:1rem}#snakeBackdrop{background-color:rgba(0,0,0,.2)}#snakeGrid{background-color:#000;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);display:grid;grid-template-columns:repeat(20,1fr);width:50vw;min-width:400px;max-width:800px;height:50vw;min-height:400px;max-height:800px}';

  const Directions = {
    UP: 0,
    RIGHT: 1,
    DOWN: 2,
    LEFT: 3,
  };

  const snake = [{
      x: 10,
      y: 10
    },
    {
      x: 10,
      y: 10
    },
    {
      x: 10,
      y: 10
    },
  ];

  const apples = [];

  let currentDirection = Directions.RIGHT;
  let newDirections = [];

  let currentSpeed = 200;
  let shouldIncreaseSpeed = false;

  let gameLoop;

  function toggleGameArea() {
    const grid = document.getElementById("snakeGameArea");
    grid.style.display = grid.style.display === "none" ? "block" : "none";

    if (grid.style.display === "none") {
      clearInterval(gameLoop);
      gameLoop = undefined;
    }
  }

  function addGridCells() {
    const grid = document.getElementById("snakeGrid");

    for (let i = 0; i < 400; i++) {
      const cell = document.createElement("div");
      cell.classList.add("snakeCell");
      grid.appendChild(cell);
    }
  }

  function createGameArea() {
    console.log("Creating grid...");

    const style = document.createElement("style");
    style.textContent = css;
    document.head.appendChild(style);

    const gameArea = document.createElement("div");
    gameArea.id = "snakeGameArea";
    gameArea.style.display = "none";
    gameArea.innerHTML = `
    <div id="snakeBackdrop"></div>
    <div id="snakeGrid"></div>
  `;
    document.body.appendChild(gameArea);

    const backdrop = document.getElementById("snakeBackdrop");
    backdrop.onclick = toggleGameArea;

    addGridCells();
  }

  function createUI() {
    console.log("Creating UI...");

    const openGameBtn = document.createElement("button");
    openGameBtn.id = "snakeOpenGameBtn";
    openGameBtn.textContent = "Snake";
    openGameBtn.onclick = toggleGameArea;
    document.body.appendChild(openGameBtn);
  }

  function updateGridUI() {
    const cells = document.querySelectorAll(".snakeCell");

    for (let i = 0; i < 400; i++) {
      const cell = cells[i];
      cell.style.backgroundColor = "black";
    }

    snake.forEach((segment) => {
      const index = segment.x + segment.y * 20;
      cells[index].style.backgroundColor = "green";
    });

    apples.forEach((apple) => {
      const index = apple.x + apple.y * 20;
      cells[index].style.backgroundColor = "red";
    });
  }

  function checkSnakeDied(position) {
    if (
      position.x < 0 ||
      position.x >= 20 ||
      position.y < 0 ||
      position.y >= 20
    ) {
      return true;
    }

    if (
      snake.some(
        (segment) => segment.x === position.x && segment.y === position.y
      )
    ) {
      return true;
    }

    return false;
  }

  function snakeDied() {
    console.log("Snake died...");

    clearInterval(gameLoop);
    gameLoop = undefined;
  }

  function generateApple() {
    if (snake.length + apples.length === 400) return;

    const newApple = {
      x: Math.floor(Math.random() * 20),
      y: Math.floor(Math.random() * 20),
    };

    if (
      snake.some(
        (segment) => segment.x === newApple.x && segment.y === newApple.y
      ) ||
      apples.some((apple) => apple.x === newApple.x && apple.y === newApple.y)
    ) {
      generateApple();
      return;
    }

    console.log("NEW APPLE", newApple);

    apples.push(newApple);
  }

  function update() {
    const currentHead = snake[snake.length - 1];

    if (newDirections.length > 0) {
      currentDirection = newDirections.shift();
    }

    let newPos;

    switch (currentDirection) {
      case Directions.UP:
        newPos = {
          x: currentHead.x,
          y: currentHead.y - 1
        };
        break;
      case Directions.RIGHT:
        newPos = {
          x: currentHead.x + 1,
          y: currentHead.y
        };
        break;
      case Directions.DOWN:
        newPos = {
          x: currentHead.x,
          y: currentHead.y + 1
        };
        break;
      case Directions.LEFT:
        newPos = {
          x: currentHead.x - 1,
          y: currentHead.y
        };
        break;
    }

    if (checkSnakeDied(newPos)) {
      snakeDied();
      return;
    }

    const appleIndex = apples.findIndex(
      (apple) => apple.x === newPos.x && apple.y === newPos.y
    );

    if (appleIndex !== -1) {
      apples.splice(appleIndex, 1);
      apples.length === 0 && generateApple();
      snake.unshift(snake[0]);
    }

    snake.push(newPos);
    snake.shift();

    updateGridUI();

    shouldIncreaseSpeed && increaseSpeed();
  }

  function init() {
    console.log("Initializing...");

    createGameArea();
    createUI();

    generateApple();

    updateGridUI();
  }

  function increaseSpeed() {
    if (currentSpeed > 50) {
      console.log("Increasing speed...");

      currentSpeed -= 50;
      clearInterval(gameLoop);
      gameLoop = setInterval(update, currentSpeed);

      shouldIncreaseSpeed = false;
    }
  }

  function start() {
    console.log(gameLoop);
    if (gameLoop === undefined) gameLoop = setInterval(update, currentSpeed);
  }

  window.addEventListener("load", () => {
    init();
  });

  function canChangeDirection(currentDirection, newDirection) {
    return (
      (currentDirection === Directions.UP && newDirection !== Directions.DOWN) ||
      (currentDirection === Directions.RIGHT &&
        newDirection !== Directions.LEFT) ||
      (currentDirection === Directions.DOWN && newDirection !== Directions.UP) ||
      (currentDirection === Directions.LEFT && newDirection !== Directions.RIGHT)
    );
  }

  window.addEventListener("keydown", (event) => {
    switch (event.code) {
      case "ArrowUp":
        if (
          (newDirections.length === 0 &&
            canChangeDirection(currentDirection, Directions.UP)) ||
          (newDirections.length === 1 &&
            canChangeDirection(newDirections[0], Directions.UP))
        ) {
          newDirections.push(Directions.UP);
        }
        break;
      case "ArrowRight":
        if (
          (newDirections.length === 0 &&
            canChangeDirection(currentDirection, Directions.RIGHT)) ||
          (newDirections.length === 1 &&
            canChangeDirection(newDirections[0], Directions.RIGHT))
        ) {
          newDirections.push(Directions.RIGHT);
        }
        break;
      case "ArrowDown":
        if (
          (newDirections.length === 0 &&
            canChangeDirection(currentDirection, Directions.DOWN)) ||
          (newDirections.length === 1 &&
            canChangeDirection(newDirections[0], Directions.DOWN))
        ) {
          newDirections.push(Directions.DOWN);
        }
        break;
      case "ArrowLeft":
        if (
          (newDirections.length === 0 &&
            canChangeDirection(currentDirection, Directions.LEFT)) ||
          (newDirections.length === 1 &&
            canChangeDirection(newDirections[0], Directions.LEFT))
        ) {
          newDirections.push(Directions.LEFT);
        }
        break;
      case "Space":
        start();
        break;
      case "Enter":
        shouldIncreaseSpeed = true;
        break;
    }
  });

  window.addEventListener("snake__increaseSpeed", () => {
    shouldIncreaseSpeed = true;
  });

  window.addEventListener("snake__generateApple", () => {
    generateApple();
  });
})();