2
0
Эх сурвалжийг харах

moved RandomNumGen to DIRECT

Darren Ranalli 21 жил өмнө
parent
commit
c48f63ceac

+ 135 - 0
direct/src/showbase/RandomNumGen.py

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