Jofthomas's picture
Upload 4781 files
5c2ed06 verified
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var prng_exports = {};
__export(prng_exports, {
Gen5RNG: () => Gen5RNG,
PRNG: () => PRNG,
SodiumRNG: () => SodiumRNG
});
module.exports = __toCommonJS(prng_exports);
var import_ts_chacha20 = require("ts-chacha20");
var import_utils = require("../lib/utils");
var crypto = __toESM(require("crypto"));
/**
* PRNG
* Pokemon Showdown - http://pokemonshowdown.com/
*
* This simulates the on-cartridge PRNG used in the real games.
*
* In addition to potentially allowing us to read replays from in-game,
* this also makes it possible to record an "input log" (a seed +
* initial teams + move/switch decisions) and "replay" a simulation to
* get the same result.
*
* @license MIT license
*/
class PRNG {
/** Creates a new source of randomness for the given seed. */
constructor(seed = null, initialSeed) {
if (!seed)
seed = PRNG.generateSeed();
if (Array.isArray(seed)) {
seed = seed.join(",");
}
if (typeof seed !== "string") {
throw new Error(`PRNG: Seed ${seed} must be a string`);
}
this.startingSeed = initialSeed ?? seed;
this.setSeed(seed);
}
setSeed(seed) {
if (seed.startsWith("sodium,")) {
this.rng = new SodiumRNG(seed.split(","));
} else if (seed.startsWith("gen5,")) {
const gen5Seed = [seed.slice(5, 9), seed.slice(9, 13), seed.slice(13, 17), seed.slice(17, 21)];
this.rng = new Gen5RNG(gen5Seed.map((n) => parseInt(n, 16)));
} else if (/[0-9]/.test(seed.charAt(0))) {
this.rng = new Gen5RNG(seed.split(",").map(Number));
} else {
throw new Error(`Unrecognized RNG seed ${seed}`);
}
}
getSeed() {
return this.rng.getSeed();
}
/**
* Creates a clone of the current PRNG.
*
* The new PRNG will have its initial seed set to the seed of the current instance.
*/
clone() {
return new PRNG(this.rng.getSeed(), this.startingSeed);
}
/**
* Retrieves the next random number in the sequence.
* This function has three different results, depending on arguments:
* - random() returns a real number in [0, 1), just like Math.random()
* - random(n) returns an integer in [0, n)
* - random(m, n) returns an integer in [m, n)
* m and n are converted to integers via Math.floor. If the result is NaN, they are ignored.
*/
random(from, to) {
const result = this.rng.next();
if (from)
from = Math.floor(from);
if (to)
to = Math.floor(to);
if (from === void 0) {
return result / 2 ** 32;
} else if (!to) {
return Math.floor(result * from / 2 ** 32);
} else {
return Math.floor(result * (to - from) / 2 ** 32) + from;
}
}
/**
* Flip a coin (two-sided die), returning true or false.
*
* This function returns true with probability `P`, where `P = numerator
* / denominator`. This function returns false with probability `1 - P`.
*
* The numerator must be a non-negative integer (`>= 0`).
*
* The denominator must be a positive integer (`> 0`).
*/
randomChance(numerator, denominator) {
return this.random(denominator) < numerator;
}
/**
* Return a random item from the given array.
*
* This function chooses items in the array with equal probability.
*
* If there are duplicate items in the array, each duplicate is
* considered separately. For example, sample(['x', 'x', 'y']) returns
* 'x' 67% of the time and 'y' 33% of the time.
*
* The array must contain at least one item.
*
* The array must not be sparse.
*/
sample(items) {
if (items.length === 0) {
throw new RangeError(`Cannot sample an empty array`);
}
const index = this.random(items.length);
const item = items[index];
if (item === void 0 && !Object.prototype.hasOwnProperty.call(items, index)) {
throw new RangeError(`Cannot sample a sparse array`);
}
return item;
}
/**
* A Fisher-Yates shuffle. This is how the game resolves speed ties.
*
* At least according to V4 in
* https://github.com/smogon/pokemon-showdown/issues/1157#issuecomment-214454873
*/
shuffle(items, start = 0, end = items.length) {
while (start < end - 1) {
const nextIndex = this.random(start, end);
if (start !== nextIndex) {
[items[start], items[nextIndex]] = [items[nextIndex], items[start]];
}
start++;
}
}
static generateSeed() {
return PRNG.convertSeed(SodiumRNG.generateSeed());
}
static convertSeed(seed) {
return seed.join(",");
}
static get(prng) {
return prng && typeof prng !== "string" && !Array.isArray(prng) ? prng : new PRNG(prng);
}
}
const _SodiumRNG = class {
/** Creates a new source of randomness for the given seed. */
constructor(seed) {
this.setSeed(seed);
}
setSeed(seed) {
const seedBuf = new Uint8Array(32);
import_utils.Utils.bufWriteHex(seedBuf, seed[1].padEnd(64, "0"));
this.seed = seedBuf;
}
getSeed() {
return `sodium,${import_utils.Utils.bufReadHex(this.seed)}`;
}
next() {
const zeroBuf = new Uint8Array(36);
const buf = new import_ts_chacha20.Chacha20(this.seed, _SodiumRNG.NONCE).encrypt(zeroBuf);
this.seed = buf.slice(0, 32);
return buf.slice(32, 36).reduce((a, b) => a * 256 + b);
}
static generateSeed() {
return [
"sodium",
crypto.randomBytes(16).toString("hex")
];
}
};
let SodiumRNG = _SodiumRNG;
// nonce chosen to be compatible with libsodium's randombytes_buf_deterministic
// https://github.com/jedisct1/libsodium/blob/ce07d6c82c0e6c75031cf627913bf4f9d3f1e754/src/libsodium/randombytes/randombytes.c#L178
SodiumRNG.NONCE = Uint8Array.from([..."LibsodiumDRG"].map((c) => c.charCodeAt(0)));
class Gen5RNG {
/** Creates a new source of randomness for the given seed. */
constructor(seed = null) {
this.seed = [...seed || Gen5RNG.generateSeed()];
}
getSeed() {
return this.seed.join(",");
}
next() {
this.seed = this.nextFrame(this.seed);
return (this.seed[0] << 16 >>> 0) + this.seed[1];
}
/**
* Calculates `a * b + c` (with 64-bit 2's complement integers)
*/
multiplyAdd(a, b, c) {
const out = [0, 0, 0, 0];
let carry = 0;
for (let outIndex = 3; outIndex >= 0; outIndex--) {
for (let bIndex = outIndex; bIndex < 4; bIndex++) {
const aIndex = 3 - (bIndex - outIndex);
carry += a[aIndex] * b[bIndex];
}
carry += c[outIndex];
out[outIndex] = carry & 65535;
carry >>>= 16;
}
return out;
}
/**
* The RNG is a Linear Congruential Generator (LCG) in the form: `x_{n + 1} = (a x_n + c) % m`
*
* Where: `x_0` is the seed, `x_n` is the random number after n iterations,
*
* ````
* a = 0x5D588B656C078965
* c = 0x00269EC3
* m = 2^64
* ````
*/
nextFrame(seed, framesToAdvance = 1) {
const a = [23896, 35685, 27655, 35173];
const c = [0, 0, 38, 40643];
for (let i = 0; i < framesToAdvance; i++) {
seed = this.multiplyAdd(seed, a, c);
}
return seed;
}
static generateSeed() {
return [
Math.trunc(Math.random() * 2 ** 16),
Math.trunc(Math.random() * 2 ** 16),
Math.trunc(Math.random() * 2 ** 16),
Math.trunc(Math.random() * 2 ** 16)
];
}
}
//# sourceMappingURL=prng.js.map