Zero padding a 2D array in C# - c#

I currently have an issue with zero padding my 2d Array. I want to transfer my current data in my array to a new array, which is the exact same array but with a border of 0's around it.
Example:
|1 2 3|
|4 5 6|
|7 8 9|
Should become
|0 0 0 0 0|
|0 1 2 3 0|
|0 4 5 6 0|
|0 7 8 9 0|
|0 0 0 0 0|
int[,] Array = new int[,] { { 1, 2, 3 }, { 3, 4, 5 }, { 6, 7, 8 } };
int[,] ArrayZeroPad = new int[Array.GetLength(0) + 2, Array.GetLength(1) + 2];
for (int y = 0; y < Array.GetLength(1); y++)
{
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{
if (y == 0)
{ ArrayZeroPad[y, x] = 0; }
else if (y == ArrayZeroPad.GetLength(1))
{ ArrayZeroPad[y, x] = 0; }
else if (x == 0)
{
ArrayZeroPad[y, x] = 0;
}
else if (x == ArrayZeroPad.GetLength(0))
{ ArrayZeroPad[y, x] = 0; }
else ArrayZeroPad[y, x] = Array[y, x];
}
}
for (int y = 0; y < ArrayZeroPad.GetLength(1); y++)
{
Console.WriteLine();
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{ Console.Write(ArrayZeroPad[y, x]); }
Console.ReadLine();
}
}
This is what I have come to thus far, but I keep getting stuck on out of bounds errors, is there anyone who could work this out for me with some explanation?
Kind regards,
D.

This is not quite what you are asking (I thought a completely different alternative would be interesting).
Here is a No-Copy version that works for any type of array, of any size. It's appropriate if the original array is quite large (since it doesn't require a copy).
It uses a 2-dimensional indexer that either returns the default value of T (zero or null) for items on the edge, and uses the original array (with the indexes offset) for non-edge values:
public class ZeroPadArray <T>
{
private readonly T[,] _initArray;
public ZeroPadArray(T[,] arrayToPad)
{
_initArray = arrayToPad;
}
public T this[int i, int j]
{
get
{
if (i < 0 || i > _initArray.GetLength(0) + 1)
{
throw new ArgumentOutOfRangeException(nameof(i),
$#"Index {nameof(i)} must be between 0 and the width of the padded array");
}
if (j < 0 || j > _initArray.GetLength(1) + 1)
{
throw new ArgumentOutOfRangeException(nameof(j),
$#"Index {nameof(j)} must be between 0 and the width of the padded array");
}
if (i == 0 || j == 0)
{
return default(T);
}
if (i == _initArray.GetLength(0) + 1)
{
return default(T);
}
if (j == _initArray.GetLength(1) + 1)
{
return default(T);
}
//otherwise, just offset into the original array
return _initArray[i - 1, j - 1];
}
}
}
I just tested it with some Debug.Assert calls. The test coverage is weak, but it was good enough to say "this probably works":
int[,] array = new int[,] { { 1, 2, 3 }, { 11, 12, 13 }, { 21, 22, 23 } };
var paddedArray = new ZeroPadArray<int>(array);
Debug.Assert(paddedArray[0, 0] == 0);
Debug.Assert(paddedArray[4,4] == 0);
Debug.Assert(paddedArray[2,3] == 13);
And, finally, for fun, I added a nice little hack to make creating these things require less typing. When you call a method, the compiler is often able to deduce the generic type of the object from the method parameters. This doesn't work for constructors. That's why you need to specify new ZeroPadArray<int>(array) even though array is obviously an array of int.
The way to get around this is to create a second, non-generic class that you use as a static factory for creating things. Something like:
public static class ZeroPadArray
{
public static ZeroPadArray<T> Create<T>(T[,] arrayToPad)
{
return new ZeroPadArray<T>(arrayToPad);
}
}
Now, instead of typing:
var paddedArray = new ZeroPadArray<int>(array);
you can type:
var paddedArray = ZeroPadArray.Create(array);
Saving you two characters of typing (but, you need to admit that typing the <int> is frustrating).

int[,] Array = new int[,] { { 1, 2, 3 }, { 3, 4, 5 }, { 6, 7, 8 } };
int[,] ArrayZeroPad = new int[Array.GetLength(0) + 2, Array.GetLength(1) + 2];
for (int x = 0; x < ArrayZeroPad.GetLength(0); x++)
{
for (int y = 0; y < ArrayZeroPad.GetLength(0); y++)
{
//First row and last row
if (x == 0 || x == ArrayZeroPad.GetLength(0) - 1)
ArrayZeroPad[x, y] = 0;
else
{
//Fist column and last column
if (y == 0 || y == ArrayZeroPad.GetLength(0) - 1)
ArrayZeroPad[x, y] = 0;
else
{
//Content
ArrayZeroPad[x, y] = Array[x-1, y-1];
}
}
}
}

It seems that you are confusing dimensions - Array.GetLength(0) is for the first one in access Array[i, j] and Array.GetLength(1) is for the second. Also you can simplify copy by just scanning through Array elements and adjusting destination indexes by one, you don't need to explicitly set others to 0 cause it would be done for you (unless you are using stackalloc and skipping local init but I highly doubt that this is the case):
var length0 = Array.GetLength(0);
var length1 = Array.GetLength(1);
for (int i = 0; i < length0; i++)
{
for (int j = 0; j < length1; j++)
{
ArrayZeroPad[i + 1, j + 1] = Array[i, j];
}
}
And in the "print" method too - y should be the first dimension and x - second:
var length = ArrayZeroPad.GetLength(0);
for (int y = 0; y < length; y++)
{
Console.WriteLine();
var i = ArrayZeroPad.GetLength(1);
for (int x = 0; x < i; x++)
{
Console.Write(ArrayZeroPad[y, x]);
}
Console.ReadLine();
}

You can also solve this using Array.Copy(). If you require highest performance and the arrays are big enough, then this might be faster than explicitly copying each element:
public static int[,] Pad(int[,] input)
{
int h = input.GetLength(0);
int w = input.GetLength(1);
var output = new int[h+2, w+2];
for (int r = 0; r < h; ++r)
{
Array.Copy(input, r*w, output, (r+1)*(w+2)+1, w);
}
return output;
}
The (r+1)*(w+2)+1 requires some explanation. Array.Copy() treats a 2D array as a linear 1D array, and you must specify the destination offset for the copy as an offset from the start of the 1D array (in row-major order).
Since w is the width of the input array and r is the current row of the input array, the destination for the copy of the current input row will be the output row number, (r+1) times the output row width (w+2), plus 1 for to account for the left-hand column of 0 in the output array.
It's possible that using Buffer.BlockCopy() (which operates on bytes) could be even faster:
public static int[,] Pad(int[,] input)
{
int h = input.GetLength(0);
int w = input.GetLength(1);
var output = new int[h+2, w+2];
for (int r = 0; r < h; ++r)
{
Buffer.BlockCopy(input, r*w*sizeof(int), output, ((r+1)*(w+2)+1)*sizeof(int), w*sizeof(int));
}
return output;
}
As always, this is only worth worrying about if performance is critical, and even then only after you've benchmarked the code to verify that it actually is faster.

Related

C# how to check if adjacent values are greater or smaller in an array and allocate points based on values?

I am trying to figure out how to assign points based on adjacent number values in an array. By default, each number is worth one point, and it increases in value if the adjacent numbers are lower than the current number.
For values at the start and the end of the array, the adjacent values are the end and start, so for numbers[0] adjacent values are numbers[5] && numbers[1] and for numbers[5] the values would be numbers[4] && numbers[0]. Consider it going in a circle.
As an example of numbers let's say we have int[] numbers = new int[] { 5, 6, 4, 2, 5, 6 }; after point allocation the output should be something like 1 3 2 1 2 3.
The issue I'm facing is that I don't know how to write the code in such a way that I don't get an error. And I would like to avoid using a lot of if nested functions. Managed to get it working like that but it looks horrible, so scrapped that and figured there must be a better way of doing this but I seem to be stuck...
static int[] AssignPoints (int[] numbers) {
int[] points = new int[numbers.Length];
for (int i = 0; i < numbers.Length; i++) {
points[i]++; //allocating initial point
???
}
return points;
}
static int[] AssignPoints (int[] numbers) {
int[] points = new int[numbers.Length];
for (int i = 0; i < numbers.Length; i++)
{
int prevIndex = (i-1 < 0 ? numbers.Length -1 : i-1);
int nextIndex = (i+1 >= numbers.Length ? 0 : i + 1);
if (numbers[i] > numbers[prevIndex])
points[i]++;
if (numbers[i] > numbers[nextIndex])
points[i]++;
}
return points;
}
A very explicit example:
static int[] AssignPoints (int[] numbers) {
int[] points = new int[numbers.Length];
for (int i = 0; i < numbers.Length; i++) {
var leftIndex = i == 0 ? numbers.Length - 1 : i - 1;
var rightIndex = i == numbers.Length - 1 ? 0 : i + 1;
points[i] = 1
+ (numbers[i] > numbers[leftIndex] ? 1 : 0)
+ (numbers[i] > numbers[rightIndex] ? 1 : 0);
}
return points;
}
static List<int> AssignPoints(List<int> numbers)
{
List<int> points =new List<int>(numbers.Capacity);
int i = 0, point;
foreach (int item in numbers)
{
point = 1;
if(i != 0)
if (numbers[i - 1] < numbers[i]) point++;
if(i != numbers.Capacity -1)
if (numbers[i + 1] < numbers[i]) point++;
i++;
points.Add(point);
}
return points;
}

How could the bug in the below C# code be fixed by only changing 1 or 2 lines of code?

I'm trying to solve the below problem in C# which I was unable to answer within the time limit during a technical interview.
The provided code contains a bug and it can only be fixed by amending 2 lines of code at most.
The function takes 2 integer arrays as parameters.
All integers within both parameters will always be positive integers.
There may be multiple occurrences of the same integer in each array parameter.
The function should return the lowest positive integer that exists in both arrays.
The function should return -1 if no such integer exists
public int test(int[] A, int[] B)
{
int m = A.Length;
int n = B.Length;
Array.Sort(A);
Array.Sort(B);
int i = 0;
for (int k = 0; k < m; k++)
{
if (i < n - 1 && B[i] < A[k])
i += 1;
if (A[k] == B[i])
return A[k];
}
return -1;
}
I'm struggling to come up with a solution that only amends 1-2 lines of code. I thought I could replace i += 1; with i += 1; k = 0; but this is obviously adding a new line.
The original code will work for some inputs but not something like the below example because we don't want to increase k when B[i] < A[k]:
int[] A = { 3, 4, 5, 6 };
int[] B = { 2, 2, 2, 3 ,5 };
Considering you can only amend the code and not add new lines to it, I believe the following should be working:
public int test(int[] A, int[] B)
{
int m = A.Length;
int n = B.Length;
Array.Sort(A);
Array.Sort(B);
int i = 0;
for (int k = 0; k < m; k++)
{
while (i < n - 1 && B[i] < A[k])
{
i += 1;
}
if (A[k] == B[i])
return A[k];
}
return -1;
}
What changed is that the if that was checking for i is now a while
It's not exactly the prettiest code, but since those are the requirements...

Method to guess possible combination

For practice I want to write a program that will guess random positions of x and y. For example the first point would be
int x = 0;
int y = 0;
x += rand.Next(0, 4);
y += rand.Next(0, 4);
Then from that random point I will add the another random value to x and y to have a second point. However I want to go back to find those points randomly.
To make the points:
int x = 0;
int y = 0;
List<Point> points = new List<Point>();
for (int i = 0; i < numberOfPointsWanted; i++)
{
x += rand.Next(0, 4);
y += rand.Next(0, 4);
points.Add(new Point(x, y));
}
Now I wish to guess those random points almost as if I did not have them stored in a list. Because each new point relies on its predecessor I assume some sort of recursion would be necessary. Almost like a brute force guessing application that will find those points. I am having trouble completing the method that would be able to guess every possible point given a number of desired points.
This is what I have thus far to find the rounds:
class Program
{
static int nRounds = 2;
static Point[] points = new Point[nRounds];
static Point[] test = { new Point(1, 2), new Point(4, 1) };
static bool CheckArray()
{
for (int i = 0; i < points.Length; i++)
if (points[i] != test[i]) { return false; }
return true;
}
static void PrintArray()
{
for (int i = 0; i < points.Length; i++)
Console.Write("[" + tCount + "]\t" + points[i].X + " : " + points[i].Y + "\t");
Console.Write("\n");
}
static int tCount = 0;
static int rCount = 0;
static void GetRounds(int inX, int inY)
{
for (int x = inX; x < 5; x++)
{
for (int y = inY; y < 5; y++)
{
if (rCount < nRounds)
{
tCount++;
points[rCount] = new Point(x, y);
rCount++;
GetRounds(x, y);
if (CheckArray())
{
PrintArray();
return;
}
PrintArray();
}
}
}
rCount--;
}
static void Main(string[] args)
{
GetRounds(0, 0);
Console.ReadKey();
}
}
}
I am trying to randomly generate points as shown above and then guess them based on a hashed value representing all of those points together.
This is what im expecting to see:
If only guessing two points
Point one :: Point two x and y respectively
x y :: x y
0 0 :: 0 1
0 0 :: 0 2
0 0 :: 0 3
0 0 :: 1 0
0 0 :: 1 1
0 0 :: 1 2
0 0 :: 1 3
0 0 :: 2 0
0 0 :: 2 1
0 0 :: 2 2
0 0 :: 2 3
0 0 :: 3 0
0 0 :: 3 1
0 0 :: 3 2
0 0 :: 3 3
0 1 :: 0 0
0 1 :: 0 1
0 1 :: 0 2
And so on until all possibilities of point one and point two are guessed
I'm not sure if this is exactly what you're looking for, but one way to get all those combinations is to use nested for loops:
for (int ax = 0; ax < 4; ax++)
{
for (int ay = 0; ay < 4; ay++)
{
var pointA = new Point(ax, ay);
for (int bx = 0; bx < 4; bx++)
{
for (int by = 0; by < 4; by++)
{
var pointB = new Point(bx, by);
Console.WriteLine($"{pointA.X} {pointA.Y} :: {pointB.X} {pointB.Y}");
}
}
}
}
Output
You were asking about a solution that would allow a variable number of points to be passed in. This is fairly simple to do - you just keep a List<List<Point>> of the results, and on each iteration you generate a list of possible point values (16 possible values when min is 0 and max is 3), and then generate a new list for every item in the existing results for each Point in the new set.
The problem is the size of the result set. Since a single point has 16 possible combinations of X and Y if we have a min value of 0 and a max value of 3, then for each additional point, we raise 16 to that power. So for 10 points, there are over a billion combinations.
private static List<List<Point>> GetAllCombinations(int min, int max, int count)
{
var results = new List<List<Point>>();
for (int i = 0; i < count; i++)
{
var thisSet = new List<Point>();
for (int x = min; x <= max; x++)
{
for (int y = min; y <= max; y++)
{
thisSet.Add(new Point(x, y));
}
}
// If this is our first time through, we just add each point
// as a single-item list to our results
if (results.Count == 0)
{
foreach (var item in thisSet)
{
results.Add(new List<Point> {item});
}
}
// On subsequent iterations, for each list in our results, and
// for each item in this set, we create a new list for each item,
// adding to it a copy of the existing result list. We clear
// the results in the beginning (after making a copy) and then
// add each new list to it in the inner loop.
else
{
// Make a copy of our existing results and clear the original list
var tempResults = results.ToList();
results.Clear();
foreach (var existingItem in tempResults)
{
foreach (var newPoint in thisSet)
{
// Now we populate our results again with a new set of
// lists for each existingItem and each newPoint
var newItem = existingItem.ToList();
newItem.Add(newPoint);
results.Add(newItem);
}
}
}
}
return results;
}
Example usage:
private static void Main()
{
var results = GetAllCombinations(0, 3, 5);
foreach (var result in results)
{
Console.WriteLine(string.Join(" :: ", result.Select(p => $"{p.X} {p.Y}")));
}
Console.WriteLine("With a min value of 0 and max value of 3, " +
$"5 points generated {results.Count} results.");
GetKeyFromUser("Done! Press any key to exit...");
}
Output

Recursion to iteration conversion using dynamic programming

public static int n;
public static int w;
public static int[] s;
public static int[] p;
static void Main(string[] args)
{
n = 5;
w = 5;
s = new int[n + 1];
p = new int[n + 1];
Random rnd = new Random();
for (int i = 1; i <= n; i++)
{
s[i] = rnd.Next(1, 10);
p[i] = rnd.Next(1, 10);
}
Console.WriteLine(F_recursion(n, w));
Console.WriteLine(DP(n, w));
}
// recursive approach
public static int F_recursion(int n, int w)
{
if (n == 0 || w == 0)
return 0;
else if (s[n] > w)
return F_recursion(n - 1, w);
else
{
return Math.Max(F_recursion(n - 1, w), (p[n] + F_recursion(n - 1, w - s[n])));
}
}
// iterative approach
public static int DP(int n, int w)
{
int result = 0;
for (int i = 1; i <= n; i++)
{
if (s[i] > w)
{
continue;
}
else
{
result += p[i];
w = w - s[i];
}
}
return result;
}
I need to convert F_recursion function to iterative. I currently written following function DP that sometimes works but not always. I learned that problem is in F_recursion(n - 1, w - s[n]) I have no idea how to make w - s[n] work correctly in iterative solution. If change w - s[n] and w - s[i] to only w then program always work.
In Console:
s[i] = 2 p[i] = 3
-------
s[i] = 3 p[i] = 4
-------
s[i] = 5 p[i] = 3
-------
s[i] = 3 p[i] = 8
-------
s[i] = 6 p[i] = 6
-------
Recursive:11
Iteration:7
but sometimes it works
s[i] = 5 p[i] = 6
-------
s[i] = 8 p[i] = 1
-------
s[i] = 3 p[i] = 5
-------
s[i] = 3 p[i] = 1
-------
s[i] = 7 p[i] = 7
-------
Recursive:6
Iteration:6
The following approach might be useful, when bigger numbers are involved (specially for s) and consequently a 2 dimensional array would be unnecessary big and only a few w values would actually be used in computing the result.
The idea: precompute possible w values, by starting at w and for each i in [n, n-1, ..., 1] determine the values w_[i], where w_[i+1] >= s[i] without duplicates.
Then iterate i_n over n and compute sub-results only for valid w_[i] values.
I chose an array of Dictionary as datastructure, since it's relatively easy to design sparse data this way.
public static int DP(int n, int w)
{
// compute possible w values for each iteration from 0 to n
Stack<HashSet<int>> validW = new Stack<HashSet<int>>();
validW.Push(new HashSet<int>() { w });
for (int i = n; i > 0; i--)
{
HashSet<int> validW_i = new HashSet<int>();
foreach (var prevValid in validW.Peek())
{
validW_i.Add(prevValid);
if (prevValid >= s[i])
{
validW_i.Add(prevValid - s[i]);
}
}
validW.Push(validW_i);
}
// compute sub-results for all possible n,w values.
Dictionary<int, int>[] value = new Dictionary<int,int>[n + 1];
for (int n_i = 0; n_i <= n; n_i++)
{
value[n_i] = new Dictionary<int, int>();
HashSet<int> validSubtractW_i = validW.Pop();
foreach (var w_j in validSubtractW_i)
{
if (n_i == 0 || w_j == 0)
value[n_i][w_j] = 0;
else if (s[n_i] > w_j)
value[n_i][w_j] = value[n_i - 1][w_j];
else
value[n_i][w_j] = Math.Max(value[n_i - 1][w_j], (p[n_i] + value[n_i - 1][w_j - s[n_i]]));
}
}
return value[n][w];
}
It's important to understand that some space and computation is "wasted" in order to precompute possible w values and to support the sparse data structures. So this approach might perform bad for large data sets with small values in s, where most w values will be possible sub-results.
After some more thought I realized, if space is a concern, you can actually throw away the sub-results of everything except the previous outer loop iteration, since the recursion in this algorithm follows a strict n-1 pattern. However, I'm not including this into my code for now.
Your approach does not work because your dynamic programmig state space (which apparently is only one variable) does not match the signature of the recursive method. The goal of the dynamic programming approach should be to define and fill a state space such that all results for evaluation are available when needed. On inspection of the recursive method, notice that the recursive calls of F_recursion may change both arguments, n and w. This is an indication that a two-dimensional state space should be used.
The first argument (which apparently limits the range of items) can range from 0 to n while the second argument (which apparently is some bound for the total of an item property) can range from 0 to w.
You should define a two dimensional state space
int[,] value = new int[n,w];
for accomodation of the values. Next, you should initialize the values to undefined; you can use the value Int32.MaxValue for this, because it will behave in a suitable way if the minimum with some different value is calculated.
Next, the iterative version of the algorithm shoud use two loops which iterate in a forwad manner, unlike the recursive iteration which decreases the arguments.
for (int i = 0; i < n; i++)
{
for (int j = 0; j < w; j++)
{
// logic for the recurrence relation goes here
}
}
In the innermost block you can use a modified version of the recurrence relation. Instead of using recursive calls, you access values which are stored in value; instead of returning values, you write the values to value.
Semantically this is the same as memoization, but instead of using actual recursive calls, the order of evaluation asserts that necessary values always exist, making additional logic unneccessary.
Once the state space is filled, you have to examine its last state (namely the part of the array where the first index is n-1) to determine the maximal value for the entire input.

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