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

Speed up Random Battles species selection #10138

Merged
merged 8 commits into from
Jan 29, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
3 changes: 2 additions & 1 deletion data/mods/gen1/random-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ export class RandomGen1Teams extends RandomGen2Teams {
const weaknessCount: {[k: string]: number} = {Electric: 0, Psychic: 0, Water: 0, Ice: 0, Ground: 0, Fire: 0};
let numMaxLevelPokemon = 0;

const pokemonPool = this.getPokemonPool(type, pokemon, isMonotype, Object.keys(this.randomData))[0];
const pokemonPool = Object.keys(this.getPokemonPool(type, pokemon, isMonotype, Object.keys(this.randomData))[0]);

while (pokemonPool.length && pokemon.length < this.maxTeamSize) {
const species = this.dex.species.get(this.sampleNoReplace(pokemonPool));
if (!species.exists) continue;
Expand Down
15 changes: 6 additions & 9 deletions data/mods/gen3/random-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -684,15 +684,12 @@ export class RandomGen3Teams extends RandomGen4Teams {
let numMaxLevelPokemon = 0;

const pokemonList = (this.gen === 3) ? Object.keys(this.randomSets) : Object.keys(this.randomData);
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);
while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
const currentSpeciesPool: Species[] = [];
for (const poke of pokemonPool) {
const species = this.dex.species.get(poke);
if (species.baseSpecies === baseSpecies) currentSpeciesPool.push(species);
}
const species = this.sample(currentSpeciesPool);
const [pokemonPool, shuffledBaseSpecies] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);

while (shuffledBaseSpecies.length && pokemon.length < this.maxTeamSize) {
// repeated popping from weighted shuffle is equivalent to repeated weighted sampling without replacement
const baseSpecies = shuffledBaseSpecies.pop()!.baseSpecies;
const species = this.dex.species.get(this.sample(pokemonPool[baseSpecies].speciesArray));
if (!species.exists) continue;

// Limit to one of each species (Species Clause)
Expand Down
15 changes: 6 additions & 9 deletions data/mods/gen5/random-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,15 +952,12 @@ export class RandomGen5Teams extends RandomGen6Teams {
let numMaxLevelPokemon = 0;

const pokemonList = Object.keys(this.randomSets);
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);
while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
const currentSpeciesPool: Species[] = [];
for (const poke of pokemonPool) {
const species = this.dex.species.get(poke);
if (species.baseSpecies === baseSpecies) currentSpeciesPool.push(species);
}
const species = this.sample(currentSpeciesPool);
const [pokemonPool, shuffledBaseSpecies] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);

while (shuffledBaseSpecies.length && pokemon.length < this.maxTeamSize) {
// repeated popping from weighted shuffle is equivalent to repeated weighted sampling without replacement
const baseSpecies = shuffledBaseSpecies.pop()!.baseSpecies;
const species = this.dex.species.get(this.sample(pokemonPool[baseSpecies].speciesArray));
if (!species.exists) continue;

// Limit to one of each species (Species Clause)
Expand Down
26 changes: 13 additions & 13 deletions data/mods/gen7/random-doubles-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1367,25 +1367,25 @@ export class RandomGen7DoublesTeams extends RandomGen8Teams {
if (pokemon.length >= this.maxTeamSize) break;

const pokemonList = Object.keys(this.randomDoublesData);
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);
while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
const [pokemonPool, shuffledBaseSpecies] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);

while (shuffledBaseSpecies.length && pokemon.length < this.maxTeamSize) {
// repeated popping from weighted shuffle is equivalent to repeated weighted sampling without replacement
const baseSpecies = shuffledBaseSpecies.pop()!.baseSpecies;
const currentSpeciesPool: Species[] = [];
// Check if the base species has a mega forme available
let canMega = false;
for (const poke of pokemonPool) {
for (const poke of pokemonPool[baseSpecies].speciesArray) {
const species = this.dex.species.get(poke);
if (!hasMega && species.baseSpecies === baseSpecies && species.isMega) canMega = true;
if (!hasMega && species.isMega) canMega = true;
}
for (const poke of pokemonPool) {
for (const poke of pokemonPool[baseSpecies].speciesArray) {
const species = this.dex.species.get(poke);
if (species.baseSpecies === baseSpecies) {
// Prevent multiple megas
if (hasMega && species.isMega) continue;
// Prevent base forme, if a mega is available
if (canMega && !species.isMega) continue;
currentSpeciesPool.push(species);
}
// Prevent multiple megas
if (hasMega && species.isMega) continue;
// Prevent base forme, if a mega is available
if (canMega && !species.isMega) continue;
currentSpeciesPool.push(species);
}
const species = this.sample(currentSpeciesPool);
if (!species.exists) continue;
Expand Down
26 changes: 13 additions & 13 deletions data/mods/gen7/random-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1323,25 +1323,25 @@ export class RandomGen7Teams extends RandomGen8Teams {
if (pokemon.length >= this.maxTeamSize) break;

const pokemonList = Object.keys(this.randomSets);
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);
while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
const [pokemonPool, shuffledBaseSpecies] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);

while (shuffledBaseSpecies.length && pokemon.length < this.maxTeamSize) {
// repeated popping from weighted shuffle is equivalent to repeated weighted sampling without replacement
const baseSpecies = shuffledBaseSpecies.pop()!.baseSpecies;
const currentSpeciesPool: Species[] = [];
// Check if the base species has a mega forme available
let canMega = false;
for (const poke of pokemonPool) {
for (const poke of pokemonPool[baseSpecies].speciesArray) {
const species = this.dex.species.get(poke);
if (!hasMega && species.baseSpecies === baseSpecies && species.isMega) canMega = true;
if (!hasMega && species.isMega) canMega = true;
}
for (const poke of pokemonPool) {
for (const poke of pokemonPool[baseSpecies].speciesArray) {
const species = this.dex.species.get(poke);
if (species.baseSpecies === baseSpecies) {
// Prevent multiple megas
if (hasMega && species.isMega) continue;
// Prevent base forme, if a mega is available
if (canMega && !species.isMega) continue;
currentSpeciesPool.push(species);
}
// Prevent multiple megas
if (hasMega && species.isMega) continue;
// Prevent base forme, if a mega is available
if (canMega && !species.isMega) continue;
currentSpeciesPool.push(species);
}
const species = this.sample(currentSpeciesPool);

Expand Down
55 changes: 36 additions & 19 deletions data/mods/gen8/random-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2413,11 +2413,10 @@ export class RandomGen8Teams {
pokemonToExclude: RandomTeamsTypes.RandomSet[] = [],
isMonotype = false,
pokemonList: string[]
) {
): [{[k: string]: {count: number, speciesArray: ID[]}}, {baseSpecies: string, score: number}[]] {
const exclude = pokemonToExclude.map(p => toID(p.species));
const pokemonPool = [];
const baseSpeciesPool = [];
const baseSpeciesCount: {[k: string]: number} = {};
const pokemonPool: {[k: string]: {count: number, speciesArray: ID[]}} = {};
const shuffledBaseSpecies = [];
for (const pokemon of pokemonList) {
let species = this.dex.species.get(pokemon);
if (exclude.includes(species.id)) continue;
Expand All @@ -2428,16 +2427,37 @@ export class RandomGen8Teams {
if (!species.types.includes(type)) continue;
}
}
pokemonPool.push(pokemon);
baseSpeciesCount[species.baseSpecies] = (baseSpeciesCount[species.baseSpecies] || 0) + 1;

if (species.baseSpecies in pokemonPool) {
pokemonPool[species.baseSpecies].count++;
pokemonPool[species.baseSpecies].speciesArray.push(species.id);
} else {
pokemonPool[species.baseSpecies] = {count: 1, speciesArray: [species.id]};
}
}
// Include base species 1x if 1-3 formes, 2x if 4-6 formes, 3x if 7+ formes
for (const baseSpecies of Object.keys(baseSpeciesCount)) {
for (let i = 0; i < Math.min(Math.ceil(baseSpeciesCount[baseSpecies] / 3), 3); i++) {
baseSpeciesPool.push(baseSpecies);
for (const baseSpecies of Object.keys(pokemonPool)) {
// Squawkabilly has 4 formes, but only 2 functionally different formes, so only include it 1x
if (baseSpecies === 'Squawkabilly') {
pokemonPool[baseSpecies].count = 1;
} else {
pokemonPool[baseSpecies].count = Math.min(Math.ceil(pokemonPool[baseSpecies].count / 3), 3);
}

/**
* Weighted random shuffle
* Uses the fact that for two uniform variables x1 and x2, x1^(1/w1) is larger than x2^(1/w2)
* with probability equal to w1/(w1+w2), which is what we want. See e.g. here https://arxiv.org/pdf/1012.0256.pdf,
* original paper is behind a paywall.
*/
const sortObject = {
baseSpecies: baseSpecies,
score: Math.pow(this.prng.next(), 1 / pokemonPool[baseSpecies].count),
};
shuffledBaseSpecies.push(sortObject);
}
return [pokemonPool, baseSpeciesPool];
shuffledBaseSpecies.sort((a, b) => a.score - b.score);
return [pokemonPool, shuffledBaseSpecies];
}

randomTeam() {
Expand Down Expand Up @@ -2471,15 +2491,12 @@ export class RandomGen8Teams {
pokemonList.push(poke);
}
}
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);
while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
const currentSpeciesPool: Species[] = [];
for (const poke of pokemonPool) {
const species = this.dex.species.get(poke);
if (species.baseSpecies === baseSpecies) currentSpeciesPool.push(species);
}
let species = this.sample(currentSpeciesPool);
const [pokemonPool, shuffledBaseSpecies] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);

while (shuffledBaseSpecies.length && pokemon.length < this.maxTeamSize) {
// repeated popping from weighted shuffle is equivalent to repeated weighted sampling without replacement
const baseSpecies = shuffledBaseSpecies.pop()!.baseSpecies;
let species = this.dex.species.get(this.sample(pokemonPool[baseSpecies].speciesArray));
if (!species.exists) continue;

// Limit to one of each species (Species Clause)
Expand Down
21 changes: 7 additions & 14 deletions data/mods/potd/random-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,9 @@ export class RandomPOTDTeams extends RandomTeams {
const teamDetails: RandomTeamsTypes.TeamDetails = {};

const pokemonList = isDoubles ? Object.keys(this.randomDoublesSets) : Object.keys(this.randomSets);
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);

// Remove PotD from baseSpeciesPool
if (baseSpeciesPool.includes(potd.baseSpecies)) {
this.fastPop(baseSpeciesPool, baseSpeciesPool.indexOf(potd.baseSpecies));
}
const [pokemonPool, shuffledBaseSpecies] = this.getPokemonPool(
type, pokemon, isMonotype, pokemonList.filter(m => this.dex.species.get(m).baseSpecies !== potd.baseSpecies)
);

// Add PotD to type counts
for (const typeName of potd.types) {
Expand All @@ -54,14 +51,10 @@ export class RandomPOTDTeams extends RandomTeams {
}
}

while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
const currentSpeciesPool: Species[] = [];
for (const poke of pokemonPool) {
const species = this.dex.species.get(poke);
if (species.baseSpecies === baseSpecies) currentSpeciesPool.push(species);
}
let species = this.sample(currentSpeciesPool);
while (shuffledBaseSpecies.length && pokemon.length < this.maxTeamSize) {
// repeated popping from weighted shuffle is equivalent to repeated weighted sampling without replacement
const baseSpecies = shuffledBaseSpecies.pop()!.baseSpecies;
let species = this.dex.species.get(this.sample(pokemonPool[baseSpecies].speciesArray));
if (!species.exists) continue;

// Limit to one of each species (Species Clause)
Expand Down
59 changes: 37 additions & 22 deletions data/random-teams.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1716,11 +1716,10 @@ export class RandomTeams {
pokemonToExclude: RandomTeamsTypes.RandomSet[] = [],
isMonotype = false,
pokemonList: string[]
) {
): [{[k: string]: {count: number, speciesArray: ID[]}}, {baseSpecies: string, score: number}[]] {
const exclude = pokemonToExclude.map(p => toID(p.species));
const pokemonPool = [];
const baseSpeciesPool = [];
const baseSpeciesCount: {[k: string]: number} = {};
const pokemonPool: {[k: string]: {count: number, speciesArray: ID[]}} = {};
livid-washed marked this conversation as resolved.
Show resolved Hide resolved
const shuffledBaseSpecies = [];
for (const pokemon of pokemonList) {
let species = this.dex.species.get(pokemon);
if (exclude.includes(species.id)) continue;
Expand All @@ -1731,18 +1730,37 @@ export class RandomTeams {
if (!species.types.includes(type)) continue;
}
}
pokemonPool.push(pokemon);
baseSpeciesCount[species.baseSpecies] = (baseSpeciesCount[species.baseSpecies] || 0) + 1;

if (species.baseSpecies in pokemonPool) {
pokemonPool[species.baseSpecies].count++;
pokemonPool[species.baseSpecies].speciesArray.push(species.id);
} else {
pokemonPool[species.baseSpecies] = {count: 1, speciesArray: [species.id]};
}
livid-washed marked this conversation as resolved.
Show resolved Hide resolved
}
// Include base species 1x if 1-3 formes, 2x if 4-6 formes, 3x if 7+ formes
for (const baseSpecies of Object.keys(baseSpeciesCount)) {
for (let i = 0; i < Math.min(Math.ceil(baseSpeciesCount[baseSpecies] / 3), 3); i++) {
baseSpeciesPool.push(baseSpecies);
// Squawkabilly has 4 formes, but only 2 functionally different formes, so only include it 1x
if (baseSpecies === 'Squawkabilly') break;
}
for (const baseSpecies of Object.keys(pokemonPool)) {
// Squawkabilly has 4 formes, but only 2 functionally different formes, so only include it 1x
if (baseSpecies === 'Squawkabilly') {
pokemonPool[baseSpecies].count = 1;
} else {
pokemonPool[baseSpecies].count = Math.min(Math.ceil(pokemonPool[baseSpecies].count / 3), 3);
}
livid-washed marked this conversation as resolved.
Show resolved Hide resolved

/**
* Weighted random shuffle
* Uses the fact that for two uniform variables x1 and x2, x1^(1/w1) is larger than x2^(1/w2)
* with probability equal to w1/(w1+w2), which is what we want. See e.g. here https://arxiv.org/pdf/1012.0256.pdf,
* original paper is behind a paywall.
*/
const sortObject = {
baseSpecies: baseSpecies,
score: Math.pow(this.prng.next(), 1 / pokemonPool[baseSpecies].count),
};
livid-washed marked this conversation as resolved.
Show resolved Hide resolved
shuffledBaseSpecies.push(sortObject);
}
return [pokemonPool, baseSpeciesPool];
shuffledBaseSpecies.sort((a, b) => a.score - b.score);
return [pokemonPool, shuffledBaseSpecies];
}

randomSets: {[species: string]: RandomTeamsTypes.RandomSpeciesData} = require('./random-sets.json');
Expand Down Expand Up @@ -1774,17 +1792,14 @@ export class RandomTeams {
let numMaxLevelPokemon = 0;

const pokemonList = isDoubles ? Object.keys(this.randomDoublesSets) : Object.keys(this.randomSets);
const [pokemonPool, baseSpeciesPool] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);
const [pokemonPool, shuffledBaseSpecies] = this.getPokemonPool(type, pokemon, isMonotype, pokemonList);

let leadsRemaining = this.format.gameType === 'doubles' ? 2 : 1;
while (baseSpeciesPool.length && pokemon.length < this.maxTeamSize) {
const baseSpecies = this.sampleNoReplace(baseSpeciesPool);
const currentSpeciesPool: Species[] = [];
for (const poke of pokemonPool) {
const species = this.dex.species.get(poke);
if (species.baseSpecies === baseSpecies) currentSpeciesPool.push(species);
}
let species = this.sample(currentSpeciesPool);

while (shuffledBaseSpecies.length && pokemon.length < this.maxTeamSize) {
// repeated popping from weighted shuffle is equivalent to repeated weighted sampling without replacement
const baseSpecies = shuffledBaseSpecies.pop()!.baseSpecies;
let species = this.dex.species.get(this.sample(pokemonPool[baseSpecies].speciesArray));
livid-washed marked this conversation as resolved.
Show resolved Hide resolved
if (!species.exists) continue;

// Limit to one of each species (Species Clause)
Expand Down
Loading