File size: 4,875 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
/**
 * 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]);
}