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}