Pokemon_server / sim /tools /random-player-ai.ts
Jofthomas's picture
Upload 4781 files
5c2ed06 verified
/**
* Example random player AI.
*
* Pokemon Showdown - http://pokemonshowdown.com/
*
* @license MIT
*/
import type { ObjectReadWriteStream } from '../../lib/streams';
import { BattlePlayer } from '../battle-stream';
import { PRNG, type PRNGSeed } from '../prng';
import type { ChoiceRequest } from '../side';
export class RandomPlayerAI extends BattlePlayer {
protected readonly move: number;
protected readonly mega: number;
protected readonly prng: PRNG;
constructor(
playerStream: ObjectReadWriteStream<string>,
options: { move?: number, mega?: number, seed?: PRNG | PRNGSeed | null } = {},
debug = false
) {
super(playerStream, debug);
this.move = options.move || 1.0;
this.mega = options.mega || 0;
this.prng = PRNG.get(options.seed);
}
receiveError(error: Error) {
// If we made an unavailable choice we will receive a followup request to
// allow us the opportunity to correct our decision.
if (error.message.startsWith('[Unavailable choice]')) return;
throw error;
}
override receiveRequest(request: ChoiceRequest) {
if (request.wait) {
// wait request
// do nothing
} else if (request.forceSwitch) {
// switch request
const pokemon = request.side.pokemon;
const chosen: number[] = [];
const choices = request.forceSwitch.map((mustSwitch, i) => {
if (!mustSwitch) return `pass`;
const canSwitch = range(1, 6).filter(j => (
pokemon[j - 1] &&
// not active
j > request.forceSwitch.length &&
// not chosen for a simultaneous switch
!chosen.includes(j) &&
// not fainted or fainted and using Revival Blessing
!pokemon[j - 1].condition.endsWith(` fnt`) === !pokemon[i].reviving
));
if (!canSwitch.length) return `pass`;
const target = this.chooseSwitch(
undefined,
canSwitch.map(slot => ({ slot, pokemon: pokemon[slot - 1] }))
);
chosen.push(target);
return `switch ${target}`;
});
this.choose(choices.join(`, `));
} else if (request.teamPreview) {
this.choose(this.chooseTeamPreview(request.side.pokemon));
} else if (request.active) {
// move request
let [canMegaEvo, canUltraBurst, canZMove, canDynamax, canTerastallize] = [true, true, true, true, true];
const pokemon = request.side.pokemon;
const chosen: number[] = [];
const choices = request.active.map((active: AnyObject, i: number) => {
if (pokemon[i].condition.endsWith(` fnt`) || pokemon[i].commanding) return `pass`;
canMegaEvo = canMegaEvo && active.canMegaEvo;
canUltraBurst = canUltraBurst && active.canUltraBurst;
canZMove = canZMove && !!active.canZMove;
canDynamax = canDynamax && !!active.canDynamax;
canTerastallize = canTerastallize && !!active.canTerastallize;
// Determine whether we should change form if we do end up switching
const change = (canMegaEvo || canUltraBurst || canDynamax) && this.prng.random() < 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
!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: possibleMoves[j - 1].move,
target: possibleMoves[j - 1].target,
zMove: false,
}));
if (canZMove) {
canMove.push(...range(1, active.canZMove.length)
.filter(j => active.canZMove[j - 1])
.map(j => ({
slot: j,
move: active.canZMove[j - 1].move,
target: active.canZMove[j - 1].target,
zMove: true,
})));
}
// Filter out adjacentAlly moves if we have no allies left, unless they're our
// only possible move options.
const hasAlly = pokemon.length > 1 && !pokemon[i ^ 1].condition.endsWith(` fnt`);
const filtered = canMove.filter(m => m.target !== `adjacentAlly` || hasAlly);
canMove = filtered.length ? filtered : canMove;
const moves = canMove.map(m => {
let move = `move ${m.slot}`;
// NOTE: We don't generate all possible targeting combinations.
if (request.active.length > 1) {
if ([`normal`, `any`, `adjacentFoe`].includes(m.target)) {
move += ` ${1 + this.prng.random(2)}`;
}
if (m.target === `adjacentAlly`) {
move += ` -${(i ^ 1) + 1}`;
}
if (m.target === `adjacentAllyOrSelf`) {
if (hasAlly) {
move += ` -${1 + this.prng.random(2)}`;
} else {
move += ` -${i + 1}`;
}
}
}
if (m.zMove) move += ` zmove`;
return { choice: move, move: m };
});
const canSwitch = range(1, 6).filter(j => (
pokemon[j - 1] &&
// not active
!pokemon[j - 1].active &&
// not chosen for a simultaneous switch
!chosen.includes(j) &&
// not fainted
!pokemon[j - 1].condition.endsWith(` fnt`)
));
const switches = active.trapped ? [] : canSwitch;
if (switches.length && (!moves.length || this.prng.random() > 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(active, moves);
if (move.endsWith(` zmove`)) {
canZMove = false;
return move;
} else if (change) {
if (canTerastallize) {
canTerastallize = false;
return `${move} terastallize`;
} else if (canDynamax) {
canDynamax = false;
return `${move} dynamax`;
} else if (canMegaEvo) {
canMegaEvo = false;
return `${move} mega`;
} else {
canUltraBurst = false;
return `${move} ultra`;
}
} else {
return move;
}
} else {
throw new Error(`${this.constructor.name} unable to make choice ${i}. request='${typeof request}',` +
` chosen='${chosen}', (mega=${canMegaEvo}, ultra=${canUltraBurst}, zmove=${canZMove},` +
` dynamax='${canDynamax}', terastallize=${canTerastallize})`);
}
});
this.choose(choices.join(`, `));
}
}
protected chooseTeamPreview(team: AnyObject[]): string {
return `default`;
}
protected chooseMove(active: AnyObject, moves: { choice: string, move: AnyObject }[]): string {
return this.prng.sample(moves).choice;
}
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;
}