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}