1
0
forked from sent/waves
waves-fork/public/assets/g/snake/js/game.js
2025-04-09 17:11:14 -05:00

461 lines
12 KiB
JavaScript

let
canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
scoreBlock = document.getElementById('score'),
scoreCount = 0,
bestScoreBlock = document.getElementById('best-score'),
dir = '', // snake direction
diff = 'Easy', // difficulty
diffBlock = document.getElementById('difficulty'),
btnChange = document.getElementById('changeDif');
const config = { // General setting
sizeCell: 24,
sizeFood: 24,
step: 0,
stepMax: 7,
}
const snake = { // Snake settings
x: config.sizeCell,
y: config.sizeCell,
dirX: 0, // direction X
dirY: 0, // direction Y
body: [],
maxBodySize: 1,
}
const snakeSkins = [ // array snake skins
'./img/snake/head.svg',
];
const snakeImages = [
imgHead = new Image(),
];
for ( let i = 0; i < snakeImages.length; i++ ) {
snakeImages[i].src = snakeSkins[i];
}
const food = { // food settings
x: randomInt(0, canvas.width / config.sizeCell) * config.sizeCell,
y: randomInt(0, canvas.height / config.sizeCell) * config.sizeCell,
}
const images = [ // array with images path
'./img/food/apple.svg',
'./img/food/carrot.svg',
'./img/food/eggplant.svg',
'./img/food/banana.svg',
];
let img = new Image();
img.src = images[0]; // the default will be the first image
const bomb = { // bomb settings
x: -config.sizeCell, // the default bomb will be hidden
y: -config.sizeCell,
};
const bombImg = new Image();
bombImg.src = './img/food/bomb.svg';
// аудио
const audio = [ // array with audio
'./audio/eat.mp3',
'./audio/turn.mp3',
'./audio/dead.mp3',
'./audio/hit.mp3',
];
const audioNames = [
audioEat = new Audio(),
audioTurn = new Audio(),
audioDead = new Audio(),
audioHit = new Audio(),
];
for ( let i = 0; i < audio.length; i++ ) {
audioNames[i].src = audio[i];
}
// canvas settings
window.addEventListener('load', (e) => {
if ( window.innerWidth <= 650 ) {
canvas.width = 300;
canvas.height = 300;
ctx.fillStyle = '#000000'
ctx.fillRect(0, 0, canvas.width, canvas.height);
config.sizeCell = 15;
config.sizeFood = 15;
canvas.style = 'background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIHZpZXdCb3g9IjAgMCAxNSAxNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE1IiBoZWlnaHQ9IjE1IiBmaWxsPSJibGFjayIvPgo8Y2lyY2xlIGN4PSI3LjUiIGN5PSI3LjUiIHI9IjIuNSIgZmlsbD0iIzUxNDk0OSIvPgo8L3N2Zz4K");';
restart();
}
else if ( window.innerWidth > 650 ) {
canvas.width = 600;
canvas.height = 480;
ctx.fillStyle = '#000000'
ctx.fillRect(0, 0, canvas.width, canvas.height);
config.sizeCell = 24;
config.sizeFood = 24;
canvas.style = 'background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSJibGFjayIvPgo8Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSI0IiBmaWxsPSIjNTE0OTQ5Ii8+Cjwvc3ZnPgo=");';
restart();
}
});
// adaptive
window.addEventListener('resize', (e) => {
if ( window.innerWidth <= 650 ) {
canvas.width = 300;
canvas.height = 300;
ctx.fillStyle = '#000000'
ctx.fillRect(0, 0, canvas.width, canvas.height);
config.sizeCell = 15;
config.sizeFood = 15;
canvas.style = 'background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTUiIGhlaWdodD0iMTUiIHZpZXdCb3g9IjAgMCAxNSAxNSIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjE1IiBoZWlnaHQ9IjE1IiBmaWxsPSJibGFjayIvPgo8Y2lyY2xlIGN4PSI3LjUiIGN5PSI3LjUiIHI9IjIuNSIgZmlsbD0iIzUxNDk0OSIvPgo8L3N2Zz4K");';
}
else if ( window.innerWidth > 650 ) {
canvas.width = 600;
canvas.height = 480;
ctx.fillStyle = '#000000'
ctx.fillRect(0, 0, canvas.width, canvas.height);
config.sizeCell = 24;
config.sizeFood = 24;
canvas.style = 'background-image: url("data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHJlY3Qgd2lkdGg9IjI0IiBoZWlnaHQ9IjI0IiBmaWxsPSJibGFjayIvPgo8Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSI0IiBmaWxsPSIjNTE0OTQ5Ii8+Cjwvc3ZnPgo=");';
}
});
// score
function score() {
scoreCount++;
bestScore();
if ( scoreCount > 15 ) config.stepMax = 5;
else if ( scoreCount <= 15 ) config.stepMax = 6;
drawScore();
}
function drawScore() {
scoreBlock.innerHTML = scoreCount;
}
function bestScore() {
if ( !localStorage.getItem('best score') ) {
localStorage.setItem('best score', 0);
}
if ( scoreCount > localStorage.getItem('best score') ) {
localStorage.setItem('best score', scoreCount);
}
bestScoreBlock.innerHTML = localStorage.getItem('best score');
}
// game
function gameLoop() {
requestAnimationFrame(gameLoop);
if ( ++config.step < config.stepMax ) return;
config.step = 0;
ctx.clearRect(0, 0, canvas.width, canvas.height); // очистка canvas
drawFood();
drawSnake();
if (diff === 'Hard') {
ctx.strokeStyle = '#f00';
ctx.lineWidth = 5;
ctx.strokeRect(0, 0, canvas.width, canvas.height);
drawBomb();
};
}
gameLoop();
// difficulty, restart
function checkBorder() { // A function that checks for going beyond the border
// x
if ( snake.x < 0 ) {
snake.x = canvas.width - config.sizeCell;
} else if ( snake.x >= canvas.width ) {
snake.x = 0;
}
// y
if ( snake.y < 0 ) {
snake.y = canvas.height - config.sizeCell;
} else if ( snake.y >= canvas.height ) {
snake.y = 0;
}
}
function withoutBorder() { // A function that checks if the snake is touched border
if ( snake.x < 0 ) {
audioPlay('dead');
restart();
} else if ( snake.x >= canvas.width ) {
audioPlay('dead');
restart();
}
if ( snake.y < 0 ) {
audioPlay('dead');
restart();
} else if ( snake.y >= canvas.height ) {
audioPlay('dead');
restart();
}
}
function restart() { // A function restart game
config.stepMax = 6;
scoreCount = 0;
drawScore();
snake.x = config.sizeCell;
snake.y = config.sizeCell;
snake.body = [];
snake.maxBodySize = 1;
snake.dirX = 0;
snake.dirY = 0;
dir = '';
randomPosFood();
if ( diff === 'Hard' ) randomPosBomb();
}
// draw snake
function drawSnake() {
snake.x += snake.dirX;
snake.y += snake.dirY;
if (diff === 'Easy') checkBorder();
if (diff === 'Hard') withoutBorder();
// work with snake length
snake.body.unshift({x: snake.x, y: snake.y});
if ( snake.body.length > snake.maxBodySize ) {
snake.body.pop();
}
snake.body.forEach((e, index) => {
snakeStyles(e, index); // snake styles
if ( e.x == food.x && e.y == food.y ) { // If snake ate food
audioPlay('eat');
score();
randomPosFood();
snake.maxBodySize++;
if ( diff === 'Hard' ) {
randomPosBomb();
}
}
if ( diff === 'Hard' ) { // if snake has touched the bomd, reduce the length of the snake
if ( e.x === bomb.x && e.y === bomb.y ) {
if ( scoreCount >= 2 ) {
audioPlay('hit');
scoreCount = Math.ceil(scoreCount / 2);
snake.maxBodySize = scoreCount + 1;
for ( let i = 0; i < snake.maxBodySize; i++ ) {
snake.body.pop();
}
drawScore();
randomPosFood();
randomPosBomb();
} else {
audioPlay('dead');
restart()
}
}
}
// Checking if the snake has touched the tail
for ( let i = index + 1; i < snake.body.length; i++ ) {
if ( e.x === snake.body[i].x && e.y === snake.body[i].y ) {
audioPlay('dead');
restart();
}
}
});
}
// snake styles
function snakeStyles(e, index) {
if ( index === 0 ) { // for the first snake element(head)
ctx.drawImage(snakeImages[0], e.x, e.y, config.sizeCell, config.sizeCell);
}
else { // other elements
ctx.fillStyle = '#093D14';
ctx.strokeStyle = '#071510';
ctx.lineWidth = 1;
ctx.fillRect(e.x, e.y, config.sizeCell, config.sizeCell);
ctx.strokeRect(e.x, e.y, config.sizeCell - 1, config.sizeCell - 1);
}
}
// draw food
function drawFood() {
ctx.drawImage(img, food.x, food.y, config.sizeFood, config.sizeFood);
}
// draw bomb
function drawBomb() { // функция добовления бомбы
ctx.drawImage(bombImg, bomb.x, bomb.y, config.sizeFood, config.sizeFood);
}
// Load the best score
document.addEventListener('load', bestScore());
// constrols
function turnUp() {
if ( dir != 'down' ) {
audioPlay('turn');
dir = 'up';
snake.dirY = -config.sizeCell;
snake.dirX = 0;
}
}
function turnLeft() {
if ( dir != 'right' ) {
audioPlay('turn');
dir = 'left';
snake.dirX = -config.sizeCell;
snake.dirY = 0;
}
}
function turnDown() {
if ( dir != 'up' ) {
audioPlay('turn');
dir = 'down';
snake.dirY = config.sizeCell;
snake.dirX = 0;
}
}
function turnRight() {
if ( dir != 'left' ) {
audioPlay('turn');
dir = 'right';
snake.dirX = config.sizeCell;
snake.dirY = 0;
}
}
document.addEventListener('keydown', (e) => {
if ( e.keyCode == 87 || e.keyCode == 38 ) { // W (up) or arrow up
turnUp();
}
else if ( e.keyCode == 65 || e.keyCode == 37 ) { // A (left) or arrow left
turnLeft();
}
else if ( e.keyCode == 83 || e.keyCode == 40 ) { // S (down) or arrow down
turnDown();
}
else if ( e.keyCode == 68 || e.keyCode == 39 ) { // D (right) or arrow right
turnRight();
}
});
// touch
const isMobile = {
Android: function() {
return navigator.userAgent.match(/Android/i);
},
BlackBerry: function() {
return navigator.userAgent.match(/BlackBerry/i);
},
iOS: function() {
return navigator.userAgent.match(/iPhone|iPad|iPod/i);
},
Opera: function() {
return navigator.userAgent.match(/Opera Mini/i);
},
Windows: function() {
return navigator.userAgent.match(/IEMobile/i);
},
any: function() {
return (isMobile.Android() || isMobile.BlackBerry() || isMobile.iOS() || isMobile.Opera() || isMobile.Windows());
}
};
if ( isMobile.any() ) {
alert('Please click any where for play game sounds');
document.addEventListener('touchstart', handleTouchStart);
document.addEventListener('touchmove', handleTouchMove);
let x1 = null;
let y1 = null;
function handleTouchStart(event) {
const firstTouch = event.touches[0];
x1 = firstTouch.clientX;
y1 = firstTouch.clientY;
}
function handleTouchMove(event) {
if ( !x1 || !y1 ) return false; // check swipe
let x2 = event.touches[0].clientX;
let y2 = event.touches[0].clientY;
let xDiff = x2 - x1;
let yDiff = y2 - y1;
if ( Math.abs(xDiff) > Math.abs(yDiff) ) {
// swipe left or right
xDiff > 0 ? turnRight() : turnLeft();
} else {
// swipe up or down
yDiff < 0 ? turnUp() : turnDown();
}
x1 = null;
y1 = null;
}
}
// change difficulty
btnChange.addEventListener('click', (e) => {
if ( diff === 'Easy' ) {
diff = 'Hard';
diffBlock.innerHTML = 'Hard';
restart();
} else {
diff = 'Easy';
diffBlock.innerHTML = 'Easy';
restart();
}
});
// Additional functions
function randomInt(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
function randomImg() { // random img ( for food )
let imgCount = randomInt(0, images.length);
let imgPath = images[imgCount];
img.src = imgPath;
return img;
}
function randomPosFood() { // random food position
ctx.drawImage(randomImg(), food.x, food.y, config.sizeFood, config.sizeFood);
food.x = randomInt(0, canvas.width / config.sizeCell) * config.sizeCell;
food.y = randomInt(0, canvas.height / config.sizeCell) * config.sizeCell;
drawFood();
}
function randomPosBomb() { // random bomb position
let chance = randomInt(1, 5);
if ( chance === 3 ) {
bomb.x = randomInt(0, canvas.width / config.sizeCell) * config.sizeCell;
bomb.y = randomInt(0, canvas.height / config.sizeCell) * config.sizeCell;
drawBomb();
}
else {
bomb.x = -config.sizeCell;
bomb.y = -config.sizeCell;
drawBomb();
}
}
function audioPlay(name) { // play audio
if ( name === 'eat' ) {
audioNames[0].play();
}
if ( name === 'turn' ) {
audioNames[1].play();
}
if ( name === 'dead' ) {
audioNames[2].play();
}
if ( name === 'hit' ) {
audioNames[3].play();
}
}