001package squidpony.squidmath; 002 003import squidpony.StringKit; 004 005import java.io.Serializable; 006import java.util.*; 007 008/** 009 * An RNG variant that has 16 possible grades of value it can produce and shuffles them like a deck of cards. 010 * It repeats grades of value, but not exact values, every 16 numbers requested from it. Grades go in increments of 011 * 0.0625 from 0.0 to 0.9375, and are added to a random double less than 0.0625 to get the random number for that 012 * grade. 013 * <p> 014 * You can get values from this generator with: {@link #nextDouble()}, {@link #nextInt()}, 015 * {@link #nextLong()}, and the bounded variants on each of those. 016 * 017 * Created by Tommy Ettinger on 5/2/2015. 018 */ 019public class DeckRNG extends StatefulRNG implements Serializable { 020 private static final long serialVersionUID = 7828346657944720807L; 021 private int step; 022 private long lastShuffledState; 023 private static final double[] baseDeck = new double[]{0.0, 0.0625, 0.125, 0.1875, 0.25, 0.3125, 0.375, 0.4375, 024 0.5, 0.5625, 0.625, 0.6875, 0.75, 0.8125, 0.875, 0.9375}; 025 private final double[] deck = new double[16]; 026 027 /** 028 * Constructs a DeckRNG with a pseudo-random seed from Math.random(). 029 */ 030 public DeckRNG() 031 { 032 this((long)(Math.random() * ((1L << 50) - 1))); 033 } 034 /** 035 * Construct a new DeckRNG with the given seed. 036 * 037 * @param seed used to seed the default RandomnessSource. 038 */ 039 public DeckRNG(final long seed) { 040 lastShuffledState = seed; 041 random = new LightRNG(seed); 042 step = 0; 043 } 044 045 /** 046 * String-seeded constructor uses the hash of the String as a seed for LightRNG, which is of high quality, but low 047 * period (which rarely matters for games), and has good speed and tiny state size. 048 * 049 * @param seedString a String to use as a seed; will be hashed in a uniform way across platforms. 050 */ 051 public DeckRNG(CharSequence seedString) { 052 this(CrossHash.hash64(seedString)); 053 } 054 /** 055 * Seeds this DeckRNG using the RandomnessSource it is given. Does not assign the RandomnessSource to any fields 056 * that would affect future pseudo-random number generation. 057 * @param random will be used to generate a new seed, but will not be assigned as this object's RandomnessSource 058 */ 059 public DeckRNG(RandomnessSource random) { 060 this(random.nextLong()); 061 062 } 063 064 /** 065 * Generate a random double, altering the result if recently generated results have been leaning 066 * away from this class' fairness value. 067 * @return a double between 0.0 (inclusive) and 1.0 (exclusive) 068 */ 069 @Override 070 public double nextDouble() { 071 if(step == 0) 072 shuffleInPlace(deck); 073 double gen = deck[step++]; 074 step %= 16; 075 return gen; 076 } 077 078 /** 079 * This returns a random double between 0.0 (inclusive) and max (exclusive). 080 * 081 * @return a value between 0 (inclusive) and max (exclusive) 082 */ 083 @Override 084 public double nextDouble(double max) { 085 return nextDouble() * max; 086 } 087 088 /** 089 * Returns a value from a even distribution from min (inclusive) to max 090 * (exclusive). 091 * 092 * @param min the minimum bound on the return value (inclusive) 093 * @param max the maximum bound on the return value (exclusive) 094 * @return the found value 095 */ 096 @Override 097 public double between(double min, double max) { 098 return min + (max - min) * nextDouble(); 099 } 100 101 /** 102 * Returns a value between min (inclusive) and max (exclusive). 103 * 104 * The inclusive and exclusive behavior is to match the behavior of the 105 * similar method that deals with floating point values. 106 * 107 * @param min the minimum bound on the return value (inclusive) 108 * @param max the maximum bound on the return value (exclusive) 109 * @return the found value 110 */ 111 @Override 112 public int between(int min, int max) { 113 return nextInt(max - min) + min; 114 } 115 116 /** 117 * Returns the average of a number of randomly selected numbers from the 118 * provided range, with min being inclusive and max being exclusive. It will 119 * sample the number of times passed in as the third parameter. 120 * 121 * The inclusive and exclusive behavior is to match the behavior of the 122 * similar method that deals with floating point values. 123 * 124 * This can be used to weight RNG calls to the average between min and max. 125 * 126 * @param min the minimum bound on the return value (inclusive) 127 * @param max the maximum bound on the return value (exclusive) 128 * @param samples the number of samples to take 129 * @return the found value 130 */ 131 @Override 132 public int betweenWeighted(int min, int max, int samples) { 133 int sum = 0; 134 for (int i = 0; i < samples; i++) { 135 sum += between(min, max); 136 } 137 138 return Math.round((float) sum / samples); 139 } 140 141 /** 142 * Returns a random element from the provided array and maintains object 143 * type. 144 * 145 * @param <T> the type of the returned object 146 * @param array the array to get an element from 147 * @return the randomly selected element 148 */ 149 @Override 150 public <T> T getRandomElement(T[] array) { 151 if (array.length < 1) { 152 return null; 153 } 154 return array[nextInt(array.length)]; 155 } 156 157 /** 158 * Returns a random element from the provided list. If the list is empty 159 * then null is returned. 160 * 161 * @param <T> the type of the returned object 162 * @param list the list to get an element from 163 * @return the randomly selected element 164 */ 165 @Override 166 public <T> T getRandomElement(List<T> list) { 167 if (list.size() <= 0) { 168 return null; 169 } 170 return list.get(nextInt(list.size())); 171 } 172 173 /** 174 * Returns a random element from the provided Collection, which should have predictable iteration order if you want 175 * predictable behavior for identical RNG seeds, though it will get a random element just fine for any Collection 176 * (just not predictably in all cases). If you give this a Set, it should be a LinkedHashSet or some form of sorted 177 * Set like TreeSet if you want predictable results. Any List or Queue should be fine. Map does not implement 178 * Collection, thank you very much Java library designers, so you can't actually pass a Map to this, though you can 179 * pass the keys or values. If coll is empty, returns null. 180 * 181 * <p> 182 * Requires iterating through a random amount of coll's elements, so performance depends on the size of coll but is 183 * likely to be decent, as long as iteration isn't unusually slow. This replaces {@code getRandomElement(Queue)}, 184 * since Queue implements Collection and the older Queue-using implementation was probably less efficient. 185 * </p> 186 * @param <T> the type of the returned object 187 * @param coll the Collection to get an element from; remember, Map does not implement Collection 188 * @return the randomly selected element 189 */ 190 public <T> T getRandomElement(Collection<T> coll) { 191 if (coll.size() <= 0) { 192 return null; 193 } 194 int n = nextInt(coll.size()); 195 T t = null; 196 Iterator<T> it = coll.iterator(); 197 while (n-- >= 0 && it.hasNext()) 198 t = it.next(); 199 return t; 200 } 201 202 /** 203 * Returns a random integer below the given bound, or 0 if the bound is 0 or 204 * negative. Affects the current fortune. 205 * 206 * @param bound the upper bound (exclusive) 207 * @return the found number 208 */ 209 @Override 210 public int nextInt(int bound) { 211 if (bound <= 0) { 212 return 0; 213 } 214 215 return (int)(nextDouble() * bound); 216 } 217 218 /** 219 * Shuffle an array using the Fisher-Yates algorithm. Not GWT-compatible; use the overload that takes two arrays. 220 * <br> 221 * https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 222 * 223 * @param elements an array of T; will not be modified 224 * @return a shuffled copy of elements 225 */ 226 @Override 227 public <T> T[] shuffle(T[] elements) 228 { 229 int n = elements.length; 230 T[] array = Arrays.copyOf(elements, n); 231 for (int i = 0; i < n; i++) 232 { 233 int r = i + nextIntHasty(n - i); 234 T t = array[r]; 235 array[r] = array[i]; 236 array[i] = t; 237 } 238 return array; 239 } 240 241 242 /** 243 * Generates a random permutation of the range from 0 (inclusive) to length (exclusive). 244 * Useful for passing to OrderedMap or OrderedSet's reorder() methods. 245 * 246 * @param length the size of the ordering to produce 247 * @return a random ordering containing all ints from 0 to length (exclusive) 248 */ 249 @Override 250 public int[] randomOrdering(int length) 251 { 252 int[] dest = new int[length]; 253 for (int i = 0; i < length; i++) 254 { 255 int r = nextIntHasty(i + 1); 256 if(r != i) 257 dest[i] = dest[r]; 258 dest[r] = i; 259 } 260 return dest; 261 } 262 263 /** 264 * Returns a random non-negative integer below the given bound, or 0 if the bound is 0. 265 * Uses a slightly optimized technique. This method is considered "hasty" since 266 * it should be faster than nextInt() doesn't check for "less-valid" bounds values. It also 267 * has undefined behavior if bound is negative, though it will probably produce a negative 268 * number (just how negative is an open question). 269 * 270 * @param bound the upper bound (exclusive); behavior is undefined if bound is negative 271 * @return the found number 272 */ 273 @Override 274 public int nextIntHasty(int bound) { 275 return (int)(nextDouble() * bound); 276 } 277 278 /** 279 * Returns a random integer, which may be positive or negative. 280 * @return A random int 281 */ 282 @Override 283 public int nextInt() { 284 return (int)((nextDouble() * 2.0 - 1.0) * 0x7FFFFFFF); 285 } 286 287 /** 288 * Returns a random long, which may be positive or negative. 289 * @return A random long 290 */ 291 @Override 292 public long nextLong() { 293 double nx = nextDouble(); 294 return (long)((nx * 2.0 - 1.0) * 0x7FFFFFFFFFFFFFFFL); 295 } 296 297 /** 298 * Returns a random long below the given bound, or 0 if the bound is 0 or 299 * negative. 300 * 301 * @param bound the upper bound (exclusive) 302 * @return the found number 303 */ 304 @Override 305 public long nextLong(long bound) { 306 if (bound <= 0) { 307 return 0; 308 } 309 double nx = nextDouble(); 310 return (long)(nx * bound); 311 //return ((long)(nx * bound)) ^ (long)((nx * 0xFFFFFL) % bound) ^ (long)((nx * 0xFFFFF00000L) % bound); 312 } 313 /** 314 * 315 * @param bits the number of bits to be returned 316 * @return a random int of the number of bits specified. 317 */ 318 @Override 319 public int next(int bits) { 320 if(bits <= 0) 321 return 0; 322 if(bits > 32) 323 bits = 32; 324 return (int)(nextDouble() * (1L << bits)); 325 326 } 327 328 @Override 329 public Random asRandom() { 330 if(ran == null) 331 { 332 ran = new CustomRandom(random.copy()); 333 } 334 return ran; 335 } 336 337 /** 338 * Returns a value between min (inclusive) and max (exclusive). 339 * <p/> 340 * The inclusive and exclusive behavior is to match the behavior of the 341 * similar method that deals with floating point values. 342 * 343 * @param min the minimum bound on the return value (inclusive) 344 * @param max the maximum bound on the return value (exclusive) 345 * @return the found value 346 */ 347 @Override 348 public long between(long min, long max) { 349 return nextLong(max - min) + min; 350 } 351 352 @Override 353 public float nextFloat() { 354 return (float)nextDouble(); 355 } 356 357 @Override 358 public boolean nextBoolean() { 359 return nextDouble() >= 0.5; 360 } 361 362 @Override 363 public RandomnessSource getRandomness() { 364 return random; 365 } 366 367 /** 368 * Reseeds this DeckRNG using the RandomnessSource it is given. Does not assign the RandomnessSource to any fields 369 * that would affect future pseudo-random number generation. 370 * @param random will be used to generate a new seed, but will not be assigned as this object's RandomnessSource 371 */ 372 @Override 373 public void setRandomness(RandomnessSource random) { 374 setState(((long)random.next(32) << 32) | random.next(32)); 375 376 } 377 378 /** 379 * Creates a copy of this DeckRNG; it will generate the same random numbers, given the same calls in order, as 380 * this DeckRNG at the point copy() is called. The copy will not share references with this DeckRNG. 381 * 382 * @return a copy of this DeckRNG 383 */ 384 @Override 385 public DeckRNG copy() 386 { 387 DeckRNG next = new DeckRNG(lastShuffledState); 388 next.random = random.copy(); 389 System.arraycopy(deck, 0, next.deck, 0, deck.length); 390 next.step = step; 391 return next; 392 } 393 394 /** 395 * Shuffle an array using the Fisher-Yates algorithm. 396 * @param array an array of double; WILL be modified 397 */ 398 private void shuffleInPlace(double[] array) 399 { 400 lastShuffledState = ((StatefulRandomness)random).getState(); 401 final int n = array.length; 402 System.arraycopy(baseDeck, 0, array, 0, n); 403// for (int i = 0; i < n; i++) 404// { 405// int r = i + LightRNG.determineBounded(lastShuffledState + (i << 1), n - i); 406// double t = array[r]; 407// array[r] = array[i]; 408// array[i] = LightRNG.determineDouble(lastShuffledState + (i << 1) + 1) * 0.0625 + t; 409// } 410 for (int i = n; i > 1; i--) { 411 final int r = DiverRNG.determineBounded(lastShuffledState + i, i); 412 final double t = array[i - 1]; 413 array[i - 1] = array[r]; 414 array[r] = t; 415 } 416 for (int i = 0; i < n; i++) { 417 array[i] += DiverRNG.determineDouble(lastShuffledState ^ ~i) * 0.0625; 418 } 419 420 } 421 422 /** 423 * Get a long that can be used to reproduce the sequence of random numbers this object will generate starting now. 424 * 425 * @return a long that can be used as state. 426 */ 427 @Override 428 public long getState() { 429 return lastShuffledState; 430 } 431 432 /** 433 * Sets the state of the random number generator to a given long, which will alter future random numbers this 434 * produces based on the state. Setting the state always causes the "deck" of random grades to be shuffled. 435 * 436 * @param state any long (this can tolerate states of 0) 437 */ 438 @Override 439 public void setState(long state) { 440 ((StatefulRandomness)random).setState(state); 441 shuffleInPlace(deck); 442 step = 0; 443 } 444 445 @Override 446 public String toString() { 447 return "DeckRNG{state: 0x" + StringKit.hex(lastShuffledState) + "L, step: 0x" + StringKit.hex(step) + "}"; 448 } 449 450 @Override 451 public boolean equals(Object o) { 452 if (this == o) return true; 453 if (o == null || getClass() != o.getClass()) return false; 454 if (!super.equals(o)) return false; 455 456 DeckRNG deckRNG = (DeckRNG) o; 457 458 return step == deckRNG.step && lastShuffledState == deckRNG.lastShuffledState; 459 } 460 461 @Override 462 public int hashCode() { 463 int result = random.hashCode(); 464 result = 31 * result + step; 465 result = 31 * result + (int) (lastShuffledState ^ (lastShuffledState >>> 32)); 466 return result; 467 } 468 469 public int getStep() { 470 return step; 471 } 472 473 public void setStep(int step) { 474 this.step = step; 475 } 476 477 /** 478 * Returns this DeckRNG in a way that can be deserialized even if only {@link IRNG}'s methods can be called. 479 * @return a {@link Serializable} view of this DeckRNG; always {@code this} 480 */ 481 @Override 482 public Serializable toSerializable() { 483 return this; 484 } 485 486}