Spaces:
Running
Running
File size: 7,199 Bytes
5c2ed06 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 |
/**
* 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;
}
|