001package squidpony.squidmath; 002 003import java.io.Serializable; 004import java.util.Map; 005 006/** 007 * A class that wraps an RNG and allows different String keys to be associated with biases toward low or high results 008 * when a method is called that gets a number from the wrapped RNG. With this, you could make a category of "blessed" or 009 * "cursed" biases that, instead of using a uniform distribution that produces all numbers approximately with equal 010 * likelihood (with doubles between 0.0 and 1.0 averaging at 0.5), have different averages, like 0.7 for blessed or 0.3 011 * for cursed, when generating between 0.0 and 1.0. You could also use this to favor or disfavor the player for "easy 012 * mode" or "hard mode" categories of play. 013 * <br> 014 * The API allows you to associate an alternative average with a kind as a String, like "blessed to-hit" or "hard mode 015 * enemy damage", if you expect to use that number more than once and might want to tweak any averages by changing one 016 * number at a later point. You can also give an average as a double in place of a kind as a String, which avoids a 017 * HashMap lookup and lets you give flexibly-adjusted numbers, but does need more effort to change many values 018 * throughout a codebase if averages aren't all generated by a formula. You can also set the distribution in the 019 * constructor or by changing the public distribution field; you can use constants in this class, TRIANGULAR, 020 * EXPONENTIAL, TRUNCATED, SOFT_TRIANGULAR, and EXP_TRI (the average of EXPONENTIAL and TRIANGULAR), for different 021 * choices, with the default being EXP_TRI. Each one of these has different behavior regarding a preference toward 022 * extreme values; TRIANGULAR almost never produces very high or very low values, EXPONENTIAL frequently produces the 023 * highest or lowest values for high or low expected averages, respectively, TRUNCATED will simply never generate values 024 * that are too far from the average (otherwise it's uniform), SOFT_TRIANGULAR will produce a rounded version of 025 * TRIANGULAR's distribution with less of an angular peak and more frequent high and low values, and EXP_TRI will have 026 * something like a curve shape that may "collide" slightly with the upper bound if the average is high enough. 027 * <br> 028 * Credit for the technique used for the exponential modification to distributions goes to user pjs on StackOverflow, 029 * http://stackoverflow.com/a/17796997 . 030 * Credit should also be given to user vydd of the LispGames community, who made a visualization of the distribution 031 * changing as the expected average changed (at the time, the typical behavior of an exponential distribution looked 032 * like a bug, and the visualization showed that it was correct behavior). Derrick Creamer noticed how strange the 033 * exponential distribution would seem to most players, and that led to adding the simple triangular distribution. 034 * Created by Tommy Ettinger on 3/20/2016. 035 */ 036public class RandomBias implements Serializable { 037 private OrderedMap<String, Double> biases; 038 public IRNG rng; 039 public int distribution = EXP_TRI; 040 041 /** 042 * A constant for a distribution that linearly increases in probability from a 0.0 chance of 0.0. to a 0.3333... 043 * chance of getting the expected average, then linearly decreases until it reaches a 0.0 chance of 1.0. Doesn't 044 * really support expected averages below 1/3 or above 2/3, due to how the triangular distribution works. 045 */ 046 public static final int TRIANGULAR = 0, 047 /** 048 * A constant for a distribution that, for all values other than 0.5, will strongly favor either high or low 049 * results to push the odds in favor of a high or low expected average. This is probably not what most players 050 * expect, since it leads to massively more critical hits or failures if min or max results are counted as such. 051 */ 052 EXPONENTIAL = 1, 053 /** 054 * Not like the other distributions; this is a constant for a distribution that simply truncates a random number's 055 * possible range to less than 1.0, and adjusts the minimum or maximum value so that the average is the desired one. 056 * This is a uniform random number generator, unlike the others which have a bias toward certain values; it simply 057 * cannot generate values outside a certain range, and the values within the range it generates are all equally 058 * likely. The range gets smaller the closer the expected average is to 0.0 or 1.0, with an expected average of 0.4 059 * producing values between 0.0 and 0.8, and an expected average of 0.9 producing values of 0.8 to 1.0 (in all 060 * cases, this is exclusive on the upper bound). 061 */ 062 TRUNCATED = 2, 063 /** 064 * A constant for a distribution that averages two random floats, each with a triangular distribution (the same as 065 * what using the TRIANGULAR constant would produce, but the distribution becomes more curved when multiple random 066 * "dice rolls" are involved), to soften the point of the triangle and make very high or very low values appear 067 * somewhat more frequently, while the expected average appears less frequently. This should not be used to generate 068 * very large numbers, since the floats this uses lose precision after 24 bits, or about 16 million. It should 069 * produce very reasonable results for common values in games, like 0 to 100 or 0 to 20. Doesn't really support 070 * expected averages below 1/3 or above 2/3, due to how the triangular distribution works. 071 */ 072 SOFT_TRIANGULAR = 3, 073 /** 074 * A constant for a distribution that averages two random floats, one with a triangular distribution (the same as 075 * what using the TRIANGULAR constant would produce), and one with an exponential distribution (the same as what 076 * using the EXPONENTIAL constant would produce) to soften the point of the triangle and make very high or very low 077 * values appear much more frequently, while the expected average appears somewhat less frequently. This should not 078 * be used to generate very large numbers, since the floats this uses lose precision after 24 bits, or about 16 079 * million. It should produce very reasonable results for common values in games, like 0 to 100 or 0 to 20. Has 080 * limited support for expected averages below 1/3 or above 2/3; unlike TRIANGULAR or SOFT_TRIANGULAR, expected 081 * averages outside that range will still affect the generated average due to the EXPONENTIAL distribution 082 * contributing half of the correction needed to match the expected average. An expected average of 5/6 will produce 083 * an approximate average with this of 3/4, as opposed to 2/3 (for pure TRIANGULAR) or 5/6 (for EXPONENTIAL). 084 */ 085 EXP_TRI = 4, 086 /** 087 * "Bathtub-shaped" or "U-shaped" distribution (technically the arcsine distribution) that is significantly more 088 * likely to produce results at either extreme than it is to generate them in the center. The extremes in this case 089 * are the same as the truncated distribution, so not all values are possible unless the expected average is 0.5. 090 */ 091 BATHTUB_TRUNCATED = 5; 092 093 private static final long serialVersionUID = 4245874924013134958L; 094 095 public RandomBias() 096 { 097 biases = new OrderedMap<>(32); 098 rng = new GWTRNG(); 099 } 100 public RandomBias(IRNG rng) 101 { 102 this.rng = rng; 103 biases = new OrderedMap<>(32); 104 } 105 public RandomBias(IRNG rng, Map<String, Double> mapping) 106 { 107 this(rng, mapping, EXP_TRI); 108 } 109 public RandomBias(IRNG rng, Map<String, Double> mapping, int distribution) { 110 this.rng = rng; 111 this.distribution = distribution; 112 if (mapping == null) { 113 biases = new OrderedMap<>(32); 114 } else { 115 biases = new OrderedMap<>(mapping.size()); 116 double exp; 117 for (Map.Entry<String, Double> kv : mapping.entrySet()) { 118 exp = kv.getValue(); 119 if (exp <= 0) exp = 0.001; 120 if (exp >= 1) exp = 0.999; 121 biases.put(kv.getKey(), exp); 122 } 123 } 124 } 125 126 /** 127 * Adds a kind of bias that can be used to change the average of random numbers generated when specified with that 128 * kind. 129 * @param kind a String that will be used as a key in a Map; can be given later on to bias results using this key 130 * @param expectedAverage above 0.0 and below 1.0, with 0.5 as the normal average but other values are more useful. 131 * @return this for chaining 132 */ 133 public RandomBias putBias(String kind, double expectedAverage) 134 { 135 if(expectedAverage <= 0) expectedAverage = 0.001; 136 if(expectedAverage >= 1) expectedAverage = 0.999; 137 biases.put(kind, expectedAverage); 138 return this; 139 } 140 /** 141 * Adds a number of kinds of bias that can be used to change the average of random numbers generated when specified 142 * with one of those kinds. 143 * @param mapping should have String keys that can be used later, and double values greater than 0 but less than 1. 144 * @return this for chaining 145 */ 146 public RandomBias putBiases(Map<String, Double> mapping) 147 { 148 double exp; 149 for(Map.Entry<String, Double> kv : mapping.entrySet()) 150 { 151 exp = kv.getValue(); 152 if(exp <= 0) exp = 0.001; 153 if(exp >= 1) exp = 0.999; 154 biases.put(kv.getKey(), exp); 155 } 156 return this; 157 } 158 159 private double quantile(double expected) 160 { 161 switch (distribution) 162 { 163 case EXPONENTIAL: return exponentialQuantile(expected); 164 case TRUNCATED: return truncatedQuantile(expected); 165 case TRIANGULAR: return triangularQuantile(expected); 166 case SOFT_TRIANGULAR: return softQuantile(expected); 167 case BATHTUB_TRUNCATED: return bathtubTruncatedQuantile(expected); 168 default: return mixQuantile(expected); 169 } 170 } 171 172 private double triangularQuantile(double expected) 173 { 174 expected = Math.max(0.001, Math.min(0.999, expected * 3.0 - 1.0)); 175 double p = rng.nextDouble(); 176 if(p < expected) 177 return Math.sqrt(expected * p); 178 if(p > expected) 179 return 1 - Math.sqrt((1 - expected) * (1 - p)); 180 return expected; 181 } 182 private double truncatedQuantile(double expected) 183 { 184 if(expected >= 0.5) 185 return rng.nextDouble() * (1.0 - expected) * 2 + expected - (1.0 - expected); 186 return rng.nextDouble() * expected * 2; 187 } 188 private double bathtubQuantile(double expected) 189 { 190 expected = Math.sin(expected * Math.PI * 0.4999999966); // can't be 0.5 because it becomes inclusive on 1.0 191 return expected * expected; 192 } 193 private double bathtubTruncatedQuantile(double expected) 194 { 195 if(expected >= 0.5) 196 return bathtubQuantile(rng.nextDouble()) * (0.9999999999999999 - expected) * 2 + expected - (0.9999999999999999 - expected); 197 return bathtubQuantile(rng.nextDouble()) * expected * 2; 198 } 199 200 private double exponentialQuantile(double expected) 201 { 202 return 0.9999999999999999 - Math.pow( rng.nextDouble(), 1.0 / (1.0 - expected) - 1.0); 203 } 204 205 private double softQuantile(double expected) 206 { 207 expected = Math.max(0.001, Math.min(0.999, expected * 3.0 - 1.0)); 208 long pair = rng.nextLong(); 209 float left = (pair >>> 40) * 0x1p-24f, right = (pair & 0xFFFFFFL) * 0x1p-24f; 210 double v; 211 212 if(left < expected) 213 v = Math.sqrt(expected * left); 214 else if(left > expected) 215 v = 1 - Math.sqrt((1 - expected) * (1 - left)); 216 else 217 v = expected; 218 if(right < expected) 219 return (v + Math.sqrt(expected * right)) * 0.5; 220 if(right > expected) 221 return (v + 1 - Math.sqrt((1 - expected) * (1 - right))) * 0.5; 222 return expected; 223 } 224 private double mixQuantile(double expected) 225 { 226 double d2 = Math.max(0.001, Math.min(0.999, expected * 3.0 - 1.0)), v; 227 long pair = rng.nextLong(); 228 float left = (pair >>> 40) * 0x1p-24f, right = (pair & 0xFFFFFFL) * 0x1p-24f; 229 230 if(left < d2) 231 v = Math.sqrt(d2 * left); 232 else if(left > d2) 233 v = 1 - Math.sqrt((1 - d2) * (1 - left)); 234 else 235 v = d2; 236 return (Math.pow( right, 1.0 / expected - 1.0) + v) * 0.5; 237 } 238 /** 239 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 240 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 241 * expected average the kind was associated with. The returned number will be a positive long in either case, but 242 * not all long values are possible if this is biased, in part because of generating a double, which has less 243 * precision than long, and in part because some numbers need to be more common than others. If the kind is not in 244 * the map, this generates a positive long, using 63 bits instead of RNG's normal 64 bits since it never generates 245 * negative numbers. 246 * @param kind the kind of bias to look up 247 * @return a random 63-bit positive long, potentially influenced by the bias associated with kind, if present 248 */ 249 public long biasedLong(String kind) 250 { 251 Double d = biases.get(kind); 252 if(d == null) 253 return rng.nextLong() >>> 1; 254 return (long)(quantile(d) * Long.MAX_VALUE); 255 } 256 257 /** 258 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 259 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 260 * expected average the kind was associated with. The returned number will be a long between 0 and bound (exclusive 261 * on bound), where bound can be negative (and this behavior is allowed even though RNG normally returns 0 for all 262 * negative bounds). If the kind is not in the map, this generates a long between 0 and bound (exclusive on bound), 263 * even if bound is negative. 264 * @param kind the kind of bias to look up 265 * @param bound the outer bound, exclusive; can be negative 266 * @return a random long between 0 and bound, potentially influenced by the bias associated with kind, if present 267 */ 268 public long biasedLong(String kind, long bound) 269 { 270 boolean n = bound < 0; 271 Double d = biases.get(kind); 272 if(d == null) 273 return n ? rng.nextLong(-bound) * -1 : rng.nextLong(bound); 274 return (long)(quantile(d) * bound); 275 } 276 277 /** 278 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 279 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 280 * expected average the kind was associated with. The returned number will be a double between 0.0 and 1.0 281 * (exclusive on 1.0). If the kind is not in the map, this generates a double using RNG and no further changes. 282 * @param kind the kind of bias to look up 283 * @return a random double between 0.0 and 1.0, potentially influenced by the bias associated with kind, if present 284 */ 285 public double biasedDouble(String kind) 286 { 287 Double d = biases.get(kind); 288 if(d == null) 289 return rng.nextDouble(); 290 return quantile(d); 291 } 292 293 /** 294 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 295 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 296 * expected average the kind was associated with. The returned number will be a double between 0 and bound (exclusive 297 * on bound), where bound can be negative (the same as RNG). If the kind is not in the map, this doesn't adjust the 298 * average, and acts exactly like RNG. 299 * @param kind the kind of bias to look up 300 * @param bound the outer bound, exclusive; can be negative 301 * @return a random double between 0 and bound, potentially influenced by the bias associated with kind, if present 302 */ 303 public double biasedDouble(String kind, double bound) 304 { 305 Double d = biases.get(kind); 306 if(d == null) 307 return rng.nextDouble(bound); 308 return quantile(d) * bound; 309 } 310 311 /** 312 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 313 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 314 * expected average the kind was associated with. The returned number will be a positive int in either case. If the 315 * kind is not in the map, this generates a positive int, using 31 bits instead of RNG's normal 32 bits since it 316 * never generates negative numbers. 317 * @param kind the kind of bias to look up 318 * @return a random 31-bit positive int, potentially influenced by the bias associated with kind, if present 319 */ 320 public int biasedInt(String kind) 321 { 322 Double d = biases.get(kind); 323 if(d == null) 324 return rng.nextInt() >>> 1; 325 return (int)(quantile(d) * Integer.MAX_VALUE); 326 } 327 328 /** 329 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 330 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 331 * expected average the kind was associated with. The returned number will be an int between 0 and bound (exclusive 332 * on bound), where bound can be negative (and this behavior is allowed even though RNG normally returns 0 for all 333 * negative bounds). If the kind is not in the map, this generates an int between 0 and bound (exclusive on bound), 334 * even if bound is negative. 335 * @param kind the kind of bias to look up 336 * @param bound the outer bound, exclusive; can be negative 337 * @return a random int between 0 and bound, potentially influenced by the bias associated with kind, if present 338 */ 339 public int biasedInt(String kind, int bound) 340 { 341 boolean n = bound < 0; 342 Double d = biases.get(kind); 343 if(d == null) 344 return n ? rng.nextInt(-bound) * -1 : rng.nextInt(bound); 345 return (int)(quantile(d) * bound); 346 } 347 348 /** 349 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 350 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 351 * expected average the kind was associated with. The returned number will be a float between 0.0 and 1.0 352 * (exclusive on 1.0). If the kind is not in the map, this generates a float using RNG and no further changes. 353 * @param kind the kind of bias to look up 354 * @return a random float between 0.0 and 1.0, potentially influenced by the bias associated with kind, if present 355 */ 356 public float biasedFloat(String kind) 357 { 358 Double d = biases.get(kind); 359 if(d == null) 360 return rng.nextFloat(); 361 return (float) quantile(d); 362 } 363 364 /** 365 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 366 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 367 * expected average the kind was associated with. The returned number will be a float between 0 and bound (exclusive 368 * on bound), where bound can be negative. If the kind is not in the map, this doesn't adjust the average. 369 * @param kind the kind of bias to look up 370 * @param bound the outer bound, exclusive; can be negative 371 * @return a random double between 0 and bound, potentially influenced by the bias associated with kind, if present 372 */ 373 public float biasedFloat(String kind, float bound) 374 { 375 Double d = biases.get(kind); 376 if(d == null) 377 return rng.nextFloat() * bound; 378 return (float)(quantile(d) * bound); 379 } 380 381 /** 382 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 383 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 384 * expected average the kind was associated with. The returned boolean will be true if the random number (between 385 * 0.0 and 1.0, exclusive upper) is greater than or equal to 0.5. If the kind is not in the map, this generates a 386 * boolean using RNG and no further changes. 387 * @param kind the kind of bias to look up 388 * @return a random float between 0.0 and 1.0, potentially influenced by the bias associated with kind, if present 389 */ 390 public boolean biasedBoolean(String kind) 391 { 392 Double d = biases.get(kind); 393 if(d == null) 394 return rng.nextBoolean(); 395 return quantile(d) >= 0.5; 396 } 397 /** 398 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 399 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 400 * expected average the kind was associated with. The returned number will be an int between min and max (exclusive 401 * on max), where min and/or max can be negative, and the difference between the two can be either positive or 402 * negative. If the kind is not in the map, this doesn't adjust the average. 403 * @param kind the kind of bias to look up 404 * @param min the inner bound, inclusive; can be negative 405 * @param max the outer bound, exclusive; can be negative 406 * @return a random int between min and max, potentially influenced by the bias associated with kind, if present 407 */ 408 public int biasedBetween(String kind, int min, int max) 409 { 410 return biasedInt(kind, max - min) + min; 411 } 412 413 /** 414 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 415 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 416 * expected average the kind was associated with. The returned number will be a long between min and max (exclusive 417 * on max), where min and/or max can be negative, and the difference between the two can be either positive or 418 * negative. If the kind is not in the map, this doesn't adjust the average. 419 * @param kind the kind of bias to look up 420 * @param min the inner bound, inclusive; can be negative 421 * @param max the outer bound, exclusive; can be negative 422 * @return a random long between min and max, potentially influenced by the bias associated with kind, if present 423 */ 424 public long biasedBetween(String kind, long min, long max) 425 { 426 return biasedLong(kind, max - min) + min; 427 } 428 429 /** 430 * Looks up the given kind in the Map of biases this stores, and generates a random number using this object's RNG. 431 * If the kind is in the Map, this adjusts the generated number so it matches a distribution that would have the 432 * expected average the kind was associated with. The returned number will be a double between min and max 433 * (exclusive on max), where min and/or max can be negative, and the difference between the two can be either 434 * positive or negative. If the kind is not in the map, this doesn't adjust the average. 435 * @param kind the kind of bias to look up 436 * @param min the inner bound, inclusive; can be negative 437 * @param max the outer bound, exclusive; can be negative 438 * @return a random double between min and max, potentially influenced by the bias associated with kind, if present 439 */ 440 public double biasedBetween(String kind, double min, double max) 441 { 442 return biasedDouble(kind, max - min) + min; 443 } 444 445 446 /** 447 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 448 * that would have the given expected average. The returned number will be a positive long in either case, but 449 * not all long values are possible if this is biased, in part because of generating a double, which has less 450 * precision than long, and in part because some numbers need to be more common than others. 451 * @param expectedAverage the desired average if the minimum value was 0.0 and the exclusive max was 1.0 452 * @return a random 63-bit positive long, potentially influenced by the bias associated with kind, if present 453 */ 454 public long biasedLong(double expectedAverage) 455 { 456 if(expectedAverage <= 0) expectedAverage = 0.001; 457 if(expectedAverage >= 1) expectedAverage = 0.999; 458 return (long)(quantile(expectedAverage) * Long.MAX_VALUE); 459 } 460 461 /** 462 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 463 * that would have the given expected average. The returned number will be a long between 0 and bound (exclusive 464 * on bound), where bound can be negative (and this behavior is allowed even though RNG normally returns 0 for all 465 * negative bounds). 466 * @param expectedAverage the desired average if the minimum value was 0.0 and the exclusive max was 1.0 467 * @param bound the outer bound, exclusive; can be negative 468 * @return a random long between 0 and bound, potentially influenced by the bias associated with kind, if present 469 */ 470 public long biasedLong(double expectedAverage, long bound) 471 { 472 if(expectedAverage <= 0) expectedAverage = 0.001; 473 if(expectedAverage >= 1) expectedAverage = 0.999; 474 return (long)(quantile(expectedAverage) * bound); 475 } 476 477 /** 478 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 479 * that would have the given expected average. The returned number will be a double between 0.0 and 1.0 (exclusive 480 * on 1.0). 481 * @param expectedAverage the desired average 482 * @return a random double between 0.0 and 1.0, potentially influenced by the bias associated with kind, if present 483 */ 484 public double biasedDouble(double expectedAverage) 485 { 486 if(expectedAverage <= 0) expectedAverage = 0.001; 487 if(expectedAverage >= 1) expectedAverage = 0.999; 488 return quantile(expectedAverage); 489 } 490 491 /** 492 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 493 * that would have the given expected average. The returned number will be a double between 0 and bound (exclusive 494 * on bound), where bound can be negative (the same as RNG). 495 * @param expectedAverage the desired average 496 * @param bound the outer bound, exclusive; can be negative 497 * @return a random double between 0 and bound, potentially influenced by the bias associated with kind, if present 498 */ 499 public double biasedDouble(double expectedAverage, double bound) 500 { 501 if(expectedAverage <= 0) expectedAverage = 0.001; 502 if(expectedAverage >= 1) expectedAverage = 0.999; 503 return quantile(expectedAverage) * bound; 504 } 505 506 /** 507 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 508 * that would have the given expected average. The returned number will be a positive int from 0 to (2 to the 31)-1 509 * in either case. 510 * @param expectedAverage the desired average if the minimum value was 0.0 and the exclusive max was 1.0 511 * @return a random 31-bit positive int, potentially influenced by the bias associated with kind, if present 512 */ 513 public int biasedInt(double expectedAverage) 514 { 515 if(expectedAverage <= 0) expectedAverage = 0.001; 516 if(expectedAverage >= 1) expectedAverage = 0.999; 517 return (int)(quantile(expectedAverage) * Integer.MAX_VALUE); 518 } 519 520 /** 521 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 522 * that would have the given expected average. The returned number will be an int between 0 and bound (exclusive 523 * on bound), where bound can be negative (and this behavior is allowed even though RNG normally returns 0 for all 524 * negative bounds). 525 * @param expectedAverage the desired average if the minimum value was 0.0 and the exclusive max was 1.0 526 * @param bound the outer bound, exclusive; can be negative 527 * @return a random int between 0 and bound, potentially influenced by the bias associated with kind, if present 528 */ 529 public int biasedInt(double expectedAverage, int bound) 530 { 531 if(expectedAverage <= 0) expectedAverage = 0.001; 532 if(expectedAverage >= 1) expectedAverage = 0.999; 533 return (int)(quantile(expectedAverage) * bound); 534 } 535 536 /** 537 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 538 * that would have the given expected average. The returned number will be a float between 0.0f and 1.0f (exclusive 539 * on 1.0f). 540 * @param expectedAverage the desired average 541 * @return a random float between 0.0 and 1.0, potentially influenced by the bias associated with kind, if present 542 */ 543 public float biasedFloat(double expectedAverage) 544 { 545 if(expectedAverage <= 0) expectedAverage = 0.001; 546 if(expectedAverage >= 1) expectedAverage = 0.999; 547 return (float) quantile(expectedAverage); 548 } 549 550 /** 551 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 552 * that would have the given expected average. The returned number will be a float between 0f and bound (exclusive 553 * on bound), where bound can be negative. 554 * @param expectedAverage the desired average if the minimum value was 0.0 and the exclusive max was 1.0 555 * @param bound the outer bound, exclusive; can be negative 556 * @return a random double between 0 and bound, potentially influenced by the bias associated with kind, if present 557 */ 558 public float biasedFloat(double expectedAverage, float bound) 559 { 560 if(expectedAverage <= 0) expectedAverage = 0.001; 561 if(expectedAverage >= 1) expectedAverage = 0.999; 562 return (float)(quantile(expectedAverage) * bound); 563 } 564 565 /** 566 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 567 * that would have the given expected average. The returned boolean will be true if the random number (between 0.0 568 * and 1.0, exclusive upper) is greater than or equal to 0.5. 569 * @param expectedAverage the desired probability of a true result, between 0.0 and 1.0 570 * @return a random float between 0.0 and 1.0, potentially influenced by the bias associated with kind, if present 571 */ 572 public boolean biasedBoolean(double expectedAverage) 573 { 574 if(expectedAverage <= 0) expectedAverage = 0.001; 575 if(expectedAverage >= 1) expectedAverage = 0.999; 576 return quantile(expectedAverage) >= 0.5; 577 } 578 /** 579 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 580 * that would have the given expected average. The returned number will be an int between min and max (exclusive 581 * on max), where min and/or max can be negative, and the difference between the two can be either positive or 582 * negative. 583 * @param expectedAverage the desired average if the minimum value was 0.0 and the exclusive max was 1.0 584 * @param min the inner bound, inclusive; can be negative 585 * @param max the outer bound, exclusive; can be negative 586 * @return a random int between min and max, potentially influenced by the bias associated with kind, if present 587 */ 588 public int biasedBetween(double expectedAverage, int min, int max) 589 { 590 return biasedInt(expectedAverage, max - min) + min; 591 } 592 593 /** 594 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 595 * that would have the given expected average. The returned number will be a long between min and max (exclusive 596 * on max), where min and/or max can be negative, and the difference between the two can be either positive or 597 * negative. 598 * @param expectedAverage the desired average if the minimum value was 0.0 and the exclusive max was 1.0 599 * @param min the inner bound, inclusive; can be negative 600 * @param max the outer bound, exclusive; can be negative 601 * @return a random long between min and max, potentially influenced by the bias associated with kind, if present 602 */ 603 public long biasedBetween(double expectedAverage, long min, long max) 604 { 605 return biasedLong(expectedAverage, max - min) + min; 606 } 607 608 /** 609 * Generates a random number using this object's RNG and adjusts the generated number so it matches a distribution 610 * that would have the given expected average. The returned number will be a double between min and max (exclusive 611 * on max), where min and/or max can be negative, and the difference between the two can be either positive or 612 * negative. 613 * @param expectedAverage the desired average if the minimum value was 0.0 and the exclusive max was 1.0 614 * @param min the inner bound, inclusive; can be negative 615 * @param max the outer bound, exclusive; can be negative 616 * @return a random double between min and max, potentially influenced by the bias associated with kind, if present 617 */ 618 public double biasedBetween(double expectedAverage, double min, double max) 619 { 620 return biasedDouble(expectedAverage, max - min) + min; 621 } 622 623 @Override 624 public String toString() { 625 return "RandomBias{" + 626 "biases=" + biases + 627 ", rng=" + rng + 628 ", distribution=" + distribution + 629 '}'; 630 } 631 632}