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

Data: Fix huge perf bugs in randbat tests, part 1 #10616

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
4 changes: 1 addition & 3 deletions data/random-battles/gen3/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,9 +129,7 @@ export class RandomGen3Teams extends RandomGen4Teams {

// Develop additional move lists
const badWithSetup = ['knockoff', 'rapidspin', 'toxic'];
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
7 changes: 4 additions & 3 deletions data/random-battles/gen4/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@ export class RandomGen4Teams extends RandomGen5Teams {
Steel: (movePool, moves, abilities, types, counter, species) => (!counter.get('Steel') && species.id === 'metagross'),
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
};
this.cachedStatusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
}

cullMovePool(
Expand Down Expand Up @@ -164,9 +167,7 @@ export class RandomGen4Teams extends RandomGen5Teams {

// Develop additional move lists
const badWithSetup = ['pursuit', 'toxic'];
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
9 changes: 5 additions & 4 deletions data/random-battles/gen5/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,10 @@ export class RandomGen5Teams extends RandomGen6Teams {
),
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
};
// Nature Power is Earthquake this gen
this.cachedStatusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
.map(move => move.id);
}

cullMovePool(
Expand Down Expand Up @@ -177,10 +181,7 @@ export class RandomGen5Teams extends RandomGen6Teams {

// Develop additional move lists
const badWithSetup = ['healbell', 'pursuit', 'toxic'];
// Nature Power is Earthquake this gen
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
7 changes: 4 additions & 3 deletions data/random-battles/gen6/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,9 @@ export class RandomGen6Teams extends RandomGen7Teams {
Steel: (movePool, moves, abilities, types, counter, species) => (!counter.get('Steel') && species.baseStats.atk >= 100),
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
};
this.cachedStatusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
}

cullMovePool(
Expand Down Expand Up @@ -196,9 +199,7 @@ export class RandomGen6Teams extends RandomGen7Teams {

// Develop additional move lists
const badWithSetup = ['defog', 'dragontail', 'haze', 'healbell', 'nuzzle', 'pursuit', 'rapidspin', 'toxic'];
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
10 changes: 6 additions & 4 deletions data/random-battles/gen7/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ function sereneGraceBenefits(move: Move) {

export class RandomGen7Teams extends RandomGen8Teams {
randomSets: {[species: string]: RandomTeamsTypes.RandomSpeciesData} = require('./sets.json');
protected cachedStatusMoves: ID[];

constructor(format: Format | string, prng: PRNG | PRNGSeed | null) {
super(format, prng);
Expand Down Expand Up @@ -141,6 +142,10 @@ export class RandomGen7Teams extends RandomGen8Teams {
Steel: (movePool, moves, abilities, types, counter, species) => (!counter.get('Steel') && species.baseStats.atk >= 100),
Water: (movePool, moves, abilities, types, counter) => !counter.get('Water'),
};
// Nature Power is Tri Attack this gen
this.cachedStatusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
.map(move => move.id);
}

newQueryMoves(
Expand Down Expand Up @@ -311,10 +316,7 @@ export class RandomGen7Teams extends RandomGen8Teams {

// Develop additional move lists
const badWithSetup = ['defog', 'dragontail', 'haze', 'healbell', 'nuzzle', 'pursuit', 'rapidspin', 'toxic'];
// Nature Power is Tri Attack this gen
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status' && move.id !== 'naturepower')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// General incompatibilities
const incompatiblePairs = [
Expand Down
50 changes: 38 additions & 12 deletions data/random-battles/gen8/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ function sereneGraceBenefits(move: Move) {
}

export class RandomGen8Teams {
dex: ModdedDex;
readonly dex: ModdedDex;
gen: number;
factoryTier: string;
format: Format;
Expand All @@ -116,6 +116,11 @@ export class RandomGen8Teams {
*/
moveEnforcementCheckers: {[k: string]: MoveEnforcementChecker};

/** Used by .getPools() */
private poolsCacheKey: [string | undefined, number | undefined, RuleTable | undefined, boolean] | undefined;
private cachedPool: number[] | undefined;
private cachedSpeciesPool: Species[] | undefined;

constructor(format: Format | string, prng: PRNG | PRNGSeed | null) {
format = Dex.formats.get(format);
this.dex = Dex.forFormat(format);
Expand Down Expand Up @@ -232,6 +237,9 @@ export class RandomGen8Teams {
return abilities.includes('Huge Power') && movePool.includes('aquajet');
},
};
this.poolsCacheKey = undefined;
this.cachedPool = undefined;
this.cachedSpeciesPool = undefined;
}

setSeed(prng?: PRNG | PRNGSeed) {
Expand Down Expand Up @@ -526,19 +534,18 @@ export class RandomGen8Teams {
return team;
}

randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Picks `n` random pokemon--no repeats, even among formes
// Also need to either normalize for formes or select formes at random
// Unreleased are okay but no CAP
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}

private getPools(requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Memoize pool and speciesPool because, at least during tests, they are constructed with the same parameters
// hundreds of times and are expensive to compute.
const isNotCustom = !ruleTable;

const pool: number[] = [];
let pool: number[] = [];
let speciesPool: Species[] = [];
if (isNotCustom) {
const ck = this.poolsCacheKey;
if (ck && this.cachedPool && this.cachedSpeciesPool &&
ck[0] === requiredType && ck[1] === minSourceGen && ck[2] === ruleTable && ck[3] === requireMoves) {
speciesPool = this.cachedSpeciesPool.slice();
pool = this.cachedPool.slice();
} else if (isNotCustom) {
speciesPool = [...this.dex.species.all()];
for (const species of speciesPool) {
if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue;
Expand All @@ -552,6 +559,9 @@ export class RandomGen8Teams {
if (num <= 0 || pool.includes(num)) continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
} else {
const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent'];
const nonexistentBanReason = ruleTable.check('nonexistent');
Expand Down Expand Up @@ -596,7 +606,23 @@ export class RandomGen8Teams {
if (pool.includes(num)) continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
}
return {pool, speciesPool};
}

randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Picks `n` random pokemon--no repeats, even among formes
// Also need to either normalize for formes or select formes at random
// Unreleased are okay but no CAP
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}

const {pool, speciesPool} = this.getPools(requiredType, minSourceGen, ruleTable, requireMoves);
const isNotCustom = !ruleTable;

const hasDexNumber: {[k: string]: number} = {};
for (let i = 0; i < n; i++) {
Expand Down
56 changes: 41 additions & 15 deletions data/random-battles/gen9/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ function sereneGraceBenefits(move: Move) {
}

export class RandomTeams {
dex: ModdedDex;
readonly dex: ModdedDex;
gen: number;
factoryTier: string;
format: Format;
Expand All @@ -164,6 +164,12 @@ export class RandomTeams {
*/
moveEnforcementCheckers: {[k: string]: MoveEnforcementChecker};

/** Used by .getPools() */
private poolsCacheKey: [string | undefined, number | undefined, RuleTable | undefined, boolean] | undefined;
private cachedPool: number[] | undefined;
private cachedSpeciesPool: Species[] | undefined;
protected cachedStatusMoves: ID[];

constructor(format: Format | string, prng: PRNG | PRNGSeed | null) {
format = Dex.formats.get(format);
this.dex = Dex.forFormat(format);
Expand Down Expand Up @@ -233,6 +239,10 @@ export class RandomTeams {
),
Water: (movePool, moves, abilities, types, counter) => (!counter.get('Water') && !types.includes('Ground')),
};
this.poolsCacheKey = undefined;
this.cachedPool = undefined;
this.cachedSpeciesPool = undefined;
this.cachedStatusMoves = this.dex.moves.all().filter(move => move.category === 'Status').map(move => move.id);
}

setSeed(prng?: PRNG | PRNGSeed) {
Expand Down Expand Up @@ -476,9 +486,7 @@ export class RandomTeams {
}

// Develop additional move lists
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// Team-based move culls
if (teamDetails.screens) {
Expand Down Expand Up @@ -1972,19 +1980,18 @@ export class RandomTeams {
return team;
}

randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Picks `n` random pokemon--no repeats, even among formes
// Also need to either normalize for formes or select formes at random
// Unreleased are okay but no CAP
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}

private getPools(requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Memoize pool and speciesPool because, at least during tests, they are constructed with the same parameters
// hundreds of times and are expensive to compute.
const isNotCustom = !ruleTable;

const pool: number[] = [];
let pool: number[] = [];
let speciesPool: Species[] = [];
if (isNotCustom) {
const ck = this.poolsCacheKey;
if (ck && this.cachedPool && this.cachedSpeciesPool &&
ck[0] === requiredType && ck[1] === minSourceGen && ck[2] === ruleTable && ck[3] === requireMoves) {
speciesPool = this.cachedSpeciesPool.slice();
pool = this.cachedPool.slice();
} else if (isNotCustom) {
speciesPool = [...this.dex.species.all()];
for (const species of speciesPool) {
if (species.isNonstandard && species.isNonstandard !== 'Unobtainable') continue;
Expand All @@ -1998,6 +2005,9 @@ export class RandomTeams {
if (num <= 0 || pool.includes(num)) continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
} else {
const EXISTENCE_TAG = ['past', 'future', 'lgpe', 'unobtainable', 'cap', 'custom', 'nonexistent'];
const nonexistentBanReason = ruleTable.check('nonexistent');
Expand Down Expand Up @@ -2042,7 +2052,23 @@ export class RandomTeams {
if (pool.includes(num)) continue;
pool.push(num);
}
this.poolsCacheKey = [requiredType, minSourceGen, ruleTable, requireMoves];
this.cachedPool = pool.slice();
this.cachedSpeciesPool = speciesPool.slice();
}
return {pool, speciesPool};
}

randomNPokemon(n: number, requiredType?: string, minSourceGen?: number, ruleTable?: RuleTable, requireMoves = false) {
// Picks `n` random pokemon--no repeats, even among formes
// Also need to either normalize for formes or select formes at random
// Unreleased are okay but no CAP
if (requiredType && !this.dex.types.get(requiredType).exists) {
throw new Error(`"${requiredType}" is not a valid type.`);
}

const {pool, speciesPool} = this.getPools(requiredType, minSourceGen, ruleTable, requireMoves);
const isNotCustom = !ruleTable;

const hasDexNumber: {[k: string]: number} = {};
for (let i = 0; i < n; i++) {
Expand Down
4 changes: 1 addition & 3 deletions data/random-battles/gen9baby/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,9 +102,7 @@ export class RandomBabyTeams extends RandomTeams {
}

// Create list of all status moves to be used later
const statusMoves = this.dex.moves.all()
.filter(move => move.category === 'Status')
.map(move => move.id);
const statusMoves = this.cachedStatusMoves;

// Team-based move culls
if (teamDetails.screens && movePool.length >= this.maxMoveCount + 2) {
Expand Down
9 changes: 5 additions & 4 deletions sim/teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -618,13 +618,14 @@ export const Teams = new class Teams {
getGenerator(format: Format | string, seed: PRNG | PRNGSeed | null = null) {
let TeamGenerator;
format = Dex.formats.get(format);
if (toID(format).includes('gen9computergeneratedteams')) {
const formatID = toID(format);
if (formatID.includes('gen9computergeneratedteams')) {
TeamGenerator = require(Dex.forFormat(format).dataDir + '/cg-teams').default;
} else if (toID(format).includes('gen9superstaffbrosultimate')) {
} else if (formatID.includes('gen9superstaffbrosultimate')) {
TeamGenerator = require(`../data/mods/gen9ssb/random-teams`).default;
} else if (toID(format).includes('gen9babyrandombattle')) {
} else if (formatID.includes('gen9babyrandombattle')) {
TeamGenerator = require(`../data/random-battles/gen9baby/teams`).default;
} else if (toID(format).includes('gen9randombattle') && format.ruleTable?.has('+pokemontag:cap')) {
} else if (formatID.includes('gen9randombattle') && format.ruleTable?.has('+pokemontag:cap')) {
TeamGenerator = require(`../data/random-battles/gen9cap/teams`).default;
} else {
TeamGenerator = require(`../data/random-battles/${format.mod}/teams`).default;
Expand Down
10 changes: 6 additions & 4 deletions test/random-battles/tools.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,13 @@ const {TeamValidator, PokemonSources} = require('../../dist/sim/team-validator')
function testSet(pokemon, options, test) {
const rounds = options.rounds || 1000;

const isDoubles = options.isDoubles || (options.format && options.format.includes('doubles'));
const isDynamax = options.isDynamax || !(options.format && options.format.includes('nodmax'));
const isDoubles = !!(options.isDoubles || (options.format && options.format.includes('doubles')));
const isDynamax = !!(options.isDynamax || !(options.format && options.format.includes('nodmax')));
const generator = Teams.getGenerator(options.format, [0, 0, 0, 0]);
for (let i = 0; i < rounds; i++) {
// If undefined, test lead 1/6 of the time
const isLead = options.isLead === undefined ? i % 6 === 2 : options.isLead;
const generator = Teams.getGenerator(options.format, options.seed || [i, i, i, i]);
generator.setSeed(options.seed || [i, i, i, i]);
const set = generator.randomSet(pokemon, {}, isLead, isDoubles, isDynamax);
test(set);
}
Expand Down Expand Up @@ -107,8 +108,9 @@ function testAlwaysHasMove(pokemon, options, move) {
function testTeam(options, test) {
const rounds = options.rounds || 1000;

const generator = Teams.getGenerator(options.format, [0, 0, 0, 0]);
for (let i = 0; i < rounds; i++) {
const generator = Teams.getGenerator(options.format, options.seed || [i, i, i, i]);
generator.setSeed(options.seed || [i, i, i, i]);
const team = generator.getTeam();
test(team);
}
Expand Down
Loading