mirror of
https://github.com/panda3d/panda3d.git
synced 2025-10-01 09:23:03 -04:00
moved RandomNumGen to DIRECT
This commit is contained in:
parent
1ba8ac05c8
commit
c48f63ceac
135
direct/src/showbase/RandomNumGen.py
Normal file
135
direct/src/showbase/RandomNumGen.py
Normal file
@ -0,0 +1,135 @@
|
||||
"""RandomNumGen module: contains the RandomNumGen class"""
|
||||
|
||||
from direct.directnotify import DirectNotifyGlobal
|
||||
from pandac import Mersenne
|
||||
|
||||
def randHash(num):
|
||||
""" this returns a random 16-bit integer, given a seed integer.
|
||||
It will always return the same output given the same input.
|
||||
This is useful for repeatably mapping numbers with predictable
|
||||
bit patterns (i.e. doIds or zoneIds) to numbers with random bit patterns
|
||||
"""
|
||||
rng = RandomNumGen(num)
|
||||
return rng.randint(0,1<<16)
|
||||
|
||||
class RandomNumGen:
|
||||
notify = \
|
||||
DirectNotifyGlobal.directNotify.newCategory("RandomNumGen")
|
||||
|
||||
def __init__(self, seed):
|
||||
"""seed must be an integer or another RandomNumGen"""
|
||||
if isinstance(seed,RandomNumGen):
|
||||
# seed this rng with the other rng
|
||||
rng = seed
|
||||
seed = rng.randint(0,1L << 16)
|
||||
|
||||
self.notify.debug("seed: " + str(seed))
|
||||
seed = int(seed)
|
||||
rng = Mersenne.Mersenne(seed)
|
||||
self.__rng = rng
|
||||
|
||||
def __rand(self, N):
|
||||
"""returns integer in [0..N)"""
|
||||
"""
|
||||
# using modulus biases the numbers a little bit
|
||||
# the bias is worse for larger values of N
|
||||
return self.__rng.getUint31() % N
|
||||
"""
|
||||
|
||||
# this technique produces an even distribution.
|
||||
# random.py would solve this problem like so:
|
||||
# where:
|
||||
# M=randomly generated number
|
||||
# O=1 greater than the maximum value of M
|
||||
# return int(float(M)*(float(N)/float(O))) # M*(N/O)
|
||||
#
|
||||
# that generally works fine, except that it relies
|
||||
# on floating-point numbers, which are not guaranteed
|
||||
# to produce identical results on different machines.
|
||||
#
|
||||
# for our purposes, we need an entirely-integer approach,
|
||||
# since integer operations *are* guaranteed to produce
|
||||
# identical results on different machines.
|
||||
#
|
||||
# SO, we take the equation M*(N/O) and change the order of
|
||||
# operations to (M*N)/O.
|
||||
#
|
||||
# this requires that we have the ability to hold the result of
|
||||
# M*N. Luckily, Python has support for large integers. One
|
||||
# alternative would be to limit the RNG to a 16-bit range,
|
||||
# in which case we could do this math down in C++; but 16 bits
|
||||
# really doesn't provide a large enough range (0..65535).
|
||||
# Finally, since our O happens to be a power of two (0x80000000),
|
||||
# we can replace the divide with a shift.
|
||||
# boo-ya
|
||||
|
||||
# the maximum for N ought to be 0x80000000, but Python treats
|
||||
# that as a negative number.
|
||||
assert (N >= 0)
|
||||
assert (N <= 0x7fffffff)
|
||||
|
||||
# the cast to 'long' prevents python from importing warnings.py,
|
||||
# presumably to warn that the multiplication result is too
|
||||
# large for an int and is implicitly being returned as a long.
|
||||
# import of warnings.py was taking a few seconds
|
||||
return int((self.__rng.getUint31() * long(N)) >> 31)
|
||||
|
||||
def choice(self, seq):
|
||||
"""returns a random element from seq"""
|
||||
return seq[self.__rand(len(seq))]
|
||||
|
||||
def shuffle(self, x):
|
||||
"""randomly shuffles x in-place"""
|
||||
for i in xrange(len(x)-1, 0, -1):
|
||||
# pick an element in x[:i+1] with which to exchange x[i]
|
||||
j = int(self.__rand(i+1))
|
||||
x[i], x[j] = x[j], x[i]
|
||||
|
||||
def randrange(self, start, stop=None, step=1):
|
||||
"""randrange([start,] stop[, step])
|
||||
same as choice(range(start, stop[, step])) without construction
|
||||
of a list"""
|
||||
## this was lifted from Python2.2's random.py
|
||||
# This code is a bit messy to make it fast for the
|
||||
# common case while still doing adequate error checking
|
||||
istart = int(start)
|
||||
if istart != start:
|
||||
raise ValueError, "non-integer arg 1 for randrange()"
|
||||
if stop is None:
|
||||
if istart > 0:
|
||||
return self.__rand(istart)
|
||||
raise ValueError, "empty range for randrange()"
|
||||
istop = int(stop)
|
||||
if istop != stop:
|
||||
raise ValueError, "non-integer stop for randrange()"
|
||||
if step == 1:
|
||||
if istart < istop:
|
||||
return istart + self.__rand(istop - istart)
|
||||
raise ValueError, "empty range for randrange()"
|
||||
istep = int(step)
|
||||
if istep != step:
|
||||
raise ValueError, "non-integer step for randrange()"
|
||||
if istep > 0:
|
||||
n = (istop - istart + istep - 1) / istep
|
||||
elif istep < 0:
|
||||
n = (istop - istart + istep + 1) / istep
|
||||
else:
|
||||
raise ValueError, "zero step for randrange()"
|
||||
|
||||
if n <= 0:
|
||||
raise ValueError, "empty range for randrange()"
|
||||
return istart + istep*int(self.__rand(n))
|
||||
|
||||
def randint(self, a,b):
|
||||
"""returns integer in [a,b]"""
|
||||
assert (a <= b)
|
||||
range = b-a+1
|
||||
r = self.__rand(range)
|
||||
return a+r
|
||||
|
||||
# since floats are involved, I would recommend not trusting
|
||||
# this function for important decision points where remote
|
||||
# synchronicity is critical
|
||||
def random(self):
|
||||
"""returns random float in [0.0,1.0)"""
|
||||
return float(self.__rng.getUint31()) / float(1L << 31)
|
Loading…
x
Reference in New Issue
Block a user