Skip to content Skip to sidebar Skip to footer

Preventing Food From Generating Inside My Snake

Can someone tell me how I can make it so the food doesn't generate inside my snake? I've tried using a for loop that checks every index of the snake to see if it's matching with th

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 unshifting 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"