Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

PRNGSeed is now a string #10826

Merged
merged 1 commit into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion data/cg-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export default class TeamGenerator {
this.dex = Dex.forFormat(format);
this.format = Dex.formats.get(format);
this.teamSize = this.format.ruleTable?.maxTeamSize || 6;
this.prng = seed instanceof PRNG ? seed : new PRNG(seed);
this.prng = PRNG.get(seed);
this.itemPool = this.dex.items.all().filter(i => i.exists && i.isNonstandard !== 'Past' && !i.isPokeball);
this.specialItems = {};
for (const i of this.itemPool) {
Expand Down
4 changes: 2 additions & 2 deletions data/random-battles/gen8/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ export class RandomGen8Teams {

this.factoryTier = '';
this.format = format;
this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
this.prng = PRNG.get(prng);

this.moveEnforcementCheckers = {
screens: (movePool, moves, abilities, types, counter, species, teamDetails) => {
Expand Down Expand Up @@ -243,7 +243,7 @@ export class RandomGen8Teams {
}

setSeed(prng?: PRNG | PRNGSeed) {
this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
this.prng = PRNG.get(prng);
}

getTeam(options?: PlayerOptions | null): PokemonSet[] {
Expand Down
4 changes: 2 additions & 2 deletions data/random-battles/gen9/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ export class RandomTeams {

this.factoryTier = '';
this.format = format;
this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
this.prng = PRNG.get(prng);

this.moveEnforcementCheckers = {
Bug: (movePool, moves, abilities, types, counter) => (
Expand Down Expand Up @@ -252,7 +252,7 @@ export class RandomTeams {
}

setSeed(prng?: PRNG | PRNGSeed) {
this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
this.prng = PRNG.get(prng);
}

getTeam(options?: PlayerOptions | null): PokemonSet[] {
Expand Down
2 changes: 1 addition & 1 deletion pokemon-showdown
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) {
{
ensureBuilt();
var Teams = require('./dist/sim/teams.js').Teams;
var seed = process.argv[4] ? process.argv[4].split(',').map(Number) : undefined;
var seed = process.argv[4] || undefined;
console.log(Teams.pack(Teams.generate(process.argv[3], {seed})));
}
break;
Expand Down
7 changes: 2 additions & 5 deletions sim/battle-stream.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,12 +136,9 @@ export class BattleStream extends Streams.ObjectReadWriteStream<string> {
this.battle!.inputLog.push(`>forcelose ${message}`);
break;
case 'reseed':
const seed = message ? message.split(',').map(
n => /[0-9]/.test(n.charAt(0)) ? Number(n) : n
) as PRNGSeed : null;
this.battle!.resetRNG(seed);
this.battle!.resetRNG(message as PRNGSeed);
// could go inside resetRNG, but this makes using it in `eval` slightly less buggy
this.battle!.inputLog.push(`>reseed ${this.battle!.prng.getSeed().join(',')}`);
this.battle!.inputLog.push(`>reseed ${this.battle!.prng.getSeed()}`);
break;
case 'tiebreak':
this.battle!.tiebreak();
Expand Down
58 changes: 40 additions & 18 deletions sim/prng.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import {Chacha20} from 'ts-chacha20';
import {Utils} from '../lib/utils';

export type PRNGSeed = SodiumRNGSeed | Gen5RNGSeed;
export type PRNGSeed = `${'sodium' | 'gen5' | number},${string}`;
export type SodiumRNGSeed = ['sodium', string];
/** 64-bit big-endian [high -> low] int */
export type Gen5RNGSeed = [number, number, number, number];
Expand Down Expand Up @@ -44,15 +44,27 @@ export class PRNG {
/** Creates a new source of randomness for the given seed. */
constructor(seed: PRNGSeed | null = null, initialSeed?: PRNGSeed) {
if (!seed) seed = PRNG.generateSeed();
this.startingSeed = initialSeed || [...seed]; // make a copy
if (Array.isArray(seed)) {
// compat for old inputlogs
seed = seed.join(',') as PRNGSeed;
}
if (typeof seed !== 'string') {
throw new Error(`PRNG: Seed ${seed} must be a string`);
}
this.startingSeed = initialSeed ?? seed;
this.setSeed(seed);
}

setSeed(seed: PRNGSeed) {
if (seed[0] === 'sodium') {
this.rng = new SodiumRNG(seed);
if (seed.startsWith('sodium,')) {
this.rng = new SodiumRNG(seed.split(',') as SodiumRNGSeed);
} 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)) as Gen5RNGSeed);
} else if (/[0-9]/.test(seed.charAt(0))) {
this.rng = new Gen5RNG(seed.split(',').map(Number) as Gen5RNGSeed);
} else {
this.rng = new Gen5RNG(seed as Gen5RNGSeed);
throw new Error(`Unrecognized RNG seed ${seed}`);
}
}
getSeed(): PRNGSeed {
Expand Down Expand Up @@ -145,15 +157,14 @@ export class PRNG {
}
}

static generateSeed(): SodiumRNGSeed {
return [
'sodium',
// 32 bits each, 128 bits total (16 bytes)
Math.trunc(Math.random() * 2 ** 32).toString(16).padStart(8, '0') +
Math.trunc(Math.random() * 2 ** 32).toString(16).padStart(8, '0') +
Math.trunc(Math.random() * 2 ** 32).toString(16).padStart(8, '0') +
Math.trunc(Math.random() * 2 ** 32).toString(16).padStart(8, '0'),
];
static generateSeed(): PRNGSeed {
return PRNG.convertSeed(SodiumRNG.generateSeed());
}
static convertSeed(seed: SodiumRNGSeed | Gen5RNGSeed): PRNGSeed {
return seed.join(',') as PRNGSeed;
}
static get(prng?: PRNG | PRNGSeed | null) {
return prng && typeof prng !== 'string' && !Array.isArray(prng) ? prng : new PRNG(prng as PRNGSeed);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is the typecast necessary here? doesn't it default to PRNGSeed | null after the first check is done?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

image

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Array.isArray part confuses it. Reasonable because arrays are not PRNGSeeds anymore, but annoying for us and requires a cast.

}
}

Expand All @@ -180,8 +191,8 @@ export class SodiumRNG implements RNG {
Utils.bufWriteHex(seedBuf, seed[1].padEnd(64, '0'));
this.seed = seedBuf;
}
getSeed(): SodiumRNGSeed {
return ['sodium', Utils.bufReadHex(this.seed)];
getSeed(): PRNGSeed {
return `sodium,${Utils.bufReadHex(this.seed)}`;
}

next() {
Expand All @@ -197,6 +208,17 @@ export class SodiumRNG implements RNG {
// alternative, probably slower (TODO: benchmark)
// return parseInt(Utils.bufReadHex(buf, 32, 36), 16);
}

static generateSeed(): SodiumRNGSeed {
return [
'sodium',
// 32 bits each, 128 bits total (16 bytes)
Math.trunc(Math.random() * 2 ** 32).toString(16).padStart(8, '0') +
Math.trunc(Math.random() * 2 ** 32).toString(16).padStart(8, '0') +
Math.trunc(Math.random() * 2 ** 32).toString(16).padStart(8, '0') +
Math.trunc(Math.random() * 2 ** 32).toString(16).padStart(8, '0'),
];
}
}

/**
Expand All @@ -210,8 +232,8 @@ export class Gen5RNG implements RNG {
this.seed = [...seed || Gen5RNG.generateSeed()];
}

getSeed() {
return this.seed;
getSeed(): PRNGSeed {
return this.seed.join(',') as PRNGSeed;
}

next(): number {
Expand Down
7 changes: 3 additions & 4 deletions sim/tools/exhaustive-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,7 @@ export class ExhaustiveRunner {
constructor(options: ExhaustiveRunnerOptions) {
this.format = options.format;
this.cycles = options.cycles || ExhaustiveRunner.DEFAULT_CYCLES;
this.prng = (options.prng && !Array.isArray(options.prng)) ?
options.prng : new PRNG(options.prng);
this.prng = PRNG.get(options.prng);
this.log = !!options.log;
this.maxGames = options.maxGames;
this.maxFailures = options.maxFailures || ExhaustiveRunner.MAX_FAILURES;
Expand Down Expand Up @@ -100,7 +99,7 @@ export class ExhaustiveRunner {
this.failures++;
console.error(
`\n\nRun \`node tools/simulate exhaustive --cycles=${this.cycles} ` +
`--format=${this.format} --seed=${seed.join()}\`:\n`,
`--format=${this.format} --seed=${seed}\`:\n`,
err
);
}
Expand Down Expand Up @@ -198,7 +197,7 @@ class TeamGenerator {
signatures: Map<string, {item: string, move?: string}[]>
) {
this.dex = dex;
this.prng = prng && !Array.isArray(prng) ? prng : new PRNG(prng);
this.prng = PRNG.get(prng);
this.pools = pools;
this.signatures = signatures;

Expand Down
5 changes: 2 additions & 3 deletions sim/tools/multi-random-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,7 @@ export class MultiRandomRunner {

this.totalGames = options.totalGames;

this.prng = (options.prng && !Array.isArray(options.prng)) ?
options.prng : new PRNG(options.prng);
this.prng = PRNG.get(options.prng);
this.options.prng = this.prng;

this.format = options.format;
Expand Down Expand Up @@ -75,7 +74,7 @@ export class MultiRandomRunner {
const game = new Runner({format, ...this.options}).run().catch(err => {
failures++;
console.error(
`Run \`node tools/simulate multi 1 --format=${format} --seed=${seed.join()}\` ` +
`Run \`node tools/simulate multi 1 --format=${format} --seed=${seed}\` ` +
`to debug (optionally with \`--output\` and/or \`--input\` for more info):\n`,
err
);
Expand Down
2 changes: 1 addition & 1 deletion sim/tools/random-player-ai.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ export class RandomPlayerAI extends BattlePlayer {
super(playerStream, debug);
this.move = options.move || 1.0;
this.mega = options.mega || 0;
this.prng = options.seed && !Array.isArray(options.seed) ? options.seed : new PRNG(options.seed);
this.prng = PRNG.get(options.seed);
}

receiveError(error: Error) {
Expand Down
5 changes: 2 additions & 3 deletions sim/tools/runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,7 @@ export class Runner {
constructor(options: RunnerOptions) {
this.format = options.format;

this.prng = (options.prng && !Array.isArray(options.prng)) ?
options.prng : new PRNG(options.prng);
this.prng = PRNG.get(options.prng);
this.p1options = {...Runner.AI_OPTIONS, ...options.p1options};
this.p2options = {...Runner.AI_OPTIONS, ...options.p2options};
this.p3options = {...Runner.AI_OPTIONS, ...options.p3options};
Expand Down Expand Up @@ -144,7 +143,7 @@ export class Runner {
this.prng.random(2 ** 16),
this.prng.random(2 ** 16),
this.prng.random(2 ** 16),
];
].join(',') as PRNGSeed;
}

private getPlayerSpec(name: string, options: AIOptions) {
Expand Down
2 changes: 1 addition & 1 deletion test/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ function capitalize(word) {
/**
* The default random number generator seed used if one is not given.
*/
const DEFAULT_SEED = [0x09917, 0x06924, 0x0e1c8, 0x06af0];
const DEFAULT_SEED = 'gen5,99176924e1c86af0';

class TestTools {
constructor(mod = 'base') {
Expand Down
2 changes: 1 addition & 1 deletion test/random-battles/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ function testTeam(options, test) {

const generator = Teams.getGenerator(options.format, [0, 0, 0, 0]);
for (let i = 0; i < rounds; i++) {
generator.setSeed(options.seed || [i, i, i, i]);
generator.setSeed(options.seed || [i, i, i, i].join(','));
const team = generator.getTeam();
test(team);
}
Expand Down
2 changes: 1 addition & 1 deletion test/sim/misc/prng.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
const PRNG = require('../../../dist/sim/prng').PRNG;
const assert = require('../../assert');

const testSeed = ['sodium', '00000001000000020000000300000004'];
const testSeed = 'sodium,00000001000000020000000300000004';

describe(`PRNG`, function () {
it("should always generate the same results off the same seed", function () {
Expand Down
4 changes: 2 additions & 2 deletions test/sim/misc/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ describe('State', function () {
describe('Battles', function () {
it('should be able to be serialized and deserialized without affecting functionality (slow)', function () {
this.timeout(5000);
const control = common.createBattle({seed: ['sodium', '00000001000000020000000300000004']}, TEAMS);
let test = common.createBattle({seed: ['sodium', '00000001000000020000000300000004']}, TEAMS);
const control = common.createBattle({seed: 'sodium,00000001000000020000000300000004'}, TEAMS);
let test = common.createBattle({seed: 'sodium,00000001000000020000000300000004'}, TEAMS);

while (!(control.ended || test.ended)) {
control.makeChoices();
Expand Down
2 changes: 1 addition & 1 deletion test/sim/moves/thrash.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe('Thrash [Gen 1]', function () {
});

it("Four turn Thrash", function () {
battle = common.gen(1).createBattle({seed: [1, 1, 1, 1]});
battle = common.gen(1).createBattle({seed: 'gen5,0001000100010001'});
battle.setPlayer('p1', {team: [{species: "Nidoking", moves: ['thrash']}]});
battle.setPlayer('p2', {team: [{species: "Golem", moves: ['splash']}]});
const nidoking = battle.p1.active[0];
Expand Down
Loading