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 &lt;= fairness &lt; 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}