001package squidpony.squidmath;
002
003import squidpony.StringKit;
004
005/**
006 * An RNG that cannot be seeded and should be fairly hard to predict what it will return next. Useful for competitions
007 * where a seeded RNG is used for dungeon generation and enemy placement but an unpredictable RNG is needed for combat,
008 * so players can't abuse the RNG to make improbable events guaranteed or unfavorable outcomes impossible.
009 * <br>
010 * This is intended to be used as a RandomnessSource for an RNG, and does not have any methods other than those needed
011 * for that interface, with one exception -- the randomize() method, which can be used to completely change all (many)
012 * bits of state using cryptographic random numbers. If you create a ChaosRNG and keep it around for later, then you can
013 * pass it to the RNG constructor and later call randomize() on the ChaosRNG if you suspect it may be becoming
014 * predictable. The period on this RNG is preposterously large, since it involves a pair of IsaacRNGs as well as other
015 * random state, so predicting it may be essentially impossible unless the user can poke around in the application, use
016 * reflection, and so on.
017 * Created by Tommy Ettinger on 3/17/2016.
018 */
019public class ChaosRNG implements RandomnessSource{
020
021    private transient long[] z;
022    private transient int choice;
023    private transient IsaacRNG r0, r1;
024    private static final long serialVersionUID = -254415589291474491L;
025
026    private long determine()
027    {
028        long state = (z[(choice += 0xC6BC278D) >>> 28] += 0x9E3779B97F4A7C15L ^ choice * 0x2C9277B5000000L);
029        state = (state ^ state >>> 26) * 0x2545F4914F6CDD1DL;
030        return state ^ state >>> 28;
031
032    }
033    /**
034     * Builds a ChaosRNG with a fairly-random seed derived from somewhat-OK sources of non-seed randomness, such as time
035     * before and after garbage collection. We're forced to use sub-par techniques here due to GWT not supporting any
036     * better methods. Future random generation uses less secure methods but should still make it extremely difficult to
037     * "divine" the future RNG results from the outputs.
038     */
039    public ChaosRNG()
040    {
041        z = new long[16];
042        // produces some garbage; this is intentional
043        String s = StringKit.hexHash(System.currentTimeMillis(), System.identityHashCode(this), System.identityHashCode(z));
044        s += StringKit.LATIN_LETTERS_LOWER;
045        // sway causes most of the state of Math's Random field to be non-visible in the output
046        s += NumberTools.sway((126.621 + Math.random()) * (17.71 - Math.random()) + (71.17 * Math.random()));
047        // this should take some time because of the earlier garbage
048        System.gc();
049        // so this should have a better chance of being different.
050        s += System.currentTimeMillis();
051        r0 = new IsaacRNG(s);
052        r1 = new IsaacRNG(r0.nextBlock());
053        r1.fillBlock(z);
054        choice = r0.next(32);
055    }
056
057    @Override
058    public final int next( int bits ) {
059        return (int)( nextLong() & ( 1L << bits ) - 1 );
060    }
061
062    /**
063     * Can return any long, positive or negative, of any size permissible in a 64-bit signed integer.
064     * @return any long, all 64 bits are random
065     */
066    @Override
067    public final long nextLong() {
068        long rot = (determine() & 31) + 12;
069        return r0.nextLong() << rot ^ r1.nextLong() >>> 45 - rot;
070    }
071
072    /**
073     * Produces another ChaosRNG with no relation to this one; this breaks the normal rules that RandomnessSource.copy
074     * abides by because this class should never have its generated number sequence be predictable.
075     * @return a new, unrelated ChaosRNG as a RandomnessSource
076     */
077    @Override
078    public ChaosRNG copy() {
079        return new ChaosRNG();
080    }
081
082    /**
083     * Changes the internal state to a new, fully-random version that should have no relation to the previous state.
084     * May be somewhat slow; you shouldn't need to call this often.
085     */
086    public void randomize()
087    {
088        String s = System.currentTimeMillis() + "0" + System.identityHashCode(this);
089        s += StringKit.LATIN_LETTERS_LOWER;
090        System.gc();
091        s += System.currentTimeMillis();
092        r0.init(s);
093        r1.init(r0.nextBlock());
094        r1.fillBlock(z);
095        choice = r0.next(32);
096    }
097
098    @Override
099    public String toString() {
100        return "ChaosRNG with hidden state (id is " + System.identityHashCode(this) + ')';
101    }
102}