Random point 2D with limits - c#

I need to generate a 1000 random numbers as [x,y] coordinates. The highest x value is 76, while for y it is 15. I have also made a Point 2D class which should read the X and Y coorinates and do some math equations (the class is called Punkt2D, and it uses X and Y like distance.X and distance.Y to calculate).
For now I only get 1000 random numbers between 0 to 75. How do I turn it into coordinate values?
(kolonner means columns, rader mens rows and nummer mens number).
public const int MaxX = 76;
public const int MaxY = 15;
public const int min = 0;
public const int rader = 1000;
public const int kolonner = 2;
public static void Main(string[] args)
{
Random r = new Random();
int[,] nummer = new int[kolonner,rader];
for (int m = 0; m < kolonner; m++)
{
for (int n = 0; n < 1000; n++)
{
nummer[m,n] = r.Next(0,MaxX);
Console.WriteLine(Convert.ToString(nummer[m, n]));
}
}
}
I did also start on this method here, where I removed the loops.
int row = r.Next(nummer.GetLength(0));
int column = r.Next(nummer.GetLength(1));
int randomNummer = nummer[row, column];
Console.WriteLine(Convert.ToString(nummer));
Thanks in advance.

I see two ways to generate random points. Let's assume ranges of x = {0..M-1} and y = {0..N-1}
Create two separate random numbers int x = random.Next(M); int y = random.Next(N). The lower bound is 0. Note that the upper bound is exclusive. Therefore M and N must be 1 higher than the maximum values. They represent the number of possible values along one axis (the number of pixels in an image).
const int M = 77, Y = 16; // Gives range of { 0..76, 0..15 }.
Generate one random value int r = random.Next(M * N); and then get the coordinates with int x = r % M; int y = r / M; (% is the modulo operator yielding the remainder of the division).
You can get a point with:
var p = new Point(x, y);

Related

How to split an array into intervals with given width and check count how many times, the values have appeared in each intervals C#?

In my array, arr3 has 1000 numbers in it. I have to split this array into k subintervals of width differenceofMaxMin . How can I do that? Later I have to count how many times, the values in arr3 have matched to each interval. But I am stuck at creating intervals from array with a given width.
Any kind of help will be really appreciated!
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
double[] Statistics1 = new double[500];
double[] Statistics2 = new double[500];
double Alpha1;
double Alpha2;
double RV1;
double RV2;
Random random = new Random();
public double RandomDoubleInclusive() //We are using this method because random.NextDouble() method gives random number
//between 0 and 1 where 0 is inclusive and 1 is exclusive.
//Since the value of probability lies between 0 and 1, both inclusive that's why we need
//to use this method.
{
double d = 0.0;
int i = 0;
do
{
d = random.NextDouble();
i = random.Next(2);
}
while (i == 1 && d > 0);
return d + i;
}
private void label3_Click(object sender, EventArgs e)
{
}
int i,j;
private void button1_Click(object sender, EventArgs e)
{
int SampleSize = Convert.ToInt32(textBox3.Text);
for ( i = 0; i<500;)
{
for (j = 0; j < 500;)
{
Alpha1 = RandomDoubleInclusive();
Alpha2 = RandomDoubleInclusive();
double LnPart = Math.Log(Alpha1);
double part1 = (-2) * LnPart;
double part2 = 2 * 3.14159 * Alpha2;
double CosPart = Math.Cos(part2);
double SinPart = Math.Sin(part2);
RV1 = Math.Sqrt(part1) * CosPart;
Statistics1[i] = RV1;
RV2 = Math.Sqrt(part1) * SinPart;
Statistics2[j] = RV2;
i++;
j++;
}
}
var myList = new List<double>();
myList.AddRange(Statistics1);
myList.AddRange(Statistics2);
double[] arr3 = myList.ToArray();
double Max = arr3.Max();
double Min = arr3.Min();
double differenceofMaxMin = Max - Min; //calculating size of width of interval
double k;
k = Math.Log(SampleSize,2) + 1; //calculating number of subintervals
}
}
I'm not sure I fully understand what exactly you're trying to achieve, but I can certainly try to help you out with an example on how to split an array arr3 into k subintervals with (max) number of elements differenceofMaxMin
var arr3 = Enumerable.Range(0, 1000);
// given: the max number of elements
var differenceofMaxMin = 300;
// determine the number of subintervals
// note that the last subinterval may contain less than differenceofMaxMin elements
var k = (int)Math.Ceiling((double)arr3.Count() / differenceofMaxMin);
var arr3_split = Enumerable.Range(0, k)
.Select(i => arr3.Skip(i * differenceofMaxMin).Take(differenceofMaxMin));
Looking at your method to generate a random double from [0, 1], I think it's overkill since the likelihood to actually draw exactly 1.0 is extremely low.

Normalization of a decimal value

My problem is like this: I need to generate a random number from Gaussin/Norma distribution and create a histogram of a width of 0.1.
class Gaussian
{
public static double Next(Random r, double mu = 0, double sigma = 1)
{
var u1 = r.NextDouble();
var u2 = r.NextDouble();
var rand_std_normal = Math.Sqrt(-2.0 * Math.Log(u1)) *
Math.Sin(2.0 * Math.PI * u2);
var rand_normal = mu + sigma * rand_std_normal;
return rand_normal;
}
}
I am using the above function to generate a Gaussian random value.
Now, in order to create a histogram, I am in need of such a calculation that is able to automatically convert the gaussian value into an array index. Something like the following:
static void Main(string[] args)
{
const int N = 1000;
int time = N;
const double binsDistance = 0.1;
int binsCount = (int)(N * binsDistance);
Random rand = new Random();
int[] array = new int[binsCount];
int side = 0;
for (int i = 0; i < time; i++)
{
double gauss = Gaussian.Next(rand);
int binNo = Normalization.Normalize(0, binsCount - 1, gauss);
array[binNo]++;
}
}
For that, I tried two calculations.
the first one is here.
the second one is here.
The problem with the first one is, it can't handle negative numbers properly.
The problem with the second one is, it is generating too many zero values.
So, I have two questions:
What is the basic difference between #1 and #2?
How can I achieve what I am trying to do?

generate random integers with a specific sum

I have 5 fields, I want them all to have a generated number between 0 and 100. But, the sum of the 5 fields should be 100.
When I want to give a random number for one field I would do the following:
Random rnd = new Random();
int x= rnd.Next(1, 10);
But how should I do that for multiple fields that needs to have a sum of 100 together?
You can use the following approach:
generate 4 random integers in [0, 100]
sort them, let's denote the sorted values as 0 ≤ x1 ≤ x2 ≤ x3 ≤ x4 ≤ 100
use the following 5 values as the random numbers with sum 100:
N1 = x1
N2 = x2 - x1
N3 = x3 - x2
N4 = x4 - x3
N5 = 100 - x4
It basically corresponds to randomly choosing 4 sectioning points on the [0, 100] interval, and using the lengths of the 5 resulting intervals as the random numbers:
const int k = 5;
const int sum = 100;
Random rnd = new Random();
int[] x = new int[k + 1];
// the endpoints of the interval
x[0] = 0;
x[k] = sum;
// generate the k - 1 random sectioning points
for (int i = 1; i < k; i++) {
x[i] = rnd.Next(0, sum + 1);
}
// sort the sectioning points
Array.Sort(x);
// obtain the k numbers with sum s
int[] N = new int[k];
for (int i = 0; i < k; i++) {
N[i] = x[i + 1] - x[i];
}
In order to make your distribution uniform, you could try the following aproach:
Generate some random numbers.
Normalize them.
Correct the last field to get exactly the expected sum, if needed.
The code:
const int ExpectedSum = 100;
Random rnd = new Random();
int[] fields = new int[5];
// Generate 4 random values and get their sum
int sum = 0;
for (int i = 0; i < fields.Length - 1; i++)
{
fields[i] = rnd.Next(ExpectedSum);
sum += fields[i];
}
// Adjust the sum as if there were 5 random values
int actualSum = sum * fields.Length / (fields.Length - 1);
// Normalize 4 random values and get their sum
sum = 0;
for (int i = 0; i < fields.Length - 1; i++)
{
fields[i] = fields[i] * ExpectedSum / actualSum;
sum += fields[i];
}
// Set the last value
fields[fields.Length - 1] = ExpectedSum - sum;
Live example: https://dotnetfiddle.net/5yXwOP
To achieve a truly random distribution, with every element having the chance to be 100 with a total sum of 100, you can use the following solution:
public static int[] GetRandomDistribution(int sum, int amountOfNumbers)
{
int[] numbers = new int[amountOfNumbers];
var random = new Random();
for (int i = 0; i < sum; i++)
{
numbers[random.Next(0, amountOfNumbers)]++;
}
return numbers;
}
static void Main(string[] args)
{
var result = GetRandomDistribution(100, 5);
}
It increases a random number by one until the sum is reached. This should fulfill all your criterias.
After thinking about it, I prefer the following solution, because it's less likely to generate an equal distribution:
public static int[] GetRandomDistribution2(int sum, int amountOfNumbers)
{
int[] numbers = new int[amountOfNumbers];
var random = new Random();
for (int i = 0; i < amountOfNumbers; i++)
{
numbers[i] = random.Next(sum);
}
var compeleteSum = numbers.Sum();
// Scale the numbers down to 0 -> sum
for (int i = 0; i < amountOfNumbers; i++)
{
numbers[i] = (int)(((double)numbers[i] / compeleteSum) * sum);
}
// Due to rounding the number will most likely be below sum
var resultSum = numbers.Sum();
// Add +1 until we reach "sum"
for (int i = 0; i < sum - resultSum; i++)
{
numbers[random.Next(0, amountOfNumbers)]++;
}
return numbers;
}
For Example.
int sum=100;
int i = 5;
Random rnd = new Random();
while (true)
{
int cur;
--i;
if (i == 0) {
Console.WriteLine(sum + " ");
break;
} else
cur=rnd.Next(1, sum);
sum -= cur;
Console.WriteLine(cur + " ");
}
Live Example: https://dotnetfiddle.net/ltIK40
or
Random rnd = new Random();
int x= rnd.Next(1, 10);
int y= rnd.Next(x,x+10);
int y2=rnd.Next(y,y+10);
int y3=rnd.Next(y2,y2+10);
int y4=100-(x+y+y2+y3);
My approach is this:
var rnd = new Random();
var numbers = Enumerable.Range(0, 5).Select(x => rnd.Next(0, 101)).ToArray().OrderBy(x => x).ToArray();
numbers = numbers.Zip(numbers.Skip(1), (n0, n1) => n1 - n0).ToArray();
numbers = numbers.Concat(new[] { 100 - numbers.Sum() }).ToArray();
This is as uniform as I think is possible.
Create your first random number. After that you take the difference between the value of num1 and 100 as the max def of rnd. But to guarantee that their sum is 100, you have to check at the last num if the sum of all nums is 100. If not the value of your last num is the difference that their sum and 100.
And to simply your code and get a clean strcuture, put that code in a loop and instead of single numbers work with an int[5] array.
private int[] CreateRandomNumbersWithSum()
{
int[] nums = new int[5];
int difference = 100;
Random rnd = new Random();
for (int i = 0; i < nums.Length; i++)
{
nums[i] = rnd.Next(0, difference);
difference -= nums[i];
}
int sum = 0;
foreach (var num in nums)
sum += num;
if (sum != 100)
{
nums[4] = 100 - sum;
}
return nums;
}
I think this is a very simple solution:
public void GenerateRandNr(int total)
{
var rnd = new Random();
var nr1 = rnd.Next(0, total);
var nr2 = rnd.Next(0, total - nr1);
var nr3 = rnd.Next(0, total - nr1 - nr2);
var nr4 = rnd.Next(0, total - nr1 - nr2 - nr3);
var nr5 = total - nr1 - nr2 - nr3 - nr4;
}
EDIT:
Just tested it, works fine for me:
The solution is that it's not the numbers that need to be random so much as the distribution needs to be random. The randomness of the numbers will be a side effect of their random distribution.
So you would start with five random numbers in a given range. The exact range doesn't matter as long as the range is the same for all five, although a broader range allows for more variation. I'd use Random.NextDouble() which returns random numbers between 0 and 1.
Each of those individual numbers divided by the sum of those numbers represents a distribution.
For example, say your random numbers are .4, .7, .2, .5, .2. (Using fewer digits for simplicity.)
The total of those numbers is 2. So now the distributions are each of those numbers divided by the total.
.4 / 2 = .20
.7 / 2 = .35
.2 / 2 = .10
.5 / 2 = .25
.2 / 2 = .10
You'll notice that those distributions will equal 100% or really close to it if there are a lot more decimal places.
The output is going to be each of those distributions times the target number, in this case, 100. In other words, each of those numbers represents a piece of 100.
So multiplying each of those distributions times the target, we get 20, 35, 10, 25, and 100, which add up to 100.
The trouble is that because of rounding your numbers won't always perfectly add up to 100. To fix that you might add one to the smallest number if the sum is less than 100, or subtract one from the largest number of the the sum is greater than 100. Or you could choose to add or subtract on one of the numbers at random.
Here's a class to create the distributions. (I'm just playing around so I haven't exactly optimized this to death.)
public class RandomlyDistributesNumbersTotallingTarget
{
public IEnumerable<int> GetTheNumbers(int howManyNumbers, int targetTotal)
{
var random = new Random();
var distributions = new List<double>();
for (var addDistributions = 0; addDistributions < howManyNumbers; addDistributions++)
{
distributions.Add(random.NextDouble());
}
var sumOfDistributions = distributions.Sum();
var output = distributions.Select(
distribution =>
(int)Math.Round(distribution / sumOfDistributions * targetTotal, 0)).ToList();
RoundUpOutput(output, targetTotal);
return output;
}
private void RoundUpOutput(List<int> output, int targetTotal)
{
var difference = targetTotal - output.Sum();
if (difference !=0)
{
var indexToAdjust =
difference > 0 ? output.IndexOf(output.Min()) : output.IndexOf(output.Max());
output[indexToAdjust]+= difference;
}
}
}
And here's a not-perfectly-scientific unit test that tests it many times over and ensures that the results always total 100.
[TestMethod]
public void OutputTotalsTarget()
{
var subject = new RandomlyDistributesNumbersTotallingTarget();
for (var x = 0; x < 10000; x++)
{
var output = subject.GetTheNumbers(5, 100);
Assert.AreEqual(100, output.Sum());
}
}
Some sample outputs:
5, 30, 27, 7, 31
15, 7, 26, 27, 25
10, 11, 23, 2, 54
The numbers are always going to average to 20, so while 96, 1, 1, 1 is a hypothetical possibility they're going to tend to hover closer to 20.
Okay. Having been burned by my previous attempt at this seemingly trivial problem, I decided to have another go. Why not normalise all the numbers after generation? This guarantees randomness, and avoids O(n log n) performance from a sort. It also has the advantage that even with my basic maths, I can work out that the numbers are uniformly distributed.
public static int[] UniformNormalization(this Random r, int valueCount, int valueSum)
{
var ret = new int[valueCount];
long sum = 0;
for (int i = 0; i < valueCount; i++)
{
var next = r.Next(0, valueSum);
ret[i] = next;
sum += next;
}
var actualSum = 0;
for (int i = 0; i < valueCount; i++)
{
actualSum += ret[i] = (int)((ret[i] * valueSum) / sum);
}
//Fix integer rounding errors.
if (valueSum > actualSum)
{
for (int i = 0; i < valueSum - actualSum; i++)
{
ret[r.Next(0, valueCount)]++;
}
}
return ret;
}
This should also be one of the fastest solutions.

Char in random position

I`m facing with the problem of putting char in random position.
I have a table full of dots and I have to replace 30% of these dots with *
Size: 10x5
I used function Random.
Random rnd = new Random();
if (rnd.Next() % 10 > 3)
Console.Write(". ");
else
Console.Write("* ");
Everything is in 2 loops which hold Length and Height of table (10x5).
But it only makes PROBABILITY of 30% to make * instead of .
It takes good position but every time I start a program there is different amount of *.
It should just have 16 of * (17 - if rounded) every time I start the program
How should I suppose to make 30% always instead of probability?
You have 50 dots. calculate 50*30/100, it becomes 15.
Then generate 15 unique random numbers within range of 0 to 50. those numbers are indexes you have to replace . with *
var indexes = Enumerable.Range(0,50).OrderBy(x => rng.Next()).Take(50*30/100).ToList();
If you are working with 2d index, its fairly easy to convert 1d index into 2d index.
var i = index % 5;
var j = index / 5;
According to what #KonradRudolph said if you don't want to use OrderBy you can check out other ways to shuffle array (or create randomized set) posted here Best way to randomize an array with .NET
Here is more efficient way using Fisher-Yates algorithm that I suggest you to use instead of using OrderBy
var indexes = Enumerable.Range(0, 50).ToArray();
RandomExtensions.Shuffle(rng, indexes);
Write code that does the following:
Declare an array with x * y elements
Populate the entire array with .
Declare a loop with 0.30 * x * y iterations
For each iteration, change a randomly selected element from . to * (you must keep looking until you find one that isn't already a *)
Output the array, x elements per line
Imagine your array to be just 50 elements and forget about the rectangular shape for now. How would you approach that? Declare X dots and 50-X stars, then randomly order them. Like you would randomly order a 1->50 list of numbers.
Now how to randomly order a list of 1->50 numbers? One simple and intuitive way is imagining shuffling cards. Go through the loop and for each position obtain a random number in 1->50. Swap elements chosen (say for i=1 we got random number 7 => swap elements 1 and 7).
Here, you simply need to map this rectangle to those 50 points, which is trivial enough for 2D.
I would randomly select 30% of the possible positions
// create char array
int arrayRows = 5;
int arrayCols = 10;
char[,] arr= new char[arrayRows ,arrayCols];
// populate array with dots
for (int i = 0; i < arrayRows; i++)
{
for (int j = 0; j < arrayCols; j++)
{
arr[i,j] = '.';
}
}
Random rnd = new Random();
int numberOfPossiblePositions = arrayRows * arrayCols;
int k = 0;
while (k < numberOfPossiblePositions * 0.3) {
int position = rnd.Next(numberOfPossiblePositions);
int colIndex = position / 10;
int rowIndex = position % 10;
// if the cell already has * try again
if (arr[rowIndex,colIndex] == '*') {
continue;
}
arr[rowIndex,colIndex] = '*';
k++;
}
Create an array that holds x*y/3 starts and the rest are dots. order by random and iterate through it.
This is the array:
Enumerable.Range(0, count).Select(i => new {Text = "*", Order = rnd.Next() })
.Concat(Enumerable.Range(0, x*y - count)
.Select(i=>new { Text = ".", Order = rnd.Next() }))
.OrderBy(i => i.Order).Select(i=>i.Text).ToList();
And this is the code for iteration:
Random rnd = new Random();
int x = 10;
int y = 5;
int count = x*y/3;
var allPlaces =
Enumerable.Range(0, count).Select(i => new {Text = "*", Order = rnd.Next() })
.Concat(Enumerable.Range(0, x*y - count)
.Select(i=>new { Text = ".", Order = rnd.Next() }))
.OrderBy(i => i.Order).Select(i=>i.Text).ToList();
for (var i = 0; i < x; x++)
{
for (var j = 0; j < y; j++) { Console.Write(allPlaces[i*j + j]); }
Console.WriteLine();
}

Randomly generate blocks on a flat map

I'm trying to randomly generate blocks on a flat map and make it so that they don't overlap each other.
I have made a matrix (c# array) of the size of the map (500x500), the blocks have a scale between 1 and 5.
The code works but if a generated block overlaps another one, it is destroyed and not regenerated somewhere else.
Only around 80 of the 1000 blocks I try to generate don't overlap another block.
Here is a picture of the map with around 80 blocks generated, the green squares are blocks
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
bool elementFound = false;
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el;
// Randomly generate block size and position
int size = Random.Range(minScale, maxScale + 1);
int x = Random.Range(0, mapSizex + 1 - size);
int y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
if (elementFound)
continue;
else {
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
}
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
}
}
I thought of 3 possible fixes
I should set the size of the block depending of the place it has.
I should use another randomization algorithm.
I'm not doing this right.
What do you think is the best idea ?
UPDATE
I got the code working much better. I now try to instantiate the blocks multiple times if needed (maximum 5 for the moment) and I fixed the bugs. If there are already many elements on the map, they will not always be instantiated and that's what I wanted, I just have to find the right amount of times it will try to instantiate the block.
I tried instantiating 1280 elements on a 500x500 map. It takes only about 1.5 second and it instantiated 1278/1280 blocks (99.843%).
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
bool elementFound = false;
int cnt = 0;
// Generate every block
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el = null;
// Randomly generate block size and position
int size, x, y, tryCnt = 0;
// Try maximum 5 times to generate the block
do {
elementFound = false;
// Randomly set block size and position
size = Random.Range(minScale, maxScale + 1);
x = Random.Range(0, mapSizex + 1 - size);
y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
tryCnt++;
} while (elementFound && tryCnt < 5);
if (tryCnt >= 5 && elementFound) continue;
// Instantiate the block
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
cnt++;
}
print("Instantiated " + cnt + "/" + ratio * generationDefault);
}
This is incredibly difficult to do well.
Here's a quick solution you'll maybe like ... depending on your scene.
actualWidth = 500 //or whatever. assume here is square
// your blocks are up to 5 size
chunkWidth = actualWidth / 5
// it goes without saying, everything here is an int
kChunks = chunkWidth*chunkWidth
List<int> shuf = Enumerable.Range(1,kChunks).OrderBy(r=>Random.value).ToList();
howManyWanted = 1000
shuf = shuf.Take(howManyWanted)
foreach( i in shuf )
x = i % actualWidth
y = i / actualWidth
make block at x y
put block in list allBlocks
HOWEVER ............
...... you'll see that this looks kind of "regular", so do this:
Just randomly perturb all the blocks. Remember, video game programming is about clever tricks!
Ideally, you have to start from the middle and work your way out; in any event you can't just do them in a line. Shuffling is OK. So, do this ..
harmonic = 3 //for example. TRY DIFFERENT VALUES
function rh = Random.Range(1,harmonic) (that's 1 not 0)
function rhPosNeg
n = rh
n = either +n or -n
return n
function onePerturbation
{
allBlocks = allBlocks.OrderBy(r => Random.value) //essential
foreach b in allBlocks
newPotentialPosition = Vector2(rhPosNeg,rhPosNeg)
possible = your function to check if it is possible
to have a block at newPotentialPosition,
however be careful not to check "yourself"
if possible, move block to newPotentialPosition
}
The simplest approach is just run onePerturbation, say, three times. Have a look at it between each run. Also try different values of the harmonic tuning factor.
There are many ways to perturb fields of differently-sized blocks, above is a KISS solution that hopefully looks good for your situation.
Coding note...
How to get sets of unique random numbers.
Just to explain this line of code...
List<int> shuf = Enumerable.Range(1,kChunks).OrderBy(r=>Random.value).ToList();
If you are new to coding: say you want to do this: "get a hundred random numbers, from 1 to million, but with no repeats".
Fortunately, this is a very well known problem with a very simple solution.
The way you get numbers with no repeats, is simply shuffle all the numbers, and then take how many you want off the top.
For example, say you need a random couple of numbers from 1-10 but with no repeats.
So, here's the numbers 1-10 shuffled: 3,8,6,1,2,7,10,9,4,5
Simply take what you need off the front: so, 3, 8, 6 etc.
So to make an example let's say you want twelve numbers, no repeats, from 1 through 75. So the first problem is, you want a List with all the numbers up to 75, but shuffled. In fact you do that like this ..
List<int> shuf = Enumerable.Range(1,75).OrderBy(r=>Random.value).ToList();
So that list is 75 items long. You can check it by saying foreach(int r in shuf) Debug.Log(r);. Next in the example you only want 12 of those numbers. Fortunately there's a List call that does this:
shuf = shuf.Take(12)
So, that's it - you now have 12 numbers, no repeats, all random between 1 and 75. Again you can check with foreach(int r in shuf) Debug.Log(r);
In short, when you want "n" numbers, no repeats, between 1 and Max, all you have to so is this:
List<int> shuf = Enumerable.Range(1,Max).OrderBy(r=>Random.value).ToList();
shuf = shuf.Take(n);
et voilà, you can check the result with foreach(int r in shuf) Debug.Log(r);
I just explain this at length because the question is often asked "how to get random numbers that are unique". This is an "age-old" programming trick and the answer is simply that you shuffle an array of all the integers involved.
Interestingly, if you google this question ("how to get random numbers that are unique") it's one of those rare occasions where google is not much help, because: whenever this question is asked, you get a plethora of keen new programmers (who have not heard the simple trick to do it properly!!) writing out huge long complicated ideas, leading to further confusion and complication.
So that's how you make random numbers with no repeats, fortunately it is trivial.
if (elementFound) continue; will skip out this current loop iteration. You need to wrap the int x=Random..; int y=Random()..; part in a while loop with the condition being while(/* position x/y already occupued*/) { /* generate new valid point */} like this for example:
void generateElement(int ratio, int minScale, int maxScale, GameObject g) {
for (int i = 0; i < ratio * generationDefault; i++) {
GameObject el;
// Randomly generate block size and position
bool elementFound = false;
int size, x, y;
do
{
elementFound = false;
size = Random.Range(minScale, maxScale + 1);
x = Random.Range(0, mapSizex + 1 - size);
y = Random.Range(0, mapSizey + 1 - size);
// Check if there is already an element
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] != null)
elementFound = true;
} while(elementFound);
el = (GameObject)Instantiate(g, new Vector3(x + (float)size / 2, (float)size / 2, y + (float)size / 2), Quaternion.Euler(0, 0, 0));
el.transform.localScale *= size;
// Create element on map array
for (int j = x; j < x + size; j++)
for (int k = y; k < y + size; k++)
if (map[j][k] == null) {
map[j][k] = el.GetComponent<ObjectInterface>();
}
}
}
You shouldn't be getting that many collisions.
Assuming your blocks were ALL 5 units wide and you're trying to fit them into a grid of 500,500 you would have 100*100 spaces for them at minimum, which gives 10,000 spaces into which to fit 1,000 blocks.
Try playing around with this code:
using System;
using System.Collections.Generic;
namespace ConsoleApplication1
{
class Program
{
static void Main()
{
var result = PlaceNonOverlappingBlocks(1000, 5, 500, 500);
}
static List<Block> PlaceNonOverlappingBlocks(int count, int maxBlockSize, int mapX, int mapY)
{
var map = new bool[mapY, mapX];
var rng = new Random();
var result = new List<Block>(count);
int collisions = 0;
while (count > 0)
{
int size = rng.Next(1, maxBlockSize + 1);
int x = rng.Next(0, mapX - size);
int y = rng.Next(0, mapY - size);
if (fits(map, x, y, size))
{
result.Add(new Block(x, y, size));
addToMap(map, x, y, size);
--count;
}
else
{
if (++collisions> 100000)
throw new InvalidOperationException("Hell has frozen over");
}
}
// This is just for diagnostics, and can be removed.
Console.WriteLine($"There were {collisions} collisions.");
return result;
}
static void addToMap(bool[,] map, int px, int py, int size)
{
for (int x = px; x < px+size; ++x)
for (int y = py; y < py + size; ++y)
map[y, x] = true;
}
static bool fits(bool[,] map, int px, int py, int size)
{
for (int x = px; x < px + size; ++x)
for (int y = py; y < py + size; ++y)
if (map[y, x])
return false;
return true;
}
internal class Block
{
public int X { get; }
public int Y { get; }
public int Size { get; }
public Block(int x, int y, int size)
{
X = x;
Y = y;
Size = size;
}
}
}
}

Categories

Resources