#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;
}
}
}
}
}
Примечание: набрано на смартфоне, но я надеюсь, что идея станет понятной