3

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. e.g. 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. e.g. 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));
}
Munkybunky
  • 173
  • 1
  • 7
  • Is there some maximum dimension of the required map, such as 1024 by 1024 entries? – Codor Sep 22 '15 at 11:17
  • No I am trying to make it appear infinite. e.g. you may start at viewing coord 10000, 445554, which will also be completely randomly generated. – Munkybunky Sep 22 '15 at 11:18
  • Too keep it simple, say the world was 100 by 100 and the viewport was at location 60,60. Therefore you could see a grid of 60-66 in the x and 60-66 in the y. x(60),y(60) should be populated with a random 1 or 0 and x(61),y(60) should also be populated with a random 1 or 0. Therefore I need to have a unique value to seed from based on 2 co-ordinates I think? – Munkybunky Sep 22 '15 at 11:23
  • is if (randomseed) true or false... if false what is levelseed? – Paul Zahra Sep 22 '15 at 11:23
  • `levellocation+offsetx+x*offsety+y` will get you a huge number of collisions. No wonder it looks repetitive :) In any case, to get something really nice, you'll need to experiment a lot more. There's a lot of tricks you can use after you fix your outright mistakes. – Luaan Sep 22 '15 at 11:23
  • 3
    @Munkybunky 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 – Paul Zahra Sep 22 '15 at 11:26
  • Just for clarification, the generation of the map works but the results don't look 'random' enough? – Codor Sep 22 '15 at 11:27
  • @PaulZahra the seed if false uses the hashcode of a word the user can type in. I have been using "Kurt" as the seed to experiment. I am creating new seeds inside the loops to ensure as a user scrolls that if they scroll back to the same location they end up with the same pattern. e.g. if at location 200,200 it is set to 1 and then the user scrolls 50 to the right and then they scroll 50 back it need to use the psuedo random generator to regenerate the 1. – Munkybunky Sep 22 '15 at 11:32
  • @codor the generation of the map works but it clearly isn't random. I think I need to generate a 1 or 0 at a co-ordinate by using a seed which is created from the co-ordinates x and y. e.g. if you start position is (20001,55000) then I need to generate a pretty unique number from this. To simplify the example if you have a 3x3 grid and x,y = 2,3 if your unique number is x*y=6 you will also get the same number from 3,2=6 – Munkybunky Sep 22 '15 at 11:35
  • You can implement `rnd(x, y, seed)` function (which should return same value for same x, y and seed, but random if either changes). This value is then used as a seed in cell-specific generation algorithm (if cell is big, can contain something, is a part of multi-cell object generation, etc). – Sinatr Sep 22 '15 at 11:48
  • @Sinatr this is helpful can you give me an example ? – Munkybunky Sep 22 '15 at 12:02
  • @Munkybunky If your seed is random, i.e. you use random levellocation and random offsets, then the same location will have random numbers. Then for the same location to have the same seed perhaps simply use x as the random seed? But then your use of Next() enters another random element, so the numbers will not ever be the same for a given cord / view. – Paul Zahra Sep 22 '15 at 12:23
  • @Munkybunky I'm thinking you shouldn't use random number generator, instead create an algorithm that generates 0 or 1 for a given matrix cord based initially upon x and y and perhaps complexity, that way you will have the same numbers generated for a given coord etc. – Paul Zahra Sep 22 '15 at 12:30

4 Answers4

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 e.g. 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.

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

Community
  • 1
  • 1
Paul Zahra
  • 9,522
  • 8
  • 54
  • 76
  • Also you should probably point out that it is a waste of time calling `new Random(someSeedValue)`. If you create a single instance of `Random` then it is enough to call `new Random()`. – Enigmativity Sep 22 '15 at 11:36
  • I think that would work if it was static, e.g. with no movement. Example 5x5 grid, 10001 on every row generated from randomness as above using x and y co-ords. you move left generating random from new co-ords, coloumn 2 moves to column 1 and then you have a different looking column 1 which may be filled with 1's. – Munkybunky Sep 22 '15 at 11:38
  • I think what I need is a way to generate a random number consistently based on co-ordinate values. So if I reference the offset in the map x and y I will always get the same outcome – Munkybunky Sep 22 '15 at 11:41
  • If i asked you give me a random number between 1 and 0 for co-ordinate 200,50 and you said 0, then I asked you for a load more and then asked you again for a random number between 1 and 0 for co-ordinate 200,50 you would give me the same result. – Munkybunky Sep 22 '15 at 11:43
  • @Munkybunky - It sounds like you you do need to use `new Random(someSeedValue)` as you want the same value from the same co-ordinate. The problem is that `.GetHashCode()` isn't particularly random so your random numbers aren't that random. – Enigmativity Sep 22 '15 at 11:47
  • @Enigmativity I think I have two problems, 1 using gethashcode and 2 what seedvalue is would be approriate to use from x and y? – Munkybunky Sep 22 '15 at 11:52
  • @Munkybunky Just an idea but why not concatenate x = 1 and y = 2 gives "12" then convert to an int = 12 and use that as the seed, i.e. that will be reproducible for given coords and 'random'. – Paul Zahra Sep 22 '15 at 15:50
0

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:

CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
0

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).

Sinatr
  • 20,892
  • 15
  • 90
  • 319
0

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);
        }
    }
Munkybunky
  • 173
  • 1
  • 7