简体   繁体   中英

I can't seem to generate PsuedoRandom numbers based on co-ordinates

I am trying to create an infinite map of 1 and 0's procedurally generated by PRNG but only storing a grid of 7x7 which is viewable. eg on the screen you will see a 7x7 grid which shows part of the map of 1's and 0's and when you push right it then shifts the 1's and 0's across and puts the next row in place.

If you then shift left as it is pseudo randomly generated it regenerates and shows the original. You will be able to shift either way and up and down infinitely.

The problem I have is that the initial map does not look that random and the pattern repeats itself as you scroll right or left and I think it is something to do with how I generate the numbers. eg you could start at viewing x=5000 and y= 10000 and therefore it need to generate a unique seed from these two values.

(Complexity is the variable for the size of the viewport)

void CreateMap(){
    if(complexity%2==0) {
        complexity--;
    }

    if (randomseed) {
        levelseed=Time.time.ToString();
    }

    psuedoRandom = new System.Random(levelseed.GetHashCode());
    offsetx=psuedoRandom.Next();
    offsety=psuedoRandom.Next();
    levellocation=psuedoRandom.Next();

    level = new int[complexity,complexity];
    middle=complexity/2;

    for (int x=0;x<complexity;x++){
        for (int y=0;y<complexity;y++){
            int hashme=levellocation+offsetx+x*offsety+y;
            psuedoRandom = new System.Random(hashme.GetHashCode());
            level[x,y]=psuedoRandom.Next(0,2);
        }
    }
}

This is what I use for left and right movement,

    void MoveRight(){
    offsetx++;
    for (int x=0;x<complexity-1;x++){
        for (int y=0;y<complexity;y++){
            level[x,y]=level[x+1,y];
        }
    }

    for (int y=0;y<complexity;y++){
        psuedoRandom = new System.Random(levellocation+offsetx*y);
        level[complexity-1,y]=psuedoRandom.Next(0,2);
    }
}

void MoveLeft(){
    offsetx--;
    for (int x=complexity-1;x>0;x--){
        for (int y=0;y<complexity;y++){
            level[x,y]=level[x-1,y];
        }
    }

    for (int y=0;y<complexity;y++){
        psuedoRandom = new System.Random(levellocation+offsetx*y);
        level[0,y]=psuedoRandom.Next(0,2);
    }
}

To distill it down I need to be able to set

Level[x,y]=Returnrandom(offsetx,offsety)

int RandomReturn(int x, int y){
    psuedoRandom = new System.Random(Whatseedshouldiuse);   
    return (psuedoRandom.Next (0,2));
}

Your problem I think is that you are creating a 'new' random in loops... don't redeclare inside loops as you are... instead declare it at class level and then simply use psuedoRandom.Next eg See this SO post for an example of the issue you are experiencing

instead of re-instantiating a Random() class at every iteration like you are doing:

for (int x=0;x<complexity;x++){
    for (int y=0;y<complexity;y++){
        int hashme=levellocation+offsetx+x*offsety+y;
        psuedoRandom = new System.Random(hashme.GetHashCode());
        level[x,y]=psuedoRandom.Next(0,2);
    }
}

do something more like

for (int x=0;x<complexity;x++){
    for (int y=0;y<complexity;y++){
        // Give me the next random integer
        moreRandom = psuedoRandom.Next(); 
    }
}

EDIT: As Enigmativity has pointed out in a comment below this post, reinstiating at every iteration is also a waste of time / resources too.

PS If you really need to do it then why not use the 'time-dependent default seed value' instead of specifying one?

I'd use SipHash to hash the coordinates:

  • It's relatively simple to implement
  • It takes both a key (seed for your map) and a message (the coordinates) as inputs
  • You can increase or decrease the number of rounds as a quality/performance trade-off.
  • Unlike your code, the results will be reproducible across processes and machines.

The SipHash website lists two C# implementations:

I've play a bit to create rnd(x, y, seed) :

class Program
{
    const int Min = -1000; // define the starting point

    static void Main(string[] args)
    {
        for (int x = 0; x < 10; x++)
        {
            for (int y = 0; y < 10; y++)
                Console.Write((RND(x, y, 123) % 100).ToString().PadLeft(4));
            Console.WriteLine();
        }
        Console.ReadKey();
    }

    static int RND(int x, int y, int seed)
    {
        var rndX = new Random(seed);
        var rndY = new Random(seed + 123); // a bit different
        // because Random is LCG we can only move forward
        for (int i = Min; i < x - 1; i++)
            rndX.Next();
        for (int i = Min; i < y - 1; i++)
            rndY.Next();
        // return some f(x, y, seed) = function(rnd(x, seed), rnd(y, seed))
        return rndX.Next() + rndY.Next() << 1;
    }
}

It works, but ofc an afwul implementation (because Random is LCG ), it should give a general idea though. It's memory efficient (no arrays). Acceptable compromise is to implement value caching , then while located in certain part of map surrounding values will be taken from cache instead of generating.

For same x , y and seed you will always get same value (which in turn can be used as a cell seed).

TODO: find a way to make Rnd(x) and Rnd(y) without LCG and highly inefficient initialization (with cycle).

I solved it with this, after all of you suggested approaches.

    void Drawmap(){
    for (int x=0;x<complexity;x++){
        for (int y=0;y<complexity;y++){
            psuedoRandom = new System.Random((x+offsetx)*(y+offsety));
            level[x,y]=psuedoRandom.Next (0,2);
        }
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM