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() 函数
  • 一个recursivecreateApple()函数,如果随机生成的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)