Javascript Snake Game - слишком много ошибок рекурсии

так что я работаю над этой игрой со змеями, и я в основном пытаюсь предотвратить появление еды на вершине змеиного хвоста. Мои настройки переменных:

let headX = 10; //snake starting position
let headY = 10;

let appleX = 5; //food starting position
let appleY = 5;

Это функция, которая проверяет столкновение головы и еды.

function checkAppleCollision() {
  if (appleX === headX && appleY === headY) {
    generateApplePosition();
    tailLength++;
    score++;
  }
}

А это функция, которая рандомизирует положение яблока после столкновения, а также возвращает ошибку «слишком много рекурсии» после пары столкновений:

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;
}

Пожалуйста, помогите, я понятия не имею, что здесь делать. Все остальное работает как задумано.

Почему это вообще должно быть рекурсивным?

VLAZ 22.03.2022 11:04

Я просто подумал, что это может быть путь, конечно, это, вероятно, не оптимально. У меня нет предыдущего опыта работы с проектами такого типа, поэтому не могли бы вы посоветовать, как лучше ввести функцию?

Eddie Benzon 22.03.2022 11:06

1. Задача вообще не требует рекурсии. Вы мог делаете это, но это немного лишнее. 2. Код не работает для рекурсии. Суть рекурсии состоит в том, чтобы итеративно создавать более простые проблемы, пока не дойдете до той, которую можно решить (базовый случай), а затем итеративно решать упрощенные задачи в обратном порядке. Здесь нет базового случая. Нет ничего, чтобы остановить рекурсию. while (collisionDetect === true) гарантирует, что если условие истинно, вы получите ошибку всегда. Поскольку функция вызывается бесконечное количество раз, пока рекурсивный вызов также не попадет в while и т. д.

VLAZ 22.03.2022 11:10

Ваша рекурсия зависит от переменной, которая никогда не обновляется - collisionDetect в вызываемой функции полностью отличается от той, что в вызывающей функции.

phuzi 22.03.2022 13:55

Что вы пытались решить проблему? Где ты застрял?

Nico Haase 22.03.2022 14:28
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
2
5
74
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Как уже говорили другие, это не обязательно должно быть рекурсивным, и вы также должны учитывать (хотя и маловероятную) возможность, когда больше нет плиток для появления, что приведет к бесконечному циклу.

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);
    }
}
if (tilesLeft > 0) { смысла нет. Если плиток не осталось, это часть логики isGameOver(). Функция, которая генерирует позицию яблока, не должна беспокоиться о ненужных вещах.
Roko C. Buljan 22.03.2022 14:36

Согласен, однако, поскольку никакая остальная игровая логика не была представлена, в этом контексте вполне обоснованно следует избегать бесконечного цикла.

Mike D Sutton 22.03.2022 18:27
Ответ принят как подходящий

Использование рекурсий или do while — плохая идея (объясню позже)


тем временем вы можете упростить свою логику, создав:

  • многоразовые функции samePos() и collides()
  • функция рекурсивныйcreateApple(), которая вернет себя, если случайно сгенерированные позиции x, y будут заняты телом змеи

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.info(`position ${randPos.x} ${randPos.y} is occupied by snake`);
    return createApple(); // Try another position.
  }
  
  // Finally a free spot!
  apple.pos = randPos;
  console.info(`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 игра:

Рассмотрим эту 2D-карту размером 4x3 как индексы:

0  1  2  3
4  5  6  7
8  9  10 11 

теперь давайте поместим змею на эту карту:

0  ⬛  2  3
4  ⬛  ⬛  7
8  9  ⬛  11 

вот линейная карта со Змеей в виде одномерного списка:

[ 0  ⬛  2  3  4  ⬛  ⬛  7  8  9  ⬛  11 ]

поэтому вместо использования массива объектов {x:n, y:n} для положения тела змеи все, что вам нужно, это:

[1, 5, 6, 10]  // Snake body as indexes

Теперь, когда вы знаете все индексы, в которых вам не разрешено размещать яблоко, все, что вам нужно сделать при создании нового яблока, это:

  • Создайте массив индексов 0-Н длины: 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.info("New apple position: %o", applePos);
};

createApple();
Run this demo multiple times

Имея этот индекс свободного места, просто нарисуйте свое яблоко в координатах XY, используя эту простую формулу.

Х = индекс % ширина карты
Y = пол (индекс / ширина карты)

Вау, какой подробный и информативный ответ. Буду внимательно изучать, спасибо за информацию! Кажется, теперь игра работает так, как задумано, но я воспользуюсь вашим советом и попытаюсь реализовать то, что вы здесь указали.

Eddie Benzon 22.03.2022 19:01

@EddieBenzon Спасибо. Безусловно! В качестве хорошего домашнего задания вы можете попробовать однажды переделать ту же игру в линейное 1D (используя индексы). Единственной более сложной задачей будет преобразование ключевых направлений в соответствующий индекс N, S, E, W (с некоторой базовой математикой). Остальное довольно просто. Даже если вы создаете свою игру, используя элементы DIV в сетке, проще раскрасить определенные элементы DIV, используя эти индексы. Попробуйте.

Roko C. Buljan 22.03.2022 19:09

Другие вопросы по теме