Ошибка рекурсии стекового потока при реализации моего кода в Unity

#c# #unity3d #recursion

#c# #unity3d #рекурсия

Вопрос:

Хорошо, итак, я хочу, чтобы пользователь указывал ширину и высоту доски / матрицы, а затем устанавливал случайные точки / координаты как «бомбы» (случайные координаты имеют x между 0 и количеством чисел в строке, а y между 0 и количеством чисел в строке).столбец), я использую список, чтобы увидеть, были ли координаты уже помечены как бомбы, если НЕТ, пометьте место как бомбу и добавьте координаты в список, если ДА, рекурсия выполняется до тех пор, пока координаты не были использованы ранее. Затем я проверяю каждый элемент в матрице, если это нормальное место (0), я печатаю «нормальное», если это место бомбы (1), я печатаю «бомба».

Код работает нормально, когда я не запускаю его в unity, но когда я это делаю, я получаю ошибку стекового потока

КОД, КОТОРЫЙ РАБОТАЕТ ПРИ ЗАПУСКЕ В ОДИНОЧКУ:

ЭТОТ ФАЙЛ Board.cs

 using System;
using System.Collections.Generic;


public class Board
{
    byte rows;
    byte columns;
    byte bombsAmount;
    public byte[,] matrix;

    // MEMOIZATION
    List<byte[]> bombPositions = new List<byte[]>();


    public Board(byte rows, byte columns, byte bombsAmount)
    {
        this.rows = rows;
        this.columns = columns;
        this.bombsAmount = bombsAmount;

        MakeMatrix();

        for (int i = 0; i < bombsAmount; i  )
        {
            SetBombs();   
        }
    }

    // makes a matrix with the specified width and height
    public byte[,] MakeMatrix()
    {
        matrix = new byte[rows, columns]; // makes 2d array

        return matrix;
    }

    // gets a random number between 0 and last index of rows
    // gets a random number between 0 and last index of columns
    // checks if random pair of nums has already been used
    // if so, check again
    // if not, set coordinates to bomb and mark it as used
    public byte[,] SetBombs()
    {
        Random RNG = new Random();
        byte xPos = (byte)RNG.Next(0, rows); // the length of a row is the number of columns
        byte yPos = (byte)RNG.Next(0, columns); // the length of a column is the number of rows

        byte[] coords = new byte[] { xPos, yPos };
        bool isOccupied = false;

        try{   
            foreach (var position in bombPositions)
            {
                if (position[0] == coords[0] amp;amp; position[1] == coords[1])
                {
                    isOccupied = true;
                }
            }

            // BASE CASE
            if (isOccupied == false)
            {
                matrix[coords[0], coords[1]]  = 1;
                bombPositions.Add(coords);
            }
            // RECURSIVE CASE
            else
            {
                SetBombs();
            }

        } catch (NullReferenceException)
        {

        } 

        return matrix;
    }
} 
 

ЭТОТ ФАЙЛ Program.cs

 using System;
using System.Collections.Generic;

namespace Name
{
    class Program{
    public static void Main()
    {
        Board gameboard = new Board(5, 6, 4);
        foreach (var row in gameboard.matrix)
        {
            switch (row)
            {
                case 0:
                    Console.WriteLine("normal");
                    break;
                case 1:
                    Console.WriteLine("bomb");
                    break;
            }
        }
        
        Console.ReadLine();
    }
}
} 
 

КОД, КОТОРЫЙ НЕ РАБОТАЕТ В UNITY:

они используют тот же Board.cs

 using System;
using System.Collections.Generic;
using UnityEngine;


class Program : MonoBehaviour
{
    void Start()
    {
        Debug.Log("normal");
        Board gameboard = new Board(5, 6, 4);
        foreach (var row in gameboard.matrix)
        {
            switch (row)
            {
                case 0:
                    Debug.Log("normal");
                    break;
                case 1:
                    Debug.Log("bomb");
                    break;
            }
        }
        
            Console.ReadLine();
    }
}
 

Комментарии:

1. Вместо того, чтобы использовать ваш рекурсивный метод Монте-Карло , сохраняйте список всех незанятых местоположений, затем каждый раз, когда вы размещаете бомбу, вы выбираете случайный элемент в этом списке, размещаете бомбу в этом месте, затем удаляете этот элемент из списка.

2. Вызов Random() конструктора без параметров каждый раз, когда вам нужна координата, также вызывает беспокойство. » Если [этот пример] выполняется в .NET Framework, поскольку первые два случайных объекта создаются в близкой последовательности, их экземпляры создаются с использованием идентичных начальных значений на основе системных часов и, следовательно, они создают идентичную последовательность случайных чисел .. Таким образом, ваш алгоритм, вероятно, будет пытаться использовать только 3 или 4 разные координаты, даже если SetBombs вызывается сотни раз.

3. Также этого Console.ReadLine(); вообще не должно быть в проекте Unity ^^ .. а также действительно НЕ делайте этого catch (NullReferenceException) { } !! Скорее исправьте ваше исключение или предотвратите его, но это просто делает вашу отладку невозможной! Результатом может быть просто -> Вы вызываете что-то => Вообще ничего не происходит… почему? catch Знает ^^

Ответ №1:

Как уже сказал Рузим: не рекурсируйте здесь вообще! Лучше иметь список, который отслеживает уже используемые позиции.

Чтобы упростить задачу (поскольку вы все равно находитесь в Unity) Я бы предложил использовать int вместо byte и вместо вашего byte[2] использования Vector2Int , которое уже обеспечивает, в частности, сравнение равенства!

Для случайного я бы использовал встроенный Unity Random.Range(int,int)

 using UnityEngine;
using Random = UnityEngine.Random;

public class Board
{
    private int rows;
    private int columns;
    private int bombsAmount;

    public int[,] matrix;

    // Unity already provides the wonderful type Vector2Int
    // which implements equality comparisons so we can simply use
    private readonly HashSet<Vector2Int> bombPositions = new HashSet<Vector2Int>();

    // Readonly access
    public HashSet<Vector2Int> BombPositions => new Hashset(bombPositions );

    public Board(int rows, int columns, int bombsAmount)
    {
        this.rows = rows;
        this.columns = columns;
        this.bombsAmount = bombsAmount;

        ResetMatrix();

        SetBombs();
    }

    private Vector2Int GetNextFreeRandomPosition()
    {
        Vector2Int randomPosition;

        // optional emergency break ;)
        var iterations = 0;

        do
        {
            randomPosition = new Vector2Int(Random.Range(0, columns), Random.Range(0, rows));
           
            iterations  ;
            if(iterations > rows * columns * 10)
            {
                Debug.LogError("In my opinion I tried it now a reasonable amount of times but couldn't find an unused position ^^");
                return Vector2Int(-1,-1);
            }

        } while (bombPositions.Contains(randomPosition));

        return randomPosition;
    }

    private void ResetMatrix()
    {
        bombPositions.Clear();
        matrix = new int[columns,rows];
    }

    private void SetBombs()
    {
        if(bombAmount > rows * columns)
        {
            Debug.LogError($"Impossible to place {bombAmount} on a {rows}x{columns} board!");
            return null;
        }

        for(var i = 0; i < bombAmount; i  )
        {
            var randomPos = GetNextFreeRandomPosition(alreadyUsedPositions);
            bombPositions.Add(randomPos);

            matrix[randomPos.x, randomPos.y] = 1;
        }
    }
}
 

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

 using System;
using System.Collections.Generic;
using UnityEngine;

public class Program : MonoBehaviour
{
    void Start()
    {
        var gameboard = new Board(5, 6, 4);
     
        for(var y = 0; y < 5; y  )
        {
            for(var x = 0; x < 6; x  )
            {
                var field = gameboard.matrix[x, y];
                switch (field)
                {
                    case 0:
                        Debug.Log("normal");
                        break;
                    case 1:
                        Debug.Log($"bomb at {x},{y}");
                        break;
                }
            }
        }
    }
}
 

Примечание: набрано на смартфоне, но я надеюсь, что идея станет понятной