001package squidpony.squidmath; 002 003import java.io.Serializable; 004 005/** 006 * An alteration to a RandomnessSource that attempts to produce values that are perceived as fair to an imperfect user. 007 * <p> 008 * This takes a RandomnessSource, defaulting to a DiverRNG, and uses it to generate random values, but tracks the total 009 * and compares it to the potential total of a generator of only numbers with a desired value (default 0.54, 010 * so it compares against a sequence of all 0.54). If the current generated total is too high or low compared to the 011 * desired total, the currently used seed is possibly changed, the generated number is moved in the direction of the 012 * desired fairness, and it returns that instead of the number that would have pushed the current generated total 013 * beyond the desired threshold. The new number, if one is changed, will always be closer to the desired fairness. 014 * This is absolutely insecure for cryptographic purposes, but should seem more "fair" to a player than a 015 * random number generator that seeks to be truly random. 016 * You can create multiple DharmaRNG objects with different fairness values and seeds, and use favorable generators 017 * (with fairness greater than 0.54) for characters that need an easier time, or unfavorable generators if you want 018 * the characters that use that RNG to be impeded somewhat. 019 * The name comes from the Wheel of Dharma. 020 * This class currently will have a slight bias toward lower numbers with many RNGs unless fairness is tweaked; 0.54 021 * can be used as a stand-in because 0.5 leans too low. 022 * 023 * <p> 024 * You can get values from this generator with: {@link #nextDouble()}, {@link #nextInt()}, 025 * {@link #nextLong()}, and the bounded variants on each of those. 026 * <p> 027 * You can alter the tracking information or requested fairness with {@link #resetFortune()}, 028 * {@link #setFairness(double)}, and {@link #getFairness()}. 029 * 030 * Created by Tommy Ettinger on 5/2/2015. 031 */ 032public class DharmaRNG extends RNG implements Serializable{ 033 034 /** Used to tweak the generator toward high or low values. */ 035 private double fairness = 0.54; 036 037 /** Running total for what this has actually produced. */ 038 private double produced; 039 040 /** Running total for what this would produce if it always produced a value equal to fairness. */ 041 private double baseline; 042 043 private static final long serialVersionUID = -8919455766853811999L; 044 045 /** 046 * Constructs a DharmaRNG with a pseudo-random seed from Math.random(). 047 */ 048 public DharmaRNG() 049 { 050 this((long)(Math.random() * ((1L << 50) - 1))); 051 } 052 /** 053 * Construct a new DharmaRNG with the given seed. 054 * 055 * @param seed used to seed the default RandomnessSource. 056 */ 057 public DharmaRNG(final long seed) { 058 super(seed); 059 } 060 061 /** 062 * Construct a new DharmaRNG with the given seed. 063 * 064 * @param seed used to seed the default RandomnessSource. 065 * @param fairness the desired fairness metric, which must be between 0.0 and 1.0 066 */ 067 public DharmaRNG(final long seed, final double fairness) { 068 super(seed); 069 if(fairness < 0.0 || fairness >= 1.0) 070 this.fairness = 0.54; 071 else 072 this.fairness = fairness; 073 } 074 075 076 /** 077 * String-seeded constructor; uses a platform-independent hash of the String (it does not use String.hashCode) as a 078 * seed for DiverRNG, which is of high quality, but low period (which rarely matters for games), and has good speed, 079 * tiny state size, and excellent 64-bit number generation. 080 * 081 * @param seedString a String as a seed 082 */ 083 public DharmaRNG(CharSequence seedString) { 084 super(seedString); 085 } 086 087 088 /** 089 * String-seeded constructor; uses a platform-independent hash of the String (it does not use String.hashCode) as a 090 * seed for DiverRNG, which is of high quality, but low period (which rarely matters for games), and has good speed, 091 * tiny state size, and excellent 64-bit number generation. 092 * 093 * @param seedString a String as a seed 094 */ 095 public DharmaRNG(String seedString, double fairness) { 096 super(seedString); 097 if(fairness < 0.0 || fairness >= 1.0) 098 this.fairness = 0.54; 099 else 100 this.fairness = fairness; 101 102 } 103 /** 104 * Construct a new DharmaRNG with the given seed. 105 * 106 * @param rs the implementation used to generate random bits. 107 */ 108 public DharmaRNG(final RandomnessSource rs) { 109 super(rs); 110 } 111 /** 112 * Construct a new DharmaRNG with the given seed. 113 * 114 * @param rs the implementation used to generate random bits. 115 * @param fairness the desired fairness metric, which must be between 0.0 and 1.0 116 */ 117 public DharmaRNG(final RandomnessSource rs, final double fairness) { 118 super(rs); 119 if(fairness < 0.0 || fairness >= 1.0) 120 this.fairness = 0.54; 121 else 122 this.fairness = fairness; 123 } 124 125 /** 126 * Generate a random double, altering the result if recently generated results have been leaning 127 * away from this class' fairness value. 128 * @return a double between 0.0 (inclusive) and 1.0 (exclusive) 129 */ 130 @Override 131 public double nextDouble() { 132 double gen = (random.nextLong() & 0x1fffffffffffffL) * 0x1p-53; 133 /*if(Math.abs((produced + gen) - (baseline + fairness)) > 1.5) { 134 //do some reseeding here if possible 135 }*/ 136 if(Math.abs((produced + gen) - (baseline + fairness)) > 0.5) 137 { 138 gen = (gen + fairness) / 2.0; 139 produced *= 0.5; 140 baseline *= 0.5; 141 produced += gen; 142 baseline += fairness; 143 return gen; 144 } 145 else 146 { 147 produced += gen; 148 baseline += fairness; 149 return gen; 150 } 151 } 152 153 /** 154 * Returns a random integer below the given bound, or 0 if the bound is 0 or 155 * negative. Affects the current fortune. 156 * 157 * @param bound the upper bound (exclusive) 158 * @return the found number 159 */ 160 @Override 161 public int nextInt(int bound) { 162 if (bound <= 0) { 163 return 0; 164 } 165 166 return (int)(nextDouble() * bound); 167 } 168 169 /** 170 * Returns a random integer, which may be any positive or negative value. Affects the current fortune. 171 * @return A random int (can be any int, without restriction) 172 */ 173 @Override 174 public int nextInt() { 175 return (int)((nextDouble() - 0.5) * 2.0 * 0x7FFFFFFF); 176 } 177 178 /** 179 * Returns a random long, which may be any positive or negative value. Affects the current fortune. 180 * @return A random long (can be any long, without restriction) 181 */ 182 @Override 183 public long nextLong() { 184 return (long)((nextDouble() - 0.5) * 2.0 * 0x7FFFFFFFFFFFFFFFL); 185 } 186 187 /** 188 * Returns a random long below the given bound, or 0 if the bound is 0 or 189 * negative. 190 * 191 * @param bound the upper bound (exclusive) 192 * @return the found number 193 */ 194 @Override 195 public long nextLong(long bound) { 196 if (bound <= 0) { 197 return 0; 198 } 199 return (long)(nextDouble() * bound); 200 } 201 /** 202 * Gets the measure that this class uses for RNG fairness, defaulting to 0.54 (always between 0.0 and 1.0). 203 * @return the current fairness metric. 204 */ 205 public double getFairness() { 206 return fairness; 207 } 208 209 /** 210 * Sets the measure that this class uses for RNG fairness, which must always be between 0.0 and 1.0, and will be 211 * set to 0.54 if an invalid value is passed. 212 * @param fairness the desired fairness metric, which must be 0.0 <= fairness < 1.0 213 */ 214 public void setFairness(double fairness) { 215 if(fairness < 0.0 || fairness >= 1.0) 216 this.fairness = 0.54; 217 else 218 this.fairness = fairness; 219 } 220 221 /** 222 * Gets the status of the fortune used when calculating fairness adjustments. 223 * @return the current value used to determine whether the results should be adjusted toward fairness. 224 */ 225 public double getFortune() 226 { 227 return Math.abs(produced - baseline); 228 } 229 230 /** 231 * Resets the stored history this RNG uses to try to ensure fairness. 232 */ 233 public void resetFortune() 234 { 235 produced = 0.0; 236 baseline = 0.0; 237 } 238 /** 239 * 240 * @param bits the number of bits to be returned 241 * @return a random int of the number of bits specified. 242 */ 243 @Override 244 public int next(int bits) { 245 if(bits <= 0) 246 return 0; 247 if(bits > 32) 248 bits = 32; 249 return (int)(nextDouble() * (1L << bits)); 250 251 } 252 253 /** 254 * Returns a value between min (inclusive) and max (exclusive). 255 * <p> 256 * The inclusive and exclusive behavior is to match the behavior of the 257 * similar method that deals with floating point values. 258 * 259 * @param min the minimum bound on the return value (inclusive) 260 * @param max the maximum bound on the return value (exclusive) 261 * @return the found value 262 */ 263 @Override 264 public long between(long min, long max) { 265 return min + nextLong(max - min); 266 } 267 268 /** 269 * Returns a random non-negative integer below the given bound, or 0 if the bound is 0. 270 * Uses a slightly optimized technique. This method is considered "hasty" since 271 * it should be faster than nextInt() doesn't check for "less-valid" bounds values. It also 272 * has undefined behavior if bound is negative, though it will probably produce a negative 273 * number (just how negative is an open question). 274 * 275 * @param bound the upper bound (exclusive); behavior is undefined if bound is negative 276 * @return the found number 277 */ 278 @Override 279 public int nextIntHasty(int bound) { 280 return (int)(nextDouble() * bound); 281 } 282 283 @Override 284 public float nextFloat() { 285 return (float)nextDouble(); 286 } 287 288 @Override 289 public boolean nextBoolean() { 290 return nextDouble() >= 0.5; 291 } 292 293 @Override 294 public RandomnessSource getRandomness() { 295 return random; 296 } 297 298 @Override 299 public void setRandomness(RandomnessSource random) { 300 this.random = random; 301 } 302 303 /** 304 * Creates a copy of this DharmaRNG; it will generate the same random numbers, given the same calls in order, as 305 * this DharmaRNG at the point copy() is called. The copy will not share references with this DharmaRNG. 306 * 307 * @return a copy of this DharmaRNG 308 */ 309 @Override 310 public DharmaRNG copy() { 311 DharmaRNG next = new DharmaRNG(random.copy(), fairness); 312 next.produced = produced; 313 next.baseline = baseline; 314 return next; 315 } 316 317 @Override 318 public String toString() { 319 return "DharmaRNG{" + 320 "fairness=" + fairness + 321 ", produced=" + produced + 322 ", baseline=" + baseline + 323 ", Randomness Source=" + random + 324 '}'; 325 } 326 327 @Override 328 public boolean equals(Object o) { 329 if (this == o) return true; 330 if (o == null || getClass() != o.getClass()) return false; 331 332 DharmaRNG dharmaRNG = (DharmaRNG) o; 333 334 if (Double.compare(dharmaRNG.fairness, fairness) != 0) return false; 335 return Double.compare(dharmaRNG.produced, produced) == 0 && Double.compare(dharmaRNG.baseline, baseline) == 0; 336 337 } 338 339 @Override 340 public int hashCode() { 341 int result; 342 long temp; 343 temp = NumberTools.doubleToLongBits(fairness); 344 result = (int) (temp ^ (temp >>> 32)); 345 temp = NumberTools.doubleToLongBits(produced); 346 result = 31 * result + (int) (temp ^ (temp >>> 32)); 347 temp = NumberTools.doubleToLongBits(baseline); 348 result = 31 * result + (int) (temp ^ (temp >>> 32)); 349 return result; 350 } 351 /** 352 * Returns this DharmaRNG in a way that can be deserialized even if only {@link IRNG}'s methods can be called. 353 * @return a {@link Serializable} view of this DharmaRNG; always {@code this} 354 */ 355 @Override 356 public Serializable toSerializable() { 357 return this; 358 } 359 360}