001package squidpony.squidmath;
002
003import java.util.*;
004
005/**
006 * A type of RNG that can generate values larger or smaller than the normal maximum or minimum, based on a modifier.
007 * You should not use this as a general-purpose substitute for {@link RNG}; it is meant for cases where there is no hard
008 * maximum or minimum for a random value, so it is a poor fit for getting random items from collections or shuffling.
009 * It also uses a curved distribution (almost Gaussian, but slightly more shallow), which makes its results to be most
010 * often near the center of the range they can fall into. The {@link #luck} field affects the distribution simply, and
011 * should generally be between -0.5f and 0.5f except in cases where a character or event routinely defies all odds.
012 * There is no value for luck that will prevent this from sometimes producing values outside the requested range, though
013 * at luck = 0 it is somewhat rare for the bounds to be significantly exceeded.
014 * <br>
015 * The name comes from "critical hit," the rare but potentially very significant strike in many role-playing games.
016 * <br>
017 * Created by Tommy Ettinger on 9/20/2017.
018 */
019public class CriticalRNG extends RNG {
020    /**
021     * Positive for higher results, negative for lower results; usually this is small, between -0.5f and 0.5f .
022     */
023    public float luck;
024
025    /**
026     * Makes a CriticalRNG with a luck factor of 0 and a randomly-seeded DiverRNG for its RandomnessSource.
027     */
028    public CriticalRNG() {
029        super(new DiverRNG());
030    }
031
032    /**
033     * Makes a CriticalRNG with a luck factor of 0 and a DiverRNG with the given seed for its RandomnessSource.
034     * @param seed any long
035     */
036    public CriticalRNG(long seed) {
037        super(new DiverRNG(seed));
038    }
039    /**
040     * Makes a CriticalRNG with a luck factor of 0 and a DiverRNG with the given seed for its RandomnessSource (this
041     * will hash seedString using {@link CrossHash#hash64(CharSequence)} and use the result to seed the DiverRNG).
042     * @param seedString any String
043     */
044    public CriticalRNG(CharSequence seedString) {
045        super(new DiverRNG(CrossHash.hash64(seedString)));
046    }
047
048    /**
049     * Makes a CriticalRNG with a luck factor of 0 and the given RandomnessSource.
050     * @param random a RandomnessSource, such as a {@link LongPeriodRNG} or {@link LightRNG}
051     */
052    public CriticalRNG(RandomnessSource random) {
053        super(random);
054    }
055
056    /**
057     * Makes a CriticalRNG with the given luck factor and a randomly-seeded DiverRNG for its RandomnessSource.
058     * @param luck typically a small float, often between -0.5f and 0.5f, that will affect the results this returns
059     */
060    public CriticalRNG(float luck) {
061        super(new DiverRNG());
062        this.luck = luck;
063    }
064
065    /**
066     * Makes a CriticalRNG with the given luck factor and a DiverRNG with the given seed for its RandomnessSource.
067     * @param seed any long
068     * @param luck typically a small float, often between -0.5f and 0.5f, that will affect the results this returns
069     */
070    public CriticalRNG(long seed, float luck) {
071        super(new DiverRNG(seed));
072        this.luck = luck;
073    }
074
075    /**
076     * Makes a CriticalRNG with a luck factor of 0 and a DiverRNG with the given seed for its RandomnessSource (this
077     * will hash seedString using {@link CrossHash#hash64(CharSequence)} and use the result to seed the DiverRNG).
078     * @param seedString any String
079     * @param luck typically a small float, often between -0.5f and 0.5f, that will affect the results this returns
080     */
081    public CriticalRNG(CharSequence seedString, float luck) {
082        super(new DiverRNG(CrossHash.hash64(seedString)));
083        this.luck = luck;
084    }
085
086    /**
087     * Makes a CriticalRNG with a luck factor of 0 and the given RandomnessSource.
088     * @param random a RandomnessSource, such as a {@link LongPeriodRNG} or {@link LightRNG}
089     * @param luck typically a small float, often between -0.5f and 0.5f, that will affect the results this returns
090     */
091    public CriticalRNG(RandomnessSource random, float luck) {
092        super(random);
093        this.luck = luck;
094    }
095
096    @Override
097    public double nextDouble() {
098        return NumberTools.formCurvedDouble(random.nextLong()) * 0.875 + 0.5 + luck;
099    }
100
101    @Override
102    public double nextDouble(double max) {
103        return (NumberTools.formCurvedDouble(random.nextLong()) * 0.875 + 0.5 + luck) * max;
104    }
105
106    @Override
107    public float nextFloat() {
108        return NumberTools.formCurvedFloat(random.nextLong()) * 0.875f + 0.5f + luck;
109    }
110
111    @Override
112    public boolean nextBoolean() {
113        return NumberTools.formCurvedFloat(random.nextLong()) * 0.875f + 0.5f + luck >= 0f;
114    }
115
116    private static int intify(final double t) {
117        return t >= 0.0 ? (int) (t + 0.5) : (int) (t - 0.5);
118    }
119    private static long longify(final double t) {
120        return t >= 0.0 ? (long) (t + 0.5) : (long) (t - 0.5);
121    }
122
123    @Override
124    public long nextLong() {
125        return longify((NumberTools.formCurvedDouble(random.nextLong()) + luck * -2.0) * 0x8000000000000000L);
126    }
127
128    @Override
129    public long nextLong(long bound) {
130        return longify((NumberTools.formCurvedDouble(random.nextLong()) * 0.875 + 0.5 + luck) * bound);
131    }
132
133    @Override
134    public int nextInt(int bound) {
135        return intify((NumberTools.formCurvedDouble(random.nextLong()) * 0.875 + 0.5 + luck) * bound);
136    }
137
138    @Override
139    public int nextIntHasty(int bound) {
140        return intify((NumberTools.formCurvedDouble(random.nextLong()) * 0.875 + 0.5 + luck) * bound);
141    }
142
143    @Override
144    public int nextInt() {
145        return intify((NumberTools.formCurvedDouble(random.nextLong()) + luck * -2.0) * 0x80000000);
146    }
147
148    @Override
149    public <T> T getRandomElement(T[] array) {
150        if (array.length < 1) {
151            return null;
152        }
153        return array[super.nextIntHasty(array.length)];
154    }
155
156    @Override
157    public <T> T getRandomElement(List<T> list) {
158        if (list.isEmpty()) {
159            return null;
160        }
161        return list.get(super.nextIntHasty(list.size()));
162    }
163
164    @Override
165    public <T> T getRandomElement(Collection<T> coll) {
166        int n;
167        if ((n = coll.size()) <= 0) {
168            return null;
169        }
170        n = super.nextIntHasty(n);
171        T t = null;
172        Iterator<T> it = coll.iterator();
173        while (n-- >= 0 && it.hasNext())
174            t = it.next();
175        return t;
176    }
177}