package squidpony.squidmath; import squidpony.annotation.GwtIncompatible; import java.security.SecureRandom; /** * An RNG that cannot be seeded and should be fairly hard to predict what it will return next. Useful for competitions * where a seeded RNG is used for dungeon generation and enemy placement but an unpredictable RNG is needed for combat, * so players can't abuse the RNG to make improbable events guaranteed or unfavorable outcomes impossible. The * performance of this as a RandomnessSource is also fairly good, taking approximately 1.5x to 1.7x as long as LightRNG * to produce random 64-bit data, and of course it is far faster than java.util.Random (which is 10x slower than this). * In the secure random numbers category, where this isn't quite as secure as most, ChaosRNG is about 80x faster than * SecureRandom once SecureRandom warms up, which takes about 10 minutes of continuous number generation. Before that, * ChaosRNG is about 110x faster than SecureRandom for 64-bit data. * <br> * This is intended to be used as a RandomnessSource for an RNG, and does not have any methods other than those needed * for that interface, with one exception -- the randomize() method, which can be used to completely change all 1024 * bits of state using cryptographic random numbers. If you create a ChaosRNG and keep it around for later, then you can * pass it to the RNG constructor and later call randomize() on the ChaosRNG if you suspect it may be becoming * predictable. The period on this RNG is (2 to the 1024) - 1, so predicting it may be essentially impossible unless the * user can poke around in the application, use reflection, etc. * Created by Tommy Ettinger on 3/17/2016. */ @GwtIncompatible public class ChaosRNG implements RandomnessSource{ private transient long[] state = new long[16]; private transient int choice; private transient SecureRandom sec; private static final long serialVersionUID = -254415589291474491L; /** * Builds a ChaosRNG with a cryptographically-random seed. Future random generation uses less secure methods but * should still make it extremely difficult to "divine" the future RNG results. */ public ChaosRNG() { sec = new SecureRandom(); byte[] bytes = new byte[128]; sec.nextBytes(bytes); for (int i = sec.nextInt() & 127, c = 0; c < 128; c++, i = i + 1 & 127) { state[i & 15] |= bytes[c] << ((i >> 4) << 3); } choice = sec.nextInt(16); } @Override public int next( int bits ) { return (int)( nextLong() & ( 1L << bits ) - 1 ); } /** * Can return any long, positive or negative, of any size permissible in a 64-bit signed integer. * @return any long, all 64 bits are random */ @Override public long nextLong() { final long s0 = state[choice]; long s1 = state[choice = (choice + 1) & 15]; s1 ^= s1 << 31; // a state[choice] = s1 ^ s0 ^ (s1 >>> 11) ^ (s0 >>> 30); // b,c return state[choice] * 1181783497276652981L; } /** * Produces another ChaosRNG with no relation to this one; this breaks the normal rules that RandomnessSource.copy * abides by because this class should never have its generated number sequence be predictable. * @return a new, unrelated ChaosRNG as a RandomnessSource */ @Override public RandomnessSource copy() { return new ChaosRNG(); } /** * Changes the internal state to a new, fully-random version that should have no relation to the previous state. * May be somewhat slow; you shouldn't need to call this often. */ public void randomize() { byte[] bytes = sec.generateSeed(128); for (int i = sec.nextInt() & 127, c = 0; c < 128; c++, i = i + 1 & 127) { state[i & 15] |= bytes[c] << ((i >> 4) << 3); } choice = sec.nextInt(16); } @Override public String toString() { return "ChaosRNG with hidden state (id is " + System.identityHashCode(this) + ')'; } }