001package squidpony.squidmath; 002 003import java.io.Serializable; 004 005/** 006 * A subclass of StatefulRNG (and thus RNG) that allows customizing many parts of the random number generation. 007 * This is meant to be a more comprehensible version of the functionality present in RandomBias, and also for it to be 008 * easier to use with methods that expect an RNG. 009 * <br> 010 * You can change the expected average for the values this produces, which uses the RandomBias.EXPONENTIAL distribution, 011 * with all the caveats it has: it strongly favors either high or low values when the average gets especially high or 012 * low, but it can essentially cover all averages between 0.0 and 1.0 (this class limits it to 0.1 and 0.9, so other 013 * techniques can be used effectively). 014 * <br> 015 * You can also affect the "centrality" of random numbers, causing more to occur near the expected average (a bell curve 016 * effect), or cause more near extreme ends of the random number spectrum. In practice, centrality changes are hard to 017 * notice, but may be useful to simulate certain effects. An example of centrality changes in existing games include the 018 * Nintendo title Advance Wars 2, where a brutish commander could increase the amount of damage his units dealt but also 019 * suffered unpredictability; attacks could deal even more or much less damage than normal without any way to build 020 * tactics around it. Square Enix's Final Fantasy XII also notably differentiated certain weapons (axes, hammers, and 021 * "hand-cannons") from other similar options by making them deal less predictable damage. In both cases the connotation 022 * is that more randomness is fitting for a brute-force approach to combat where pre-planned strategies are less 023 * emphasized. It should also be noted that increasing the frequency of extreme results makes small bonuses to defense 024 * or offense typically less useful, and small penalties less harmful. The opposite can be true for a carefully tuned 025 * game where the most common results are tightly clustered, and most target numbers are just slightly above the 026 * ordinary average. In tabletop games, 1d20 and 3d6 have the same average, but 1d20 is uniform, where 3d6 is clustered 027 * around 10 and 11, each the result of 1/8 of rolls on their own and 1/4 together. This makes the case where a +1 bonus 028 * to succeed changes the outcome on approximately 5% of 1d20 rolls, regardless of the required number to succeed if it 029 * is less than 20. However, a +1 bonus matters on a variable portion of 3d6 rolls; if you become able to succeed on a 030 * 10 or 11 where that was a failure before, the bonus applies approximately 12.5% of the time. Becoming able to succeed 031 * on an 18 where that was a failure before is essentially worthless, affecting less than 0.5% of rolls. This property 032 * of centralized results should be considered if game balance and/or the lethality of combat is important. One lengthy 033 * stretch of extreme results by enemies that work against the favor of a player character generally result in a dead 034 * player character, and RNGs that make extreme results more common may seem particularly cruel to players. 035 * <br> 036 * This generator sets a field, rawLatest, every time a random number is produced. This stores a pseudo-random double 037 * between 0.0 (inclusive) and 1.0 (exclusive) that is not subject to the bias an expected average introduces, and is 038 * close to uniformly distributed. You should expect rawLatest to be higher when higher numbers are returned from a 039 * method like nextInt(), and lower when lower numbers are returned. This can be useful for rare effects that should not 040 * be drastically more or less likely when slight changes are made to the expected average; if the expected average is 041 * 0.65, many more random doubles from nextDouble() will be between 0.95 and 1.0 (probably more than 10% of random 042 * numbers), but rawLatest will only be between 0.95 and 1.0 for close to 5% of all generations. 043 * <br> 044 * You can get and set the state this uses internally, and this is stored as a 64-bit long. 045 * <br> 046 * The choice of RandomnessSource doesn't really matter since this will always use a LightRNG internally. 047 * LightRNG is the current best StatefulRandomness implementation, with excellent performance characteristics and 048 * few flaws, and though its relatively low period may sometimes be a detriment, all StatefulRandomness implementations 049 * will have the same or lower period. 050 * <br> 051 * More customizations may be added in the future to the ones available currently. 052 */ 053public class EditRNG extends StatefulRNG implements Serializable{ 054 055 /** Used to tweak the generator toward high or low values. */ 056 private double expected = 0.5; 057 058 // These are tied to expected, and must change when it does. 059 private double offset; 060 private double range = 1.0; 061 /** 062 * When positive, makes the generator more likely to generate values close to the average (bell curve). 063 * When zero (the default), makes no changes to the centering of values. 064 * When negative, makes the generator swing more toward extremes rather than gravitate toward the average. 065 * Values are typically between -100 and 100, but can go as low as -200 or as high as 200 (stopping there). 066 */ 067 private double centrality; 068 069 070 // This lets us avoid a conversion to double every time we generate a number. 071 private long centralityCalculated = 0x0008000000000000L; 072 073 /** 074 * The latest generated double, between 0.0 and 1.0, before changes for centrality and expected average. 075 * Doubles are used to generate all random numbers this class produces, so be aware that calling getRandomElement() 076 * will change this just as much as nextDouble(), nextInt(), or between() will. Primarily useful to obtain 077 * uniformly-distributed random numbers that are related to the biased random numbers this returns as a main result, 078 * such as to find when the last number generated was in the bottom 5% (less than 0.05, which could represent some 079 * kind of critical failure or fumble) or top 10% (greater than or equal to 0.9, which could grant a critical 080 * success or luck-based reward of some kind). 081 */ 082 public double rawLatest = 0.5; 083 private static final long serialVersionUID = -2458726316853811777L; 084 085 /** 086 * Constructs an EditRNG with a pseudo-random seed from Math.random(). 087 */ 088 public EditRNG() 089 { 090 } 091 /** 092 * Construct a new EditRNG with the given seed. 093 * 094 * @param seed used to seed the default RandomnessSource. 095 */ 096 public EditRNG(final long seed) { 097 super(seed); 098 } 099 /** 100 * Construct a new EditRNG with the given seed. 101 * 102 * @param seed used to seed the default RandomnessSource. 103 */ 104 public EditRNG(final CharSequence seed) { 105 super(seed); 106 } 107 108 /** 109 * Construct a new EditRNG with the given seed. 110 * 111 * @param seed used to seed the default RandomnessSource. 112 * @param expected the expected average for random doubles, which will be capped between 0.1 and 0.9 113 */ 114 public EditRNG(final long seed, double expected) { 115 super(seed); 116 setExpected(expected); 117 } 118 /** 119 * Construct a new EditRNG with the given seed. 120 * 121 * @param seed used to seed the default RandomnessSource. 122 * @param expected the expected average for random doubles, which will be capped between 0.1 and 0.9 123 */ 124 public EditRNG(final String seed, double expected) { 125 super(seed); 126 setExpected(expected); 127 } 128 129 /** 130 * Construct a new EditRNG with the given seed. 131 * 132 * @param seed used to seed the default RandomnessSource. 133 * @param expected the expected average for random doubles, which will be capped between 0.1 and 0.9 134 * @param centrality if positive, makes results more likely to be near expected; if negative, the opposite. The 135 * absolute value of centrality affects how centered results will be, with 0 having no effect 136 */ 137 public EditRNG(final long seed, double expected, double centrality) { 138 super(seed); 139 setExpected(expected); 140 setCentrality(centrality); 141 } 142 /** 143 * Construct a new EditRNG with the given seed. 144 * 145 * @param seed used to seed the default RandomnessSource. 146 * @param expected the expected average for random doubles, which will be capped between 0.1 and 0.9 147 * @param centrality if positive, makes results more likely to be near expected; if negative, the opposite. The 148 * absolute value of centrality affects how centered results will be, with 0 having no effect 149 */ 150 public EditRNG(final String seed, double expected, double centrality) { 151 super(seed); 152 setExpected(expected); 153 setCentrality(centrality); 154 } 155 156 /** 157 * Construct a new EditRNG with the given seed. 158 * 159 * @param rs the implementation used to generate random bits. 160 */ 161 public EditRNG(final RandomnessSource rs) { 162 super(rs); 163 } 164 /** 165 * Construct a new EditRNG with the given seed. 166 * 167 * @param rs the implementation used to generate random bits. 168 * @param expected the expected average for random doubles, which will be capped between 0.1 and 0.9 169 */ 170 public EditRNG(final RandomnessSource rs, double expected) { 171 super(rs); 172 setExpected(expected); 173 } 174 /** 175 * Construct a new EditRNG with the given seed. 176 * 177 * @param rs the implementation used to generate random bits. 178 * @param expected the expected average for random doubles, which will be capped between 0.1 and 0.9 179 * @param centrality if positive, makes results more likely to be near expected; if negative, the opposite. The 180 * absolute value of centrality affects how centered results will be, with 0 having no effect 181 */ 182 public EditRNG(final RandomnessSource rs, double expected, double centrality) { 183 super(rs); 184 setExpected(expected); 185 setCentrality(centrality); 186 } 187 private double twist(double input) { 188 return (input += 1.0) - (int)input; 189 } 190 191 /** 192 * Generate a random double, altered to try to match the expected average and centrality. 193 * @return a double between 0.0 (inclusive) and 1.0 (exclusive) 194 */ 195 @Override 196 public double nextDouble() { 197 return offset + range * ((centralityCalculated > (random.nextLong() & 0xfffffffffffffL) ? 198 ((random.nextLong() & 0xfffffffffffffL) - (random.nextLong() & 0xfffffffffffffL)) * 0x1p-53 + 0.5 : 199 twist(((random.nextLong() & 0xfffffffffffffL) - (random.nextLong() & 0xfffffffffffffL)) * 0x1p-53))); 200 } 201 202 /** 203 * This returns a random double between 0.0 (inclusive) and max (exclusive). 204 * 205 * @return a value between 0 (inclusive) and max (exclusive) 206 */ 207 @Override 208 public double nextDouble(double max) { 209 return nextDouble() * max; 210 } 211 212 /** 213 * Returns a random integer below the given bound, or 0 if the bound is 0 or 214 * negative. 215 * 216 * @param bound the upper bound (exclusive) 217 * @return the found number 218 */ 219 @Override 220 public int nextInt(int bound) { 221 if (bound <= 0) { 222 return 0; 223 } 224 225 return (int)(nextDouble() * bound); 226 } 227 228 /** 229 * Returns a random integer, which may be positive or negative. 230 * @return A random int 231 */ 232 @Override 233 public int nextInt() { 234 return (int)((nextDouble() * 2.0 - 1.0) * 0x7FFFFFFF); 235 } 236 237 /** 238 * Returns a random long, which may be positive or negative. 239 * @return A random long 240 */ 241 @Override 242 public long nextLong() { 243 return (long)((nextDouble() * 2.0 - 1.0) * 0x7FFFFFFFFFFFFFFFL); 244 } 245 246 /** 247 * Returns a random long below the given bound, or 0 if the bound is 0 or 248 * negative. 249 * 250 * @param bound the upper bound (exclusive) 251 * @return the found number 252 */ 253 @Override 254 public long nextLong(long bound) { 255 if (bound <= 0) { 256 return 0; 257 } 258 return (long)(nextDouble() * bound); 259 } 260 /** 261 * Gets the current expected average for this EditRNG. 262 * @return the current expected average. 263 */ 264 public double getExpected() { 265 return expected; 266 } 267 268 /** 269 * Sets the expected average for random doubles this produces, which must always be between 0.1 and 0.9, and will be 270 * set to 0.5 if an invalid value is passed. 271 * @param expected the expected average to use, which should be 0.1 <= fairness < 0.9 272 */ 273 public void setExpected(double expected) { 274 if(expected < 0.1 || expected >= 0.9) 275 this.expected = 0.5; 276 else 277 this.expected = expected; 278 offset = Math.max(0.0, this.expected - 0.5) * 2.0; 279 range = this.expected <= 0.5 ? this.expected * 2.0 : 1.0 - offset; 280 } 281 282 /** 283 * Gets the current centrality measure of this EditRNG. 284 * Centrality has several possible effects: 285 * When positive, makes the generator more likely to generate values close to the average (bell curve). 286 * When zero (the default), makes no changes to the centering of values. 287 * When negative, makes the generator swing more toward extremes rather than gravitate toward the average. 288 * <br> 289 * Values are typically between -100 and 100, but can have extreme weight and overshadow other parts of the RNG if 290 * they go much higher than 200. 291 * @return the current centrality 292 */ 293 public double getCentrality() { 294 return centrality; 295 } 296 297 /** 298 * Gets the current centrality measure of this EditRNG. 299 * Centrality has several possible effects: 300 * When positive, makes the generator more likely to generate values close to the average (bell curve). 301 * When zero (the default), makes no changes to the centering of values. 302 * When negative, makes the generator swing more toward extremes rather than gravitate toward the average. 303 * <br> 304 * Values are typically between -100 and 100, but can have extreme weight and overshadow other parts of the RNG if 305 * they go much higher than 200. 306 * @param centrality the new centrality measure to use 307 */ 308 public void setCentrality(double centrality) { 309 this.centrality = Math.max(-200, Math.min(200, centrality)); 310 centralityCalculated = NumberTools.doubleToLongBits(this.centrality * 0.0024999 + 1.5) & 0xfffffffffffffL; 311 } 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 return (int)(nextDouble() * (1L << bits)); 321 } 322 323 @Override 324 public float nextFloat() { 325 return (float)nextDouble(); 326 } 327 328 @Override 329 public boolean nextBoolean() { 330 return nextDouble() >= 0.5; 331 } 332 333 @Override 334 public RandomnessSource getRandomness() { 335 return random; 336 } 337 338 @Override 339 public void setRandomness(RandomnessSource random) { 340 this.random = random; 341 } 342 343 /** 344 * Gets the latest "un-biased" random double used to produce the most recent (potentially) biased random number 345 * generated for another method in this class, such as nextDouble(), between(), or getRandomElement(). This is a 346 * double between 0.0 (inclusive) and 1.0 (exclusive). 347 * @return the latest uniformly-distributed double before bias is added; between 0.0 and 1.0 (exclusive upper) 348 */ 349 public double getRawLatest() { 350 return rawLatest; 351 } 352 353 /** 354 * Creates a copy of this StatefulRNG; it will generate the same random numbers, given the same calls in order, as 355 * this StatefulRNG at the point copy() is called. The copy will not share references with this StatefulRNG. 356 * 357 * @return a copy of this StatefulRNG 358 */ 359 @Override 360 public EditRNG copy() { 361 EditRNG next = new EditRNG(random.copy(), expected, centrality); 362 next.rawLatest = rawLatest; 363 return next; 364 } 365 366 @Override 367 public String toString() { 368 return "EditRNG{" + 369 "expected=" + expected + 370 ", centrality=" + centrality + 371 ", Randomness Source=" + random + 372 '}'; 373 } 374 375 @Override 376 public boolean equals(Object o) { 377 if (this == o) return true; 378 if (o == null || getClass() != o.getClass()) return false; 379 if (!super.equals(o)) return false; 380 381 EditRNG editRNG = (EditRNG) o; 382 383 if (Double.compare(editRNG.expected, expected) != 0) return false; 384 return Double.compare(editRNG.centrality, centrality) == 0; 385 } 386 387 @Override 388 public int hashCode() { 389 int result = super.hashCode() * 31; 390 result += NumberTools.doubleToMixedIntBits(expected); 391 result = 31 * result + NumberTools.doubleToMixedIntBits(centrality); 392 return result; 393 } 394 395 /** 396 * Returns a random non-negative integer below the given bound, or 0 if the bound is 0. 397 * Uses a slightly optimized technique. This method is considered "hasty" since 398 * it should be faster than nextInt() doesn't check for "less-valid" bounds values. It also 399 * has undefined behavior if bound is negative, though it will probably produce a negative 400 * number (just how negative is an open question). 401 * 402 * @param bound the upper bound (exclusive); behavior is undefined if bound is negative 403 * @return the found number 404 */ 405 @Override 406 public int nextIntHasty(int bound) { 407 return (int)(nextDouble() * bound); 408 } 409 /** 410 * Returns this EditRNG in a way that can be deserialized even if only {@link IRNG}'s methods can be called. 411 * @return a {@link Serializable} view of this EditRNG; always {@code this} 412 */ 413 @Override 414 public Serializable toSerializable() { 415 return this; 416 } 417 418 419}