Javascript 贪吃蛇游戏 - 递归错误太多
Javascript Snake Game - too much recursion error
所以我正在做这个贪吃蛇游戏,我主要是想防止食物在蛇尾巴上生成。我的设置变量:
let headX = 10; //snake starting position
let headY = 10;
let appleX = 5; //food starting position
let appleY = 5;
这是检查head/food碰撞的函数
function checkAppleCollision() {
if (appleX === headX && appleY === headY) {
generateApplePosition();
tailLength++;
score++;
}
}
这是在碰撞后随机化苹果位置的函数,也是在几次碰撞后 returns“太多递归”错误:
function generateApplePosition() {
let collisionDetect = false;
let newAppleX = Math.floor(Math.random() * tileCount);
let newAppleY = Math.floor(Math.random() * tileCount);
for (let i = 0; i < snakeTail.length; i++) {
let segment = snakeTail[i];
if (newAppleX === segment.x && newAppleY === segment.y) {
collisionDetect = true;
}
}
while (collisionDetect === true) {
generateApplePosition();
}
appleX = newAppleX;
appleY = newAppleY;
}
请帮忙,我不知道该怎么办。其他一切都按预期工作。
正如其他人所说,这不需要是递归的,您还应该考虑(尽管不太可能)没有更多瓷砖生成的可能性,这将导致无限循环。
function generateApplePosition() {
// Count how many tiles are left for spawning in
const tilesLeft = (tileCount * tileCount) - snakeTail.length;
let collisionDetect;
if (tilesLeft > 0) {
do {
const newAppleX = Math.floor(Math.random() * tileCount);
const newAppleY = Math.floor(Math.random() * tileCount);
collisionDetect = false;
for (let i = 0; i < snakeTail.length; i++) {
const { x, y } = snakeTail[i];
if (newAppleX === x && newAppleY === y) {
collisionDetect = true; // Collision
break;
}
}
if (!collisionDetect) {
// Found spawn point
appleX = newAppleX;
appleY = newAppleY;
}
} while (collisionDetect);
}
}
使用递归或do while
是个坏主意(我稍后会解释)
同时,您可以通过创建以下内容来简化您的逻辑:
- 可重用
samePos()
和 collides()
函数
- 一个recursive
createApple()
函数,如果随机生成的x,y位置被蛇体占据,它会return自己
const world = {w:6, h:1}; // height set to 1 for this demo only
const snake = [{x:0, y:0}, {x:1, y:0}, {x:2, y:0}, {x:3, y:0}];
const apple = {pos: {x:0, y:0}};
// Check if two object's x,y match
const samePos = (a, b) => a.x === b.x && a.y === b.y;
// Check if object x,y is inside an array of objects
const collides = (ob, arr) => arr.some(o => samePos(ob, o));
const createApple = () => {
const randPos = {
x: ~~(Math.random() * world.w),
y: ~~(Math.random() * world.h),
};
if (collides(randPos, snake)) {
console.log(`position ${randPos.x} ${randPos.y} is occupied by snake`);
return createApple(); // Try another position.
}
// Finally a free spot!
apple.pos = randPos;
console.log(`Apple to free position: ${apple.pos.x} ${apple.pos.y}`);
}
createApple();
Run this demo multiple times
问题
无用的随机猜测!
从上面的例子可以看出,如果你 运行 多次,随机生成的数字往往与之前生成的数字相同:
...
position 2 0 is occupied by snake <<<
position 1 0 is occupied by snake
position 2 0 is occupied by snake <<<
position 2 0 is occupied by snake <<<
position 1 0 is occupied by snake
position 2 0 is occupied by snake <<<
...
因此,随着你的蛇变大,递归可能会变得疯狂——荒谬,迭代太多次,在相同的 xy 位置重复和失败,直到最终找到一个罕见的空闲点...
这个设计真的很糟糕。
解决方案
一个解决方案是跟踪数组中已经使用的随机位置 - 但这意味着不必要地遍历这样的数组。
最好的解决方案实际上不是将游戏视为 2D 游戏,而是 1D游戏:
将这张尺寸为 4x3 的 2D 地图视为索引:
0 1 2 3
4 5 6 7
8 9 10 11
现在,让我们在这张地图中放置一条蛇:
0 ⬛ 2 3
4 ⬛ ⬛ 7
8 9 ⬛ 11
这是将 Snake 作为一维列表的线性映射:
[ 0 ⬛ 2 3 4 ⬛ ⬛ 7 8 9 ⬛ 11 ]
因此,不要使用对象数组 {x:n, y:n}
作为蛇体位置,您只需要:
[1, 5, 6, 10] // Snake body as indexes
既然您知道了所有不允许放置苹果的索引,那么在创建新苹果时您需要做的就是:
- 创建一个 0-N 索引的数组,长度为:world.w * world.h
- 循环蛇体索引和
delete
索引数组中的那些索引以获得自由点索引数组
- 只需从该空闲点数组中仅获取一次随机密钥!
const indexToXY = (index, width) => ({ x: index%width, y: Math.trunc(index/width) });
const world = {w:4, h:3};
const snakeBody = [1, 5, 6, 10];
const createApple = () => {
const arr = [...Array(world.w * world.h).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
snakeBody.forEach(i => delete arr[i]);
const freeIndexes = arr.filter(k => k !== undefined); // [0, 2, 3, 4, 7, 8, 9, 11]
const appleIndex = freeIndexes[~~(Math.random() * freeIndexes.length)];
const applePos = indexToXY(appleIndex, world.w);
console.log("New apple position: %o", applePos);
};
createApple();
Run this demo multiple times
有了那个空闲点索引,只需使用这个简单的公式在 XY 坐标处绘制你的苹果
X = index % mapWidth
Y = floor(index / mapWidth)
所以我正在做这个贪吃蛇游戏,我主要是想防止食物在蛇尾巴上生成。我的设置变量:
let headX = 10; //snake starting position
let headY = 10;
let appleX = 5; //food starting position
let appleY = 5;
这是检查head/food碰撞的函数
function checkAppleCollision() {
if (appleX === headX && appleY === headY) {
generateApplePosition();
tailLength++;
score++;
}
}
这是在碰撞后随机化苹果位置的函数,也是在几次碰撞后 returns“太多递归”错误:
function generateApplePosition() {
let collisionDetect = false;
let newAppleX = Math.floor(Math.random() * tileCount);
let newAppleY = Math.floor(Math.random() * tileCount);
for (let i = 0; i < snakeTail.length; i++) {
let segment = snakeTail[i];
if (newAppleX === segment.x && newAppleY === segment.y) {
collisionDetect = true;
}
}
while (collisionDetect === true) {
generateApplePosition();
}
appleX = newAppleX;
appleY = newAppleY;
}
请帮忙,我不知道该怎么办。其他一切都按预期工作。
正如其他人所说,这不需要是递归的,您还应该考虑(尽管不太可能)没有更多瓷砖生成的可能性,这将导致无限循环。
function generateApplePosition() {
// Count how many tiles are left for spawning in
const tilesLeft = (tileCount * tileCount) - snakeTail.length;
let collisionDetect;
if (tilesLeft > 0) {
do {
const newAppleX = Math.floor(Math.random() * tileCount);
const newAppleY = Math.floor(Math.random() * tileCount);
collisionDetect = false;
for (let i = 0; i < snakeTail.length; i++) {
const { x, y } = snakeTail[i];
if (newAppleX === x && newAppleY === y) {
collisionDetect = true; // Collision
break;
}
}
if (!collisionDetect) {
// Found spawn point
appleX = newAppleX;
appleY = newAppleY;
}
} while (collisionDetect);
}
}
使用递归或do while
是个坏主意(我稍后会解释)
同时,您可以通过创建以下内容来简化您的逻辑:
- 可重用
samePos()
和collides()
函数 - 一个recursive
createApple()
函数,如果随机生成的x,y位置被蛇体占据,它会return自己
const world = {w:6, h:1}; // height set to 1 for this demo only
const snake = [{x:0, y:0}, {x:1, y:0}, {x:2, y:0}, {x:3, y:0}];
const apple = {pos: {x:0, y:0}};
// Check if two object's x,y match
const samePos = (a, b) => a.x === b.x && a.y === b.y;
// Check if object x,y is inside an array of objects
const collides = (ob, arr) => arr.some(o => samePos(ob, o));
const createApple = () => {
const randPos = {
x: ~~(Math.random() * world.w),
y: ~~(Math.random() * world.h),
};
if (collides(randPos, snake)) {
console.log(`position ${randPos.x} ${randPos.y} is occupied by snake`);
return createApple(); // Try another position.
}
// Finally a free spot!
apple.pos = randPos;
console.log(`Apple to free position: ${apple.pos.x} ${apple.pos.y}`);
}
createApple();
Run this demo multiple times
问题
无用的随机猜测!
从上面的例子可以看出,如果你 运行 多次,随机生成的数字往往与之前生成的数字相同:
...
position 2 0 is occupied by snake <<<
position 1 0 is occupied by snake
position 2 0 is occupied by snake <<<
position 2 0 is occupied by snake <<<
position 1 0 is occupied by snake
position 2 0 is occupied by snake <<<
...
因此,随着你的蛇变大,递归可能会变得疯狂——荒谬,迭代太多次,在相同的 xy 位置重复和失败,直到最终找到一个罕见的空闲点...
这个设计真的很糟糕。
解决方案
一个解决方案是跟踪数组中已经使用的随机位置 - 但这意味着不必要地遍历这样的数组。
最好的解决方案实际上不是将游戏视为 2D 游戏,而是 1D游戏:
将这张尺寸为 4x3 的 2D 地图视为索引:
0 1 2 3
4 5 6 7
8 9 10 11
现在,让我们在这张地图中放置一条蛇:
0 ⬛ 2 3
4 ⬛ ⬛ 7
8 9 ⬛ 11
这是将 Snake 作为一维列表的线性映射:
[ 0 ⬛ 2 3 4 ⬛ ⬛ 7 8 9 ⬛ 11 ]
因此,不要使用对象数组 {x:n, y:n}
作为蛇体位置,您只需要:
[1, 5, 6, 10] // Snake body as indexes
既然您知道了所有不允许放置苹果的索引,那么在创建新苹果时您需要做的就是:
- 创建一个 0-N 索引的数组,长度为:world.w * world.h
- 循环蛇体索引和
delete
索引数组中的那些索引以获得自由点索引数组 - 只需从该空闲点数组中仅获取一次随机密钥!
const indexToXY = (index, width) => ({ x: index%width, y: Math.trunc(index/width) });
const world = {w:4, h:3};
const snakeBody = [1, 5, 6, 10];
const createApple = () => {
const arr = [...Array(world.w * world.h).keys()]; // [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
snakeBody.forEach(i => delete arr[i]);
const freeIndexes = arr.filter(k => k !== undefined); // [0, 2, 3, 4, 7, 8, 9, 11]
const appleIndex = freeIndexes[~~(Math.random() * freeIndexes.length)];
const applePos = indexToXY(appleIndex, world.w);
console.log("New apple position: %o", applePos);
};
createApple();
Run this demo multiple times
有了那个空闲点索引,只需使用这个简单的公式在 XY 坐标处绘制你的苹果
X = index % mapWidth
Y = floor(index / mapWidth)