SOURCE

console 命令行工具 X clear

                    
>
console
let replay = document.querySelector("#replay");
let score = document.querySelector("#score");
let canvas = document.createElement("canvas");
document.querySelector("#canvas").appendChild(canvas);
let ctx = canvas ? canvas.getContext("2d") : null;

function cnvRes(width = 400, height = 400) {
  ctx.canvas.width = width;
  ctx.canvas.height = height;
}
let pubVars = {
  snake: undefined,
  snakeLength: undefined,
  food: undefined,
  currentHue: undefined,
  segments: undefined,
  historyPath: [],
  gameOver: false,
  tails: [],
  update: undefined,
  maxScore: window.localStorage.getItem("maxScore") || undefined,
  effects: []
};
let helpers = {
  collision(isSelfCol, snakeHead) {
    if (isSelfCol) {
      if (snakeHead.x == pubVars.food.x && snakeHead.y == pubVars.food.y) {
        pubVars.food.respawnFood();
        pubVars.tails.push(new Snake(pubVars.snakeLength - 1, "tail"));
        pubVars.snakeLength++;
        pubVars.snake.delay - 0.5;
      }
    } else {
      for (let i = 1; i < pubVars.historyPath.length; i++) {
        if (
          snakeHead.x == pubVars.historyPath[i].x &&
          snakeHead.y == pubVars.historyPath[i].y
        ) {
          pubVars.gameOver = true;
        }
      }
    }
  },
  randHue() {
    return Math.floor(Math.random() * 360);
  },
  randCor(newCors) {
    let randX =
      (Math.floor(Math.random() * pubVars.segments) * ctx.canvas.width) /
      pubVars.segments;
    let randY =
      (Math.floor(Math.random() * pubVars.segments) * ctx.canvas.height) /
      pubVars.segments;

    if (newCors) {
      randX =
        (Math.floor(Math.random() * pubVars.segments) * ctx.canvas.width) /
        pubVars.segments;
      randY =
        (Math.floor(Math.random() * pubVars.segments) * ctx.canvas.height) /
        pubVars.segments;
      return { randX, randY };
    } else {
      return { randX, randY };
    }
  },
  positionLogger(limit, loc) {
    pubVars.historyPath.push(loc);
    if (pubVars.historyPath.length > limit) {
      pubVars.historyPath.shift();
    }
  },
  hsl2rgb(hue, saturation, lightness) {
    if (hue == undefined) {
      return [0, 0, 0];
    }

    var chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
    var huePrime = hue / 60;
    var secondComponent = chroma * (1 - Math.abs((huePrime % 2) - 1));

    huePrime = Math.floor(huePrime);
    var red;
    var green;
    var blue;

    if (huePrime === 0) {
      red = chroma;
      green = secondComponent;
      blue = 0;
    } else if (huePrime === 1) {
      red = secondComponent;
      green = chroma;
      blue = 0;
    } else if (huePrime === 2) {
      red = 0;
      green = chroma;
      blue = secondComponent;
    } else if (huePrime === 3) {
      red = 0;
      green = secondComponent;
      blue = chroma;
    } else if (huePrime === 4) {
      red = secondComponent;
      green = 0;
      blue = chroma;
    } else if (huePrime === 5) {
      red = chroma;
      green = 0;
      blue = secondComponent;
    }

    var lightnessAdjustment = lightness - chroma / 2;
    red += lightnessAdjustment;
    green += lightnessAdjustment;
    blue += lightnessAdjustment;

    return [
      Math.round(red * 255),
      Math.round(green * 255),
      Math.round(blue * 255)
    ];
  }
};

let input = {
  left: false,
  down: false,
  right: true,
  up: false,
  listen() {
    addEventListener(
      "keydown",
      (e) => {
        switch (e.key) {
          case "ArrowLeft":
            if (!this.right) {
              this.left = true;
              this.down = false;
              this.right = false;
              this.up = false;
            }
            break;
          case "ArrowRight":
            if (!this.left) {
              this.left = false;
              this.down = false;
              this.right = true;
              this.up = false;
            }
            break;
          case "ArrowUp":
            if (!this.down) {
              this.left = false;
              this.down = false;
              this.right = false;
              this.up = true;
            }
            break;
          case "ArrowDown":
            if (!this.up) {
              this.left = false;
              this.down = true;
              this.right = false;
              this.up = false;
            }
            break;
          default:
            break;
        }
      },
      false
    );
  }
};

class Snake {
  constructor(i, type) {
    this.x = type == "tail" ? pubVars.historyPath[i].x : 0;
    this.y = type == "tail" ? pubVars.historyPath[i].y : 0;
    this.type = type;
    this.index = i;
    this.delay = 10;
    this.localDelay = 10;
    this.size = ctx.canvas.width / pubVars.segments;
    this.color = "white";
  }
  draw() {
    ctx.lineWidth = 1;
    ctx.fillStyle = this.color;
    ctx.strokeStyle = "#181825";
    ctx.strokeRect(this.x, this.y, this.size, this.size);
    ctx.fillRect(this.x, this.y, this.size, this.size);
  }
  update() {
    this.draw();
    if (this.localDelay < 0) {
      if (this.type == "tail") {
        this.x = pubVars.historyPath[this.index].x;
        this.y = pubVars.historyPath[this.index].y;
      } else {
        this.localDelay = this.delay;
        if (input.left) {
          this.x -= ctx.canvas.width / pubVars.segments;
        }
        if (input.right) {
          this.x += ctx.canvas.width / pubVars.segments;
        }
        if (input.up) {
          this.y -= ctx.canvas.width / pubVars.segments;
        }
        if (input.down) {
          this.y += ctx.canvas.width / pubVars.segments;
        }
        if (this.x + ctx.canvas.width / pubVars.segments > ctx.canvas.width) {
          this.x = 0;
        }
        if (this.y + ctx.canvas.height / pubVars.segments > ctx.canvas.width) {
          this.y = 0;
        }
        if (this.y < 0) {
          this.y = ctx.canvas.height - ctx.canvas.height / pubVars.segments;
        }
        if (this.x < 0) {
          this.x = ctx.canvas.width - ctx.canvas.width / pubVars.segments;
        }
        helpers.collision(true, { ...this });
        helpers.collision(false, { ...this });
        helpers.positionLogger(pubVars.snakeLength, { x: this.x, y: this.y });
      }
    } else {
      this.localDelay--;
    }
  }
}

class Food extends Snake {
  constructor() {
    super();
    this.x =
      (Math.floor(Math.random() * pubVars.segments) * ctx.canvas.width) /
      pubVars.segments;
    this.y =
      (Math.floor(Math.random() * pubVars.segments) * ctx.canvas.height) /
      pubVars.segments;
    this.color = pubVars.currentHue = `hsl(${helpers.randHue()}, 100%, 55%)`;
  }
  draw() {
    ctx.save();
    ctx.shadowColor = this.color;
    ctx.shadowBlur = 50;
    ctx.fillStyle = this.color;
    ctx.fillRect(this.x, this.y, this.size, this.size);
    ctx.restore();
  }
  respawnFood() {
    pubVars.effects.push(
      new Effect(
        pubVars.food.x,
        pubVars.food.y,
        pubVars.currentHue,
        pubVars.food.size,
        pubVars.effects.length - 1
      )
    );
    this.color = pubVars.currentHue = `hsl(${helpers.randHue()}, 100%, 50%)`;
    this.x =
      (Math.floor(Math.random() * pubVars.segments) * ctx.canvas.width) /
      pubVars.segments;
    this.y =
      (Math.floor(Math.random() * pubVars.segments) * ctx.canvas.height) /
      pubVars.segments;
    for (let i = 0; i < pubVars.historyPath.length; i++) {
      if (
        this.x == pubVars.historyPath[i].x &&
        this.y == pubVars.historyPath[i].y
      ) {
        this.respawnFood();
      }
    }
  }
}

class Effect {
  constructor(x, y, color, size, i) {
    this.x = x;
    this.y = y;
    this.color = color;
    this.size = size;
    this.ttl = 0;
    this.angle = 1;
    this.index = i;
  }
  draw() {
    let hsl = this.color
      .split("")
      .filter((l) => l.match(/[^hsl()$% ]/g))
      .join("")
      .split(",")
      .map((n) => +n);
    let [r, g, b] = helpers.hsl2rgb(hsl[0], hsl[1] / 100, hsl[2] / 100);
    ctx.save();
    ctx.translate(this.size / 2 + this.x, this.size / 2 + this.y);
    ctx.rotate((this.angle * Math.PI) / 180.0);
    ctx.lineWidth = 2;
    ctx.strokeStyle = `rgb(${r},${g},${b},${1 / this.ttl})`;
    ctx.strokeRect(-this.size / 2, -this.size / 2, this.size, this.size);
    ctx.restore();
  }
  update() {
    this.draw();
    if (this.size > 0) {
      this.y -= 1;
      this.ttl >= 40
        ? pubVars.effects.splice(this.i + 1, 1)
        : (this.ttl += 0.5);
      this.angle += 7;
    }
  }
}
function scoreManager() {
  let currentScore = pubVars.snakeLength - 1;
  score.innerText = currentScore.toString();
}

function setup() {
  // Intialization of the game.
  cnvRes();
  input.listen();
  pubVars.segments = 32;
  pubVars.snakeLength = 1;
  pubVars.snake = new Snake("head");
  pubVars.food = new Food();
  loop();
}

function loop() {
  pubVars.update = setInterval(() => {
    if (!pubVars.gameOver) {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      pubVars.snake.update();
      if (pubVars.tails.length) {
        for (let i = 0; i < pubVars.tails.length; i++) {
          pubVars.tails[i].update();
        }
      }
      pubVars.food.draw();
      scoreManager();
      for (let i = 0; i < pubVars.effects.length; i++) {
        pubVars.effects[i].update();
      }
    } else {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      gameOver();
    }
  }, 1000 / 60);
}
setup();

replay.addEventListener("click", () => {
  reset();
});
function gameOver() {
  pubVars.maxScore ? null : (pubVars.maxScore = pubVars.snakeLength - 1);
  pubVars.snakeLength - 1 > pubVars.maxScore
    ? (pubVars.maxScore = pubVars.snakeLength - 1)
    : null;
  window.localStorage.setItem("maxScore", pubVars.maxScore);
  ctx.fillStyle = "#4cffd7";
  ctx.textAlign = "center";
  ctx.font = "bold 30px Poppins, sans-serif";
  ctx.fillText("GAME OVER", ctx.canvas.width / 2, ctx.canvas.height / 2);
  ctx.font = "15px Poppins, sans-serif";
  ctx.fillText(
    `SCORE   ${pubVars.snakeLength - 1}`,
    ctx.canvas.width / 2,
    ctx.canvas.height / 2 + 60
  );
  ctx.fillText(
    `MAXSCORE   ${pubVars.maxScore}`,
    ctx.canvas.width / 2,
    ctx.canvas.height / 2 + 80
  );
}
function reset() {
  clearInterval(pubVars.update);
  pubVars.snake = undefined;
  pubVars.snakeLength = undefined;
  pubVars.food = undefined;
  pubVars.currentHue = undefined;
  pubVars.segments = undefined;
  pubVars.historyPath = [];
  pubVars.gameOver = false;
  pubVars.tails = [];
  pubVars.update = undefined;
  input.left = false;
  input.down = false;
  input.right = true;
  input.up = false;
  setup();
}
<div class="container noselect">
  <div class="wrapper">
    <button id="replay">
      <i class="fas fa-play"></i>
      RESTART
    </button>
    <div id="canvas">

    </div>
    <div id="ui">
      <h2>SCORE
      </h2>
      <span id="score">00</span>
    </div>
  </div>
  <div id="credit">
    <h1>SNAKE</h1> <span>by Fariat</span>
  </div>
</div>
@font-face {
  font-family: "game";
  src: url("https://fonts.googleapis.com/css2?family=Poppins:wght@500;800&display=swap");
}
* {
  padding: 0;
  margin: 0;
  box-sizing: border-box;
}
button:focus {
  outline: 0;
}

html,
body {
  height: 100%;
  font-family: "Poppins", sans-serif;
  color: #6e7888;
}
body {
  background-color: #222738;
  display: flex;
  justify-content: center;
  align-items: center;
  color: #6e7888;
}
canvas {
  background-color: #181825;
}
.container {
  display: flex;
  width: 100%;
  height: 100%;
  flex-flow: column wrap;
  justify-content: center;
  align-items: center;
}
#ui {
  display: flex;
  align-items: center;
  font-size: 10px;
  flex-flow: column;
  margin-left: 10px;
}
h2 {
  font-weight: 200;
  transform: rotate(270deg);
}
#score {
  margin-top: 20px;
  font-size: 30px;
  font-weight: 800;
}
.noselect {
  user-select: none;
}
#replay {
  font-size: 10px;
  padding: 10px 20px;
  background: #6e7888;
  border: none;
  color: #222738;
  border-radius: 20px;
  font-weight: 800;
  transform: rotate(270deg);
  cursor: pointer;
  transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
#replay:hover {
  background: #a6aab5;
  background: #4cffd7;
  transition: all 200ms cubic-bezier(0.4, 0, 0.2, 1);
}
#replay svg {
  margin-right: 8px;
}
@media (max-width: 480px) {
  #replay {
    margin-bottom: 20px;
  }
  #replay,
  h2 {
    transform: rotate(0deg);
  }
  #ui {
    flex-flow: row wrap;
    margin-bottom: 20px;
  }
  #score {
    margin-top: 0;
    margin-left: 20px;
  }
  .container {
    flex-flow: column wrap;
  }
}
#credit {
  width: 100%;
  bottom: 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 600;
  color: inherit;
  text-transform: uppercase;
  padding-left: 35px;
}
#credit span {
  font-size: 10px;
  margin-left: 20px;
  color: inherit;
  letter-spacing: 4px;
}
#credit h1 {
  font-size: 25px;
}
.wrapper {
  display: flex;
  flex-flow: row wrap;
  justify-content: center;
  align-items: center;
  margin-bottom: 20px;
}