Add Gen 8 support for RandomPlayerAI and sim tools

- add support to the RandomPlayerAI for Dynamaxing
 - add support to ExhaustiveRunner for Gigantamax Pokemon
 - simplify range logic in RandomPlayerAPI
 - handle crash from toID (needed by data/scripts.js) and Config
	(needed if a Battle takes long enough to potentially allow for
	requesting ties) globals not being defined
 - mark Gen 8 formats as runnable by the runners
This commit is contained in:
Kirk Scheibelhut 2020-03-07 18:54:45 -08:00
parent e86ec0b7fe
commit 36b5b48d79
4 changed files with 68 additions and 21 deletions

View File

@ -35,6 +35,7 @@ export class ExhaustiveRunner {
// TODO: Add triple battles once supported by the AI.
static readonly FORMATS = [
'gen8customgame', 'gen8doublescustomgame',
'gen7customgame', 'gen7doublescustomgame',
'gen6customgame', 'gen6doublescustomgame',
'gen5customgame', 'gen5doublescustomgame',
@ -409,7 +410,8 @@ class CoordinatedPlayerAI extends RandomPlayerAI {
return `team ${this.choosePokemon(team.map((p, i) => ({slot: i + 1, pokemon: p}))) || 1}`;
}
protected chooseMove(moves: {choice: string, move: AnyObject}[]): string {
protected chooseMove(active: AnyObject, moves: {choice: string, move: AnyObject}[]): string {
this.markUsedIfGmax(active);
// Prefer to use a move which hasn't been used yet.
for (const {choice, move} of moves) {
const id = this.fixMove(move);
@ -418,11 +420,12 @@ class CoordinatedPlayerAI extends RandomPlayerAI {
return choice;
}
}
return super.chooseMove(moves);
return super.chooseMove(active, moves);
}
protected chooseSwitch(switches: {slot: number, pokemon: AnyObject}[]): number {
return this.choosePokemon(switches) || super.chooseSwitch(switches);
protected chooseSwitch(active: AnyObject | undefined, switches: {slot: number, pokemon: AnyObject}[]): number {
this.markUsedIfGmax(active);
return this.choosePokemon(switches) || super.chooseSwitch(active, switches);
}
private choosePokemon(choices: {slot: number, pokemon: AnyObject}[]) {
@ -442,7 +445,7 @@ class CoordinatedPlayerAI extends RandomPlayerAI {
}
// The move options provided by the simulator have been converted from the name
// which we're tracking, so we need to convert them back;
// which we're tracking, so we need to convert them back.
private fixMove(m: AnyObject) {
const id = toID(m.move);
if (id.startsWith('return')) return 'return';
@ -450,4 +453,12 @@ class CoordinatedPlayerAI extends RandomPlayerAI {
if (id.startsWith('hiddenpower')) return 'hiddenpower';
return id;
}
// Gigantamax Pokemon need to be special cased for tracking because the current
// tracking only works if you can switch in a Pokemon.
private markUsedIfGmax(active: AnyObject | undefined) {
if (active && !active.canDynamax && active.maxMoves && active.maxMoves.gigantamax) {
this.pools.pokemon.markUsed(toID(active.maxMoves.gigantamax));
}
}
}

View File

@ -20,8 +20,14 @@ export interface MultiRandomRunnerOptions extends RunnerOptions {
export class MultiRandomRunner {
static readonly FORMATS = [
'gen7randombattle', 'gen7randomdoublesbattle', 'gen7battlefactory', 'gen6randombattle', 'gen6battlefactory',
'gen5randombattle', 'gen4randombattle', 'gen3randombattle', 'gen2randombattle', 'gen1randombattle',
'gen8randombattle', 'gen8randomdoublesbattle', 'gen8battlefactory',
'gen7randombattle', 'gen7randomdoublesbattle', 'gen7battlefactory',
'gen6randombattle', 'gen6battlefactory',
'gen5randombattle',
'gen4randombattle',
'gen3randombattle',
'gen2randombattle',
'gen1randombattle',
];
private readonly options: Partial<RunnerOptions>;

View File

@ -44,7 +44,7 @@ export class RandomPlayerAI extends BattlePlayer {
const choices = request.forceSwitch.map((mustSwitch: AnyObject) => {
if (!mustSwitch) return `pass`;
const canSwitch = [1, 2, 3, 4, 5, 6].filter(i => (
const canSwitch = range(1, 6).filter(i => (
pokemon[i - 1] &&
// not active
i > request.forceSwitch.length &&
@ -56,6 +56,7 @@ export class RandomPlayerAI extends BattlePlayer {
if (!canSwitch.length) return `pass`;
const target = this.chooseSwitch(
request.active,
canSwitch.map(slot => ({slot, pokemon: pokemon[slot - 1]}))
);
chosen.push(target);
@ -65,7 +66,7 @@ export class RandomPlayerAI extends BattlePlayer {
this.choose(choices.join(`, `));
} else if (request.active) {
// move request
let [canMegaEvo, canUltraBurst, canZMove] = [true, true, true];
let [canMegaEvo, canUltraBurst, canZMove, canDynamax] = [true, true, true, true];
const pokemon = request.side.pokemon;
const chosen: number[] = [];
const choices = request.active.map((active: AnyObject, i: number) => {
@ -74,21 +75,30 @@ export class RandomPlayerAI extends BattlePlayer {
canMegaEvo = canMegaEvo && active.canMegaEvo;
canUltraBurst = canUltraBurst && active.canUltraBurst;
canZMove = canZMove && !!active.canZMove;
canDynamax = canDynamax && !!active.canDynamax;
let canMove = [1, 2, 3, 4].slice(0, active.moves.length).filter(j => (
// Determine whether we should change form if we do end up switching
const change = (canMegaEvo || canUltraBurst || canDynamax) && this.prng.next() < this.mega;
// If we've already dynamaxed or if we're planning on potentially dynamaxing
// we need to use the maxMoves instead of our regular moves
const useMaxMoves = (!active.canDynamax && active.maxMoves) || (change && canDynamax);
const possibleMoves = useMaxMoves ? active.maxMoves.maxMoves : active.moves;
let canMove = range(1, possibleMoves.length).filter(j => (
// not disabled
!active.moves[j - 1].disabled
!possibleMoves[j - 1].disabled
// NOTE: we don't actually check for whether we have PP or not because the
// simulator will mark the move as disabled if there is zero PP and there are
// situations where we actually need to use a move with 0 PP (Gen 1 Wrap).
)).map(j => ({
slot: j,
move: active.moves[j - 1].move,
target: active.moves[j - 1].target,
move: possibleMoves[j - 1].move,
target: possibleMoves[j - 1].target,
zMove: false,
}));
if (canZMove) {
canMove.push(...[1, 2, 3, 4].slice(0, active.canZMove.length)
canMove.push(...range(1, active.canZMove.length)
.filter(j => active.canZMove[j - 1])
.map(j => ({
slot: j,
@ -126,7 +136,7 @@ export class RandomPlayerAI extends BattlePlayer {
return {choice: move, move: m};
});
const canSwitch = [1, 2, 3, 4, 5, 6].filter(j => (
const canSwitch = range(1, 6).filter(j => (
pokemon[j - 1] &&
// not active
!pokemon[j - 1].active &&
@ -139,17 +149,21 @@ export class RandomPlayerAI extends BattlePlayer {
if (switches.length && (!moves.length || this.prng.next() > this.move)) {
const target = this.chooseSwitch(
active,
canSwitch.map(slot => ({slot, pokemon: pokemon[slot - 1]}))
);
chosen.push(target);
return `switch ${target}`;
} else if (moves.length) {
const move = this.chooseMove(moves);
const move = this.chooseMove(active, moves);
if (move.endsWith(` zmove`)) {
canZMove = false;
return move;
} else if ((canMegaEvo || canUltraBurst) && this.prng.next() < this.mega) {
if (canMegaEvo) {
} else if (change) {
if (canDynamax) {
canDynamax = false;
return `${move} dynamax`;
} else if (canMegaEvo) {
canMegaEvo = false;
return `${move} mega`;
} else {
@ -161,7 +175,8 @@ export class RandomPlayerAI extends BattlePlayer {
}
} else {
throw new Error(`${this.constructor.name} unable to make choice ${i}. request='${request}',` +
` chosen='${chosen}', (mega=${canMegaEvo}, ultra=${canUltraBurst}, zmove=${canZMove})`);
` chosen='${chosen}', (mega=${canMegaEvo}, ultra=${canUltraBurst}, zmove=${canZMove},` +
` dynamax='${canDynamax}')`);
}
});
this.choose(choices.join(`, `));
@ -175,11 +190,24 @@ export class RandomPlayerAI extends BattlePlayer {
return `default`;
}
protected chooseMove(moves: {choice: string, move: AnyObject}[]): string {
protected chooseMove(active: AnyObject, moves: {choice: string, move: AnyObject}[]): string {
return this.prng.sample(moves).choice;
}
protected chooseSwitch(switches: {slot: number, pokemon: AnyObject}[]): number {
protected chooseSwitch(active: AnyObject | undefined, switches: {slot: number, pokemon: AnyObject}[]): number {
return this.prng.sample(switches).slot;
}
}
// Creates an array of numbers progressing from start up to and including end
function range(start: number, end?: number, step = 1) {
if (end === undefined) {
end = start;
start = 0;
}
const result = [];
for (; start <= end; start += step) {
result.push(start);
}
return result;
}

View File

@ -42,6 +42,8 @@ const shell = cmd => child_process.execSync(cmd, {stdio: 'inherit', cwd: path.re
shell('node build');
const Dex = require('../../.sim-dist/dex').Dex;
global.toID = require('../../.sim-dist/dex').Dex.getId;
global.Config = {allowrequestingties: false};
Dex.includeModData();
const {ExhaustiveRunner} = require('../../.sim-dist/tools/exhaustive-runner');