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>