mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-05-18 03:01:00 -05:00
`import =` and `export =` are really only intended for backwards compatibility with CommonJS. While I really don't like the new module system TC39 has designed for us, it's what we should be using, and consistency is important.
152 lines
4.7 KiB
JavaScript
152 lines
4.7 KiB
JavaScript
/**
|
|
* Random Simulation harness for testing and benchmarking purposes.
|
|
* Pokemon Showdown - http://pokemonshowdown.com/
|
|
*
|
|
* Refer to `SIMULATE.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);
|
|
}
|
|
}
|
|
|
|
const child_process = require('child_process');
|
|
const path = require('path');
|
|
const shell = cmd => child_process.execSync(cmd, {stdio: 'inherit', cwd: path.resolve(__dirname, '..')});
|
|
shell('node build');
|
|
|
|
const Dex = require('../.sim-dist/dex').Dex;
|
|
Dex.includeModData();
|
|
|
|
const {ExhaustiveRunner} = require('../.sim-dist/tools/exhaustive-runner');
|
|
const {MultiRandomRunner} = require('../.sim-dist/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 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 = Object.assign({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));
|
|
(async () => {
|
|
let failures = 0;
|
|
do {
|
|
for (let format of formats) {
|
|
failures += await new ExhaustiveRunner({
|
|
format, cycles, prng, maxFailures, log: true, dual: argv.dual,
|
|
}).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]);
|
|
}
|