Detailed documentation of the BattleStream interface and player streams
The BattleStream class is the primary interface for interacting with the Pokemon Showdown battle simulator. It extends ObjectReadWriteStream<string> to provide a stream-based API.
Read/Write Stream - Sees all information including exact HP and private details.
// Start battle through omniscient streamstreams.omniscient.write('>start {"formatid":"gen9ou"}');streams.omniscient.write('>player p1 {"name":"Alice","team":"..."}');streams.omniscient.write('>player p2 {"name":"Bob","team":"..."}');// Read all battle messagesfor await (const chunk of streams.omniscient) { console.log(chunk);}
Use this for game logs, replays, or server-side processing.
Read-Only Stream - Public information only (no exact HP, no private details).
for await (const chunk of streams.spectator) { // Only receives public updates console.log(chunk);}
Use this for spectator views or public broadcasts.
Read/Write Streams - Player-specific views with their private information.
// Player writes choices without >p1 prefixstreams.p1.write('move 1');streams.p2.write('switch 2');// Player receives their choice requests and public updatesfor await (const chunk of streams.p1) { // Parse choice requests if (chunk.includes('|request|')) { const request = JSON.parse( chunk.split('|request|')[1].split('\n')[0] ); // Make decision based on request... }}
When writing to player streams, the >p1/>p2 prefix is added automatically.
class MyAI extends BattlePlayer { receiveRequest(request) { // request.active contains move choices // request.side.pokemon contains team info if (request.forceSwitch) { // Must switch this.choose('switch 2'); } else if (request.active) { // Can make a move this.choose('move 1'); } else if (request.teamPreview) { // Team preview this.choose('team 123456'); } }}const ai = new MyAI(streams.p1, true);void ai.start(); // Start listening for requests
The BattlePlayer class is abstract - you must implement the receiveRequest() method.
Once started, access the underlying Battle object:
stream.write(`>start {"formatid":"gen9ou"}`);// After start commandconst battle = stream.battle;console.log(battle.turn); // Current turn numberconsole.log(battle.sides[0].name); // Player 1 name
The battle property is null until the >start command is processed.