{ "version": 3, "sources": ["../../../sim/prng.ts"], "sourcesContent": ["/**\r\n * PRNG\r\n * Pokemon Showdown - http://pokemonshowdown.com/\r\n *\r\n * This simulates the on-cartridge PRNG used in the real games.\r\n *\r\n * In addition to potentially allowing us to read replays from in-game,\r\n * this also makes it possible to record an \"input log\" (a seed +\r\n * initial teams + move/switch decisions) and \"replay\" a simulation to\r\n * get the same result.\r\n *\r\n * @license MIT license\r\n */\r\n\r\n/** 64-bit big-endian [high -> low] int */\r\nexport type PRNGSeed = [number, number, number, number];\r\n\r\n/**\r\n * A PRNG intended to emulate the on-cartridge PRNG for Gen 5 with a 64-bit\r\n * initial seed.\r\n */\r\nexport class PRNG {\r\n\treadonly initialSeed: PRNGSeed;\r\n\tseed: PRNGSeed;\r\n\t/** Creates a new source of randomness for the given seed. */\r\n\tconstructor(seed: PRNGSeed | null = null) {\r\n\t\tif (!seed) seed = PRNG.generateSeed();\r\n\t\tthis.initialSeed = seed.slice() as PRNGSeed; // make a copy\r\n\t\tthis.seed = seed.slice() as PRNGSeed;\r\n\t}\r\n\r\n\t/**\r\n\t * Getter to the initial seed.\r\n\t *\r\n\t * This should be considered a hack and is only here for backwards compatibility.\r\n\t */\r\n\tget startingSeed(): PRNGSeed {\r\n\t\treturn this.initialSeed;\r\n\t}\r\n\r\n\t/**\r\n\t * Creates a clone of the current PRNG.\r\n\t *\r\n\t * The new PRNG will have its initial seed set to the seed of the current instance.\r\n\t */\r\n\tclone(): PRNG {\r\n\t\treturn new PRNG(this.seed);\r\n\t}\r\n\r\n\t/**\r\n\t * Retrieves the next random number in the sequence.\r\n\t * This function has three different results, depending on arguments:\r\n\t * - random() returns a real number in [0, 1), just like Math.random()\r\n\t * - random(n) returns an integer in [0, n)\r\n\t * - random(m, n) returns an integer in [m, n)\r\n\t * m and n are converted to integers via Math.floor. If the result is NaN, they are ignored.\r\n\t */\r\n\tnext(from?: number, to?: number): number {\r\n\t\tthis.seed = this.nextFrame(this.seed); // Advance the RNG\r\n\t\tlet result = (this.seed[0] << 16 >>> 0) + this.seed[1]; // Use the upper 32 bits\r\n\t\tif (from) from = Math.floor(from);\r\n\t\tif (to) to = Math.floor(to);\r\n\t\tif (from === undefined) {\r\n\t\t\tresult = result / 0x100000000;\r\n\t\t} else if (!to) {\r\n\t\t\tresult = Math.floor(result * from / 0x100000000);\r\n\t\t} else {\r\n\t\t\tresult = Math.floor(result * (to - from) / 0x100000000) + from;\r\n\t\t}\r\n\t\treturn result;\r\n\t}\r\n\r\n\t/**\r\n\t * Flip a coin (two-sided die), returning true or false.\r\n\t *\r\n\t * This function returns true with probability `P`, where `P = numerator\r\n\t * / denominator`. This function returns false with probability `1 - P`.\r\n\t *\r\n\t * The numerator must be a non-negative integer (`>= 0`).\r\n\t *\r\n\t * The denominator must be a positive integer (`> 0`).\r\n\t */\r\n\trandomChance(numerator: number, denominator: number): boolean {\r\n\t\treturn this.next(denominator) < numerator;\r\n\t}\r\n\r\n\t/**\r\n\t * Return a random item from the given array.\r\n\t *\r\n\t * This function chooses items in the array with equal probability.\r\n\t *\r\n\t * If there are duplicate items in the array, each duplicate is\r\n\t * considered separately. For example, sample(['x', 'x', 'y']) returns\r\n\t * 'x' 67% of the time and 'y' 33% of the time.\r\n\t *\r\n\t * The array must contain at least one item.\r\n\t *\r\n\t * The array must not be sparse.\r\n\t */\r\n\tsample(items: readonly T[]): T {\r\n\t\tif (items.length === 0) {\r\n\t\t\tthrow new RangeError(`Cannot sample an empty array`);\r\n\t\t}\r\n\t\tconst index = this.next(items.length);\r\n\t\tconst item = items[index];\r\n\t\tif (item === undefined && !Object.prototype.hasOwnProperty.call(items, index)) {\r\n\t\t\tthrow new RangeError(`Cannot sample a sparse array`);\r\n\t\t}\r\n\t\treturn item;\r\n\t}\r\n\r\n\t/**\r\n\t * A Fisher-Yates shuffle. This is how the game resolves speed ties.\r\n\t *\r\n\t * At least according to V4 in\r\n\t * https://github.com/smogon/pokemon-showdown/issues/1157#issuecomment-214454873\r\n\t */\r\n\tshuffle(items: T[], start = 0, end: number = items.length) {\r\n\t\twhile (start < end - 1) {\r\n\t\t\tconst nextIndex = this.next(start, end);\r\n\t\t\tif (start !== nextIndex) {\r\n\t\t\t\t[items[start], items[nextIndex]] = [items[nextIndex], items[start]];\r\n\t\t\t}\r\n\t\t\tstart++;\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Calculates `a * b + c` (with 64-bit 2's complement integers)\r\n\t *\r\n\t * If you've done long multiplication, this is the same thing.\r\n\t */\r\n\tmultiplyAdd(a: PRNGSeed, b: PRNGSeed, c: PRNGSeed) {\r\n\t\tconst out: PRNGSeed = [0, 0, 0, 0];\r\n\t\tlet carry = 0;\r\n\r\n\t\tfor (let outIndex = 3; outIndex >= 0; outIndex--) {\r\n\t\t\tfor (let bIndex = outIndex; bIndex < 4; bIndex++) {\r\n\t\t\t\tconst aIndex = 3 - (bIndex - outIndex);\r\n\r\n\t\t\t\tcarry += a[aIndex] * b[bIndex];\r\n\t\t\t}\r\n\t\t\tcarry += c[outIndex];\r\n\r\n\t\t\tout[outIndex] = carry & 0xFFFF;\r\n\t\t\tcarry >>>= 16;\r\n\t\t}\r\n\r\n\t\treturn out;\r\n\t}\r\n\r\n\t/**\r\n\t * The RNG is a Linear Congruential Generator (LCG) in the form: `x_{n + 1} = (a x_n + c) % m`\r\n\t *\r\n\t * Where: `x_0` is the seed, `x_n` is the random number after n iterations,\r\n\t *\r\n\t * ````\r\n\t * a = 0x5D588B656C078965\r\n\t * c = 0x00269EC3\r\n\t * m = 2^64\r\n\t * ````\r\n\t */\r\n\tnextFrame(seed: PRNGSeed, framesToAdvance = 1): PRNGSeed {\r\n\t\tconst a: PRNGSeed = [0x5D58, 0x8B65, 0x6C07, 0x8965];\r\n\t\tconst c: PRNGSeed = [0, 0, 0x26, 0x9EC3];\r\n\r\n\t\tfor (let i = 0; i < framesToAdvance; i++) {\r\n\t\t\tseed = this.multiplyAdd(seed, a, c);\r\n\t\t}\r\n\r\n\t\treturn seed;\r\n\t}\r\n\r\n\tstatic generateSeed() {\r\n\t\treturn [\r\n\t\t\tMath.floor(Math.random() * 0x10000),\r\n\t\t\tMath.floor(Math.random() * 0x10000),\r\n\t\t\tMath.floor(Math.random() * 0x10000),\r\n\t\t\tMath.floor(Math.random() * 0x10000),\r\n\t\t] as PRNGSeed;\r\n\t}\r\n}\r\n\r\n// The following commented-out function is designed to emulate the on-cartridge\r\n// PRNG for Gens 3 and 4, as described in\r\n// https://www.smogon.com/ingame/rng/pid_iv_creation#pokemon_random_number_generator\r\n// This RNG uses a 32-bit initial seed\r\n// m and n are converted to integers via Math.floor. If the result is NaN, they\r\n// are ignored.\r\n/*\r\nrandom(m: number, n: number) {\r\n\tthis.seed = (this.seed * 0x41C64E6D + 0x6073) >>> 0; // truncate the result to the last 32 bits\r\n\tlet result = this.seed >>> 16; // the first 16 bits of the seed are the random value\r\n\tm = Math.floor(m)\r\n\tn = Math.floor(n)\r\n\treturn (m ? (n ? (result % (n - m)) + m : result % m) : result / 0x10000)\r\n}\r\n*/\r\n"], "mappings": "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", "names": [] }