2
\$\begingroup\$

I am developing a 2D game in python and I could get Perlin Noise working with this code:

import timeit
import noise

POSITIVE =  10
NEGATIVE = -10

BLOCK = "#"
BLOCK_LIMITS = (POSITIVE, 0.3)

WATER = "-"
WATER_LIMITS = (0.3, -0.4)

GRASS = " "
GRASS_LIMITS = (-0.4, NEGATIVE)

oct = 1
outputString = str()

def GENERATE_MAP():
    global outputString

    for y in range(1000):
        outputString += "["
        for x in range(165):
            i = round(noise.pnoise2(x / 15, y / 15, octaves = oct), 5)
                if i > 0.4:
                    outputString += BLOCK
                elif i < -0.5:
                    outputString += WATER
                else:
                    outputString += GRASS
        outputString += "]\n"

time = timeit.timeit(GENERATE_MAP, number = 1)
print(outputString)
print("time = " + str(time))

But this creates the same terrain every time, so I thought to use seeds, but I don't know how to do that.
I am using python 3.6 with noise.

\$\endgroup\$
  • \$\begingroup\$ You can think of your seed as just an offset you add to x & y before looking up a noise value. \$\endgroup\$ – DMGregory Dec 13 '18 at 23:32
3
\$\begingroup\$

Looking in the code, the library provides noise.randomize() which states:

Randomize the permutation table used by the noise functions. This makes them generate a different noise pattern for the same inputs.

Thus, randomizing the function through the API provided is the most direct way to solve the problem.


Here's an alternative solution based on the the discussion in chat, which I posted before spotting the API function:

If you cannot directly set the seed for your noise (sometimes the case with library code), then you can get around this limitation by using a random scaling &/or translating (shifting) factor.

While I don't recommend using scaling for randomizing, many noise functions give a value of zero for integer coords, so we'll need to adjust for that eventually anyway. Scaling essentially means multiplying by some factor. So for example if your factor is 0.1 then the integer coords 0,1,2,3,... are transformed into the noise coords 0.0, 0.1, 0.2, 0.3, ...

Translating on the other hand can be done by adding some factor. Using 0.1 again as an example the integer coords 0,1,2,3,... becomes the noise coords 0.1, 1.1, 2.1, 3.1, ...

Next, here's how you can use them to randomize your map generation. A noise library I used was deterministic, meaning the noise values for a given coord are never randomized between runs. But I needed the values to change based on a game seed, so I translated my sampling by some random factor & that worked well enough for me. So as an example, when my map generation starts:

  • I use a random number generator (seeded to the system time) to get a translation factor for both X & Y, let's call them tX and tY. Since the system time changes between runs, tX & tY should be different across each run
  • Next, I use a scaling factor so that I sample at more than just the integer coords. Let's call that factor s.
  • Putting it all together, if I needed to get a noise value for some x,y location, I would have something like this: value = noise(x*s+tX, y*s+tY)

Depending on the size & scale of your map as well as the period of the noise generating function, translating & scaling may still leave you with unwanted repetition. The typical solution to is to combine multiple layers of noise with differing frequencies and amplitudes.

Frequency is controlled by scaling the X,Y inputs. Multiplying by a factor between 0-1 will increase the frequency, multiplying by greater than 1 will decrease it.

Amplitude is controlled by scaling the output from the noise function. Multiplying by a factor between 0-1 will decrease the Amplitude, multiplying by greater than 1 will increase it.

Together, the changes to frequency and amplitude are often referred to as octaves. Adjusting the octaves is a bit of trail & error art - the common rule of thumb is as you double one, you halve the other (or vice versa).

Even though the underlying noise function remains the same, the combinatorics of translation & scaling summed across multiple layers quickly creates enough variety to overcome repetitions.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the fast response, but would not the map be repetitive? \$\endgroup\$ – Simple coder Dec 15 '18 at 13:40
  • \$\begingroup\$ That depends on the size & scale of your map, the specifics of the noise generating function and whether or not you're using a single layer (octave) of noise or combining multiple octaves. I've added something to address that. Also, there's a solution to the problem directly in the API - I've added that as well. \$\endgroup\$ – Pikalek Dec 15 '18 at 19:01
2
\$\begingroup\$

Try setting the random seed. A common way to do this is by using the current time as the seed value.

import random
from datetime import datetime
random.seed(datetime.now())
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.