I recently decided to attempt to make my first ever OOP project. I like video games, so I decided to go with snake. So far, I have gotten the snake movement and apple almost completely finished. However, the one problem is that the player can trick the game into letting the snake move the opposite direction immediately by pressing two keys very quickly in sequence (ex: snake is moving right and player inputs up and left quickly). Can someone help me out here? Any help is very appreciated! Let me know if any clarification is needed!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Snake Game</title>
<link rel="stylesheet" href="snake.css">
</head>
<body>
<canvas id="canvas"></canvas>
<script src="snake.js"></script>
</body>
</html>
#canvas {
position: absolute;
background-color: black;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
@font-face {
font-family: 'Joystix';
font-style: normal;
font-weight: 400;
src: local('Joystix'), url('https://fonts.cdnfonts.com/s/7419/joystix.woff') format('woff');
}
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const snakeWidth = 20;
const snakeHeight = 20;
const blockSize = snakeWidth;
let snakeX = Math.floor(canvas.width / blockSize / 2) * 20;
let snakeY = Math.floor(canvas.height / blockSize / 2) * 20;
let speedX = 0;
let speedY = 0;
const snakeArray = [];
let prevKey = '';
const posHistory = [];
const canvasArea = canvas.width * canvas.height;
const rangeX = Math.trunc(canvas.width / snakeWidth);
const rangeY = Math.trunc(canvas.height / snakeHeight);
let randX = Math.floor((Math.random() * rangeX)) * snakeWidth;
let randY = Math.floor((Math.random() * rangeY)) * snakeHeight;
let time = 0;
const perimeter = [];
let stop = false;
let start = true;
let joystix = new FontFace("Joystix", "url(https://fonts.cdnfonts.com/s/7419/joystix.woff)");
joystix.load().then((font) => {
document.fonts.add(font);
console.log('Font loaded');
});
canvas.addEventListener("resize", () => {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
snakeArray[0].draw();
apple.drawApple();
});
class Snake {
constructor() {
this.width = snakeWidth;
this.height = snakeHeight;
this.x = snakeX;
this.y = snakeY;
this.speedX = speedX;
this.speedY = speedY;
}
updateHead() {
posHistory.push([this.x, this.y]);
this.x += (blockSize * speedX);
this.y += blockSize * speedY;
if (posHistory.length >= canvasArea)
posHistory.pop();
}
updateTail(index) {
this.x = posHistory[posHistory.length - index][0];
this.y = posHistory[posHistory.length - index][1];
}
snakeCollision() {
for (let i = 1; i < snakeArray.length; i++) {
if (start === false) {
if (this.x === posHistory[posHistory.length - i][0] && this.y === posHistory[posHistory.length - i][1]) {
gameOver();
}
}
}
}
draw() {
ctx.fillStyle = 'green';
ctx.fillRect(this.x, this.y, this.width, this.height);
}
}
snakeArray.push(new Snake());
class Apple {
constructor() {
this.width = snakeWidth;
this.height = snakeHeight;
this.x = randX;
this.y = randY;
}
drawApple() {
ctx.fillStyle = 'red';
ctx.fillRect(this.x, this.y, this.width, this.height);
}
appleCollision() {
for (let i = 0; i < snakeArray.length; i++) {
if (Math.abs(this.x - snakeArray[i].x) <= blockSize - 1 && Math.abs(this.y - snakeArray[i].y) <= blockSize - 1) {
start = false;
snakeArray.push(new Snake());
randX = Math.floor((Math.random() * rangeX)) * snakeWidth;
randY = Math.floor((Math.random() * rangeY)) * snakeHeight;
this.x = randX;
this.y = randY;
; }
}
}
}
const apple = new Apple();
document.addEventListener("keydown", (event) => {
switch (event.key) {
case 'ArrowUp':
if (prevKey !== 'down') {
speedX = 0;
speedY = -1;
prevKey = 'up';
}
break;
case 'ArrowRight':
if (prevKey !== 'left') {
speedX = 1;
speedY = 0;
prevKey = 'right';
}
break;
case 'ArrowDown':
if (prevKey !== 'up') {
speedX = 0;
speedY = 1;
prevKey = 'down';
}
break;
case 'ArrowLeft':
if (prevKey !== 'right') {
speedX = -1;
speedY = 0;
prevKey = 'left';
}
break;
}
});
function handleSnake() {
snakeArray[0].updateHead();
for (let i = 1; i < snakeArray.length; i++) {
snakeArray[i].updateTail(i);
}
for (let j = 0; j < snakeArray.length; j++) {
snakeArray[j].draw();
}
snakeArray[0].snakeCollision();
}
function handleApple() {
apple.appleCollision();
apple.drawApple();
}
function border() {
if (snakeArray[0].x < 0 || snakeArray[0].x > canvas.width || snakeArray[0].y < 0 || snakeArray[0].y > canvas.height)
gameOver();
}
function gameOver() {
ctx.font = "30px joystix";
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.fillText("GAME OVER", canvas.width / 2, canvas.height / 2);
stop = true;
}
function animate() {
if (time % 10 === 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
handleSnake();
handleApple();
border();
}
time++;
if (stop === false)
requestAnimationFrame(animate);
}
animate();
What I have tried:
I have tried using setTimeout() to prevent the inputs from going through so quickly, but either I implemented it incorrectly, or it was the wrong approach.