mirror of
https://github.com/Sneed-Group/Poodletooth-iLand
synced 2024-12-26 21:22:27 -06:00
138 lines
5.2 KiB
Python
138 lines
5.2 KiB
Python
|
"""RandomNumGen module: contains the RandomNumGen class"""
|
||
|
|
||
|
__all__ = ['randHash', 'RandomNumGen']
|
||
|
|
||
|
from direct.directnotify import DirectNotifyGlobal
|
||
|
from pandac.PandaModules 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) - 1)
|
||
|
|
||
|
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(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)
|