5+ JavaScript Snake Game Examples

This post contains a total of 5+ JavaScript Snake Game Examples with Source Code. All these Snake Games are made using JavaScript & Styled using CSS.

You can use the source code of these examples with credits to the original owner.

Related Posts

JavaScript Snake Game Examples

1. JavaScript Snake Game

Made by Furkan Gulsen. JavaScript Snake game with Music and Fast Paced Gameplay. Source

<!DOCTYPE html>
<html lang="en" >
<head>
  <title></title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script>
<style>
body{
    background: rgb(42, 54, 60)
}
#gameBox{
    position: absolute;
    top: 50%;
    left: 50%;
    transform: translate(-50%,-50%);
    box-shadow: 0px 0px 50px #262626;
    overflow: hidden;
    border-radius: 10px;
}
</style>
</head>
<body>

    <div id="gameArea">
        <div id="gameBox"></div>
    </div>
      <script>
class Game {
  constructor() {
    document.addEventListener("keydown", this.keyEvents.bind(this)); // tuşa basıldığında aktifleştir
    this.a_dead = new Audio("https://www.dropbox.com/s/r7o9las1ki6tr0u/fail.wav?dl=1");
    this.a_eat = new Audio("https://www.dropbox.com/s/qukhjmxog6h3we8/crunch.wav?dl=1");
    this.a_start = new Audio("https://www.dropbox.com/s/xff36yvnh2zsxzh/start.wav?dl=1");
  }

  // ekran boyutu
  gameWindow() {
    this.winWidth = 400;
    this.winHeight = 400;
    createCanvas(this.winWidth, this.winHeight).parent("gameBox");
  }


  // ekrana çiz
  draw() {
    background("rgb(73, 95, 105)"); // oyun ekranonon rengi
    stroke("rgba(255, 255, 255,.5)"); // oyun ekranının kenarlık rengi
    // sütun şeklindeki noktalar

    this.snake(); // yılanı çiz
    this.apple(); // elmayı çiz
    this.scoreBoard();
    this.bestScore();
  }

  update() {
    this.frame = false;
    this.draw(); // ekrana çiz
  }

  start() {
    this.positionX = 15; // yılanın başlangıç noktası X
    this.positionY = 10; // yılanın başlangıç noktası Y
    this.appleX = this.appleY = 10; // Apple ilk konumu
    this.trail = []; // Yılanın Kordinatlarının tutulduğu dizi
    this.tailSize = 5; // yılanın boyutu
    this.speedX = this.speedY = 0; // yılanın ilk hızı
    this.gridSize = this.tileCount = 20; // ekranın kare sayısı
    this.fps = 1000 / 18; // saniyelik görüntü sayısı
    this.timer = setInterval(this.update.bind(this), this.fps);
    this.score = 0;

  }

  reset() {
    clearInterval(this.timer); // zamanı sıfırlar
    this.a_dead.play();
    this.start(); // oyunu baştan başlatır

  }

  keyEvents(e) {
    // sola gider
    if (e.keyCode === 37 && this.speedX !== 1) {
      this.a_start.play();
      this.speedX = -1;
      this.speedY = 0;
      this.frame = true;
    }
    // sağa gider
    if (e.keyCode === 39 && this.speedX !== -1) {
      this.a_start.play();
      this.speedX = 1;
      this.speedY = 0;
      this.frame = true;
    }
    // aşağıya gider
    if (e.keyCode === 40 && this.speedY !== -1) {
      this.a_start.play();
      this.speedX = 0;
      this.speedY = 1;
      this.frame = true;
    }
    // yukarıya gider
    if (e.keyCode === 38 && this.speedY !== 1) {
      this.a_start.play();
      this.speedX = 0;
      this.speedY = -1;
      this.frame = true;
    }
  }

  // oyundaki elemanlar
  // yılan
  snake() {
    fill("rgba(255,255,255,.75)");
    this.trail.forEach(a => {
      rect(a.positionX * 20, a.positionY * 20, this.gridSize - 5, this.gridSize - 5, 20, 20);
    });
    this.positionX += this.speedX;
    this.positionY += this.speedY;

    if (this.positionX < 0) {
      this.positionX = this.tileCount - 1;
    } else if (this.positionY < 0) {
      this.positionY = this.tileCount - 1;
    } else if (this.positionX > this.tileCount - 1) {
      this.positionX = 0;
    } else if (this.positionY > this.tileCount - 1) {
      this.positionY = 0;
    }

    // kendi üstüne gelirse
    this.trail.forEach(t => {
      if (this.positionX === t.positionX && this.positionY === t.positionY) {
        this.reset();
      }
    });

    // snake position
    this.trail.push({ positionX: this.positionX, positionY: this.positionY });


    //limits the size of the snake
    while (this.trail.length > this.tailSize) {
      this.trail.shift();
    }

    while (this.trail.length > this.tailSize) {
      this.trail.shift();
    }
  }
  apple() {


    // elmayı çiz
    fill("pink");

    rect(this.appleX * this.tileCount, this.appleY * this.tileCount, this.gridSize - 5, this.gridSize - 5, 5, 5);


    if (this.appleX === this.positionX && this.appleY === this.positionY) {

      this.tailSize++;
      this.score++;
      this.appleX = Math.floor(Math.random() * this.tileCount);
      this.appleY = Math.floor(Math.random() * this.tileCount);
      this.trail.forEach(t => {
        if (this.appleX === t.positionX && this.appleY == t.positionY) {
          this.apple();
        }
      });
      this.a_eat.play();
    }
  }
  scoreBoard() {
    textSize(15);
    noStroke();
    fill(26);
    text("SCORE", 10, 20);
    textSize(20);
    text(this.score, 32.5, 45);
  }
  bestScore() {
    textSize(15);
    text("BEST", 340, 20);
    if (!localStorage.getItem("best")) {
      localStorage.setItem("best", 0);
    }
    textSize(20);
    text(localStorage.getItem("best"), 357, 45);

    if (this.score > localStorage.getItem("best")) {
      this.best = this.score;
      localStorage.setItem("best", this.best);
    }

  }}


const game = new Game();
window.onload = () => game.start();

function setup() {
  game.gameWindow();
}

function draw() {
  game.update();
}
    </script>
</body>
</html>

2. Simple JS snake game

Made by Denys Nikitin. Source

<!DOCTYPE html>
<html lang="en" >
<head>
  <title></title>
<style>
body {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    max-width: 100%;
    overflow: hidden;
    background: linear-gradient(to right, #605C3C, #3C3B3F);
}
wrapper {
    display: flex;
    flex-direction: row;
}
.counter {
    margin-left: 40px;
    color: white;
    font-family: sans-serif;
    width: 100px;
}
</style>
</head>
<body>
  <wrapper>
    <canvas id="canvas"></canvas>
    <div class="counter">
        <p>Points: <strong id="points">0</strong></p>
        <p>Record: <strong id="record"></strong></p>
    </div>
</wrapper>
      <script>
window.onload = () => {

  var canvas = document.getElementById('canvas'),
  ctx = canvas.getContext('2d'),
  cw = canvas.width = canvas.height = 250,
  s = cw / 20,x = y = 0,
  rows = cols = cw / s,
  direction = { dir: 'right', change: true },
  tail = [],
  food = { x, y },
  head,
  drawInterval,
  speed = 100,
  points,
  record;

  if (localStorage.getItem('record')) record = localStorage.getItem('record');else
  record = 0;
  _('record').innerHTML = record;

  createFood();
  draw();
  document.addEventListener('keydown', e => getDirection(e));

  function cutTale() {
    alert('shit');
    tail = [];
  }

  function createFood(caught = false) {

    var xf = Math.floor(Math.random() * cols) * s;
    var yf = Math.floor(Math.random() * rows) * s;

    for (var i = 0; i < tail.length; i++) {
      if (xf == tail[i].x + s && yf == tail[i].y) {createFood();return;};
      if (xf == tail[i].x && yf == tail[i].y + s) {createFood();return;};
      if (xf + s == tail[i].x && yf == tail[i].y) {createFood();return;};
      if (xf == tail[i].x && yf + s == tail[i].y) {createFood();return;};

      if (xf == tail[i].x && yf == tail[i].y) {createFood();return;};
    }

    food = { ...food, x: xf, y: yf };
    tail = [...tail, '1'];

    if (caught) {
      points = tail.length - 1;

      if (record < points) {
        record = points;
        localStorage.setItem('record', record);
        _('record').innerHTML = record;
      };

      _('points').innerHTML = points;
    }
  }

  function getDirection(e) {
    switch (e.keyCode) {
      case 37:if (direction.dir !== 'right' && direction.change)
        direction = { ...direction, dir: 'left', change: false };break;
      case 38:if (direction.dir !== 'bottom' && direction.change)
        direction = { ...direction, dir: 'top', change: false };break;
      case 39:if (direction.dir !== 'left' && direction.change)
        direction = { ...direction, dir: 'right', change: false };break;
      case 40:if (direction.dir !== 'top' && direction.change)
        direction = { ...direction, dir: 'bottom', change: false };break;}

  }

  function setDirection() {
    switch (direction.dir) {
      case 'left':x -= s;break;
      case 'top':y -= s;break;
      case 'right':x += s;break;
      case 'bottom':y += s;break;}

    if (x == cw) x = 0;
    if (x < 0) x = cw - s;
    if (y == cw) y = 0;
    if (y < 0) y = cw - s;
  }

  function draw() {
    drawInterval = setInterval(() => {
      setDirection();

      ctx.fillStyle = 'white';
      ctx.fillRect(0, 0, cw, cw);

      if (tail.length > 0) {
        for (var i = 1; i < tail.length; i++) {
          tail[i - 1] = tail[i];
          ctx.fillStyle = '#02A4A8';
          ctx.fillRect(tail[i].x + 2, tail[i].y + 2, s - 4, s - 4);
          if (tail[i].x == x && tail[i].y == y) cutTale();
        }
        tail[tail.length - 1] = { x, y };
      }

      ctx.fillStyle = '#F16D31';
      ctx.fillRect(food.x, food.y, s, s);

      ctx.fillStyle = '#02A4A8';
      ctx.fillRect(x, y, s, s);

      if (x == food.x && y == food.y) createFood(true);

      direction = { ...direction, dir: direction.dir, change: true };
    }, speed);
  }

  function _(id) {
    return document.getElementById(id);
  }

};
    </script>
</body>
</html>

3. Simple Snake Game

Made by Rodrigo Gomes. Source

<!DOCTYPE html>
<html lang="en" >
<head>
  <title></title>
<style>
body{
  display:flex;
  flex-direction: column;
  align-items: center;
  background-color: #0093E9;
  background-image: linear-gradient(160deg, #0093E9 0%, #80D0C7 100%);

  color:white;
}

.game{
  display:flex;
}

.game-area{
  display:flex;
  flex-direction: column;
}

.level{
  list-style:none;
  margin:0;
  padding:20px;
}

.level li{
  margin-bottom:10px;
}

.pontuacao{
  background: #222222;
  border-radius: 0 0 10px 10px;
  margin:0px;
  padding:20px;
  list-style: none;
  text-align: center;
  font-weight: 900;
}

a{
  color:white;
}

canvas{
  border:3px solid #28262C;
  border-radius:10px 10px 0 0;
}

button {
    display: inline-block;
    border: none;
    padding: 1rem 2rem;
    margin: 0;
    text-decoration: none;
    background: #0069ed;
    color: #ffffff;
    font-family: sans-serif;
    font-size: 1rem;
    cursor: pointer;
    text-align: center;
    transition: background 250ms ease-in-out, 
                transform 150ms ease;
    -webkit-appearance: none;
    -moz-appearance: none;
    width:100%;
}

button:hover,
button:focus {
    background: rgba(0,0,0,0.6);
}

button:focus {
    outline: 1px solid #fff;
    outline-offset: -4px;
}

button:active {
    transform: scale(0.99);
}
</style>
</head>
<body>
  <body>
  <h2>Snake Game</h2>
  <div class="game">
    <div class="game-area">
      <canvas id="area" width="400" height="400"></canvas>
      <div class="pontuacao">
        Pontuação: <span id="pontos">0</span>
      </div>
    </div>
      <script>
window.onload = function () {

  //pegar o elemento canvas pelo ID  
  let area = document.getElementById('area');

  //capturar o quadro de pontuação
  let pontos = document.getElementById('pontos');
  let pontoi = 0;
  // define o contexto do elemento area para 2d
  let ctx = area.getContext("2d");

  document.addEventListener("keydown", keyPush);

  const easy = document.getElementById('js-easy');
  const middle = document.getElementById('js-middle');
  const hard = document.getElementById('js-hard');

  setInterval(game, 70);


  //Quantidade de casas que a cobra irá andar em cada atualização de quadro
  const vel = 1;

  //Velocidade inicial
  let vx = 0;
  let vy = 0;

  //ponto inicial
  let px = 10;
  let py = 15;

  // Tamanho do ponto
  const tp = 20;

  // quantidade de pontos
  const qp = 20;

  // Eixo inicial da Maçã
  let applex = 15;
  let appley = 15;

  // Array para o rastro da cobra
  let trail = [];

  function game() {

    px += vx;
    py += vy;

    //controle da cobra dentro do quadro para repetição nas bordas
    if (px < 0) {
      px = qp - 1;
    }
    if (px > qp - 1) {
      px = 0;
    }
    if (py < 0) {
      py = qp - 1;
    }
    if (py > qp - 1) {
      py = 0;
    }

    ctx.fillStyle = "#111D4A";
    //sintaxe JavaScript:	contexto.fillRect ( x, y, largura, altura );
    ctx.fillRect(0, 0, area.width, area.height);

    ctx.fillStyle = "#EA2B1F";
    ctx.fillRect(applex * tp, appley * tp, tp, tp, 2, 2);

    //for ([expressaoInicial]; [condicao]; [incremento]) declaracao
    for (let i = 0; i < trail.length; i++) {
      ctx.fillStyle = "#09BC8A";
      ctx.strokeStyle = "#004346";
      ctx.fillRect(trail[i].x * tp, trail[i].y * tp, tp, tp);
      ctx.strokeRect(trail[i].x * tp, trail[i].y * tp, tp, tp);
      if (trail[i].x == px && trail[i].y == py)
      {
        vx = vy = 0;
        tail = 2;
        pontoi = 0;
      }
    }

    trail.push({ x: px, y: py });

    while (trail.length > tail) {
      trail.shift();
    }


    if (applex == px && appley == py) {
      tail++;
      applex = Math.floor(Math.random() * qp);
      appley = Math.floor(Math.random() * qp);

      pontos.innerHTML = ++pontoi;
    }


  }

  window.addEventListener("keydown", function (e) {
    // space and arrow keys
    if ([32, 37, 38, 39, 40].indexOf(e.keyCode) > -1) {
      e.preventDefault();
    }
  }, false);

  let lastKeyPressed = "";

  function keyPush(e) {
    switch (e.keyCode) {
      case 37: // Left
        if (lastKeyPressed != "right") {
          vx = -vel;
          vy = 0;
          lastKeyPressed = "left";
        }
        break;
      case 38: // up
        if (lastKeyPressed != "down") {
          vx = 0;
          vy = -vel;
          lastKeyPressed = "up";
        }
        break;
      case 39: // right
        if (lastKeyPressed != "left") {
          vx = vel;
          vy = 0;
          lastKeyPressed = "right";
        }
        break;
      case 40: // down
        if (lastKeyPressed != "up") {
          vx = 0;
          vy = vel;
          lastKeyPressed = "down";
        }
        break;}

  }

};
    </script>
</body>
</html>

4. By Canvas Snake Game

Made by Isaac. Source

<!DOCTYPE html>
<html lang="en" >
<head>
  <title></title> 
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css">
  <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Lobster'>  
<style>
* {
  margin: 0;
  padding: 0;
  outline: none;
  box-sizing: border-box;
}
html,
body {
  width: 100%;
  height: 100%;
  position: relative;
}
body {
  background: url("http://imageshack.com/a/img924/148/lo4sih.jpg") #83a41a no-repeat bottom center;
  background-size: cover;
  font-size: 62.5%;
}
.content {
  display: flex;
  flex-flow: column;
  align-items: center;
  position: relative;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  padding: 1em;
}
.content .title {
  font: 5em Lobster, cursive;
  text-shadow: 0 2px 0 rgba(0,0,0,0.5);
  color: #fafafa;
  margin: -0.5em 0 0.5em 0;
}
.content canvas {
  background: #fafafa;
  box-shadow: 0 2px 16px rgba(0,0,0,0.25);
}
</style>
</head>
<body>
  <section class="content">
  <h1 class="title">Snake Game</h1>
  <canvas id="canvas" width="640" height="480"></canvas>
</section>
      <script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var dir, score, snake, food;
var speed = 222;
var speedSnake = 20;

document.addEventListener("keydown", function (e) {
  var keyCode = e.keyCode;
  if (keyCode == 37 && dir != "right") {
    dir = "left";
  }
  if (keyCode == 38 && dir != "down") {
    dir = "up";
  }
  if (keyCode == 39 && dir != "left") {
    dir = "right";
  }
  if (keyCode == 40 && dir != "up") {
    dir = "down";
  }
});

setInterval(draw, speed);

function init() {
  dir = "right";
  score = 0;
  snake = [{ x: 40, y: 40 }, { x: 60, y: 40 }, { x: 80, y: 40 }];
  createFood();
}

function add() {
  var lastsnake = snake[snake.length - 1];

  if (dir == "right") {
    snake.push({ x: lastsnake.x + speedSnake, y: lastsnake.y });
  }
  if (dir == "down") {
    snake.push({ x: lastsnake.x, y: lastsnake.y + speedSnake });
  }
  if (dir == "left") {
    snake.push({ x: lastsnake.x - speedSnake, y: lastsnake.y });
  }
  if (dir == "up") {
    snake.push({ x: lastsnake.x, y: lastsnake.y - speedSnake });
  }
}

function createFood() {
  food = {
    x: Math.floor(Math.random() * 25),
    y: Math.floor(Math.random() * 25) };

  for (var i = 0; i < snake.length; i++) {
    var snake2 = snake[i];
    if (food.x == snake2.x && food.y == snake2.y) {
      createFood();
    }
  }
}

function draw() {
  ctx.clearRect(0, 0, 888, 555);
  snake.shift();
  add();

  var lastsnake = snake[snake.length - 1];

  if (lastsnake.x == food.x * 20 && lastsnake.y == food.y * 20) {
    score += 5;
    add();
    createFood();
  }

  for (i = 0; i < snake.length; i++) {
    snake2 = snake[i];
    if (i == snake.length - 1) {
      ctx.fillStyle = "#83a41a";
    } else {
      ctx.fillStyle = "#b4ce3a";
    }
    if (snake2.x > 640) {
      snake2.x = 0;
    }
    if (snake2.x < 0) {
      snake2.x = 640;
    }
    if (snake2.y > 480) {
      snake2.y = 0;
    }
    if (snake2.y < 0) {
      snake2.y = 480;
    }

    if (
    snake2.x == lastsnake.x &&
    snake2.y == lastsnake.y &&
    i < snake.length - 2)
    {
      alert("Fim de jogo, sua pontuação foi: " + score);
      init();
    }

    ctx.fillRect(snake2.x, snake2.y, 19, 19);
  }
  ctx.fillRect(food.x * 17, food.y * 20, 19, 19);
  ctx.fillText("Pontos: " + score, 10, 20);
}

requestAnimationFrame(init);
    </script>
</body>
</html>

5. Snake by Sergei Babko

Made by Segich. Source

<!DOCTYPE html>
<html lang="en" >
<head>
  <title></title>
<style>
.hidden {
  display: none !important;
}

body {
  margin: 0;
  height: 100vh;
  display: flex;
  overflow: hidden;
  user-select: none;
  color: #24cc44;
  display: flex;
  align-items: center;
  justify-content: center;
  text-transform: uppercase;
  background-color: #00dafe;
  font-family: "Press Start 2P";
}

.main {
  display: flex;
  height: 100vmin;
  position: absolute;
  justify-content: center;
  background-color: #00dafe;
  width: calc(100vmin / 0.75);
}
.main .name {
  top: 4%;
  color: white;
  z-index: 100;
  font-size: 5vmin;
  position: absolute;
  text-shadow: 0 1px #303030;
}
.main #best-score {
  top: 11%;
  color: white;
  z-index: 100;
  font-size: 3vmin;
  position: absolute;
  text-shadow: 0 1px #303030;
}
.main .screen-wrapper {
  top: 21%;
  width: 37.5%;
  height: 39.5%;
  overflow: hidden;
  position: absolute;
  background-color: #24cc44;
}
.main .screen-wrapper::before {
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  content: " ";
  position: absolute;
  background: linear-gradient(rgba(18, 16, 16, 0) 50%, rgba(0, 0, 0, 0.25) 50%), linear-gradient(90deg, rgba(255, 0, 0, 0.06), rgba(0, 255, 0, 0.02), rgba(0, 0, 255, 0.06));
  z-index: 1;
  pointer-events: none;
  background-size: 100% 2px, 3px 100%;
}
.main .screen-wrapper .screen {
  width: 100%;
  height: 100%;
  background-image: linear-gradient(180deg, #649664 0, #466446 30%, #141e14 100%);
  filter: blur(0.5px) brightness(1.3) saturate(1.3) sepia(0.3);
  animation: blur 30ms infinite, jerk 50ms infinite;
}
.main .screen-wrapper .screen #menu {
  width: 100%;
  height: 100%;
}
.main .screen-wrapper .screen #menu #start-menu {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  padding-top: 1.5vmin;
  flex-direction: column;
}
.main .screen-wrapper .screen #menu #start-menu .logo {
  width: 50%;
  height: 50%;
  background-size: contain;
  image-rendering: pixelated;
  background-position: center;
  background-repeat: no-repeat;
  background-image: url("https://i.ibb.co/T1rTvcS/Snake.png");
}
.main .screen-wrapper .screen #menu #start-menu #start-game,
.main .screen-wrapper .screen #menu #start-menu #paused-game {
  margin-top: 4vmin;
  font-size: 2.5vmin;
}
.main .screen-wrapper .screen #menu #start-menu #start-game {
  animation: button 1s infinite;
}
.main .screen-wrapper .screen #menu #start-menu .creds {
  z-index: 100;
  cursor: pointer;
  font-size: 1vmin;
  margin-top: 4vmin;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}
.main .screen-wrapper .screen #menu #start-menu .creds a {
  color: #24cc44;
  margin-bottom: 1vmin;
  text-decoration: none;
}
.main .screen-wrapper .screen #menu #score-menu {
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-direction: column;
}
.main .screen-wrapper .screen #menu #score-menu .logo {
  width: 30%;
  height: 30%;
  background-size: contain;
  image-rendering: pixelated;
  background-position: center;
  background-repeat: no-repeat;
  background-image: url("https://i.ibb.co/T1rTvcS/Snake.png");
}
.main .screen-wrapper .screen #menu #score-menu .game-over {
  margin-top: 4vmin;
  font-size: 3vmin;
}
.main .screen-wrapper .screen #menu #score-menu #menu-total-score,
.main .screen-wrapper .screen #menu #score-menu #menu-best-score,
.main .screen-wrapper .screen #menu #score-menu .play {
  font-size: 1.5vmin;
}
.main .screen-wrapper .screen #menu #score-menu #menu-total-score,
.main .screen-wrapper .screen #menu #score-menu .play {
  margin-top: 3vmin;
}
.main .screen-wrapper .screen #menu #score-menu #menu-best-score {
  margin-top: 1vmin;
}
.main .screen-wrapper .screen #menu #score-menu .play {
  animation: button 1s infinite;
}
.main .screen-wrapper .screen #snake-game {
  width: 100%;
  height: 100%;
}
.main .screen-wrapper .screen #snake-game .snake-game-wrapper {
  width: 100%;
  height: 100%;
}
.main .screen-wrapper .screen #snake-game .snake-game-wrapper #score {
  left: 3vmin;
  bottom: 2.5vmin;
  font-size: 2vmin;
  position: absolute;
}
.main .screen-wrapper .screen #snake-game .snake-game-wrapper #speed {
  right: 3vmin;
  bottom: 2.5vmin;
  font-size: 2vmin;
  position: absolute;
}
.main .screen-wrapper .screen #snake-game .snake-game-wrapper #game-field {
  width: 100%;
  height: 100%;
}
.main .pc {
  z-index: 10;
  width: 100%;
  height: 100%;
  background-size: contain;
  background-position: center;
  background-repeat: no-repeat;
  background-image: url("https://i.ibb.co/VN1NB7W/pc.png");
}
.main .description {
  bottom: 2%;
  opacity: 0.5;
  color: white;
  z-index: 100;
  line-height: 1.5;
  font-size: 1.75vmin;
  position: absolute;
  text-shadow: 0 1px #303030;
  transition: opacity 0.2s ease-in-out;
}
.main .description:hover {
  opacity: 1;
}

.copyright {
  color: #fff;
  z-index: 100;
  left: 1.5vmin;
  bottom: 1.5vmin;
  position: fixed;
  font-size: 1.5vmin;
  text-decoration: none;
  text-shadow: 0 1px #303030;
  transition: all 0.2s ease-in-out;
}
.copyright:hover {
  font-size: 1.7vmin;
}

audio {
  display: none;
}

@keyframes button {
  0% {
    transform: scale(1);
  }
  50% {
    transform: scale(0.9);
  }
}
@keyframes blur {
  0% {
    opacity: 0.9;
    filter: blur(0.5px) brightness(1.3) saturate(1.3) sepia(0.1);
  }
  50% {
    opacity: 1;
    filter: blur(0.5px) brightness(1.5) saturate(1.5) sepia(0.3);
  }
  100% {
    opacity: 0.9;
    filter: blur(0.5px) brightness(1.3) saturate(1.3) sepia(0.2);
  }
}
@keyframes jerk {
  50% {
    transform: translateX(1px);
  }
  80% {
    transform: translateX(0);
  }
}
</style>
</head>
<body>
  <link href="https://fonts.googleapis.com/css?family=Press%20Start%202P" rel='stylesheet' type='text/css'>

 <div class='main'>
   <div class='name'>~SNAKE~</div>
   <div id='best-score'>Best score: 0</div>
   <div class='pc'></div>
   <div class='screen-wrapper'>
     <div class='screen'>
       <div id='menu'>
         <div id='start-menu'>
           <div class='logo'></div>
           <div id='start-game'>Press 'Start'</div>
           <div id='paused-game' class='hidden'>Paused</div>
           <div class='creds'>
             <a href='https://clc.to/SBabko' target='_blank'>made by Sergei Babko</a>
             <span>2020</span>
           </div>
         </div>
         <div id='score-menu' class='hidden'>
           <div class='logo'></div>
           <div class='game-over'>Game over</div>
           <div id='menu-total-score'>Total score: 0</div>
           <div id='menu-best-score'>Best score: 0</div>
           <div class='play'>Press 'Start' to restart</div>
         </div>
       </div>
       <div id='snake-game' class='hidden'>
         <div class='snake-game-wrapper'>
           <div id='score'>Score: 0</div>
           <div id='speed'>Speed: 1</div>
           <canvas id='game-field'></canvas>
         </div>
       </div>
     </div>
   </div>
   <div class='description'>
     <div>Start: Space, Pause: Esc</div>
     <div>Move: arrow keys or WASD</div>
     <div>Run: Shift</div>
     <div>Reset score: Backspace</div>
     <div>Mute/Unmute: M</div>
   </div>
 </div>
 <a class='copyright' href='https://clc.to/SBabko' target='_blank'>Sergei Babko ©</a>
    <script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-1b93190375e9ccc259df3a57c1abc0e64599724ae30d7ea4c6877eb615f89387.js"></script>
      <script>
autoplaySound = true;
width = 640;
height = width * 0.75;
ceilSize = 30;

window.onload = function () {
  menuEl = document.getElementById("menu");
  startMenuEl = document.getElementById("start-menu");
  scoreMenuEl = document.getElementById("score-menu");
  scoreMenuTotalScoreEl = document.getElementById("menu-total-score");
  scoreMenuBestScoreEl = document.getElementById("menu-best-score");
  gameEl = document.getElementById("snake-game");

  startGameEl = document.getElementById("start-game");
  pausedGameEl = document.getElementById("paused-game");

  createSound('https://vk.com/doc92073475_569135407', 'mp3');
  createSound('https://vk.com/doc92073475_569136108', 'mp3');
  aud = createSound("https://patrickdearteaga.com/audio/Chiptronical.ogg", 'ogg');
  aud.volume = 0.5;
  if (autoplaySound)
  aud.play();
  aud.onended = () => aud.play();

  scoreEl = document.getElementById("score");
  speedEl = document.getElementById("speed");
  bestScoreEl = document.getElementById("best-score");

  canvas = document.getElementById("game-field");
  canvas.setAttribute("width", width + "px");
  canvas.setAttribute("height", height + "px");
  ctx = canvas.getContext("2d");

  document.addEventListener("keydown", keydown);
  document.addEventListener("keyup", keyup);

  updateBestScore();
};

// Best score
bestScore = 0;
// Worm initial speed
initialSpeed = 5;
// Worm current speed
currentSpeed = initialSpeed;
// Grid size
gs = Math.round(width / ceilSize);
// Ceil spacing
cc = Math.round(gs * 0.1);
// Tile count by y
tcx = Math.round(width / gs);
// Tile count by x
tcy = Math.round(height / gs);
// Player position x
px = Math.floor(tcx * 0.1);
// Player position x
py = Math.floor(tcy * 0.1);
// Apple to reach position x
ax = Math.floor(tcx * 0.5);
// Apple to reach position x
ay = Math.floor(tcy * 0.5);
// x and y velocity
xv = 1;
yv = 0;
// Trails
trail = [];
// Worm initial tails
initialTails = 4;
// Worm current tails
tails = initialTails;

// Temp
canPlay = false;
isPaused = false;
tempScore = 0;
tempSpeed = 0;
canReadBtn = true;
isRunning = false;
resetTimout = null;

startGame = () => {
  if (canPlay || isPaused) return;
  aud.play();
  canPlay = true;
  canReadBtn = true;
  menuEl.classList.add("hidden");
  gameEl.classList.remove("hidden");
  if (!startMenuEl.classList.contains("hidden"))
  startMenuEl.classList.add("hidden");
  if (!scoreMenuEl.classList.contains("hidden"))
  scoreMenuEl.classList.add("hidden");
  update();
};

endGame = () => {
  if (!canPlay || isPaused) return;
  aud.pause();
  aud.currentTime = 0;
  canPlay = false;
  canReadBtn = false;
  loseAud = createSound('https://vk.com/doc92073475_569136108', 'mp3').play();
  gameEl.classList.add("hidden");
  menuEl.classList.remove("hidden");
  if (scoreMenuEl.classList.contains("hidden"))
  scoreMenuEl.classList.remove("hidden");
  scoreMenuTotalScoreEl.innerHTML = `Total score: ${currentScore}`;
  tempScore = 0;
  currentScore = 0;
  tempSpeed = 0;
  currentSpeed = initialSpeed;
  isRunning = false;
  scoreEl.innerHTML = `Score: ${currentScore}`;
  speedEl.innerHTML = `Speed: ${initialSpeed}`;
  tails = initialTails;
  trail = [];
  xv = 1;
  yv = 0;
  px = Math.floor(tcx * 0.1);
  py = Math.floor(tcy * 0.1);
  ax = Math.floor(tcx * 0.5);
  ay = Math.floor(tcy * 0.5);
  ctx.clearRect(0, 0, canvas.width, canvas.height);
};

pauseGame = () => {
  if (!canPlay) return;
  isPaused = !isPaused;
  canReadBtn = !isPaused;
  if (isRunning)
  runFast(false);
  if (isPaused) {
    gameEl.classList.add("hidden");
    menuEl.classList.remove("hidden");
    startGameEl.classList.add("hidden");
    pausedGameEl.classList.remove("hidden");
    startMenuEl.classList.remove("hidden");
  } else {
    gameEl.classList.remove("hidden");
    menuEl.classList.add("hidden");
    startGameEl.classList.remove("hidden");
    pausedGameEl.classList.add("hidden");
    startMenuEl.classList.add("hidden");
    update();
  }
};

update = () => {
  if (!canPlay || isPaused) return;
  game();
  setTimeout(update, 1000 / currentSpeed);
};

game = () => {
  // Move player
  px += xv;
  py += yv;

  // Teleport player from corners
  if (px < 0) px = tcx - 1;
  if (py < 0) py = tcy - 1;
  if (px > tcx - 1) px = 0;
  if (py > tcy - 1) py = 0;

  // Clear canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  // Draw player in the canvas
  for (var i = 0; i < trail.length; i++) {if (window.CP.shouldStopExecution(0)) break;
    ctx.fillStyle = "#24cc44";
    ctx.fillRect(trail[i].x * gs, trail[i].y * gs, gs - cc, gs - cc);
    if (trail[i].x == px && trail[i].y == py) {
      endGame();
      return;
    }
  }window.CP.exitedLoop(0);
  trail.push({ x: px, y: py });
  while (trail.length > tails) {if (window.CP.shouldStopExecution(1)) break;
    trail.shift();
  }

  // Collect apple and move new one to the random position
  window.CP.exitedLoop(1);if (ax == px && ay == py) {
    collectAud = createSound('https://vk.com/doc92073475_569135407', 'mp3').play();
    tails++;
    ax = Math.floor(Math.random() * (tcx - 2));
    ay = Math.floor(Math.random() * (tcy - 4));
    ax = ax >= 2 ? ax : 2;
    ay = ay >= 2 ? ay : 2;
  }

  // Draw apple
  ctx.strokeStyle = "#24cc44";
  ctx.lineWidth = cc * 2;
  ctx.strokeRect(
  ax * gs + cc,
  ay * gs + cc,
  gs - cc * 2 - ctx.lineWidth / 2,
  gs - cc * 2 - ctx.lineWidth / 2);


  currentScore = tails - initialTails;
  // Update score
  if (currentScore != tempScore) {
    tempScore = currentScore;
    scoreEl.innerHTML = `Score: ${currentScore}`;
    if (bestScore < currentScore) updateBestScore(currentScore);
    if (currentScore == 0) {
      tempSpeed = initialSpeed;
      currentSpeed = initialSpeed;
      isRunning = false;
    }
    // Update speed after earn new 5 apples
    if (currentScore != 0 && currentScore % 5 == 0) {
      if (isRunning) {
        runFast(false);
        currentSpeed++;
        runFast(true);
      } else currentSpeed++;
    }
  }
  speedEl.innerHTML = `Speed: ${
  currentSpeed == initialSpeed ?
  1 :
  (isRunning ? currentSpeed / 2 : currentSpeed) - initialSpeed + 1
  }`;
  canReadBtn = true;
};

keydown = event => {
  if (event.keyCode == 38 || event.keyCode == 87) turnUp();else
  if (event.keyCode == 39 || event.keyCode == 68) turnRight();else
  if (event.keyCode == 40 || event.keyCode == 83) turnDown();else
  if (event.keyCode == 37 || event.keyCode == 65) turnLeft();else
  if (event.keyCode == 16) runFast(true);else
  if (event.keyCode == 8) resetScore();else
  if (event.keyCode == 77) mute();else
  if (event.keyCode == 32) startGame();else
  if (event.keyCode == 27) pauseGame();
};

keyup = event => {
  if (!canPlay || !canReadBtn) return;
  if (event.keyCode == 16) runFast(false);
};

turnUp = () => {
  if (yv == 1 || !canReadBtn || !canPlay) return;
  xv = 0;
  yv = -1;
  canReadBtn = false;
};

turnLeft = () => {
  if (xv == 1 || !canReadBtn || !canPlay) return;
  xv = -1;
  yv = 0;
  canReadBtn = false;
};

turnRight = () => {
  if (xv == -1 || !canReadBtn || !canPlay) return;
  xv = 1;
  yv = 0;
  canReadBtn = false;
};

turnDown = () => {
  if (yv == -1 || !canReadBtn || !canPlay) return;
  xv = 0;
  yv = 1;
  canReadBtn = false;
};

runFast = run => {
  if (isRunning && run || !canReadBtn || !canPlay) return;
  if (!isRunning && run) {
    tempSpeed = currentSpeed;
    currentSpeed = currentSpeed * 2;
  } else if (isRunning && !run) {
    halfRSpeed = currentSpeed / 2;
    currentSpeed =
    tempSpeed == halfRSpeed ?
    tempSpeed :
    tempSpeed + Math.abs(halfRSpeed - tempSpeed);
  }
  isRunning = run;
};

updateBestScore = score => {
  if (score != null) {
    localStorage.setItem("bestScore", score);
    bestScore = score != null ? score : 0;
  } else {
    storageScore = localStorage.getItem("bestScore");
    bestScore = storageScore != null ? storageScore : 0;
  }
  bestScoreEl.innerHTML = `Best score: ${bestScore}`;
  scoreMenuBestScoreEl.innerHTML = `Best score: ${bestScore}`;
};

resetScore = () => {
  if (resetTimout != null) return;
  updateBestScore(0);
  resetTimout = setTimeout(() => {
    resetTimout = null;
  }, 1000);
};

mute = () => aud.muted = !aud.muted;

createSound = (src, type) => {
  sound = document.createElement("audio");
  sound.src = src;
  sound.type = "audio/" + type;
  sound.load();
  return sound;
};
    </script>
</body>
</html>

6. Snake Game CSS Renderer

Made by Jack Rugile. Source

<!DOCTYPE html>
<html lang="en" >
<head>
  <title></title>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/modernizr/2.8.3/modernizr.min.js" type="text/javascript"></script>
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/meyer-reset/2.0/reset.min.css">
<style>
/*================================================

General

================================================*/

* {
  box-sizing: border-box;
}

html,
body {
  background-color: #000;
  height: 100%;
}

body {
  background: #222;
  background: radial-gradient(#333, #111);
  background-position: center center;
  background-repeat: no-repeat;
  background-size: cover;
  color: #fff;
  font: 100%/1.5 sans-serif;
  overflow: hidden;
}

/*================================================

Score

================================================*/

.score {
  color: rgba(255, 255, 255, 0.5);
  font-size: 16px;
  font-weight: bold;
  padding-top: 5px;
  text-align: center;
}

/*================================================

Stage

================================================*/

.stage {
  bottom: 0;
  left: 0;
  margin: auto;
  position: absolute;
  right: 0;
  top: 0;
  z-index: 2;
}

/*================================================

Tiles

================================================*/

.tile {
  background: rgba(0, 0, 0, 0.15);
  position: absolute;
  transition-property:
    background,
    box-shadow,
    opacity,
    transform
  ;
  transform: translateZ(0);
  transition-duration: 3000ms;
}

.tile:before {
  bottom: 0;
  content: '';
  height: 0;
  left: 0;
  margin: auto;
  opacity: 0;
  position: absolute;
  right: 0;
  top: 0;
  width: 0;
  transition: opacity 300ms;
}

.tile.path:before {
  opacity: 1;
}

.tile.up:before {
  border-bottom: 4px inset rgba(255, 255, 255, 0.15);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
}

.tile.down:before {
  border-top: 4px inset rgba(255, 255, 255, 0.15);
  border-left: 4px solid transparent;
  border-right: 4px solid transparent;
}

.tile.left:before { 
  border-right: 4px inset rgba(255, 255, 255, 0.15);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}

.tile.right:before { 
  border-left: 4px inset rgba(255, 255, 255, 0.15);
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
}

@media (max-width: 900px), (max-height: 900px) {
  .tile.up:before,
  .tile.down:before,
  .tile.left:before,
  .tile.right:before {
    border-width: 3px;
  }
}

@media (max-width: 500px), (max-height: 500px) {
  .tile.up:before,
  .tile.down:before,
  .tile.left:before,
  .tile.right:before {
    border-width: 2px;
  }
}

.tile.pressed {
  background: rgba(0, 0, 0, 0.3);
  box-shadow: inset 0 0 10px rgba(0, 0, 0, 0.6);
  transition-duration: 0ms;
}
</style>
</head>
<body>
  <div class="score">0</div>
<div class="stage"></div>
    <script src="https://cpwebassets.codepen.io/assets/common/stopExecutionOnTimeout-1b93190375e9ccc259df3a57c1abc0e64599724ae30d7ea4c6877eb615f89387.js"></script>
      <script>
/*================================================

Polyfill

================================================*/

(function () {'use strict';

  /*================================================
   Request Animation Frame
   ================================================*/



  var lastTime = 0;
  var vendors = ['webkit', 'moz'];
  for (var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
    window.requestAnimationFrame = window[vendors[x] + 'RequestAnimationFrame'];
    window.cancelAnimationFrame = window[vendors[x] + 'CancelAnimationFrame'] || window[vendors[x] + 'CancelRequestAnimationFrame'];
  }

  if (!window.requestAnimationFrame) {
    window.requestAnimationFrame = function (callback, element) {
      var currTime = new Date().getTime();
      var timeToCall = Math.max(0, 16 - (currTime - lastTime));
      var id = window.setTimeout(
      function () {
        callback(currTime + timeToCall);
      }, timeToCall);
      lastTime = currTime + timeToCall;
      return id;
    };
  }

  if (!window.cancelAnimationFrame) {
    window.cancelAnimationFrame = function (id) {
      clearTimeout(id);
    };
  }

})();

/*================================================

DOM Manipulation

================================================*/

(function () {'use strict';

  function hasClass(elem, className) {
    return new RegExp(' ' + className + ' ').test(' ' + elem.className + ' ');
  };

  function addClass(elem, className) {
    if (!hasClass(elem, className)) {
      elem.className += ' ' + className;
    }
  };

  function removeClass(elem, className) {
    var newClass = ' ' + elem.className.replace(/[\t\r\n]/g, ' ') + ' ';
    if (hasClass(elem, className)) {
      while (newClass.indexOf(' ' + className + ' ') >= 0) {
        newClass = newClass.replace(' ' + className + ' ', ' ');
      }
      elem.className = newClass.replace(/^\s+|\s+$/g, '');
    }
  };

  function toggleClass(elem, className) {
    var newClass = ' ' + elem.className.replace(/[\t\r\n]/g, ' ') + ' ';
    if (hasClass(elem, className)) {
      while (newClass.indexOf(' ' + className + ' ') >= 0) {
        newClass = newClass.replace(' ' + className + ' ', ' ');
      }
      elem.className = newClass.replace(/^\s+|\s+$/g, '');
    } else {
      elem.className += ' ' + className;
    }
  };

})();

/*================================================

Core

================================================*/

g = {};

(function () {'use strict';

  /*================================================
   Math
   ================================================*/



  g.m = Math;
  g.mathProps = 'E LN10 LN2 LOG2E LOG10E PI SQRT1_2 SQRT2 abs acos asin atan ceil cos exp floor log round sin sqrt tan atan2 pow max min'.split(' ');
  for (var i = 0; i < g.mathProps.length; i++) {
    g[g.mathProps[i]] = g.m[g.mathProps[i]];
  }
  g.m.TWO_PI = g.m.PI * 2;

  /*================================================
   Miscellaneous
   ================================================*/



  g.isset = function (prop) {
    return typeof prop != 'undefined';
  };

  g.log = function () {
    if (g.isset(g.config) && g.config.debug && window.console) {
      console.log(Array.prototype.slice.call(arguments));
    }
  };

})();

/*================================================

Group

================================================*/

(function () {'use strict';

  g.Group = function () {
    this.collection = [];
    this.length = 0;
  };

  g.Group.prototype.add = function (item) {
    this.collection.push(item);
    this.length++;
  };

  g.Group.prototype.remove = function (index) {
    if (index < this.length) {
      this.collection.splice(index, 1);
      this.length--;
    }
  };

  g.Group.prototype.empty = function () {
    this.collection.length = 0;
    this.length = 0;
  };

  g.Group.prototype.each = function (action, asc) {
    var asc = asc || 0,
    i;
    if (asc) {
      for (i = 0; i < this.length; i++) {
        this.collection[i][action](i);
      }
    } else {
      i = this.length;
      while (i--) {
        this.collection[i][action](i);
      }
    }
  };

})();

/*================================================

Utilities

================================================*/

(function () {'use strict';

  g.util = {};

  /*================================================
   Random
   ================================================*/



  g.util.rand = function (min, max) {
    return g.m.random() * (max - min) + min;
  };

  g.util.randInt = function (min, max) {
    return g.m.floor(g.m.random() * (max - min + 1)) + min;
  };

})();

/*================================================

State

================================================*/

(function () {'use strict';

  g.states = {};

  g.addState = function (state) {
    g.states[state.name] = state;
  };

  g.setState = function (name) {
    if (g.state) {
      g.states[g.state].exit();
    }
    g.state = name;
    g.states[g.state].init();
  };

  g.currentState = function () {
    return g.states[g.state];
  };

})();

/*================================================

Time

================================================*/

(function () {'use strict';

  g.Time = function () {
    this.reset();
  };

  g.Time.prototype.reset = function () {
    this.now = Date.now();
    this.last = Date.now();
    this.delta = 60;
    this.ndelta = 1;
    this.elapsed = 0;
    this.nelapsed = 0;
    this.tick = 0;
  };

  g.Time.prototype.update = function () {
    this.now = Date.now();
    this.delta = this.now - this.last;
    this.ndelta = Math.min(Math.max(this.delta / (1000 / 60), 0.0001), 10);
    this.elapsed += this.delta;
    this.nelapsed += this.ndelta;
    this.last = this.now;
    this.tick++;
  };

})();

/*================================================

Grid Entity

================================================*/

(function () {'use strict';

  g.Grid = function (cols, rows) {
    this.cols = cols;
    this.rows = rows;
    this.tiles = [];
    for (var x = 0; x < cols; x++) {
      this.tiles[x] = [];
      for (var y = 0; y < rows; y++) {
        this.tiles[x].push('empty');
      }
    }
  };

  g.Grid.prototype.get = function (x, y) {
    return this.tiles[x][y];
  };

  g.Grid.prototype.set = function (x, y, val) {
    this.tiles[x][y] = val;
  };

})();

/*================================================

Board Tile Entity

================================================*/

(function () {'use strict';

  g.BoardTile = function (opt) {
    this.parentState = opt.parentState;
    this.parentGroup = opt.parentGroup;
    this.col = opt.col;
    this.row = opt.row;
    this.x = opt.x;
    this.y = opt.y;
    this.z = 0;
    this.w = opt.w;
    this.h = opt.h;
    this.elem = document.createElement('div');
    this.elem.style.position = 'absolute';
    this.elem.className = 'tile';
    this.parentState.stageElem.appendChild(this.elem);
    this.classes = {
      pressed: 0,
      path: 0,
      up: 0,
      down: 0,
      left: 0,
      right: 0 };

    this.updateDimensions();
  };

  g.BoardTile.prototype.update = function () {
    for (var k in this.classes) {
      if (this.classes[k]) {
        this.classes[k]--;
      }
    }

    if (this.parentState.food.tile.col == this.col || this.parentState.food.tile.row == this.row) {
      this.classes.path = 1;
      if (this.col < this.parentState.food.tile.col) {
        this.classes.right = 1;
      } else {
        this.classes.right = 0;
      }
      if (this.col > this.parentState.food.tile.col) {
        this.classes.left = 1;
      } else {
        this.classes.left = 0;
      }
      if (this.row > this.parentState.food.tile.row) {
        this.classes.up = 1;
      } else {
        this.classes.up = 0;
      }
      if (this.row < this.parentState.food.tile.row) {
        this.classes.down = 1;
      } else {
        this.classes.down = 0;
      }
    } else {
      this.classes.path = 0;
    }

    if (this.parentState.food.eaten) {
      this.classes.path = 0;
    }
  };

  g.BoardTile.prototype.updateDimensions = function () {
    this.x = this.col * this.parentState.tileWidth;
    this.y = this.row * this.parentState.tileHeight;
    this.w = this.parentState.tileWidth - this.parentState.spacing;
    this.h = this.parentState.tileHeight - this.parentState.spacing;
    this.elem.style.left = this.x + 'px';
    this.elem.style.top = this.y + 'px';
    this.elem.style.width = this.w + 'px';
    this.elem.style.height = this.h + 'px';
  };

  g.BoardTile.prototype.render = function () {
    var classString = '';
    for (var k in this.classes) {
      if (this.classes[k]) {
        classString += k + ' ';
      }
    }
    this.elem.className = 'tile ' + classString;
  };

})();

/*================================================

Snake Tile Entity

================================================*/

(function () {'use strict';

  g.SnakeTile = function (opt) {
    this.parentState = opt.parentState;
    this.parentGroup = opt.parentGroup;
    this.col = opt.col;
    this.row = opt.row;
    this.x = opt.x;
    this.y = opt.y;
    this.w = opt.w;
    this.h = opt.h;
    this.color = null;
    this.scale = 1;
    this.rotation = 0;
    this.blur = 0;
    this.alpha = 1;
    this.borderRadius = 0;
    this.borderRadiusAmount = 0;
    this.elem = document.createElement('div');
    this.elem.style.position = 'absolute';
    this.parentState.stageElem.appendChild(this.elem);
  };

  g.SnakeTile.prototype.update = function (i) {
    this.x = this.col * this.parentState.tileWidth;
    this.y = this.row * this.parentState.tileHeight;
    if (i == 0) {
      this.color = '#fff';
      this.blur = this.parentState.dimAvg * 0.03 + Math.sin(this.parentState.time.elapsed / 200) * this.parentState.dimAvg * 0.015;
      if (this.parentState.snake.dir == 'n') {
        this.borderRadius = this.borderRadiusAmount + '% ' + this.borderRadiusAmount + '% 0 0';
      } else if (this.parentState.snake.dir == 's') {
        this.borderRadius = '0 0 ' + this.borderRadiusAmount + '% ' + this.borderRadiusAmount + '%';
      } else if (this.parentState.snake.dir == 'e') {
        this.borderRadius = '0 ' + this.borderRadiusAmount + '% ' + this.borderRadiusAmount + '% 0';
      } else if (this.parentState.snake.dir == 'w') {
        this.borderRadius = this.borderRadiusAmount + '% 0 0 ' + this.borderRadiusAmount + '%';
      }
    } else {
      this.color = '#fff';
      this.blur = 0;
      this.borderRadius = '0';
    }
    this.alpha = 1 - i / this.parentState.snake.tiles.length * 0.6;
    this.rotation = this.parentState.snake.justAteTick / this.parentState.snake.justAteTickMax * 90;
    this.scale = 1 + this.parentState.snake.justAteTick / this.parentState.snake.justAteTickMax * 1;
  };

  g.SnakeTile.prototype.updateDimensions = function () {
    this.w = this.parentState.tileWidth - this.parentState.spacing;
    this.h = this.parentState.tileHeight - this.parentState.spacing;
  };

  g.SnakeTile.prototype.render = function (i) {
    this.elem.style.left = this.x + 'px';
    this.elem.style.top = this.y + 'px';
    this.elem.style.width = this.w + 'px';
    this.elem.style.height = this.h + 'px';
    this.elem.style.backgroundColor = 'rgba(255, 255, 255, ' + this.alpha + ')';
    this.elem.style.boxShadow = '0 0 ' + this.blur + 'px #fff';
    this.elem.style.borderRadius = this.borderRadius;
  };

})();

/*================================================

Food Tile Entity

================================================*/

(function () {'use strict';

  g.FoodTile = function (opt) {
    this.parentState = opt.parentState;
    this.parentGroup = opt.parentGroup;
    this.col = opt.col;
    this.row = opt.row;
    this.x = opt.x;
    this.y = opt.y;
    this.w = opt.w;
    this.h = opt.h;
    this.blur = 0;
    this.scale = 1;
    this.hue = 100;
    this.opacity = 0;
    this.elem = document.createElement('div');
    this.elem.style.position = 'absolute';
    this.parentState.stageElem.appendChild(this.elem);
  };

  g.FoodTile.prototype.update = function () {
    this.x = this.col * this.parentState.tileWidth;
    this.y = this.row * this.parentState.tileHeight;
    this.blur = this.parentState.dimAvg * 0.03 + Math.sin(this.parentState.time.elapsed / 200) * this.parentState.dimAvg * 0.015;
    this.scale = 0.8 + Math.sin(this.parentState.time.elapsed / 200) * 0.2;

    if (this.parentState.food.birthTick || this.parentState.food.deathTick) {
      if (this.parentState.food.birthTick) {
        this.opacity = 1 - this.parentState.food.birthTick / 1 * 1;
      } else {
        this.opacity = this.parentState.food.deathTick / 1 * 1;
      }
    } else {
      this.opacity = 1;
    }
  };

  g.FoodTile.prototype.updateDimensions = function () {
    this.w = this.parentState.tileWidth - this.parentState.spacing;
    this.h = this.parentState.tileHeight - this.parentState.spacing;
  };

  g.FoodTile.prototype.render = function () {
    this.elem.style.left = this.x + 'px';
    this.elem.style.top = this.y + 'px';
    this.elem.style.width = this.w + 'px';
    this.elem.style.height = this.h + 'px';
    this.elem.style['transform'] = 'translateZ(0) scale(' + this.scale + ')';
    this.elem.style.backgroundColor = 'hsla(' + this.hue + ', 100%, 60%, 1)';
    this.elem.style.boxShadow = '0 0 ' + this.blur + 'px hsla(' + this.hue + ', 100%, 60%, 1)';
    this.elem.style.opacity = this.opacity;
  };

})();

/*================================================

Snake Entity

================================================*/

(function () {'use strict';

  g.Snake = function (opt) {
    this.parentState = opt.parentState;
    this.dir = 'e',
    this.currDir = this.dir;
    this.tiles = [];
    for (var i = 0; i < 5; i++) {
      this.tiles.push(new g.SnakeTile({
        parentState: this.parentState,
        parentGroup: this.tiles,
        col: 8 - i,
        row: 3,
        x: (8 - i) * opt.parentState.tileWidth,
        y: 3 * opt.parentState.tileHeight,
        w: opt.parentState.tileWidth - opt.parentState.spacing,
        h: opt.parentState.tileHeight - opt.parentState.spacing }));

    }
    this.last = 0;
    this.updateTick = 10;
    this.updateTickMax = this.updateTick;
    this.updateTickLimit = 3;
    this.updateTickChange = 0.2;
    this.deathFlag = 0;
    this.justAteTick = 0;
    this.justAteTickMax = 1;
    this.justAteTickChange = 0.05;

    // sync data grid of the play state
    var i = this.tiles.length;

    while (i--) {
      this.parentState.grid.set(this.tiles[i].col, this.tiles[i].row, 'snake');
    }
  };

  g.Snake.prototype.updateDimensions = function () {
    var i = this.tiles.length;
    while (i--) {
      this.tiles[i].updateDimensions();
    }
  };

  g.Snake.prototype.update = function () {
    if (this.parentState.keys.up) {
      if (this.dir != 's' && this.dir != 'n' && this.currDir != 's' && this.currDir != 'n') {
        this.dir = 'n';
      }
    } else if (this.parentState.keys.down) {
      if (this.dir != 'n' && this.dir != 's' && this.currDir != 'n' && this.currDir != 's') {
        this.dir = 's';
      }
    } else if (this.parentState.keys.right) {
      if (this.dir != 'w' && this.dir != 'e' && this.currDir != 'w' && this.currDir != 'e') {
        this.dir = 'e';
      }
    } else if (this.parentState.keys.left) {
      if (this.dir != 'e' && this.dir != 'w' && this.currDir != 'e' && this.currDir != 'w') {
        this.dir = 'w';
      }
    }

    this.parentState.keys.up = 0;
    this.parentState.keys.down = 0;
    this.parentState.keys.right = 0;
    this.parentState.keys.left = 0;

    this.updateTick += this.parentState.time.ndelta;
    if (this.updateTick >= this.updateTickMax) {
      // reset the update timer to 0, or whatever leftover there is
      this.updateTick = this.updateTick - this.updateTickMax;

      // rotate snake block array
      this.tiles.unshift(new g.SnakeTile({
        parentState: this.parentState,
        parentGroup: this.tiles,
        col: this.tiles[0].col,
        row: this.tiles[0].row,
        x: this.tiles[0].col * this.parentState.tileWidth,
        y: this.tiles[0].row * this.parentState.tileHeight,
        w: this.parentState.tileWidth - this.parentState.spacing,
        h: this.parentState.tileHeight - this.parentState.spacing }));

      this.last = this.tiles.pop();
      this.parentState.stageElem.removeChild(this.last.elem);

      this.parentState.boardTiles.collection[this.last.col + this.last.row * this.parentState.cols].classes.pressed = 2;

      // sync data grid of the play state
      var i = this.tiles.length;

      while (i--) {
        this.parentState.grid.set(this.tiles[i].col, this.tiles[i].row, 'snake');
      }
      this.parentState.grid.set(this.last.col, this.last.row, 'empty');


      // move the snake's head
      if (this.dir == 'n') {
        this.currDir = 'n';
        this.tiles[0].row -= 1;
      } else if (this.dir == 's') {
        this.currDir = 's';
        this.tiles[0].row += 1;
      } else if (this.dir == 'w') {
        this.currDir = 'w';
        this.tiles[0].col -= 1;
      } else if (this.dir == 'e') {
        this.currDir = 'e';
        this.tiles[0].col += 1;
      }

      // wrap walls
      this.wallFlag = false;
      if (this.tiles[0].col >= this.parentState.cols) {
        this.tiles[0].col = 0;
        this.wallFlag = true;
      }
      if (this.tiles[0].col < 0) {
        this.tiles[0].col = this.parentState.cols - 1;
        this.wallFlag = true;
      }
      if (this.tiles[0].row >= this.parentState.rows) {
        this.tiles[0].row = 0;
        this.wallFlag = true;
      }
      if (this.tiles[0].row < 0) {
        this.tiles[0].row = this.parentState.rows - 1;
        this.wallFlag = true;
      }

      // check death by eating self
      if (this.parentState.grid.get(this.tiles[0].col, this.tiles[0].row) == 'snake') {
        this.deathFlag = 1;
        clearTimeout(this.foodCreateTimeout);
      }

      // check eating of food
      if (this.parentState.grid.get(this.tiles[0].col, this.tiles[0].row) == 'food') {
        this.tiles.push(new g.SnakeTile({
          parentState: this.parentState,
          parentGroup: this.tiles,
          col: this.last.col,
          row: this.last.row,
          x: this.last.col * this.parentState.tileWidth,
          y: this.last.row * this.parentState.tileHeight,
          w: this.parentState.tileWidth - this.parentState.spacing,
          h: this.parentState.tileHeight - this.parentState.spacing }));

        if (this.updateTickMax - this.updateTickChange > this.updateTickLimit) {
          this.updateTickMax -= this.updateTickChange;
        }
        this.parentState.score++;
        this.parentState.scoreElem.innerHTML = this.parentState.score;
        this.justAteTick = this.justAteTickMax;

        this.parentState.food.eaten = 1;
        this.parentState.stageElem.removeChild(this.parentState.food.tile.elem);

        var _this = this;

        this.foodCreateTimeout = setTimeout(function () {
          _this.parentState.food = new g.Food({
            parentState: _this.parentState });

        }, 300);
      }

      // check death by eating self
      if (this.deathFlag) {
        g.setState('play');
      }
    }

    // update individual snake tiles
    var i = this.tiles.length;
    while (i--) {
      this.tiles[i].update(i);
    }

    if (this.justAteTick > 0) {
      this.justAteTick -= this.justAteTickChange;
    } else if (this.justAteTick < 0) {
      this.justAteTick = 0;
    }
  };

  g.Snake.prototype.render = function () {
    // render individual snake tiles
    var i = this.tiles.length;
    while (i--) {
      this.tiles[i].render(i);
    }
  };

})();

/*================================================

Food Entity

================================================*/

(function () {'use strict';

  g.Food = function (opt) {
    this.parentState = opt.parentState;
    this.tile = new g.FoodTile({
      parentState: this.parentState,
      col: 0,
      row: 0,
      x: 0,
      y: 0,
      w: opt.parentState.tileWidth - opt.parentState.spacing,
      h: opt.parentState.tileHeight - opt.parentState.spacing });

    this.reset();
    this.eaten = 0;
    this.birthTick = 1;
    this.deathTick = 0;
    this.birthTickChange = 0.025;
    this.deathTickChange = 0.05;
  };

  g.Food.prototype.reset = function () {
    var empty = [];
    for (var x = 0; x < this.parentState.cols; x++) {
      for (var y = 0; y < this.parentState.rows; y++) {
        var tile = this.parentState.grid.get(x, y);
        if (tile == 'empty') {
          empty.push({ x: x, y: y });
        }
      }
    }
    var newTile = empty[g.util.randInt(0, empty.length - 1)];
    this.tile.col = newTile.x;
    this.tile.row = newTile.y;
  };

  g.Food.prototype.updateDimensions = function () {
    this.tile.updateDimensions();
  };

  g.Food.prototype.update = function () {
    // update food tile
    this.tile.update();

    if (this.birthTick > 0) {
      this.birthTick -= this.birthTickChange;
    } else if (this.birthTick < 0) {
      this.birthTick = 0;
    }

    // sync data grid of the play state
    this.parentState.grid.set(this.tile.col, this.tile.row, 'food');
  };

  g.Food.prototype.render = function () {
    this.tile.render();
  };

})();

/*================================================

Play State

================================================*/

(function () {'use strict';

  function StatePlay() {
    this.name = 'play';
  }

  StatePlay.prototype.init = function () {
    this.scoreElem = document.querySelector('.score');
    this.stageElem = document.querySelector('.stage');
    this.dimLong = 28;
    this.dimShort = 16;
    this.padding = 0.25;
    this.boardTiles = new g.Group();
    this.keys = {};
    this.foodCreateTimeout = null;
    this.score = 0;
    this.scoreElem.innerHTML = this.score;
    this.time = new g.Time();
    this.getDimensions();
    if (this.winWidth < this.winHeight) {
      this.rows = this.dimLong;
      this.cols = this.dimShort;
    } else {
      this.rows = this.dimShort;
      this.cols = this.dimLong;
    }
    this.spacing = 1;
    this.grid = new g.Grid(this.cols, this.rows);
    this.resize();
    this.createBoardTiles();
    this.bindEvents();
    this.snake = new g.Snake({
      parentState: this });

    this.food = new g.Food({
      parentState: this });

  };

  StatePlay.prototype.getDimensions = function () {
    this.winWidth = window.innerWidth;
    this.winHeight = window.innerHeight;
    this.activeWidth = this.winWidth - this.winWidth * this.padding;
    this.activeHeight = this.winHeight - this.winHeight * this.padding;
  };

  StatePlay.prototype.resize = function () {
    var _this = g.currentState();

    _this.getDimensions();

    _this.stageRatio = _this.rows / _this.cols;

    if (_this.activeWidth > _this.activeHeight / _this.stageRatio) {
      _this.stageHeight = _this.activeHeight;
      _this.stageElem.style.height = _this.stageHeight + 'px';
      _this.stageWidth = Math.floor(_this.stageHeight / _this.stageRatio);
      _this.stageElem.style.width = _this.stageWidth + 'px';
    } else {
      _this.stageWidth = _this.activeWidth;
      _this.stageElem.style.width = _this.stageWidth + 'px';
      _this.stageHeight = Math.floor(_this.stageWidth * _this.stageRatio);
      _this.stageElem.style.height = _this.stageHeight + 'px';
    }

    _this.tileWidth = ~~(_this.stageWidth / _this.cols);
    _this.tileHeight = ~~(_this.stageHeight / _this.rows);
    _this.dimAvg = (_this.activeWidth + _this.activeHeight) / 2;
    _this.spacing = Math.max(1, ~~(_this.dimAvg * 0.0025));

    _this.stageElem.style.marginTop = -_this.stageElem.offsetHeight / 2 + _this.headerHeight / 2 + 'px';

    _this.boardTiles.each('updateDimensions');
    _this.snake !== undefined && _this.snake.updateDimensions();
    _this.food !== undefined && _this.food.updateDimensions();
  };

  StatePlay.prototype.createBoardTiles = function () {
    for (var y = 0; y < this.rows; y++) {
      for (var x = 0; x < this.cols; x++) {
        this.boardTiles.add(new g.BoardTile({
          parentState: this,
          parentGroup: this.boardTiles,
          col: x,
          row: y,
          x: x * this.tileWidth,
          y: y * this.tileHeight,
          w: this.tileWidth - this.spacing,
          h: this.tileHeight - this.spacing }));

      }
    }
  };

  StatePlay.prototype.upOn = function () {g.currentState().keys.up = 1;};
  StatePlay.prototype.downOn = function () {g.currentState().keys.down = 1;};
  StatePlay.prototype.rightOn = function () {g.currentState().keys.right = 1;};
  StatePlay.prototype.leftOn = function () {g.currentState().keys.left = 1;};
  StatePlay.prototype.upOff = function () {g.currentState().keys.up = 0;};
  StatePlay.prototype.downOff = function () {g.currentState().keys.down = 0;};
  StatePlay.prototype.rightOff = function () {g.currentState().keys.right = 0;};
  StatePlay.prototype.leftOff = function () {g.currentState().keys.left = 0;};

  StatePlay.prototype.keydown = function (e) {
    e.preventDefault();
    var e = e.keyCode ? e.keyCode : e.which,
    _this = g.currentState();
    if (e === 38 || e === 87) {_this.upOn();}
    if (e === 39 || e === 68) {_this.rightOn();}
    if (e === 40 || e === 83) {_this.downOn();}
    if (e === 37 || e === 65) {_this.leftOn();}
  };

  StatePlay.prototype.bindEvents = function () {
    var _this = g.currentState();
    window.addEventListener('keydown', _this.keydown, false);
    window.addEventListener('resize', _this.resize, false);
  };

  StatePlay.prototype.step = function () {
    this.boardTiles.each('update');
    this.boardTiles.each('render');
    this.snake.update();
    this.snake.render();
    this.food.update();
    this.food.render();
    this.time.update();
  };

  StatePlay.prototype.exit = function () {
    window.removeEventListener('keydown', this.keydown, false);
    window.removeEventListener('resize', this.resize, false);
    this.stageElem.innerHTML = '';
    this.grid.tiles = null;
    this.time = null;
  };

  g.addState(new StatePlay());

})();
(function () {'use strict';

  g.config = {
    title: 'Snakely',
    debug: window.location.hash == '#debug' ? 1 : 0,
    state: 'play' };


  g.setState(g.config.state);

  g.time = new g.Time();

  g.step = function () {
    requestAnimationFrame(g.step);
    g.states[g.state].step();
    g.time.update();
  };

  window.addEventListener('load', g.step, false);

})();
    </script>
</body>
</html>