{ "version": 3, "sources": ["../../../sim/tools/runner.ts"], "sourcesContent": ["/**\n * Battle Simulator runner.\n * Pokemon Showdown - http://pokemonshowdown.com/\n *\n * @license MIT\n */\n\nimport { strict as assert } from 'assert';\nimport * as fs from 'fs';\n\nimport { Dex } from '..';\nimport { type ObjectReadWriteStream } from '../../lib/streams';\nimport { Battle } from '../battle';\nimport * as BattleStreams from '../battle-stream';\nimport { State } from '../state';\nimport { PRNG, type PRNGSeed } from '../prng';\nimport { RandomPlayerAI } from './random-player-ai';\n\nexport interface AIOptions {\n\tcreateAI: (stream: ObjectReadWriteStream, options: AIOptions) => RandomPlayerAI;\n\tmove?: number;\n\tmega?: number;\n\tseed?: PRNG | PRNGSeed | null;\n\tteam?: PokemonSet[];\n}\n\nexport interface RunnerOptions {\n\tformat: string;\n\tprng?: PRNG | PRNGSeed | null;\n\tp1options?: AIOptions;\n\tp2options?: AIOptions;\n\tp3options?: AIOptions;\n\tp4options?: AIOptions;\n\tinput?: boolean;\n\toutput?: boolean;\n\terror?: boolean;\n\tdual?: boolean | 'debug';\n}\n\nexport class Runner {\n\tstatic readonly AI_OPTIONS: AIOptions = {\n\t\tcreateAI: (s: ObjectReadWriteStream, o: AIOptions) => new RandomPlayerAI(s, o),\n\t\tmove: 0.7,\n\t\tmega: 0.6,\n\t};\n\n\tprivate readonly prng: PRNG;\n\tprivate readonly p1options: AIOptions;\n\tprivate readonly p2options: AIOptions;\n\tprivate readonly p3options: AIOptions;\n\tprivate readonly p4options: AIOptions;\n\tprivate readonly format: string;\n\tprivate readonly input: boolean;\n\tprivate readonly output: boolean;\n\tprivate readonly error: boolean;\n\tprivate readonly dual: boolean | 'debug';\n\n\tconstructor(options: RunnerOptions) {\n\t\tthis.format = options.format;\n\n\t\tthis.prng = PRNG.get(options.prng);\n\t\tthis.p1options = { ...Runner.AI_OPTIONS, ...options.p1options };\n\t\tthis.p2options = { ...Runner.AI_OPTIONS, ...options.p2options };\n\t\tthis.p3options = { ...Runner.AI_OPTIONS, ...options.p3options };\n\t\tthis.p4options = { ...Runner.AI_OPTIONS, ...options.p4options };\n\n\t\tthis.input = !!options.input;\n\t\tthis.output = !!options.output;\n\t\tthis.error = !!options.error;\n\t\tthis.dual = options.dual || false;\n\t}\n\n\tasync run() {\n\t\tconst battleStream = this.dual ?\n\t\t\tnew DualStream(this.input, this.dual === 'debug') :\n\t\t\tnew RawBattleStream(this.input);\n\t\tconst game = this.runGame(this.format, battleStream);\n\t\tif (!this.error) return game;\n\t\treturn game.catch(err => {\n\t\t\tconsole.log(`\\n${battleStream.rawInputLog.join('\\n')}\\n`);\n\t\t\tthrow err;\n\t\t});\n\t}\n\n\tprivate async runGame(format: string, battleStream: RawBattleStream | DualStream) {\n\t\t// @ts-expect-error - DualStream implements everything relevant from BattleStream.\n\t\tconst streams = BattleStreams.getPlayerStreams(battleStream);\n\t\tconst spec = { formatid: format, seed: this.prng.getSeed() };\n\t\tconst is4P = Dex.formats.get(format).playerCount > 2;\n\t\tconst p1spec = this.getPlayerSpec(\"Bot 1\", this.p1options);\n\t\tconst p2spec = this.getPlayerSpec(\"Bot 2\", this.p2options);\n\t\tlet p3spec: typeof p1spec, p4spec: typeof p1spec;\n\t\tif (is4P) {\n\t\t\tp3spec = this.getPlayerSpec(\"Bot 3\", this.p3options);\n\t\t\tp4spec = this.getPlayerSpec(\"Bot 4\", this.p4options);\n\t\t}\n\n\t\tconst p1 = this.p1options.createAI(\n\t\t\tstreams.p1, { seed: this.newSeed(), ...this.p1options }\n\t\t);\n\t\tconst p2 = this.p2options.createAI(\n\t\t\tstreams.p2, { seed: this.newSeed(), ...this.p2options }\n\t\t);\n\t\tlet p3: RandomPlayerAI, p4: RandomPlayerAI;\n\t\tif (is4P) {\n\t\t\tp3 = this.p4options.createAI(\n\t\t\t\tstreams.p3, { seed: this.newSeed(), ...this.p3options }\n\t\t\t);\n\t\t\tp4 = this.p4options.createAI(\n\t\t\t\tstreams.p4, { seed: this.newSeed(), ...this.p4options }\n\t\t\t);\n\t\t}\n\t\t// TODO: Use `await Promise.race([streams.omniscient.read(), p1, p2])` to avoid\n\t\t// leaving these promises dangling once it no longer causes memory leaks (v8#9069).\n\t\tvoid p1.start();\n\t\tvoid p2.start();\n\t\tif (is4P) {\n\t\t\tvoid p3!.start();\n\t\t\tvoid p4!.start();\n\t\t}\n\n\t\tlet initMessage = `>start ${JSON.stringify(spec)}\\n` +\n\t\t\t`>player p1 ${JSON.stringify(p1spec)}\\n` +\n\t\t\t`>player p2 ${JSON.stringify(p2spec)}`;\n\t\tif (is4P) {\n\t\t\tinitMessage += `\\n` +\n\t\t\t\t`>player p3 ${JSON.stringify(p3spec!)}\\n` +\n\t\t\t\t`>player p4 ${JSON.stringify(p4spec!)}`;\n\t\t}\n\t\tvoid streams.omniscient.write(initMessage);\n\n\t\tfor await (const chunk of streams.omniscient) {\n\t\t\tif (this.output) console.log(chunk);\n\t\t}\n\t\treturn streams.omniscient.writeEnd();\n\t}\n\n\t// Same as PRNG#generatedSeed, only deterministic.\n\t// NOTE: advances this.prng's seed by 4.\n\tprivate newSeed(): PRNGSeed {\n\t\treturn [\n\t\t\tthis.prng.random(2 ** 16),\n\t\t\tthis.prng.random(2 ** 16),\n\t\t\tthis.prng.random(2 ** 16),\n\t\t\tthis.prng.random(2 ** 16),\n\t\t].join(',') as PRNGSeed;\n\t}\n\n\tprivate getPlayerSpec(name: string, options: AIOptions) {\n\t\tif (options.team) return { name, team: options.team };\n\t\treturn { name, seed: this.newSeed() };\n\t}\n}\n\nclass RawBattleStream extends BattleStreams.BattleStream {\n\treadonly rawInputLog: string[];\n\n\tprivate readonly input: boolean;\n\n\tconstructor(input: boolean) {\n\t\tsuper();\n\t\tthis.input = !!input;\n\t\tthis.rawInputLog = [];\n\t}\n\n\t_write(message: string) {\n\t\tif (this.input) console.log(message);\n\t\tthis.rawInputLog.push(message);\n\t\tsuper._write(message);\n\t}\n}\n\nclass DualStream {\n\tprivate debug: boolean;\n\tprivate readonly control: RawBattleStream;\n\tprivate test: RawBattleStream;\n\n\tconstructor(input: boolean, debug: boolean) {\n\t\tthis.debug = debug;\n\t\t// The input to both streams should be the same, so to satisfy the\n\t\t// input flag we only need to track the raw input of one stream.\n\t\tthis.control = new RawBattleStream(input);\n\t\tthis.test = new RawBattleStream(false);\n\t}\n\n\tget rawInputLog() {\n\t\tconst control = this.control.rawInputLog;\n\t\tconst test = this.test.rawInputLog;\n\t\tassert.deepEqual(test, control);\n\t\treturn control;\n\t}\n\n\tasync read() {\n\t\tconst control = await this.control.read();\n\t\tconst test = await this.test.read();\n\t\t// In debug mode, wait to catch this as a difference in the inputLog\n\t\t// and error there so we get the full battle state dumped instead.\n\t\tif (!this.debug) assert.equal(State.normalizeLog(test), State.normalizeLog(control));\n\t\treturn control;\n\t}\n\n\twrite(message: string) {\n\t\tthis.control._write(message);\n\t\tthis.test._write(message);\n\t\tthis.compare();\n\t}\n\n\twriteEnd() {\n\t\t// We need to compare first because _writeEnd() destroys the battle object.\n\t\tthis.compare(true);\n\t\tthis.control._writeEnd();\n\t\tthis.test._writeEnd();\n\t}\n\n\tcompare(end?: boolean) {\n\t\tif (!this.control.battle || !this.test.battle) return;\n\n\t\tconst control = this.control.battle.toJSON();\n\t\tconst test = this.test.battle.toJSON();\n\t\ttry {\n\t\t\tassert.deepEqual(State.normalize(test), State.normalize(control));\n\t\t} catch (err: any) {\n\t\t\tif (this.debug) {\n\t\t\t\t// NOTE: diffing these directly won't work because the key ordering isn't stable.\n\t\t\t\tfs.writeFileSync('logs/control.json', JSON.stringify(control, null, 2));\n\t\t\t\tfs.writeFileSync('logs/test.json', JSON.stringify(test, null, 2));\n\t\t\t}\n\t\t\tthrow new Error(err.message);\n\t\t}\n\n\t\tif (end) return;\n\t\tconst send = this.test.battle.send;\n\t\tthis.test.battle = Battle.fromJSON(test);\n\t\tthis.test.battle.restart(send);\n\t}\n}\n"], "mappings": ";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAOA,oBAAiC;AACjC,SAAoB;AAEpB,eAAoB;AAEpB,oBAAuB;AACvB,oBAA+B;AAC/B,mBAAsB;AACtB,kBAAoC;AACpC,8BAA+B;AAhB/B;AAAA;AAAA;AAAA;AAAA;AAAA;AAuCO,MAAM,UAAN,MAAa;AAAA,EAkBnB,YAAY,SAAwB;AACnC,SAAK,SAAS,QAAQ;AAEtB,SAAK,OAAO,iBAAK,IAAI,QAAQ,IAAI;AACjC,SAAK,YAAY,EAAE,GAAG,QAAO,YAAY,GAAG,QAAQ,UAAU;AAC9D,SAAK,YAAY,EAAE,GAAG,QAAO,YAAY,GAAG,QAAQ,UAAU;AAC9D,SAAK,YAAY,EAAE,GAAG,QAAO,YAAY,GAAG,QAAQ,UAAU;AAC9D,SAAK,YAAY,EAAE,GAAG,QAAO,YAAY,GAAG,QAAQ,UAAU;AAE9D,SAAK,QAAQ,CAAC,CAAC,QAAQ;AACvB,SAAK,SAAS,CAAC,CAAC,QAAQ;AACxB,SAAK,QAAQ,CAAC,CAAC,QAAQ;AACvB,SAAK,OAAO,QAAQ,QAAQ;AAAA,EAC7B;AAAA,EAEA,MAAM,MAAM;AACX,UAAM,eAAe,KAAK,OACzB,IAAI,WAAW,KAAK,OAAO,KAAK,SAAS,OAAO,IAChD,IAAI,gBAAgB,KAAK,KAAK;AAC/B,UAAM,OAAO,KAAK,QAAQ,KAAK,QAAQ,YAAY;AACnD,QAAI,CAAC,KAAK;AAAO,aAAO;AACxB,WAAO,KAAK,MAAM,SAAO;AACxB,cAAQ,IAAI;AAAA,EAAK,aAAa,YAAY,KAAK,IAAI;AAAA,CAAK;AACxD,YAAM;AAAA,IACP,CAAC;AAAA,EACF;AAAA,EAEA,MAAc,QAAQ,QAAgB,cAA4C;AAEjF,UAAM,UAAU,cAAc,iBAAiB,YAAY;AAC3D,UAAM,OAAO,EAAE,UAAU,QAAQ,MAAM,KAAK,KAAK,QAAQ,EAAE;AAC3D,UAAM,OAAO,aAAI,QAAQ,IAAI,MAAM,EAAE,cAAc;AACnD,UAAM,SAAS,KAAK,cAAc,SAAS,KAAK,SAAS;AACzD,UAAM,SAAS,KAAK,cAAc,SAAS,KAAK,SAAS;AACzD,QAAI,QAAuB;AAC3B,QAAI,MAAM;AACT,eAAS,KAAK,cAAc,SAAS,KAAK,SAAS;AACnD,eAAS,KAAK,cAAc,SAAS,KAAK,SAAS;AAAA,IACpD;AAEA,UAAM,KAAK,KAAK,UAAU;AAAA,MACzB,QAAQ;AAAA,MAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,GAAG,KAAK,UAAU;AAAA,IACvD;AACA,UAAM,KAAK,KAAK,UAAU;AAAA,MACzB,QAAQ;AAAA,MAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,GAAG,KAAK,UAAU;AAAA,IACvD;AACA,QAAI,IAAoB;AACxB,QAAI,MAAM;AACT,WAAK,KAAK,UAAU;AAAA,QACnB,QAAQ;AAAA,QAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,GAAG,KAAK,UAAU;AAAA,MACvD;AACA,WAAK,KAAK,UAAU;AAAA,QACnB,QAAQ;AAAA,QAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,GAAG,KAAK,UAAU;AAAA,MACvD;AAAA,IACD;AAGA,SAAK,GAAG,MAAM;AACd,SAAK,GAAG,MAAM;AACd,QAAI,MAAM;AACT,WAAK,GAAI,MAAM;AACf,WAAK,GAAI,MAAM;AAAA,IAChB;AAEA,QAAI,cAAc,UAAU,KAAK,UAAU,IAAI;AAAA,aAChC,KAAK,UAAU,MAAM;AAAA,aACrB,KAAK,UAAU,MAAM;AACpC,QAAI,MAAM;AACT,qBAAe;AAAA,aACA,KAAK,UAAU,MAAO;AAAA,aACtB,KAAK,UAAU,MAAO;AAAA,IACtC;AACA,SAAK,QAAQ,WAAW,MAAM,WAAW;AAEzC,qBAAiB,SAAS,QAAQ,YAAY;AAC7C,UAAI,KAAK;AAAQ,gBAAQ,IAAI,KAAK;AAAA,IACnC;AACA,WAAO,QAAQ,WAAW,SAAS;AAAA,EACpC;AAAA;AAAA;AAAA,EAIQ,UAAoB;AAC3B,WAAO;AAAA,MACN,KAAK,KAAK,OAAO,KAAK,EAAE;AAAA,MACxB,KAAK,KAAK,OAAO,KAAK,EAAE;AAAA,MACxB,KAAK,KAAK,OAAO,KAAK,EAAE;AAAA,MACxB,KAAK,KAAK,OAAO,KAAK,EAAE;AAAA,IACzB,EAAE,KAAK,GAAG;AAAA,EACX;AAAA,EAEQ,cAAc,MAAc,SAAoB;AACvD,QAAI,QAAQ;AAAM,aAAO,EAAE,MAAM,MAAM,QAAQ,KAAK;AACpD,WAAO,EAAE,MAAM,MAAM,KAAK,QAAQ,EAAE;AAAA,EACrC;AACD;AAjHO,IAAM,SAAN;AAAM,OACI,aAAwB;AAAA,EACvC,UAAU,CAAC,GAAkC,MAAiB,IAAI,uCAAe,GAAG,CAAC;AAAA,EACrF,MAAM;AAAA,EACN,MAAM;AACP;AA8GD,MAAM,wBAAwB,cAAc,aAAa;AAAA,EAKxD,YAAY,OAAgB;AAC3B,UAAM;AACN,SAAK,QAAQ,CAAC,CAAC;AACf,SAAK,cAAc,CAAC;AAAA,EACrB;AAAA,EAEA,OAAO,SAAiB;AACvB,QAAI,KAAK;AAAO,cAAQ,IAAI,OAAO;AACnC,SAAK,YAAY,KAAK,OAAO;AAC7B,UAAM,OAAO,OAAO;AAAA,EACrB;AACD;AAEA,MAAM,WAAW;AAAA,EAKhB,YAAY,OAAgB,OAAgB;AAC3C,SAAK,QAAQ;AAGb,SAAK,UAAU,IAAI,gBAAgB,KAAK;AACxC,SAAK,OAAO,IAAI,gBAAgB,KAAK;AAAA,EACtC;AAAA,EAEA,IAAI,cAAc;AACjB,UAAM,UAAU,KAAK,QAAQ;AAC7B,UAAM,OAAO,KAAK,KAAK;AACvB,kBAAAA,OAAO,UAAU,MAAM,OAAO;AAC9B,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,OAAO;AACZ,UAAM,UAAU,MAAM,KAAK,QAAQ,KAAK;AACxC,UAAM,OAAO,MAAM,KAAK,KAAK,KAAK;AAGlC,QAAI,CAAC,KAAK;AAAO,oBAAAA,OAAO,MAAM,mBAAM,aAAa,IAAI,GAAG,mBAAM,aAAa,OAAO,CAAC;AACnF,WAAO;AAAA,EACR;AAAA,EAEA,MAAM,SAAiB;AACtB,SAAK,QAAQ,OAAO,OAAO;AAC3B,SAAK,KAAK,OAAO,OAAO;AACxB,SAAK,QAAQ;AAAA,EACd;AAAA,EAEA,WAAW;AAEV,SAAK,QAAQ,IAAI;AACjB,SAAK,QAAQ,UAAU;AACvB,SAAK,KAAK,UAAU;AAAA,EACrB;AAAA,EAEA,QAAQ,KAAe;AACtB,QAAI,CAAC,KAAK,QAAQ,UAAU,CAAC,KAAK,KAAK;AAAQ;AAE/C,UAAM,UAAU,KAAK,QAAQ,OAAO,OAAO;AAC3C,UAAM,OAAO,KAAK,KAAK,OAAO,OAAO;AACrC,QAAI;AACH,oBAAAA,OAAO,UAAU,mBAAM,UAAU,IAAI,GAAG,mBAAM,UAAU,OAAO,CAAC;AAAA,IACjE,SAAS,KAAP;AACD,UAAI,KAAK,OAAO;AAEf,WAAG,cAAc,qBAAqB,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AACtE,WAAG,cAAc,kBAAkB,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,MACjE;AACA,YAAM,IAAI,MAAM,IAAI,OAAO;AAAA,IAC5B;AAEA,QAAI;AAAK;AACT,UAAM,OAAO,KAAK,KAAK,OAAO;AAC9B,SAAK,KAAK,SAAS,qBAAO,SAAS,IAAI;AACvC,SAAK,KAAK,OAAO,QAAQ,IAAI;AAAA,EAC9B;AACD;", "names": ["assert"] }