Preventing Food From Generating Inside My Snake
Solution 1:
The issue is that checkSnakeForFood()
doesn't work as you expect because newHead
hasn't yet been added to the snake body by the time you test for food-head collision.
Only the snake tail is checked and the checkSnakeForFood()
routine reports that the current food placement (right underneath newHead
) is not a collision, so the while
loop body is not executed and the food doesn't move. Here's a diagram of what's happening:
0123456789
0+---------
1|.........
2|.F######.
3|.......##
^^^^^^^
|
only these segments get
tested for collision
newHead
and food
have the same location, [2, 2]
, denoted by the F
. They're colliding, so if (headX == food.x && headY == food.y)
is true. However, when checkSnakeForFood()
is called on the next line, only the tail #
elements are taken into consideration in the loop for (let i = 0; i < snake.length; i++)
. checkSnakeForFood()
then returns true and the while
loop never repositions the food.
You can resolve this issue by unshift
ing the newHead
before attempting to call checkSnakeForFood()
as follows:
//create new headlet newHead = {
x: headX,
y: headY
}
//add head to snake, *before* checking for collision with the food
snake.unshift(newHead);
//if snake eats food -do thisif (headX == food.x && headY == food.y) {
//create new food positionwhile (!checkSnakeForFood()) { // <-- now this will successfully detect// that the snake head is touching the food
Now, the food-head collision will register correctly, and the food will be repositioned until it doesn't collide with the snake's tail or head:
0123456789
0+---------
1|.........
2|.F######.
3|.......##
^^^^^^^^
|
all segments are correctly
tested for collision
However, I recommend a refactor to help avoid bugs like this. You can make snake
an object with member functions for collision and movement and properties for its direction
and tail
. Your draw
function is very overburdened and is responsible for a lot more than its name promises.
Detaching newHead
seems like an error-prone approach; manipulating the head directly on the array will save you a lot of confusion.
In the meantime, here's the updated code to get you moving again (remember to click the Full page
link in the snippet to play the game properly:
const canvas = document.querySelector('#canvas');
const ctx = canvas.getContext('2d');
//set canvas dimension equal to css dimension
canvas.width = 768;
canvas.height = 512;
//now put those dimensions into variablesconst cvsW = canvas.width;
const cvsH = canvas.height;
//create snake unitconst unit = 16;
//create snake arraylet snake = [{
x: cvsW / 2,
y: cvsH / 2
}];
//delcare global variable to hold users directionlet direction;
//create food objectlet food = {
x: Math.floor(Math.random() * ((cvsW / unit) - 1) + 1) * unit,
y: Math.floor(Math.random() * ((cvsH / unit) - 1) + 1) * unit
}
//read user's directiondocument.addEventListener('keydown', changeDirection);
functionchangeDirection(e) {
//set directionif (e.keyCode == 37 && direction != 'right') direction = 'left';
elseif (e.keyCode == 38 && direction != 'down') direction = 'up';
elseif (e.keyCode == 39 && direction != 'left') direction = 'right';
elseif (e.keyCode == 40 && direction != 'up') direction = 'down';
}
functiondraw() {
//refresh canvas
ctx.clearRect(0, 0, cvsW, cvsH);
//draw snakefor (let i = 0; i < snake.length; i++) {
ctx.fillStyle = 'limegreen';
ctx.fillRect(snake[i].x, snake[i].y, unit, unit);
}
//grab head positionlet headX = snake[0].x;
let headY = snake[0].y;
//posistion food on board
ctx.fillStyle = 'red';
ctx.fillRect(food.x, food.y, unit, unit);
//send the snake in chosen directionif (direction == 'left') headX -= unit;
elseif (direction == 'up') headY -= unit;
elseif (direction == 'right') headX += unit;
elseif (direction == 'down') headY += unit;
// //check if snake hit wall// if(headX < 0 || headY < 0 || headX > (cvsW-unit) || headY > (cvsH-unit)) {// clearInterval(runGame);// }if (headX < 0) headX = cvsW - unit;
elseif (headX > cvsW - unit) headX = 0;
elseif (headY < 0) headY = cvsH - unit;
elseif (headY > cvsH - unit) headY = 0;
// for(let i = 0; i < snake.length; i++) {// if(headX == snake[i].x && headY == snake[i].y) {// clearInterval(game);// }// }//create new headlet newHead = {
x: headX,
y: headY
}
//add head to snake
snake.unshift(newHead);
//if snake eats food -do thisif (headX == food.x && headY == food.y) {
//create new food positionwhile (!checkSnakeForFood()) {
food = {
x: Math.floor(Math.random() * ((cvsW / unit) - 1) + 1) * unit,
y: Math.floor(Math.random() * ((cvsH / unit) - 1) + 1) * unit
}
}
//add 3 units to the snakefor (let i = 30; i > 0; i--) {
snake.unshift(newHead);
}
} else {
//remove tail
snake.pop();
}
}
//run game enginelet runGame = setInterval(draw, 40);
functioncheckSnakeForFood() {
for (let i = 0; i < snake.length; i++) {
if (snake[i].x === food.x && snake[i].y === food.y) returnfalse;
}
returntrue;
}
body {
background-color: #333;
}
canvas {
background-color: #4d4d4d;
margin: auto;
display: block;
position: absolute;
left: 0;
right: 0;
top: 0;
bottom: 0;
width: 750px;
height: 500px;
}
<canvasid="canvas"></canvas>
Solution 2:
Try wrapping your food draw into a for loop + if statement:
var foodNotPlaced;
snake.forEach(function(segment) {
if (food.x != segment.x and food.y != segment.y) {
ctx.fillStyle = 'red';
ctx.fillRect(food.x, food.y, unit, unit);
foodNotPlaced = false;
}
else {
food.x = Math.floor(Math.random()*((cvsW/unit)-1)+1)*unit;
food.y = Math.floor(Math.random()*((cvsW/unit)-1)+1)*unit;
foodNotPlaced = True;
});
Then rerun inside of a while loop to make sure the food gets placed before the rest of your code runs
while foodNotPlaced {
snake.forEach(function(segment) {
if (food.x != segment.x and food.y != segment.y) {
ctx.fillStyle = 'red';
ctx.fillRect(food.x, food.y, unit, unit);
foodIsPlaced = false;
}
else {
food.x = Math.floor(Math.random()*((cvsW/unit)-1)+1)*unit;
food.y = Math.floor(Math.random()*((cvsW/unit)-1)+1)*unit;
foodIsPlaced = true;
});
}
Post a Comment for "Preventing Food From Generating Inside My Snake"