Spaces:
Running
Running
File size: 14,886 Bytes
5c2ed06 |
1 2 3 4 5 6 7 8 |
{
"version": 3,
"sources": ["../../sim/prng.ts"],
"sourcesContent": ["/**\n * PRNG\n * Pokemon Showdown - http://pokemonshowdown.com/\n *\n * This simulates the on-cartridge PRNG used in the real games.\n *\n * In addition to potentially allowing us to read replays from in-game,\n * this also makes it possible to record an \"input log\" (a seed +\n * initial teams + move/switch decisions) and \"replay\" a simulation to\n * get the same result.\n *\n * @license MIT license\n */\n\nimport { Chacha20 } from 'ts-chacha20';\nimport { Utils } from '../lib/utils';\nimport * as crypto from 'crypto';\n\nexport type PRNGSeed = `${'sodium' | 'gen5' | number},${string}`;\nexport type SodiumRNGSeed = ['sodium', string];\n/** 64-bit big-endian [high -> low] int */\nexport type Gen5RNGSeed = [number, number, number, number];\n\n/**\n * Low-level source of 32-bit random numbers.\n */\ninterface RNG {\n\tgetSeed(): PRNGSeed;\n\t/** random 32-bit number */\n\tnext(): number;\n}\n\n/**\n * High-level PRNG API, for getting random numbers.\n *\n * Chooses the RNG implementation based on the seed passed to the constructor.\n * Seeds starting with 'sodium' use sodium. Other seeds use the Gen 5 RNG.\n * If a seed isn't given, defaults to sodium.\n *\n * The actual randomness source is in this.rng.\n */\nexport class PRNG {\n\treadonly startingSeed: PRNGSeed;\n\trng!: RNG;\n\t/** Creates a new source of randomness for the given seed. */\n\tconstructor(seed: PRNGSeed | null = null, initialSeed?: PRNGSeed) {\n\t\tif (!seed) seed = PRNG.generateSeed();\n\t\tif (Array.isArray(seed)) {\n\t\t\t// compat for old inputlogs\n\t\t\tseed = seed.join(',') as PRNGSeed;\n\t\t}\n\t\tif (typeof seed !== 'string') {\n\t\t\tthrow new Error(`PRNG: Seed ${seed} must be a string`);\n\t\t}\n\t\tthis.startingSeed = initialSeed ?? seed;\n\t\tthis.setSeed(seed);\n\t}\n\n\tsetSeed(seed: PRNGSeed) {\n\t\tif (seed.startsWith('sodium,')) {\n\t\t\tthis.rng = new SodiumRNG(seed.split(',') as SodiumRNGSeed);\n\t\t} else if (seed.startsWith('gen5,')) {\n\t\t\tconst gen5Seed = [seed.slice(5, 9), seed.slice(9, 13), seed.slice(13, 17), seed.slice(17, 21)];\n\t\t\tthis.rng = new Gen5RNG(gen5Seed.map(n => parseInt(n, 16)) as Gen5RNGSeed);\n\t\t} else if (/[0-9]/.test(seed.charAt(0))) {\n\t\t\tthis.rng = new Gen5RNG(seed.split(',').map(Number) as Gen5RNGSeed);\n\t\t} else {\n\t\t\tthrow new Error(`Unrecognized RNG seed ${seed}`);\n\t\t}\n\t}\n\tgetSeed(): PRNGSeed {\n\t\treturn this.rng.getSeed();\n\t}\n\n\t/**\n\t * Creates a clone of the current PRNG.\n\t *\n\t * The new PRNG will have its initial seed set to the seed of the current instance.\n\t */\n\tclone(): PRNG {\n\t\treturn new PRNG(this.rng.getSeed(), this.startingSeed);\n\t}\n\n\t/**\n\t * Retrieves the next random number in the sequence.\n\t * This function has three different results, depending on arguments:\n\t * - random() returns a real number in [0, 1), just like Math.random()\n\t * - random(n) returns an integer in [0, n)\n\t * - random(m, n) returns an integer in [m, n)\n\t * m and n are converted to integers via Math.floor. If the result is NaN, they are ignored.\n\t */\n\trandom(from?: number, to?: number): number {\n\t\tconst result = this.rng.next();\n\n\t\tif (from) from = Math.floor(from);\n\t\tif (to) to = Math.floor(to);\n\t\tif (from === undefined) {\n\t\t\treturn result / 2 ** 32;\n\t\t} else if (!to) {\n\t\t\treturn Math.floor(result * from / 2 ** 32);\n\t\t} else {\n\t\t\treturn Math.floor(result * (to - from) / 2 ** 32) + from;\n\t\t}\n\t}\n\n\t/**\n\t * Flip a coin (two-sided die), returning true or false.\n\t *\n\t * This function returns true with probability `P`, where `P = numerator\n\t * / denominator`. This function returns false with probability `1 - P`.\n\t *\n\t * The numerator must be a non-negative integer (`>= 0`).\n\t *\n\t * The denominator must be a positive integer (`> 0`).\n\t */\n\trandomChance(numerator: number, denominator: number): boolean {\n\t\treturn this.random(denominator) < numerator;\n\t}\n\n\t/**\n\t * Return a random item from the given array.\n\t *\n\t * This function chooses items in the array with equal probability.\n\t *\n\t * If there are duplicate items in the array, each duplicate is\n\t * considered separately. For example, sample(['x', 'x', 'y']) returns\n\t * 'x' 67% of the time and 'y' 33% of the time.\n\t *\n\t * The array must contain at least one item.\n\t *\n\t * The array must not be sparse.\n\t */\n\tsample<T>(items: readonly T[]): T {\n\t\tif (items.length === 0) {\n\t\t\tthrow new RangeError(`Cannot sample an empty array`);\n\t\t}\n\t\tconst index = this.random(items.length);\n\t\tconst item = items[index];\n\t\tif (item === undefined && !Object.prototype.hasOwnProperty.call(items, index)) {\n\t\t\tthrow new RangeError(`Cannot sample a sparse array`);\n\t\t}\n\t\treturn item;\n\t}\n\n\t/**\n\t * A Fisher-Yates shuffle. This is how the game resolves speed ties.\n\t *\n\t * At least according to V4 in\n\t * https://github.com/smogon/pokemon-showdown/issues/1157#issuecomment-214454873\n\t */\n\tshuffle<T>(items: T[], start = 0, end: number = items.length) {\n\t\twhile (start < end - 1) {\n\t\t\tconst nextIndex = this.random(start, end);\n\t\t\tif (start !== nextIndex) {\n\t\t\t\t[items[start], items[nextIndex]] = [items[nextIndex], items[start]];\n\t\t\t}\n\t\t\tstart++;\n\t\t}\n\t}\n\n\tstatic generateSeed(): PRNGSeed {\n\t\treturn PRNG.convertSeed(SodiumRNG.generateSeed());\n\t}\n\tstatic convertSeed(seed: SodiumRNGSeed | Gen5RNGSeed): PRNGSeed {\n\t\treturn seed.join(',') as PRNGSeed;\n\t}\n\tstatic get(prng?: PRNG | PRNGSeed | null) {\n\t\treturn prng && typeof prng !== 'string' && !Array.isArray(prng) ? prng : new PRNG(prng as PRNGSeed);\n\t}\n}\n\n/**\n * This is a drop-in replacement for libsodium's randombytes_buf_deterministic,\n * but it's implemented with ts-chacha20 instead, for a smaller dependency that\n * doesn't use NodeJS native modules, for better portability.\n */\nexport class SodiumRNG implements RNG {\n\t// nonce chosen to be compatible with libsodium's randombytes_buf_deterministic\n\t// https://github.com/jedisct1/libsodium/blob/ce07d6c82c0e6c75031cf627913bf4f9d3f1e754/src/libsodium/randombytes/randombytes.c#L178\n\tstatic readonly NONCE = Uint8Array.from([...\"LibsodiumDRG\"].map(c => c.charCodeAt(0)));\n\tseed!: Uint8Array;\n\t/** Creates a new source of randomness for the given seed. */\n\tconstructor(seed: SodiumRNGSeed) {\n\t\tthis.setSeed(seed);\n\t}\n\n\tsetSeed(seed: SodiumRNGSeed) {\n\t\t// randombytes_buf_deterministic requires 32 bytes, but\n\t\t// generateSeed generates 16 bytes, so the last 16 bytes will be 0\n\t\t// when starting out. This shouldn't cause any problems.\n\t\tconst seedBuf = new Uint8Array(32);\n\t\tUtils.bufWriteHex(seedBuf, seed[1].padEnd(64, '0'));\n\t\tthis.seed = seedBuf;\n\t}\n\tgetSeed(): PRNGSeed {\n\t\treturn `sodium,${Utils.bufReadHex(this.seed)}`;\n\t}\n\n\tnext() {\n\t\tconst zeroBuf = new Uint8Array(36);\n\t\t// tested to do the exact same thing as\n\t\t// sodium.randombytes_buf_deterministic(buf, this.seed);\n\t\tconst buf = new Chacha20(this.seed, SodiumRNG.NONCE).encrypt(zeroBuf);\n\n\t\t// use the first 32 bytes for the next seed, and the next 4 bytes for the output\n\t\tthis.seed = buf.slice(0, 32);\n\t\t// reading big-endian\n\t\treturn buf.slice(32, 36).reduce((a, b) => a * 256 + b);\n\t}\n\n\tstatic generateSeed(): SodiumRNGSeed {\n\t\treturn [\n\t\t\t'sodium',\n\t\t\tcrypto.randomBytes(16).toString('hex'),\n\t\t];\n\t}\n}\n\n/**\n * A PRNG intended to emulate the on-cartridge PRNG for Gen 5 with a 64-bit\n * initial seed.\n */\nexport class Gen5RNG implements RNG {\n\tseed: Gen5RNGSeed;\n\t/** Creates a new source of randomness for the given seed. */\n\tconstructor(seed: Gen5RNGSeed | null = null) {\n\t\tthis.seed = [...seed || Gen5RNG.generateSeed()];\n\t}\n\n\tgetSeed(): PRNGSeed {\n\t\treturn this.seed.join(',') as PRNGSeed;\n\t}\n\n\tnext(): number {\n\t\tthis.seed = this.nextFrame(this.seed); // Advance the RNG\n\t\treturn (this.seed[0] << 16 >>> 0) + this.seed[1]; // Use the upper 32 bits\n\t}\n\n\t/**\n\t * Calculates `a * b + c` (with 64-bit 2's complement integers)\n\t */\n\tmultiplyAdd(a: Gen5RNGSeed, b: Gen5RNGSeed, c: Gen5RNGSeed) {\n\t\t// If you've done long multiplication, this is the same thing.\n\t\tconst out: Gen5RNGSeed = [0, 0, 0, 0];\n\t\tlet carry = 0;\n\n\t\tfor (let outIndex = 3; outIndex >= 0; outIndex--) {\n\t\t\tfor (let bIndex = outIndex; bIndex < 4; bIndex++) {\n\t\t\t\tconst aIndex = 3 - (bIndex - outIndex);\n\n\t\t\t\tcarry += a[aIndex] * b[bIndex];\n\t\t\t}\n\t\t\tcarry += c[outIndex];\n\n\t\t\tout[outIndex] = carry & 0xFFFF;\n\t\t\tcarry >>>= 16;\n\t\t}\n\n\t\treturn out;\n\t}\n\n\t/**\n\t * The RNG is a Linear Congruential Generator (LCG) in the form: `x_{n + 1} = (a x_n + c) % m`\n\t *\n\t * Where: `x_0` is the seed, `x_n` is the random number after n iterations,\n\t *\n\t * ````\n\t * a = 0x5D588B656C078965\n\t * c = 0x00269EC3\n\t * m = 2^64\n\t * ````\n\t */\n\tnextFrame(seed: Gen5RNGSeed, framesToAdvance = 1): Gen5RNGSeed {\n\t\tconst a: Gen5RNGSeed = [0x5D58, 0x8B65, 0x6C07, 0x8965];\n\t\tconst c: Gen5RNGSeed = [0, 0, 0x26, 0x9EC3];\n\n\t\tfor (let i = 0; i < framesToAdvance; i++) {\n\t\t\t// seed = seed * a + c\n\t\t\tseed = this.multiplyAdd(seed, a, c);\n\t\t}\n\n\t\treturn seed;\n\t}\n\n\tstatic generateSeed(): Gen5RNGSeed {\n\t\treturn [\n\t\t\tMath.trunc(Math.random() * 2 ** 16),\n\t\t\tMath.trunc(Math.random() * 2 ** 16),\n\t\t\tMath.trunc(Math.random() * 2 ** 16),\n\t\t\tMath.trunc(Math.random() * 2 ** 16),\n\t\t];\n\t}\n}\n\n// The following commented-out class is designed to emulate the on-cartridge\n// PRNG for Gens 3 and 4, as described in\n// https://www.smogon.com/ingame/rng/pid_iv_creation#pokemon_random_number_generator\n// This RNG uses a 32-bit initial seed\n// m and n are converted to integers via Math.floor. If the result is NaN, they\n// are ignored.\n/*\nexport type Gen3RNGSeed = ['gen3', number];\nexport class Gen3RNG implements RNG {\n\tseed: number;\n\tconstructor(seed: Gen3RNGSeed | null = null) {\n\t\tthis.seed = seed ? seed[1] : Math.trunc(Math.random() * 2 ** 32);\n\t}\n\tgetSeed() {\n\t\treturn ['gen3', this.seed];\n\t}\n\tnext(): number {\n\t\tthis.seed = this.seed * 0x41C64E6D + 0x6073) >>> 0; // truncate the result to the last 32 bits\n\t\tconst val = this.seed >>> 16; // the first 16 bits of the seed are the random value\n\t\treturn val << 16 >>> 0; // PRNG#random expects a 32-bit number and will divide accordingly\n\t}\n}\n*/\n"],
"mappings": "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",
"names": []
}
|