/** * Random Simulation harness for testing and benchmarking purposes. * Pokemon Showdown - http://pokemonshowdown.com/ * * Refer to `README.md` for detailed usage instructions. * * @license MIT */ 'use strict'; if (process.argv[2]) { const help = ['help', '-help', '--help', 'h', '-h', '--help', '?', '-?', '--?'].includes(process.argv[2]); const unknown = !['multi', 'random', 'exhaustive'].includes(process.argv[2]) && !/^[0-9]+$/.test(process.argv[2]); if (help || unknown) { const out = help ? console.log : console.error; if (unknown) out(`Unrecognized command: ${process.argv[2]}\n`); out('tools/simulate random'); out(''); out(' Randomly simulates `--num` total games (default=100).'); out(' The format(s) played and what gets output can be altered.'); out(''); out('tools/simulate exhaustive'); out(''); out(' Plays through enough randomly simulated battles to exhaust'); out(' all options of abilities/items/moves/pokemon. `--cycles` can'); out(' used to run through multiple exhaustions of the options.'); out(''); out('tools/simulate help'); out(''); out(' Displays this reference'); out(''); out('Please refer to tools/SIMULATE.md for full documentation'); process.exit(help ? 0 : 1); } } require('child_process').execSync('node ' + __dirname + "/../../build"); const Dex = require('../../sim/dex').Dex; global.Config = { allowrequestingties: false }; Dex.includeModData(); const { ExhaustiveRunner } = require('../../sim/tools/exhaustive-runner'); const { MultiRandomRunner } = require('../../sim/tools/multi-random-runner'); // Tracks whether some promises threw errors that weren't caught so we can log // and exit with a non-zero status to fail any tests. This "shouldn't happen" // because we're "great at propagating promises (TM)", but better safe than sorry. const RejectionTracker = new class { constructor() { this.unhandled = []; } onUnhandledRejection(reason, promise) { this.unhandled.push({ reason, promise }); } onRejectionHandled(promise) { this.unhandled.splice(this.unhandled.findIndex(u => u.promise === promise), 1); } onExit(code) { let i = 0; for (const u of this.unhandled) { const error = (u.reason instanceof Error) ? u.reason : new Error(`Promise rejected with value: ${u.reason}`); console.error(`UNHANDLED PROMISE REJECTION:\n${error.stack}`); i++; } process.exit(code + i); } register() { process.on('unhandledRejection', (r, p) => this.onUnhandledRejection(r, p)); process.on('rejectionHandled', p => this.onRejectionHandled(p)); process.on('exit', c => this.onExit(c)); // TODO } }(); RejectionTracker.register(); function missing(dep) { try { require.resolve(dep); return false; } catch (err) { if (err.code !== 'MODULE_NOT_FOUND') throw err; return true; } } function shell(cmd) { require('child_process').execSync(cmd, { stdio: 'inherit', cwd: __dirname }); } function parseFlags(argv) { if (!(argv.length > 3 || argv.length === 3 && argv[2].startsWith('-'))) return { _: argv.slice(2) }; if (missing('minimist')) shell('npm install minimist'); return require('minimist')(argv.slice(2)); } if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) process.argv.splice(2, 0, 'multi'); switch (process.argv[2]) { case 'multi': case 'random': { const argv = parseFlags(process.argv); const options = { totalGames: 100, ...argv }; options.totalGames = Number(argv._[1] || argv.num) || options.totalGames; if (argv.seed) options.prng = argv.seed.split(',').map(s => Number(s)); // Run options.totalGames, exiting with the number of games with errors. (async () => process.exit(await new MultiRandomRunner(options).run()))(); } break; case 'exhaustive': { const argv = parseFlags(process.argv); let formats; if (argv.formats) { formats = argv.formats.split(','); } else if (argv.format) { formats = argv.format.split(','); } else { formats = ExhaustiveRunner.FORMATS; } let cycles = Number(argv._[1] || argv.cycles) || ExhaustiveRunner.DEFAULT_CYCLES; let forever = argv.forever; if (cycles < 0) { cycles = -cycles; forever = true; } const maxFailures = argv.maxFailures || argv.failures || (formats.length > 1 ? ExhaustiveRunner.MAX_FAILURES : 1); const prng = argv.seed && argv.seed.split(',').map(s => Number(s)); const maxGames = argv.maxGames || argv.games; (async () => { let failures = 0; do { for (const format of formats) { failures += await new ExhaustiveRunner({ format, cycles, prng, maxFailures, log: true, dual: argv.dual, maxGames, }).run(); process.stdout.write('\n'); if (failures >= maxFailures) break; } } while (forever); // eslint-disable-line no-unmodified-loop-condition process.exit(failures); })(); } break; default: throw new TypeError('Unknown command' + process.argv[2]); }