Reputation: 705
Assume I have a "grid" of 20ish by 20ish. The way that I randomly spawn food is with the following formula:
Math.floor(Math.random() * ((max - min) / 20) + 1) + 20 * min;
This basically ensures that food will spawn neatly within one of the grid cells.
I want to avoid spawning food on top of the snake's body. I haven't found the best way to do this while maintaining distributed randomness.
Solutions that don't quite work:
I've seen some people do this but it doesn't work because it doesn't guarantee that food will never spawn on the snake, it just makes it less likely. getRandomPoint() might still get me a random point on the snake.
for (let i = 0; i < snake.length; i++) {
if (foodPosition.x === snake[i].x && foodPosition.y === snake[i].y) {
foodPosition.x = getRandomPoint();
foodPosition.y = getRandomPoint();
}
}
This next one doesn't work either because food can still spawn on any cell of the snake that hasn't been checked yet. For example, when i = 0, it only checks if foodPosition.x and foodPosition.y are equal to snake[0].x and snake[0].y. On this iteration of the loop when i = 0, food can still spawn on the cell occupied by snake[2].x, snake[2].y for instance (I think?).
for (let i = 0; i < snake.length; i++) {
while (foodPosition.x === snake[i].x && foodPosition.y === snake[i].y) {
foodPosition.x = getRandomPoint();
foodPosition.y = getRandomPoint();
}
}
There are some other things I've tried but I'll leave it at that. What is a good way to do the kind of thing I'm trying to do?
Upvotes: 2
Views: 1614
Reputation: 195
I would generate a list of possible locations where the food is allowed to spawn. Something like the set of all locations minus the set of snake body locations.
I've done something similar with Minesweeper in JavaScript and p5.js. Two mines cannot occupy the same cell. As I generated mines, I removed their location from the list of possible locations.
let numRows = 20;
let numColumns = 20;
let possibleFoodLocations = [];
for (let row = 0; row < numRows; row++) {
for (let column = 0; column < numColumns; column++) {
let isLocationValid = snake.every(bodyPart => {
return row !== bodyPart.y || column !== bodyPart.x;
});
if (isLocationValid) {
possibleFoodLocations.push({ x: column, y: row });
}
}
}
Note that this will take numRows * numColumns * snake.length * 2
operations, so it will become inefficient to repeatedly perform when any of those values are large. The best method, I think, would be a combination of this when these values are large, and choosing a random point when these values are small.
Upvotes: 3
Reputation: 18137
I'd add a flag in your solution and then it will work
let colliding = true;
let new_x, new_y;
while(colliding) {
colliding = false;
new_y = getRandomPoint();
new_x = getRandomPoint();
for (let box of snake) {
if (new_x == box.x && new_y == box.y) {
colliding = true;
break;
}
}
}
foodPosition.x = new_x;
foodPosition.y = new_y;
Upvotes: 1
Reputation: 179
Would it work for you to get your random point and then check to make sure it doesn't match any of the snake's points? If it does, you could just get another random point.
Upvotes: 1