From c6f70b46ea145b3c50fedc35aae9f5b207dddb82 Mon Sep 17 00:00:00 2001 From: Nat Quayle Nelson Date: Wed, 30 Mar 2022 14:18:18 -0600 Subject: [PATCH] Prokgen generators for primitives --- projects/prokgen/build.hxml | 4 + projects/prokgen/haxelib.json | 17 + projects/prokgen/src/prokgen/Generator.hx | 7 + projects/prokgen/src/prokgen/Main.hx | 9 + projects/prokgen/src/prokgen/Main.kiss | 13 + projects/prokgen/src/prokgen/ProkMath.hx | 366 ++++++++++++++ projects/prokgen/src/prokgen/ProkRandom.hx | 469 ++++++++++++++++++ .../src/prokgen/generators/ArrayGen.hx | 51 ++ .../prokgen/src/prokgen/generators/BoolGen.hx | 21 + .../src/prokgen/generators/FloatGen.hx | 46 ++ .../prokgen/src/prokgen/generators/IntGen.hx | 45 ++ projects/prokgen/test.sh | 3 + 12 files changed, 1051 insertions(+) create mode 100644 projects/prokgen/build.hxml create mode 100644 projects/prokgen/haxelib.json create mode 100644 projects/prokgen/src/prokgen/Generator.hx create mode 100644 projects/prokgen/src/prokgen/Main.hx create mode 100644 projects/prokgen/src/prokgen/Main.kiss create mode 100644 projects/prokgen/src/prokgen/ProkMath.hx create mode 100644 projects/prokgen/src/prokgen/ProkRandom.hx create mode 100644 projects/prokgen/src/prokgen/generators/ArrayGen.hx create mode 100644 projects/prokgen/src/prokgen/generators/BoolGen.hx create mode 100644 projects/prokgen/src/prokgen/generators/FloatGen.hx create mode 100644 projects/prokgen/src/prokgen/generators/IntGen.hx create mode 100644 projects/prokgen/test.sh diff --git a/projects/prokgen/build.hxml b/projects/prokgen/build.hxml new file mode 100644 index 00000000..ca4dec03 --- /dev/null +++ b/projects/prokgen/build.hxml @@ -0,0 +1,4 @@ +-lib kiss +-cp src +--main prokgen.Main +--interp \ No newline at end of file diff --git a/projects/prokgen/haxelib.json b/projects/prokgen/haxelib.json new file mode 100644 index 00000000..b4436336 --- /dev/null +++ b/projects/prokgen/haxelib.json @@ -0,0 +1,17 @@ +{ + "main": "prokgen.Main", + "name": "prokgen", + "description": "Generalized genetic generation for Haxe classes", + "classPath": "src/", + "dependencies": { + "kiss": "" + }, + "url": "https://github.com/NQNStudios/kisslang", + "contributors": [ + "NQNStudios" + ], + "version": "0.0.0", + "releasenote": "", + "tags": [], + "license": "LGPL" +} \ No newline at end of file diff --git a/projects/prokgen/src/prokgen/Generator.hx b/projects/prokgen/src/prokgen/Generator.hx new file mode 100644 index 00000000..5f52fe43 --- /dev/null +++ b/projects/prokgen/src/prokgen/Generator.hx @@ -0,0 +1,7 @@ +package prokgen; + +interface Generator { + function use(s:ProkRandom):Void; + function makeRandom():T; + function combine(a:T, b:T):T; +} diff --git a/projects/prokgen/src/prokgen/Main.hx b/projects/prokgen/src/prokgen/Main.hx new file mode 100644 index 00000000..4d14cc08 --- /dev/null +++ b/projects/prokgen/src/prokgen/Main.hx @@ -0,0 +1,9 @@ +package prokgen; + +import kiss.Kiss; +import kiss.Prelude; +import prokgen.ProkRandom; +import prokgen.generators.*; + +@:build(kiss.Kiss.build()) +class Main {} diff --git a/projects/prokgen/src/prokgen/Main.kiss b/projects/prokgen/src/prokgen/Main.kiss new file mode 100644 index 00000000..166f6a8b --- /dev/null +++ b/projects/prokgen/src/prokgen/Main.kiss @@ -0,0 +1,13 @@ +(let [r (new ProkRandom) + :Array generators []] + (generators.push (new IntGen)) + (generators.push (new FloatGen)) + (generators.push (new BoolGen)) + (generators.push (new ArrayGen (new IntGen) 5 10)) + (doFor g generators (g.use r)) + (let [:Array aList (for g generators (g.makeRandom)) + :Array bList (for g generators (g.makeRandom)) + :Array cList (for i (range generators.length) (.combine (nth generators i) (nth aList i) (nth bList i)))] + ~aList + ~bList + ~cList)) diff --git a/projects/prokgen/src/prokgen/ProkMath.hx b/projects/prokgen/src/prokgen/ProkMath.hx new file mode 100644 index 00000000..2803fb13 --- /dev/null +++ b/projects/prokgen/src/prokgen/ProkMath.hx @@ -0,0 +1,366 @@ +package prokgen; + +/** + * A class containing a set of math-related functions. + * Based on HaxeFlixel's FlxMath, with permission. + */ +class ProkMath +{ + #if (flash || js || ios || blackberry) + /** + * Minimum value of a floating point number. + */ + public static inline var MIN_VALUE_FLOAT:Float = 0.0000000000000001; + #else + + /** + * Minimum value of a floating point number. + */ + public static inline var MIN_VALUE_FLOAT:Float = 5e-324; + #end + + /** + * Maximum value of a floating point number. + */ + public static inline var MAX_VALUE_FLOAT:Float = 1.79e+308; + + /** + * Minimum value of an integer. + */ + public static inline var MIN_VALUE_INT:Int = -MAX_VALUE_INT; + + /** + * Maximum value of an integer. + */ + public static inline var MAX_VALUE_INT:Int = 0x7FFFFFFF; + + /** + * Approximation of `Math.sqrt(2)`. + */ + public static inline var SQUARE_ROOT_OF_TWO:Float = 1.41421356237; + + /** + * Used to account for floating-point inaccuracies. + */ + public static inline var EPSILON:Float = 0.0000001; + + /** + * Round a decimal number to have reduced precision (less decimal numbers). + * + * ```haxe + * roundDecimal(1.2485, 2) = 1.25 + * ``` + * + * @param Value Any number. + * @param Precision Number of decimals the result should have. + * @return The rounded value of that number. + */ + public static function roundDecimal(Value:Float, Precision:Int):Float + { + var mult:Float = 1; + for (i in 0...Precision) + { + mult *= 10; + } + return Math.fround(Value * mult) / mult; + } + + /** + * Bound a number by a minimum and maximum. Ensures that this number is + * no smaller than the minimum, and no larger than the maximum. + * Leaving a bound `null` means that side is unbounded. + * + * @param Value Any number. + * @param Min Any number. + * @param Max Any number. + * @return The bounded value of the number. + */ + public static inline function bound(Value:Float, ?Min:Float, ?Max:Float):Float + { + var lowerBound:Float = (Min != null && Value < Min) ? Min : Value; + return (Max != null && lowerBound > Max) ? Max : lowerBound; + } + + /** + * Returns the linear interpolation of two numbers if `ratio` + * is between 0 and 1, and the linear extrapolation otherwise. + * + * Examples: + * + * ```haxe + * lerp(a, b, 0) = a + * lerp(a, b, 1) = b + * lerp(5, 15, 0.5) = 10 + * lerp(5, 15, -1) = -5 + * ``` + */ + public static inline function lerp(a:Float, b:Float, ratio:Float):Float + { + return a + ratio * (b - a); + } + + /** + * Checks if number is in defined range. A null bound means that side is unbounded. + * + * @param Value Number to check. + * @param Min Lower bound of range. + * @param Max Higher bound of range. + * @return Returns true if Value is in range. + */ + public static inline function inBounds(Value:Float, Min:Null, Max:Null):Bool + { + return (Min == null || Value >= Min) && (Max == null || Value <= Max); + } + + /** + * Returns `true` if the given number is odd. + */ + public static inline function isOdd(n:Float):Bool + { + return (Std.int(n) & 1) != 0; + } + + /** + * Returns `true` if the given number is even. + */ + public static inline function isEven(n:Float):Bool + { + return (Std.int(n) & 1) == 0; + } + + /** + * Returns `-1` if `a` is smaller, `1` if `b` is bigger and `0` if both numbers are equal. + */ + public static function numericComparison(a:Float, b:Float):Int + { + if (b > a) + { + return -1; + } + else if (a > b) + { + return 1; + } + return 0; + } + + /** + * Returns true if the given x/y coordinate is within the given rectangular block + * + * @param pointX The X value to test + * @param pointY The Y value to test + * @param rectX The X value of the region to test within + * @param rectY The Y value of the region to test within + * @param rectWidth The width of the region to test within + * @param rectHeight The height of the region to test within + * + * @return true if pointX/pointY is within the region, otherwise false + */ + public static function pointInCoordinates(pointX:Float, pointY:Float, rectX:Float, rectY:Float, rectWidth:Float, rectHeight:Float):Bool + { + if (pointX >= rectX && pointX <= (rectX + rectWidth)) + { + if (pointY >= rectY && pointY <= (rectY + rectHeight)) + { + return true; + } + } + return false; + } + + /** + * Adds the given amount to the value, but never lets the value + * go over the specified maximum or under the specified minimum. + * + * @param value The value to add the amount to + * @param amount The amount to add to the value + * @param max The maximum the value is allowed to be + * @param min The minimum the value is allowed to be + * @return The new value + */ + public static function maxAdd(value:Int, amount:Int, max:Int, min:Int = 0):Int + { + value += amount; + + if (value > max) + { + value = max; + } + else if (value <= min) + { + value = min; + } + + return value; + } + + /** + * Makes sure that value always stays between 0 and max, + * by wrapping the value around. + * + * @param value The value to wrap around + * @param min The minimum the value is allowed to be + * @param max The maximum the value is allowed to be + * @return The wrapped value + */ + public static function wrap(value:Int, min:Int, max:Int):Int + { + var range:Int = max - min + 1; + + if (value < min) + value += range * Std.int((min - value) / range + 1); + + return min + (value - min) % range; + } + + /** + * Remaps a number from one range to another. + * + * @param value The incoming value to be converted + * @param start1 Lower bound of the value's current range + * @param stop1 Upper bound of the value's current range + * @param start2 Lower bound of the value's target range + * @param stop2 Upper bound of the value's target range + * @return The remapped value + */ + public static function remapToRange(value:Float, start1:Float, stop1:Float, start2:Float, stop2:Float):Float + { + return start2 + (value - start1) * ((stop2 - start2) / (stop1 - start1)); + } + + /** + * Finds the dot product value of two vectors + * + * @param ax Vector X + * @param ay Vector Y + * @param bx Vector X + * @param by Vector Y + * + * @return Result of the dot product + */ + public static inline function dotProduct(ax:Float, ay:Float, bx:Float, by:Float):Float + { + return ax * bx + ay * by; + } + + /** + * Returns the length of the given vector. + */ + public static inline function vectorLength(dx:Float, dy:Float):Float + { + return Math.sqrt(dx * dx + dy * dy); + } + + /** + * Returns the amount of decimals a `Float` has. + */ + public static function getDecimals(n:Float):Int + { + var helperArray:Array = Std.string(n).split("."); + var decimals:Int = 0; + + if (helperArray.length > 1) + { + decimals = helperArray[1].length; + } + + return decimals; + } + + public static inline function equal(aValueA:Float, aValueB:Float, aDiff:Float = EPSILON):Bool + { + return Math.abs(aValueA - aValueB) <= aDiff; + } + + /** + * Returns `-1` if the number is smaller than `0` and `1` otherwise + */ + public static inline function signOf(n:Float):Int + { + return (n < 0) ? -1 : 1; + } + + /** + * Checks if two numbers have the same sign (using `FlxMath.signOf()`). + */ + public static inline function sameSign(a:Float, b:Float):Bool + { + return signOf(a) == signOf(b); + } + + /** + * A faster but slightly less accurate version of `Math.sin()`. + * About 2-6 times faster with < 0.05% average error. + * + * @param n The angle in radians. + * @return An approximated sine of `n`. + */ + public static inline function fastSin(n:Float):Float + { + n *= 0.3183098862; // divide by pi to normalize + + // bound between -1 and 1 + if (n > 1) + { + n -= (Math.ceil(n) >> 1) << 1; + } + else if (n < -1) + { + n += (Math.ceil(-n) >> 1) << 1; + } + + // this approx only works for -pi <= rads <= pi, but it's quite accurate in this region + if (n > 0) + { + return n * (3.1 + n * (0.5 + n * (-7.2 + n * 3.6))); + } + else + { + return n * (3.1 - n * (0.5 + n * (7.2 + n * 3.6))); + } + } + + /** + * A faster, but less accurate version of `Math.cos()`. + * About 2-6 times faster with < 0.05% average error. + * + * @param n The angle in radians. + * @return An approximated cosine of `n`. + */ + public static inline function fastCos(n:Float):Float + { + return fastSin(n + 1.570796327); // sin and cos are the same, offset by pi/2 + } + + /** + * Hyperbolic sine. + */ + public static inline function sinh(n:Float):Float + { + return (Math.exp(n) - Math.exp(-n)) / 2; + } + + /** + * Returns the bigger argument. + */ + public static inline function maxInt(a:Int, b:Int):Int + { + return (a > b) ? a : b; + } + + /** + * Returns the smaller argument. + */ + public static inline function minInt(a:Int, b:Int):Int + { + return (a > b) ? b : a; + } + + /** + * Returns the absolute integer value. + */ + public static inline function absInt(n:Int):Int + { + return (n > 0) ? n : -n; + } +} diff --git a/projects/prokgen/src/prokgen/ProkRandom.hx b/projects/prokgen/src/prokgen/ProkRandom.hx new file mode 100644 index 00000000..91730ee9 --- /dev/null +++ b/projects/prokgen/src/prokgen/ProkRandom.hx @@ -0,0 +1,469 @@ +package prokgen; + +import prokgen.ProkMath; + +#if flixel +import flixel.util.FlxColor; +#end + +/** + * A class containing a set of functions for random generation. + * Based on HaxeFlixel's FlxRandom, with permission. + */ +class ProkRandom +{ + /** + * The global base random number generator seed (for deterministic behavior in recordings and saves). + * If you want, you can set the seed with an integer between 1 and 2,147,483,647 inclusive. + * Altering this yourself may break recording functionality! + */ + public var initialSeed(default, set):Int = 1; + + /** + * Current seed used to generate new random numbers. You can retrieve this value if, + * for example, you want to store the seed that was used to randomly generate a level. + */ + public var currentSeed(get, set):Int; + + /** + * Create a new ProkRandom object. + * + * @param InitialSeed The first seed of this ProkRandom object. If ignored, a random seed will be generated. + */ + public function new(?InitialSeed:Int) + { + if (InitialSeed != null) + { + initialSeed = InitialSeed; + } + else + { + resetInitialSeed(); + } + } + + /** + * Function to easily set the global seed to a new random number. + * Please note that this function is not deterministic! + * If you call it in your game, recording may not function as expected. + * + * @return The new initial seed. + */ + public inline function resetInitialSeed():Int + { + return initialSeed = rangeBound(Std.int(Math.random() * ProkMath.MAX_VALUE_INT)); + } + + /** + * Returns a pseudorandom integer between Min and Max, inclusive. + * Will not return a number in the Excludes array, if provided. + * Please note that large Excludes arrays can slow calculations. + * + * @param Min The minimum value that should be returned. 0 by default. + * @param Max The maximum value that should be returned. 2,147,483,647 by default. + * @param Excludes Optional array of values that should not be returned. + */ + public function int(Min:Int = 0, Max:Int = ProkMath.MAX_VALUE_INT, ?Excludes:Array):Int + { + if (Min == 0 && Max == ProkMath.MAX_VALUE_INT && Excludes == null) + { + return Std.int(generate()); + } + else if (Min == Max) + { + return Min; + } + else + { + // Swap values if reversed + if (Min > Max) + { + Min = Min + Max; + Max = Min - Max; + Min = Min - Max; + } + + if (Excludes == null) + { + return Math.floor(Min + generate() / MODULUS * (Max - Min + 1)); + } + else + { + var result:Int = 0; + + do + { + result = Math.floor(Min + generate() / MODULUS * (Max - Min + 1)); + } + while (Excludes.indexOf(result) >= 0); + + return result; + } + } + } + + /** + * Returns a pseudorandom float value between Min (inclusive) and Max (exclusive). + * Will not return a number in the Excludes array, if provided. + * Please note that large Excludes arrays can slow calculations. + * + * @param Min The minimum value that should be returned. 0 by default. + * @param Max The maximum value that should be returned. 1 by default. + * @param Excludes Optional array of values that should not be returned. + */ + public function float(Min:Float = 0, Max:Float = 1, ?Excludes:Array):Float + { + var result:Float = 0; + + if (Min == 0 && Max == 1 && Excludes == null) + { + return generate() / MODULUS; + } + else if (Min == Max) + { + result = Min; + } + else + { + // Swap values if reversed. + if (Min > Max) + { + Min = Min + Max; + Max = Min - Max; + Min = Min - Max; + } + + if (Excludes == null) + { + result = Min + (generate() / MODULUS) * (Max - Min); + } + else + { + do + { + result = Min + (generate() / MODULUS) * (Max - Min); + } + while (Excludes.indexOf(result) >= 0); + } + } + + return result; + } + + // helper variables for floatNormal -- it produces TWO random values with each call so we have to store some state outside the function + var _hasFloatNormalSpare:Bool = false; + var _floatNormalRand1:Float = 0; + var _floatNormalRand2:Float = 0; + var _twoPI:Float = Math.PI * 2; + var _floatNormalRho:Float = 0; + + /** + * Returns a pseudorandom float value in a statistical normal distribution centered on Mean with a standard deviation size of StdDev. + * (This uses the Box-Muller transform algorithm for gaussian pseudorandom numbers) + * + * Normal distribution: 68% values are within 1 standard deviation, 95% are in 2 StdDevs, 99% in 3 StdDevs. + * See this image: https://github.com/HaxeFlixel/flixel-demos/blob/dev/Performance/FlxRandom/normaldistribution.png + * + * @param Mean The Mean around which the normal distribution is centered + * @param StdDev Size of the standard deviation + */ + public function floatNormal(Mean:Float = 0, StdDev:Float = 1):Float + { + if (_hasFloatNormalSpare) + { + _hasFloatNormalSpare = false; + var scale:Float = StdDev * _floatNormalRho; + return Mean + scale * _floatNormalRand2; + } + + _hasFloatNormalSpare = true; + + var theta:Float = _twoPI * (generate() / MODULUS); + _floatNormalRho = Math.sqrt(-2 * Math.log(1 - (generate() / MODULUS))); + var scale:Float = StdDev * _floatNormalRho; + + _floatNormalRand1 = Math.cos(theta); + _floatNormalRand2 = Math.sin(theta); + + return Mean + scale * _floatNormalRand1; + } + + /** + * Returns true or false based on the chance value (default 50%). + * For example if you wanted a player to have a 30.5% chance of getting a bonus, + * call bool(30.5) - true means the chance passed, false means it failed. + * + * @param Chance The chance of receiving the value. + * Should be given as a number between 0 and 100 (effectively 0% to 100%) + * @return Whether the roll passed or not. + */ + public inline function bool(Chance:Float = 50):Bool + { + return float(0, 100) < Chance; + } + + /** + * Returns either a 1 or -1. + * + * @param Chance The chance of receiving a positive value. + * Should be given as a number between 0 and 100 (effectively 0% to 100%) + * @return 1 or -1 + */ + public inline function sign(Chance:Float = 50):Int + { + return bool(Chance) ? 1 : -1; + } + + /** + * Pseudorandomly select from an array of weighted options. For example, if you passed in an array of [50, 30, 20] + * there would be a 50% chance of returning a 0, a 30% chance of returning a 1, and a 20% chance of returning a 2. + * Note that the values in the array do not have to add to 100 or any other number. + * The percent chance will be equal to a given value in the array divided by the total of all values in the array. + * + * @param WeightsArray An array of weights. + * @return A value between 0 and (SelectionArray.length - 1), with a probability equivalent to the values in SelectionArray. + */ + public function weightedPick(WeightsArray:Array):Int + { + var totalWeight:Float = 0; + var pick:Int = 0; + + for (i in WeightsArray) + { + totalWeight += i; + } + + totalWeight = float(0, totalWeight); + + for (i in 0...WeightsArray.length) + { + if (totalWeight < WeightsArray[i]) + { + pick = i; + break; + } + + totalWeight -= WeightsArray[i]; + } + + return pick; + } + + /** + * Returns a random object from an array. + * + * @param Objects An array from which to return an object. + * @param WeightsArray Optional array of weights which will determine the likelihood of returning a given value from Objects. + * If none is passed, all objects in the Objects array will have an equal likelihood of being returned. + * Values in WeightsArray will correspond to objects in Objects exactly. + * @param StartIndex Optional index from which to restrict selection. Default value is 0, or the beginning of the Objects array. + * @param EndIndex Optional index at which to restrict selection. Ignored if 0, which is the default value. + * @return A pseudorandomly chosen object from Objects. + */ + @:generic + public function getObject(Objects:Array, ?WeightsArray:Array, StartIndex:Int = 0, ?EndIndex:Null):T + { + var selected:Null = null; + + if (Objects.length != 0) + { + if (WeightsArray == null) + { + WeightsArray = [for (i in 0...Objects.length) 1]; + } + + if (EndIndex == null) + { + EndIndex = Objects.length - 1; + } + + StartIndex = Std.int(ProkMath.bound(StartIndex, 0, Objects.length - 1)); + EndIndex = Std.int(ProkMath.bound(EndIndex, 0, Objects.length - 1)); + + // Swap values if reversed + if (EndIndex < StartIndex) + { + StartIndex = StartIndex + EndIndex; + EndIndex = StartIndex - EndIndex; + StartIndex = StartIndex - EndIndex; + } + + if (EndIndex > WeightsArray.length - 1) + { + EndIndex = WeightsArray.length - 1; + } + + _arrayFloatHelper = [for (i in StartIndex...EndIndex + 1) WeightsArray[i]]; + selected = Objects[StartIndex + weightedPick(_arrayFloatHelper)]; + } + + return selected; + } + + /** + * Shuffles the entries in an array into a new pseudorandom order. + * + * @param Objects An array to shuffle. + * @param HowManyTimes How many swaps to perform during the shuffle operation. + * A good rule of thumb is 2-4 times the number of objects in the list. + * @return The newly shuffled array. + */ + @:generic + @:deprecated("Unless you rely on reproducing the exact output of shuffleArray(), you should use shuffle() instead, which is both faster and higher quality.") + public function shuffleArray(Objects:Array, HowManyTimes:Int):Array + { + HowManyTimes = Std.int(Math.max(HowManyTimes, 0)); + + var tempObject:Null = null; + + for (i in 0...HowManyTimes) + { + var pick1:Int = int(0, Objects.length - 1); + var pick2:Int = int(0, Objects.length - 1); + + tempObject = Objects[pick1]; + Objects[pick1] = Objects[pick2]; + Objects[pick2] = tempObject; + } + + return Objects; + } + + /** + * Shuffles the entries in an array in-place into a new pseudorandom order, + * using the standard Fisher-Yates shuffle algorithm. + * + * @param array The array to shuffle. + * @since 4.2.0 + */ + @:generic + public function shuffle(array:Array):Void + { + var maxValidIndex = array.length - 1; + for (i in 0...maxValidIndex) + { + var j = int(i, maxValidIndex); + var tmp = array[i]; + array[i] = array[j]; + array[j] = tmp; + } + } + + #if flixel + /** + * Returns a random color. + * + * @param Min An optional FlxColor representing the lower bounds for the generated color. + * @param Max An optional FlxColor representing the upper bounds for the generated color. + * @param Alpha An optional value for the alpha channel of the generated color. + * @param GreyScale Whether or not to create a color that is strictly a shade of grey. False by default. + * @return A color value as a FlxColor. + */ + public function color(?Min:FlxColor, ?Max:FlxColor, ?Alpha:Int, GreyScale:Bool = false):FlxColor + { + var red:Int; + var green:Int; + var blue:Int; + var alpha:Int; + + if (Min == null && Max == null) + { + red = int(0, 255); + green = int(0, 255); + blue = int(0, 255); + alpha = Alpha == null ? int(0, 255) : Alpha; + } + else if (Max == null) + { + red = int(Min.red, 255); + green = GreyScale ? red : int(Min.green, 255); + blue = GreyScale ? red : int(Min.blue, 255); + alpha = Alpha == null ? int(Min.alpha, 255) : Alpha; + } + else if (Min == null) + { + red = int(0, Max.red); + green = GreyScale ? red : int(0, Max.green); + blue = GreyScale ? red : int(0, Max.blue); + alpha = Alpha == null ? int(0, Max.alpha) : Alpha; + } + else + { + red = int(Min.red, Max.red); + green = GreyScale ? red : int(Min.green, Max.green); + blue = GreyScale ? red : int(Min.blue, Max.blue); + alpha = Alpha == null ? int(Min.alpha, Max.alpha) : Alpha; + } + + return FlxColor.fromRGB(red, green, blue, alpha); + } + #end + + /** + * Internal method to quickly generate a pseudorandom number. Used only by other functions of this class. + * Also updates the internal seed, which will then be used to generate the next pseudorandom number. + * + * @return A new pseudorandom number. + */ + inline function generate():Float + { + return internalSeed = (internalSeed * MULTIPLIER) % MODULUS; + } + + /** + * The actual internal seed. Stored as a Float value to prevent inaccuracies due to + * integer overflow in the generate() equation. + */ + var internalSeed:Float = 1; + + /** + * Internal function to update the current seed whenever the initial seed is reset, + * and keep the initial seed's value in range. + */ + inline function set_initialSeed(NewSeed:Int):Int + { + return initialSeed = currentSeed = rangeBound(NewSeed); + } + + /** + * Returns the internal seed as an integer. + */ + inline function get_currentSeed():Int + { + return Std.int(internalSeed); + } + + /** + * Sets the internal seed to an integer value. + */ + inline function set_currentSeed(NewSeed:Int):Int + { + return Std.int(internalSeed = rangeBound(NewSeed)); + } + + /** + * Internal shared function to ensure an arbitrary value is in the valid range of seed values. + */ + static inline function rangeBound(Value:Int):Int + { + return Std.int(ProkMath.bound(Value, 1, MODULUS - 1)); + } + + /** + * Internal shared helper variable. Used by getObject(). + */ + static var _arrayFloatHelper:Array = null; + + /** + * Constants used in the pseudorandom number generation equation. + * These are the constants suggested by the revised MINSTD pseudorandom number generator, + * and they use the full range of possible integer values. + * + * @see http://en.wikipedia.org/wiki/Linear_congruential_generator + * @see Stephen K. Park and Keith W. Miller and Paul K. Stockmeyer (1988). + * "Technical Correspondence". Communications of the ACM 36 (7): 105–110. + */ + static inline var MULTIPLIER:Float = 48271.0; + + static inline var MODULUS:Int = ProkMath.MAX_VALUE_INT; +} diff --git a/projects/prokgen/src/prokgen/generators/ArrayGen.hx b/projects/prokgen/src/prokgen/generators/ArrayGen.hx new file mode 100644 index 00000000..2128c5c6 --- /dev/null +++ b/projects/prokgen/src/prokgen/generators/ArrayGen.hx @@ -0,0 +1,51 @@ +package prokgen.generators; + +enum ArrayCombineBehavior { + ShortestLength; + LongestLength; +} + +class ArrayGen implements Generator> { + var elementGen:Generator; + var minLength:Int; + var maxLength:Int; + var r:ProkRandom; + var behavior:ArrayCombineBehavior; + + public function new(elementGen:Generator, minLength:Int, maxLength:Int, behavior:ArrayCombineBehavior = ShortestLength) { + this.elementGen = elementGen; + this.minLength = minLength; + this.maxLength = maxLength; + this.behavior = behavior; + } + + public function use(r:ProkRandom) { + this.r = r; + this.elementGen.use(r); + } + + public function makeRandom() { + var length = r.int(minLength, maxLength); + return [for (_ in 0... length) elementGen.makeRandom()]; + } + + public function combine(a:Array, b:Array) { + var longest = if (a.length > b.length) a else b; + var sharedLength = Math.floor(Math.min(a.length, b.length)); + var longestLength = switch (behavior) { + case LongestLength: + longest.length; + case ShortestLength: + sharedLength; + }; + + return [ + for (i in 0...sharedLength) + elementGen.combine(a[i], b[i]) + ].concat( + [ + for (i in sharedLength... longestLength) + longest[i] + ]); + } +} \ No newline at end of file diff --git a/projects/prokgen/src/prokgen/generators/BoolGen.hx b/projects/prokgen/src/prokgen/generators/BoolGen.hx new file mode 100644 index 00000000..e9b3aadf --- /dev/null +++ b/projects/prokgen/src/prokgen/generators/BoolGen.hx @@ -0,0 +1,21 @@ +package prokgen.generators; + +import prokgen.ProkRandom; + +class BoolGen implements Generator { + var r:ProkRandom; + + public function new() {} + + public function use(r:ProkRandom) { + this.r = r; + } + + public function makeRandom() { + return r.bool(); + } + + public function combine(a:Bool, b:Bool) { + return r.getObject([a, b]); + } +} diff --git a/projects/prokgen/src/prokgen/generators/FloatGen.hx b/projects/prokgen/src/prokgen/generators/FloatGen.hx new file mode 100644 index 00000000..a47c28a4 --- /dev/null +++ b/projects/prokgen/src/prokgen/generators/FloatGen.hx @@ -0,0 +1,46 @@ +package prokgen.generators; + +import prokgen.ProkMath; +import prokgen.ProkRandom; + +class FloatGen implements Generator { + private var min:Float; + private var max:Float; + private var r:ProkRandom; + + public function new(min:Float = -ProkMath.MAX_VALUE_FLOAT, max:Float = ProkMath.MAX_VALUE_FLOAT) { + this.min = min; + this.max = max; + } + + public function use(r:ProkRandom) { + this.r = r; + } + + public function makeRandom() { + var minSign = min / Math.abs(min); + var maxSign = max / Math.abs(max); + + return if (minSign != maxSign) { + var sign = r.getObject([minSign, maxSign], [Math.abs(min)/Math.abs(max), 1]); + var abs = r.float(0, Math.abs(if (sign == minSign) min else max)); + sign * abs; + } else { + r.float(min, max); + }; + + } + + public function combine(a:Float, b:Float) { + var minSign = min / Math.abs(min); + var maxSign = max / Math.abs(max); + + // Avoid overflow when taking the mean: + return if (minSign == maxSign) { + var halfDiff = Math.abs(a - b) / 2; + Math.min(a, b) + halfDiff; + } else { + (a + b) / 2; + } + } +} diff --git a/projects/prokgen/src/prokgen/generators/IntGen.hx b/projects/prokgen/src/prokgen/generators/IntGen.hx new file mode 100644 index 00000000..a61530a7 --- /dev/null +++ b/projects/prokgen/src/prokgen/generators/IntGen.hx @@ -0,0 +1,45 @@ +package prokgen.generators; + +import prokgen.ProkMath; +import prokgen.ProkRandom; + +class IntGen implements Generator { + private var min:Int; + private var max:Int; + private var r:ProkRandom; + + public function new(min:Int = ProkMath.MIN_VALUE_INT, max:Int = ProkMath.MAX_VALUE_INT) { + this.min = min; + this.max = max; + } + + public function use(r:ProkRandom) { + this.r = r; + } + + public function makeRandom() { + var minSign = min / Math.abs(min); + var maxSign = max / Math.abs(max); + + return if (minSign != maxSign) { + var sign = r.getObject([minSign, maxSign], [Math.abs(min)/Math.abs(max), 1]); + var abs = r.int(0, Std.int(Math.abs(if (sign == minSign) min else max))); + Std.int(sign * abs); + } else { + r.int(min, max); + }; + } + + public function combine(a:Int, b:Int) { + var minSign = min / Math.abs(min); + var maxSign = max / Math.abs(max); + + // Avoid overflow when taking the mean: + return if (minSign == maxSign) { + var halfDiff = Math.abs(a - b) / 2; + Math.round(Math.min(a, b) + halfDiff); + } else { + Math.round((a + b) / 2); + } + } +} diff --git a/projects/prokgen/test.sh b/projects/prokgen/test.sh new file mode 100644 index 00000000..0ee8ae95 --- /dev/null +++ b/projects/prokgen/test.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +haxe build.hxml \ No newline at end of file