samedi 31 octobre 2020

Random.Next - Am I silently creating new instances?

Random.Next() randomness failures are almost always caused by creating and then using multiple instances of System.Random with the same seed, either with a time seed or a manual one. However, this is the only instance creation code in my class:

System.Random rNG;
if (string.IsNullOrEmpty(Map.Seed))
{
    rNG = new System.Random();
}
else
{
    rNG = new System.Random(Map.Seed.GetHashCode());
}

Looping through this second attempt code correctly creates random numbers:

var resourceRoll = rNG.Next(0, this.ResourceByRoll.Count);
var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
    .Where(row => row["Resource"].Equals(
        this.ResourceByRoll[resourceRoll]
    ))

Looping through this original attempt code often creates the same number twice in a row:

var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
    .Where(row => row["Resource"].Equals(
        this.ResourceByRoll[rNG.Next(0, this.ResourceByRoll.Count)]
    ))

Am I somehow silently creating a new instance of System.Random when using a Random.Next call as a dictionary index? Why does my original code often return the same number twice in a row?

If it matters:

  • This class is a Unity script
  • I am using System.Random, not UnityEngine.Random
  • My complete class is below:

using Assets.Code.Tools;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using UnityEngine;

public class Map : MonoBehaviour
{
    public static int Length { get; set; }
    public static int Width { get; set; }
    public static int ResourceChanceDenominator { get; set; }
    public static string Seed { get; set; }
    private static int[,] objectGrid;
    private DataTable ProcGenResourceTable { get; set; }
    private Dictionary<int, string> ResourceByRoll { get; set; }
    private List<GameObject> prefabTrees;
    private List<GameObject> prefabStones;


    private void Start()
    {
        this.prefabTrees = GeneralTools.GetPrefabsWithTag("Tree");
        this.prefabStones = GeneralTools.GetPrefabsWithTag("Stone");

        GenerateMap();
    }


    public void GenerateMap()
    {
        var procGenResourceTable = Resources.Load("ProcGenResourceTable") as TextAsset;
        if (procGenResourceTable != null)
        {
            this.ProcGenResourceTable = GeneralTools.GetDataTableFromCSV(procGenResourceTable, "|", true, false);
        }
        else
        {
            Console.WriteLine("ProcGenResourceTable could not be found");
            return;
        }

        Map.objectGrid = new int[Map.Width, Map.Length];

        this.ResourceByRoll = GetPopulatedResourceByRollDictionary();

        System.Random rNG;
        if (string.IsNullOrEmpty(Map.Seed))
        {
            rNG = new System.Random();
        }
        else
        {
            rNG = new System.Random(Map.Seed.GetHashCode());
        }


        for (var i = 0; i < Map.Length; i++)
        {
            for (var j = 0; j < Map.Width; j++)
            {
                var roll = rNG.Next(Map.ResourceChanceDenominator);

                if (roll == 1)
                {
                    // var resourceRoll = rNG.Next(0, this.ResourceByRoll.Count);
                    var resourceRow = from row in this.ProcGenResourceTable.AsEnumerable()
                                      .Where(row => row["Resource"].Equals(
                                            this.ResourceByRoll[rNG.Next(0, this.ResourceByRoll.Count)]
                                          ))
                                      select new
                                      {
                                          ModelFamily = row["Model Family"],
                                          Tags = row["Tags"]
                                      };

                    foreach (var row in resourceRow)
                    {
                        GameObject resource = null;

                        switch (row.ModelFamily)
                        {
                            case "Tree":
                                resource = Instantiate(this.prefabTrees[rNG.Next(this.prefabTrees.Count - 1)], new Vector3(i, 0, j), new Quaternion());
                                break;
                            case "Stone":
                                resource = Instantiate(this.prefabStones[rNG.Next(this.prefabStones.Count - 1)], new Vector3(i, 0, j), new Quaternion());
                                break;
                            default:
                                resource = Instantiate(this.prefabTrees[rNG.Next(this.prefabTrees.Count - 1)], new Vector3(i, 0, j), new Quaternion());
                                break;
                        }

                        var tagsListForResource = row.Tags.ToString().Split(new char[] { '|' }).ToList();
                        if (tagsListForResource.Contains("Resource"))
                        {
                            resource.tag = "Resource";
                        }
                    }
                }
            }
        }
    }


    private Dictionary<int, string> GetPopulatedResourceByRollDictionary()
    {
        var resourceByRoll = new Dictionary<int, string>();

        foreach (DataRow row in this.ProcGenResourceTable.Rows)
        {
            if (!string.IsNullOrEmpty(row["Weight"].ToString()))
            {
                for (var i = 0; i < Convert.ToInt32(row["Weight"]); i++)
                {
                    resourceByRoll.Add(resourceByRoll.Count, row["Resource"].ToString());
                }
            }
        }

        return resourceByRoll;
    }
}



Aucun commentaire:

Enregistrer un commentaire