Click here to Skip to main content
15,881,938 members
Please Sign up or sign in to vote.
1.00/5 (1 vote)
Hello! I am currently working on my personal JavaScript snake game project. I have a snake and apple on the screen right now. I have not yet added the border collision. Right now, I don't know how to force the snake to line up exactly with the apple (the snake and apple can touch even when not aligned properly).

What I have tried:

I have mainly tried checking if the x and y positions are a multiple of my snake width and height before letting the snake move. At the same time for the apple, I tried to find the nearest coordinate point from my random point that would be a multiple of the snake width and height. However, that didn't seem to work, so I deleted that code and I am back on square one. My code below is what I have. Any help is greatly appreciated, and let me know if you need any clarification!

HTML
<!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>


CSS
#canvas {
    position: absolute;
    background-color: black;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
}


JavaScript
 const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const snakeWidth = 20;
const snakeHeight = 20;
let snakeX = canvas.width / 2;
let snakeY = canvas.height / 2;
let speedX = 0;
let speedY = 0;
const snakeSpeed = 1;
const snakeArray = [];
let prevKey = '';
const posHistory = [];
const canvasArea = canvas.width * canvas.height;
let randX = Math.random() * canvas.width;
let randY = Math.random() * canvas.height;
console.log(snakeX);
console.log(snakeY);

// Makes sure canvas doesn't get distorted
canvas.addEventListener("resize", () => {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    snakeArray[0].draw();
    apple.drawApple();
});

// Snake 
class Snake {
    constructor() {
        this.width = snakeWidth;
        this.height = snakeHeight;
        this.x = snakeX - (this.width / 2);
        this.y = snakeY - (this.height / 2);
        this.speedX = speedX;
        this.speedY = speedY;
    }

    updateHead() {
        posHistory.push([this.x, this.y]);
        this.x += speedX;
        this.y += speedY;

        if (posHistory.length >= canvasArea)
            posHistory.pop();
    }

    updateTail(index) {
        this.x = posHistory[posHistory.length - index][0];
        this.y = posHistory[posHistory.length - index][1];
    }

    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 - (randX % this.width);
        this.y = randY - (randY % this.height);
    }

    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) <= 20 && Math.abs(this.y - snakeArray[i].y) <= 20) {
                addSnake();
                randX = Math.random() * canvas.width;
                randY = Math.random() * canvas.height;
                this.x = randX;
                this.y = randY;
                console.log(this.x, this.y);
;            }
        }
    }
}
const apple = new Apple(); 

// Controls snake
document.addEventListener("keydown", (event) => {
    switch (event.key) {
        case 'ArrowUp': 
            // If not going down
            if (prevKey !== 'down') {
                speedX = 0;
                speedY = -1 * snakeSpeed;
                prevKey = 'up';
            }
            break;
        case 'ArrowRight':
            // If not going left
            if (prevKey !== 'left') {
                speedX = snakeSpeed;
                speedY = 0;
                prevKey = 'right';
            }
            break;
        case 'ArrowDown':
            // If not going up
            if (prevKey !== 'up') {
                speedX = 0;
                speedY = snakeSpeed;
                prevKey = 'down';
            }
            break;
        case 'ArrowLeft':
            // If not going right
            if (prevKey !== 'right') {
                speedX = -1 * snakeSpeed;
                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();
    }
}

function handleApple() {
    apple.appleCollision();
    apple.drawApple();
}

function addSnake() {
    for (let i = 0; i < Math.round(snakeWidth / snakeSpeed); i++)
        snakeArray.push(new Snake());
}

function animate() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    handleSnake();
    handleApple();
    requestAnimationFrame(animate);
}
animate();
Posted
Updated 11-Jul-23 19:44pm
v2
Comments
Member 15627495 2-May-23 2:47am    
Hello !

why the snake have an X speed, and a Y speed too, what is the difference ?

one advice :
try to integrate the Canvas as it's a class, like the class snake and Apple ...
because you can launch 'refresh' / apply 'position' on elements on it.
It's to go further with OOP and code structure.

the question is : Are the 'snake and apple' parts of the canvas, or the canvas is part of 'the snake and apple' ?
you can implement a function Canvas..apply_location(target , X, Y , history_pos ) ;

It's to keep Graphics ( the canvas ) out of the class 'Apple' and 'Snake'
Yash64 3-May-23 9:12am    
I separated the x and y speeds because they control how the snake moves, and if I have just one speed variable, I don't know how I could alternate between the two so that the snake moves properly. As for the advice, I tried to implement it but failed miserably. Could you show me how to do it please because I am stumped on how to convert canvas to a class. I will continue to try and figure out how to get the apple and snake to line up. I appreciate the help.
[no name] 2-May-23 11:57am    
Compute a center point for all your objects (usually left, top + offsets). Then you can compare their relative distances; factoring in the (half) height, (half) width or radius of the objects when necessary.

1 solution

I got the snake to line up mostly. I added a blocksize variable and updated the snake's position by a block. I also used the following equations for the apple:

JavaScript
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;


Hopefully that helps solve this problem for someone else. I still haven't gotten to make the canvas an object, but I will come back to that issue later. For now, this works.
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900