@pkmn/engine 0.1.0-dev.00879b8b
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +18 -0
- package/README.md +368 -0
- package/build/pkg/addon.d.ts +7 -0
- package/build/pkg/addon.js +111 -0
- package/build/pkg/addon.js.map +1 -0
- package/build/pkg/data/ids.json +240 -0
- package/build/pkg/data/layout.json +108 -0
- package/build/pkg/data/protocol.json +230 -0
- package/build/pkg/data.d.ts +91 -0
- package/build/pkg/data.js +150 -0
- package/build/pkg/data.js.map +1 -0
- package/build/pkg/gen1.d.ts +110 -0
- package/build/pkg/gen1.js +684 -0
- package/build/pkg/gen1.js.map +1 -0
- package/build/pkg/index.d.ts +387 -0
- package/build/pkg/index.js +122 -0
- package/build/pkg/index.js.map +1 -0
- package/build/pkg/protocol.d.ts +73 -0
- package/build/pkg/protocol.js +473 -0
- package/build/pkg/protocol.js.map +1 -0
- package/build/test/display.js +400 -0
- package/build/test/display.js.map +1 -0
- package/build/tools/debug.js +134 -0
- package/build/tools/debug.js.map +1 -0
- package/build.zig +363 -0
- package/package.json +232 -0
- package/src/bin/install-pkmn-engine +404 -0
- package/src/bin/pkmn-debug +62 -0
- package/src/lib/common/battle.zig +17 -0
- package/src/lib/common/data.zig +117 -0
- package/src/lib/common/debug.zig +74 -0
- package/src/lib/common/js.zig +118 -0
- package/src/lib/common/optional.zig +67 -0
- package/src/lib/common/options.zig +51 -0
- package/src/lib/common/protocol.zig +1836 -0
- package/src/lib/common/rational.zig +357 -0
- package/src/lib/common/rng.zig +243 -0
- package/src/lib/common/util.zig +12 -0
- package/src/lib/gen1/calc.zig +507 -0
- package/src/lib/gen1/chance.zig +1063 -0
- package/src/lib/gen1/data/moves.zig +1866 -0
- package/src/lib/gen1/data/species.zig +1105 -0
- package/src/lib/gen1/data/types.zig +131 -0
- package/src/lib/gen1/data.zig +634 -0
- package/src/lib/gen1/helpers.zig +866 -0
- package/src/lib/gen1/mechanics.zig +3156 -0
- package/src/lib/gen2/calc.zig +143 -0
- package/src/lib/gen2/chance.zig +1315 -0
- package/src/lib/gen2/data/items.zig +230 -0
- package/src/lib/gen2/data/moves.zig +2878 -0
- package/src/lib/gen2/data/species.zig +1798 -0
- package/src/lib/gen2/data/types.zig +226 -0
- package/src/lib/gen2/data.zig +463 -0
- package/src/lib/gen2/generated.zig +806 -0
- package/src/lib/gen2/helpers.zig +249 -0
- package/src/lib/gen2/mechanics.zig +3632 -0
- package/src/lib/node.zig +131 -0
- package/src/lib/pkmn.zig +128 -0
- package/src/lib/wasm.zig +13 -0
- package/src/test/display/pkmn.html.tmpl +368 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const {execFileSync} = require('child_process');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const https = require('https');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const {pipeline} = require('stream/promises');
|
|
11
|
+
const tty = require('tty');
|
|
12
|
+
|
|
13
|
+
const ZIG_VERSION = {major: 0, minor: 13, patch: 0};
|
|
14
|
+
const DEV = ZIG_VERSION.dev ? `-dev.${ZIG_VERSION.dev}` : '';
|
|
15
|
+
const VERSION = `${ZIG_VERSION.major}.${ZIG_VERSION.minor}.${ZIG_VERSION.patch}${DEV}`;
|
|
16
|
+
const INDEX = 'https://ziglang.org/download/index.json';
|
|
17
|
+
const ROOT = path.resolve(__dirname, '..', '..');
|
|
18
|
+
let SILENT = false;
|
|
19
|
+
|
|
20
|
+
const sh = (cmd, args) => execFileSync(cmd, args, {encoding: 'utf8', cwd: ROOT});
|
|
21
|
+
const log = (...args) => { if (!SILENT) console.log(...args); };
|
|
22
|
+
|
|
23
|
+
// This is hardly the browser fetch API, but it works for our primitive needs
|
|
24
|
+
const fetch = url => new Promise((resolve, reject) => {
|
|
25
|
+
let buf = '';
|
|
26
|
+
const req = https.request(url, res => {
|
|
27
|
+
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
|
28
|
+
res.on('data', d => {
|
|
29
|
+
buf += d;
|
|
30
|
+
});
|
|
31
|
+
res.on('end', () => resolve(buf));
|
|
32
|
+
});
|
|
33
|
+
req.on('error', reject);
|
|
34
|
+
req.end();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const download = (url, dest, shasum) => new Promise((resolve, reject) => {
|
|
38
|
+
const req = https.request(url, res => {
|
|
39
|
+
if (res.statusCode !== 200) return reject(new Error(`HTTP ${res.statusCode}`));
|
|
40
|
+
if (shasum) {
|
|
41
|
+
resolve(pipeline(res, async function* (source) {
|
|
42
|
+
const hash = crypto.createHash('sha256');
|
|
43
|
+
for await (const chunk of source) {
|
|
44
|
+
hash.update(chunk);
|
|
45
|
+
yield chunk;
|
|
46
|
+
}
|
|
47
|
+
if (hash.digest('hex') !== shasum) {
|
|
48
|
+
throw new Error(`SHA-256 hash of Zig tarball from ${url} does not match expected value`);
|
|
49
|
+
}
|
|
50
|
+
}, fs.createWriteStream(dest)));
|
|
51
|
+
} else {
|
|
52
|
+
resolve(pipeline(res, fs.createWriteStream(dest)));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
req.on('error', reject);
|
|
56
|
+
req.end();
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
// If we're on a system without tar or 7zip we need to download 7zip, which requires some hackery
|
|
60
|
+
const unpack7 = (input, output) => new Promise((resolve, reject) => {
|
|
61
|
+
// If we already have 7zip-min installed we can just used it, otherwise we need to install it
|
|
62
|
+
try {
|
|
63
|
+
require.resolve('7zip-min');
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (err.code !== 'MODULE_NOT_FOUND') throw err;
|
|
66
|
+
log('Installing 7zip-min package locally to unpack the archive...');
|
|
67
|
+
sh('npm', ['install', '7zip-min', '--no-audit', '--no-save']);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Node won't let us used the package we just installed above before v16 because pain
|
|
71
|
+
if (+/^v(\d+)/.exec(process.version)[1] < 16) {
|
|
72
|
+
console.error('The pkmn engine requires Node v16+');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Actually use 7zip now that we've installed it
|
|
77
|
+
require('7zip-min').unpack(input, output, err => err ? reject(err) : resolve());
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Unpacking is kind of an unholy abomination because we don't know what tool we're going to use
|
|
81
|
+
const unpack = async (input, output) => {
|
|
82
|
+
// This is basically a check for "not windows", in which case simply using `tar` will likely work
|
|
83
|
+
if (!input.endsWith('.zip')) {
|
|
84
|
+
try {
|
|
85
|
+
sh('tar', ['xf', input, '-C', output]);
|
|
86
|
+
return;
|
|
87
|
+
} catch {
|
|
88
|
+
log('tar command not found, falling back to 7z');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Try to use a 7z binary if we can, and only install 7zip-min as a last resort
|
|
92
|
+
try {
|
|
93
|
+
if (input.endsWith('.zip')) {
|
|
94
|
+
sh('7z', ['x', input, '-o' + output]);
|
|
95
|
+
} else {
|
|
96
|
+
// 7z is brain-dead and requires two commands to unpack the .tar.{gz,xz} file
|
|
97
|
+
const tar = input.slice(0, -3);
|
|
98
|
+
sh('7z', ['x', input, '-o' + tar]);
|
|
99
|
+
sh('7z', ['x', tar, '-o' + output]);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
} catch {
|
|
103
|
+
if (input.endsWith('.zip')) {
|
|
104
|
+
await unpack7(input, output);
|
|
105
|
+
} else {
|
|
106
|
+
// Sigh... see above
|
|
107
|
+
const tar = input.slice(0, -3);
|
|
108
|
+
await unpack7(input, tar);
|
|
109
|
+
await unpack7(tar, output);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const find = zig => {
|
|
115
|
+
try {
|
|
116
|
+
const {zig_exe, version} = JSON.parse(sh(zig, ['env']));
|
|
117
|
+
const [major, minor, patch, dev] = version.split('.').map(n => parseInt(n));
|
|
118
|
+
|
|
119
|
+
const compatible = major > ZIG_VERSION.major ||
|
|
120
|
+
(major >= ZIG_VERSION.major && minor > ZIG_VERSION.minor) ||
|
|
121
|
+
(major >= ZIG_VERSION.major && minor >= ZIG_VERSION.minor && patch >= ZIG_VERSION.patch &&
|
|
122
|
+
(!ZIG_VERSION.dev || (dev && dev >= ZIG_VERSION.dev)));
|
|
123
|
+
|
|
124
|
+
if (compatible) {
|
|
125
|
+
log('Found existing compatible Zig executable:', zig_exe);
|
|
126
|
+
return zig_exe;
|
|
127
|
+
} else {
|
|
128
|
+
log(`Existing Zig executable is not compatible (${version} < ${VERSION}):`, zig_exe);
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
if (err.code !== 'ENOENT') throw err;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
// https://github.com/coilhq/tigerbeetle/blob/main/scripts/install_zig.sh
|
|
136
|
+
const install = async () => {
|
|
137
|
+
// Is there an existing compatible system Zig executable in the PATH?
|
|
138
|
+
const system = find('zig');
|
|
139
|
+
if (system) return system;
|
|
140
|
+
|
|
141
|
+
const dir = path.join(ROOT, 'build', 'bin');
|
|
142
|
+
const zig = path.join(dir, 'zig', 'zig');
|
|
143
|
+
|
|
144
|
+
// Has a previous version of this script installed a compatible version locally?
|
|
145
|
+
if (find(zig)) return zig;
|
|
146
|
+
|
|
147
|
+
const arch = process.arch === 'x64'
|
|
148
|
+
? 'x86_64' : process.arch === 'arm64'
|
|
149
|
+
? 'aarch64' : process.arch;
|
|
150
|
+
const platform = process.platform === 'darwin'
|
|
151
|
+
? 'macos' : process.platform === 'win32'
|
|
152
|
+
? 'windows' : process.platform;
|
|
153
|
+
|
|
154
|
+
try {
|
|
155
|
+
await fs.promises.rm(dir, {recursive: true});
|
|
156
|
+
} catch (err) {
|
|
157
|
+
if (err.code !== 'ENOENT') throw err;
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
161
|
+
} catch (err) {
|
|
162
|
+
if (err.code !== 'EEXIST') throw err;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const index = JSON.parse(await fetch(INDEX));
|
|
166
|
+
const version = Object.keys(index)[+!ZIG_VERSION.dev];
|
|
167
|
+
const release = index[version][`${arch}-${platform}`];
|
|
168
|
+
const url = release.tarball;
|
|
169
|
+
|
|
170
|
+
let base = path.basename(url);
|
|
171
|
+
const archive = path.join(dir, path.basename(url));
|
|
172
|
+
|
|
173
|
+
log(`Downloading Zig tarball from ${url} to ${archive}`);
|
|
174
|
+
try {
|
|
175
|
+
await download(url, archive, release.shasum);
|
|
176
|
+
|
|
177
|
+
log(`Extracting tarball and installing to ${dir}`);
|
|
178
|
+
await unpack(archive, dir);
|
|
179
|
+
if (archive.endsWith('.zip')) {
|
|
180
|
+
base = base.slice(0, -4);
|
|
181
|
+
} else {
|
|
182
|
+
base = base.slice(0, -7);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
fs.renameSync(path.join(dir, base), path.join(dir, 'zig'));
|
|
186
|
+
|
|
187
|
+
try {
|
|
188
|
+
fs.chmodSync(zig, 0o755);
|
|
189
|
+
} catch (err) {
|
|
190
|
+
if (err.code !== 'ENOENT') throw err;
|
|
191
|
+
}
|
|
192
|
+
log(`Zig${version === 'master' ? '' : ` v${version}`} executable:`, zig,
|
|
193
|
+
'(this version of Zig will be retained to speed up future updates)');
|
|
194
|
+
const [pre, post] = tty.isatty(1) ? ['\x1b[1m\x1b[35m', '\x1b[0m'] : ['', ''];
|
|
195
|
+
log(
|
|
196
|
+
`${pre}Consider moving the ${path.dirname(zig)} directory and adding it to your PATH`,
|
|
197
|
+
`to use Zig on other projects, or delete this if you wish to save space.${post}`
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
return zig;
|
|
201
|
+
} finally {
|
|
202
|
+
log(`Cleaning up ${archive}`);
|
|
203
|
+
try { fs.rmSync(archive); } catch {}
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// https://github.com/coilhq/tigerbeetle-node/blob/main/scripts/download_node_headers.sh
|
|
208
|
+
const downloadHeaders = async () => {
|
|
209
|
+
const include = path.resolve(process.execPath, '..', '..', 'include', 'node');
|
|
210
|
+
if (fs.existsSync(path.join(include, 'node.h'))) {
|
|
211
|
+
log('Node headers:', include);
|
|
212
|
+
return include;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const dir = path.join(ROOT, 'build', 'include');
|
|
216
|
+
const dest = path.join(dir, 'node');
|
|
217
|
+
if (fs.existsSync(path.join(dest, 'node.h'))) {
|
|
218
|
+
log('Node headers:', dest);
|
|
219
|
+
return dest;
|
|
220
|
+
} else {
|
|
221
|
+
log(`Could not find Node headers at ${include}, downloading...`);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
226
|
+
} catch (err) {
|
|
227
|
+
if (err.code !== 'EEXIST') throw err;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const url = process.release.headersUrl;
|
|
231
|
+
const targz = path.join(dir, path.basename(url));
|
|
232
|
+
const unpacked = path.join(dir, `node-${process.version}`, 'include');
|
|
233
|
+
|
|
234
|
+
log(`Downloading Node headers from ${url} to ${targz}`);
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
await download(url, targz);
|
|
238
|
+
await unpack(targz, dir);
|
|
239
|
+
fs.renameSync(path.join(unpacked, 'node'), dest);
|
|
240
|
+
|
|
241
|
+
log('Node headers:', dest);
|
|
242
|
+
|
|
243
|
+
return dest;
|
|
244
|
+
} finally {
|
|
245
|
+
try { fs.rmSync(targz); } catch {}
|
|
246
|
+
try { fs.rmSync(unpacked, {recursive: true}); } catch {}
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const downloadImportLib = async () => {
|
|
251
|
+
const library = path.resolve(process.execPath, '..', 'node.lib');
|
|
252
|
+
if (fs.existsSync(path.join(library, 'node.h'))) {
|
|
253
|
+
log('Node import library:', library);
|
|
254
|
+
return library;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const dir = path.join(ROOT, 'build', 'lib');
|
|
258
|
+
const url = new URL('./win-x64/node.lib', process.release.headersUrl).href;
|
|
259
|
+
const dest = path.join(dir, path.basename(url));
|
|
260
|
+
if (fs.existsSync(path.join(dest))) {
|
|
261
|
+
log('Node import library:', dest);
|
|
262
|
+
return dest;
|
|
263
|
+
} else {
|
|
264
|
+
log(`Could not find Node import library at ${library}, downloading...`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
fs.mkdirSync(dir, {recursive: true});
|
|
269
|
+
} catch (err) {
|
|
270
|
+
if (err.code !== 'EEXIST') throw err;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
log(`Downloading Node import library from ${url} to ${dest}`);
|
|
274
|
+
await download(url, dest);
|
|
275
|
+
log('Node import library:', dest);
|
|
276
|
+
|
|
277
|
+
return dest;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
(async () => {
|
|
281
|
+
let only = false;
|
|
282
|
+
let options = undefined;
|
|
283
|
+
let error = false;
|
|
284
|
+
for (let i = 2; i < process.argv.length; i++) {
|
|
285
|
+
switch (process.argv[i]) {
|
|
286
|
+
case "--silent": {
|
|
287
|
+
SILENT = true;
|
|
288
|
+
break;
|
|
289
|
+
}
|
|
290
|
+
case "--zig": {
|
|
291
|
+
if (options || only) error = true;
|
|
292
|
+
only = true;
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
default: {
|
|
296
|
+
if (options || only || !process.argv[i].startsWith('--options=')) error = true;
|
|
297
|
+
options = process.argv[i].slice(10).split(' ');
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (error) {
|
|
302
|
+
console.error('Invalid arguments');
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const zig = await install();
|
|
307
|
+
if (only) process.exit(0);
|
|
308
|
+
|
|
309
|
+
let tmp;
|
|
310
|
+
try {
|
|
311
|
+
tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'pkmn-'));
|
|
312
|
+
} catch {
|
|
313
|
+
console.error('Unable to create temporary directory');
|
|
314
|
+
process.exit(1);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Determine which configurations to build based on prior runs (see below for
|
|
318
|
+
// if there were no previous runs) - try to build the same configurations as
|
|
319
|
+
// before (unless --options overridden). We also track whether any of the
|
|
320
|
+
// configurations require Node headers/import libraries
|
|
321
|
+
let node = false;
|
|
322
|
+
const configs = new Map();
|
|
323
|
+
const libs = path.join(ROOT, 'build', 'lib');
|
|
324
|
+
if (fs.existsSync(libs)) {
|
|
325
|
+
for (const file of fs.readdirSync(libs)) {
|
|
326
|
+
const type = path.extname(file).slice(1);
|
|
327
|
+
if (type !== 'node' && type !== 'wasm') continue;
|
|
328
|
+
// If we don't copy the addon before loading it Windows will return an
|
|
329
|
+
// EBUSY error when we later attempt to clobber it (Linux and macOS don't
|
|
330
|
+
// seem to care)
|
|
331
|
+
const copy = path.join(tmp, file);
|
|
332
|
+
fs.copyFileSync(path.join(libs, file), copy);
|
|
333
|
+
|
|
334
|
+
const opts = options || [];
|
|
335
|
+
if (type === 'node') {
|
|
336
|
+
node = true;
|
|
337
|
+
if (!options) {
|
|
338
|
+
const addon = require(copy);
|
|
339
|
+
if (addon.engine.options.showdown) opts.push('-Dshowdown');
|
|
340
|
+
if (addon.engine.options.log) opts.push('-Dlog');
|
|
341
|
+
if (addon.engine.options.chance) opts.push('-Dchance');
|
|
342
|
+
if (addon.engine.options.calc) opts.push('-Dcalc');
|
|
343
|
+
}
|
|
344
|
+
} else if (!options) {
|
|
345
|
+
const addon = await WebAssembly.instantiate(fs.readFileSync(copy));
|
|
346
|
+
const values = new Uint8Array(addon.instance.exports.memory.buffer);
|
|
347
|
+
if (values[addon.instance.exports.SHOWDOWN.value]) opts.push('-Dshowdown');
|
|
348
|
+
if (values[addon.instance.exports.LOG.value]) opts.push('-Dlog');
|
|
349
|
+
if (values[addon.instance.exports.CHANCE.value]) opts.push('-Dchance');
|
|
350
|
+
if (values[addon.instance.exports.CALC.value]) opts.push('-Dcalc');
|
|
351
|
+
}
|
|
352
|
+
const key = `${type} ${opts.sort().join(' ')}`;
|
|
353
|
+
if (!configs.has(key)) configs.set(key, {type, options: opts});
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// If there haven't been any builds, build both a Node and WASM extension with
|
|
358
|
+
// either the default options or whatever was passed in
|
|
359
|
+
if (!configs.size) {
|
|
360
|
+
node = true;
|
|
361
|
+
const DEFAULT = ['-Dshowdown'];
|
|
362
|
+
configs.set('node', {type: 'node', options: options || DEFAULT});
|
|
363
|
+
configs.set('wasm', {type: 'wasm', options: options || DEFAULT});
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
const headers = node ? await downloadHeaders() : undefined;
|
|
367
|
+
const lib = node && process.platform === 'win32' ? await downloadImportLib() : undefined;
|
|
368
|
+
for (const config of configs.values()) {
|
|
369
|
+
const opts = config.options.filter(Boolean);
|
|
370
|
+
const args = ['build', '-p', 'build', ...opts];
|
|
371
|
+
if (!process.env.DEBUG_PKMN_ENGINE) args.push('-Doptimize=ReleaseFast', '-Dstrip');
|
|
372
|
+
if (config.type === 'node') {
|
|
373
|
+
args.push(`-Dnode-headers=${path.relative(ROOT, headers)}`);
|
|
374
|
+
if (process.platform === 'win32') {
|
|
375
|
+
args.push(`-Dnode-import-library=${path.relative(ROOT, lib)}`);
|
|
376
|
+
}
|
|
377
|
+
} else {
|
|
378
|
+
args.push('-Dwasm');
|
|
379
|
+
}
|
|
380
|
+
const suffix =
|
|
381
|
+
opts.includes('-Dshowdown') || opts.includes('-Dshowdown=true') ? '-showdown' : '';
|
|
382
|
+
const flags = opts.length ? ` (${opts.join(' ')})` : '';
|
|
383
|
+
const artifact = path.join('build', 'lib', `pkmn${suffix}.${config.type}`);
|
|
384
|
+
log(`Building ${artifact}${flags}`);
|
|
385
|
+
sh(zig, args);
|
|
386
|
+
// TODO: ziglang/zig#14647 broke emit_to so we get to rename the file ourselves...
|
|
387
|
+
if (config.type === 'node') {
|
|
388
|
+
const prefix = process.platform === 'win32' ? '' : 'lib';
|
|
389
|
+
const ext = process.platform === 'darwin'
|
|
390
|
+
? 'dylib' : process.platform === 'win32' ? 'dll' : 'so';
|
|
391
|
+
try {
|
|
392
|
+
const actual = path.join('build', 'lib', `${prefix}pkmn${suffix}.node.${ext}`);
|
|
393
|
+
fs.renameSync(actual, artifact);
|
|
394
|
+
} catch (err) {
|
|
395
|
+
// ziglang/zig#19743 changed this path
|
|
396
|
+
const actual = path.join('build', 'bin', `${prefix}pkmn${suffix}.node.${ext}`);
|
|
397
|
+
fs.renameSync(actual, artifact);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
})().catch(e => {
|
|
402
|
+
console.error(e);
|
|
403
|
+
process.exit(1);
|
|
404
|
+
});
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
try {
|
|
5
|
+
require('source-map-support').install();
|
|
6
|
+
} catch (err) {
|
|
7
|
+
if (err.code !== 'MODULE_NOT_FOUND') throw err;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const {execFileSync} = require('child_process');
|
|
11
|
+
const {readFileSync} = require('fs');
|
|
12
|
+
const {join, resolve} = require('path');
|
|
13
|
+
|
|
14
|
+
const ROOT = resolve(__dirname, '..', '..');
|
|
15
|
+
const packageJSON = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
|
|
16
|
+
|
|
17
|
+
const missing = [];
|
|
18
|
+
for (const dep of ['@pkmn/img', 'html-minifier', 'mustache']) {
|
|
19
|
+
try {
|
|
20
|
+
require.resolve(dep);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
|
23
|
+
missing.push(`${dep}@${packageJSON.devDependencies[dep]}`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let Dex = undefined;
|
|
28
|
+
try {
|
|
29
|
+
Dex = require('@pkmn/sim').Dex;
|
|
30
|
+
} catch (err) {
|
|
31
|
+
if (err.code !== 'MODULE_NOT_FOUND') throw err;
|
|
32
|
+
try {
|
|
33
|
+
Dex = require('@pkmn/dex').Dex;
|
|
34
|
+
} catch (e) {
|
|
35
|
+
if (e.code !== 'MODULE_NOT_FOUND') throw e;
|
|
36
|
+
missing.push(`@pkmn/dex@${packageJSON.dependencies['@pkmn/data']}`);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (missing.length) {
|
|
41
|
+
if (+/^v(\d+)/.exec(process.version)[1] < 16) {
|
|
42
|
+
console.error('pkmn-debug requires Node v16+');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
execFileSync('npm', ['install', ...missing, '--global'], {encoding: 'utf8'});
|
|
47
|
+
|
|
48
|
+
const node_modules = execFileSync('npm', ['root', '--global'], {encoding: 'utf8'}).trim();
|
|
49
|
+
if (!process.env.NODE_PATH || !process.env.NODE_PATH.includes(node_modules)) {
|
|
50
|
+
console.error(`NODE_PATH must include npm's global node_modules directory: '${node_modules}'`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const {Generations} = require('@pkmn/data');
|
|
56
|
+
|
|
57
|
+
const {run} = require('../../build/tools/debug');
|
|
58
|
+
|
|
59
|
+
run(new Generations(Dex)).catch(err => {
|
|
60
|
+
console.error(err.message);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/// Stores state required by optional features to the engine that can be passed to a generation's
|
|
2
|
+
/// `Battle.update` function. All generations generically support protocol logging (enabled via
|
|
3
|
+
/// `-Dlog`), chance actions and probability tracking (enabled via `-Dchance`), and damage
|
|
4
|
+
/// computation and chance overrides (`-Dcalc`), though the concrete types for these features are
|
|
5
|
+
/// generation dependent.
|
|
6
|
+
pub fn Options(comptime Log: type, comptime Chance: type, comptime Calc: type) type {
|
|
7
|
+
return struct { log: Log, chance: Chance, calc: Calc };
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/// Helper to create a battle `Options` object with the provided `log`, `chance`, and `calc`.
|
|
11
|
+
pub fn options(
|
|
12
|
+
log: anytype,
|
|
13
|
+
chance: anytype,
|
|
14
|
+
calc: anytype,
|
|
15
|
+
) Options(@TypeOf(log), @TypeOf(chance), @TypeOf(calc)) {
|
|
16
|
+
return .{ .log = log, .chance = chance, .calc = calc };
|
|
17
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
const assert = std.debug.assert;
|
|
4
|
+
const expectEqual = std.testing.expectEqual;
|
|
5
|
+
|
|
6
|
+
/// Representation of one of the battle's participants.
|
|
7
|
+
pub const Player = enum(u1) {
|
|
8
|
+
P1,
|
|
9
|
+
P2,
|
|
10
|
+
|
|
11
|
+
/// Returns a player's opponent.
|
|
12
|
+
pub fn foe(self: Player) Player {
|
|
13
|
+
return @enumFromInt(~@intFromEnum(self));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/// Return's an identifier for the player's Pokémon at the one-indexed `id`.
|
|
17
|
+
pub fn ident(self: Player, id: u3) ID {
|
|
18
|
+
assert(id > 0 and id <= 6);
|
|
19
|
+
return .{ .id = id, .player = self };
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
test Player {
|
|
24
|
+
try expectEqual(Player.P2, Player.P1.foe());
|
|
25
|
+
try expectEqual(0b0001, @as(u8, @bitCast(Player.P1.ident(1))));
|
|
26
|
+
try expectEqual(0b1101, @as(u8, @bitCast(Player.P2.ident(5))));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// An identifier for a specific Pokémon in battle.
|
|
30
|
+
pub const ID = packed struct(u8) {
|
|
31
|
+
/// The one-indexed team slot of the Pokémon
|
|
32
|
+
id: u3 = 0,
|
|
33
|
+
/// The Pokémon's trainer.
|
|
34
|
+
player: Player = .P1,
|
|
35
|
+
_: u4 = 0,
|
|
36
|
+
|
|
37
|
+
/// Converts the identifier into a number.
|
|
38
|
+
pub fn int(self: ID) u4 {
|
|
39
|
+
return @intCast(@as(u8, @bitCast(self)));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/// Decodes the identifier from a number.
|
|
43
|
+
pub fn from(id: u4) ID {
|
|
44
|
+
return @bitCast(@as(u8, id));
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
test ID {
|
|
49
|
+
try expectEqual(0b0001, @as(u8, @bitCast(ID{ .player = .P1, .id = 1 })));
|
|
50
|
+
try expectEqual(0b1101, @as(u8, @bitCast(ID{ .player = .P2, .id = 5 })));
|
|
51
|
+
const id: ID = .{ .player = .P2, .id = 4 };
|
|
52
|
+
try expectEqual(id, ID.from(id.int()));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// A choice made by a player during battle.
|
|
56
|
+
pub const Choice = packed struct(u8) {
|
|
57
|
+
/// The choice type.
|
|
58
|
+
type: Choice.Type = .Pass,
|
|
59
|
+
/// The choice data:
|
|
60
|
+
///
|
|
61
|
+
/// - 0 for 'pass'
|
|
62
|
+
/// - 0-4 for 'move'
|
|
63
|
+
/// - 2-6 for 'switch'
|
|
64
|
+
data: u6 = 0,
|
|
65
|
+
|
|
66
|
+
/// All valid choice types.
|
|
67
|
+
pub const Type = enum(u2) {
|
|
68
|
+
Pass,
|
|
69
|
+
Move,
|
|
70
|
+
Switch,
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
test Choice {
|
|
75
|
+
const p1: Choice = .{ .type = .Move, .data = 4 };
|
|
76
|
+
const p2: Choice = .{ .type = .Switch, .data = 5 };
|
|
77
|
+
try expectEqual(5, p2.data);
|
|
78
|
+
try expectEqual(Choice.Type.Move, p1.type);
|
|
79
|
+
try expectEqual(0b0001_0001, @as(u8, @bitCast(p1)));
|
|
80
|
+
try expectEqual(0b0001_0110, @as(u8, @bitCast(p2)));
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/// The result of the battle - all results other than 'None' should be considered terminal.
|
|
84
|
+
pub const Result = packed struct(u8) {
|
|
85
|
+
/// The type of result from the perspective of Player 1.
|
|
86
|
+
/// `Error` is not possible when in Pokémon Showdown compatibility mode.
|
|
87
|
+
type: Result.Type = .None,
|
|
88
|
+
/// The choice type of the result for Player 1.
|
|
89
|
+
p1: Choice.Type = .Pass,
|
|
90
|
+
/// The choice type of the result for Player 2.
|
|
91
|
+
p2: Choice.Type = .Pass,
|
|
92
|
+
|
|
93
|
+
/// All valid result types.
|
|
94
|
+
pub const Type = enum(u4) {
|
|
95
|
+
None,
|
|
96
|
+
Win,
|
|
97
|
+
Lose,
|
|
98
|
+
Tie,
|
|
99
|
+
Error, // Desync, EBC, etc.
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/// The Tie result.
|
|
103
|
+
pub const Tie: Result = .{ .type = .Tie };
|
|
104
|
+
/// The Win result.
|
|
105
|
+
pub const Win: Result = .{ .type = .Win };
|
|
106
|
+
/// The Lost result.
|
|
107
|
+
pub const Lose: Result = .{ .type = .Lose };
|
|
108
|
+
/// The Error result.
|
|
109
|
+
pub const Error: Result = .{ .type = .Error };
|
|
110
|
+
/// The default result.
|
|
111
|
+
pub const Default: Result = .{ .p1 = .Move, .p2 = .Move };
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
test Result {
|
|
115
|
+
try expectEqual(0b0101_0000, @as(u8, @bitCast(Result.Default)));
|
|
116
|
+
try expectEqual(0b1000_0000, @as(u8, @bitCast(Result{ .p2 = .Switch })));
|
|
117
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
|
|
3
|
+
const debug = std.debug;
|
|
4
|
+
const io = std.io;
|
|
5
|
+
|
|
6
|
+
pub fn print(value: anytype) void {
|
|
7
|
+
debug.lockStdErr();
|
|
8
|
+
defer debug.unlockStdErr();
|
|
9
|
+
const stderr = io.getStdErr().writer();
|
|
10
|
+
|
|
11
|
+
nosuspend {
|
|
12
|
+
stderr.writeAll("\x1b[41m") catch return;
|
|
13
|
+
if (@TypeOf(@src()) == @TypeOf(value)) {
|
|
14
|
+
stderr.print("{s} ({s}:{d}:{d})", .{
|
|
15
|
+
value.fn_name,
|
|
16
|
+
value.file,
|
|
17
|
+
value.line,
|
|
18
|
+
value.column,
|
|
19
|
+
}) catch return;
|
|
20
|
+
} else {
|
|
21
|
+
switch (@typeInfo(@TypeOf(value))) {
|
|
22
|
+
.Struct => |info| {
|
|
23
|
+
if (info.is_tuple) {
|
|
24
|
+
inline for (info.fields, 0..) |f, i| {
|
|
25
|
+
inspect(@field(value, f.name));
|
|
26
|
+
if (i < info.fields.len - 1) stderr.writeAll(" ") catch return;
|
|
27
|
+
}
|
|
28
|
+
} else {
|
|
29
|
+
inspect(value);
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
else => inspect(value),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
stderr.writeAll("\x1b[K\x1b[0m\n") catch return;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
fn inspect(value: anytype) void {
|
|
40
|
+
const stderr = io.getStdErr().writer();
|
|
41
|
+
|
|
42
|
+
nosuspend {
|
|
43
|
+
const err = "Unable to format type '" ++ @typeName(@TypeOf(value)) ++ "'";
|
|
44
|
+
switch (@typeInfo(@TypeOf(value))) {
|
|
45
|
+
.Array => |info| {
|
|
46
|
+
if (info.child == u8) return stderr.print("{s}", .{value}) catch return;
|
|
47
|
+
@compileError(err);
|
|
48
|
+
},
|
|
49
|
+
.Pointer => |ptr_info| switch (ptr_info.size) {
|
|
50
|
+
.One => switch (@typeInfo(ptr_info.child)) {
|
|
51
|
+
.Array => |info| {
|
|
52
|
+
if (info.child == u8) return stderr.print("{s}", .{value}) catch return;
|
|
53
|
+
@compileError(err);
|
|
54
|
+
},
|
|
55
|
+
.Enum, .Union, .Struct => return inspect(value.*),
|
|
56
|
+
else => @compileError(err),
|
|
57
|
+
},
|
|
58
|
+
.Many, .C => {
|
|
59
|
+
if (ptr_info.sentinel) |_| return inspect(std.mem.span(value));
|
|
60
|
+
if (ptr_info.child == u8) {
|
|
61
|
+
return stderr.print("{s}", .{std.mem.span(value)}) catch return;
|
|
62
|
+
}
|
|
63
|
+
@compileError(err);
|
|
64
|
+
},
|
|
65
|
+
.Slice => {
|
|
66
|
+
if (ptr_info.child == u8) return stderr.print("{s}", .{value}) catch return;
|
|
67
|
+
@compileError(err);
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
.Optional => stderr.print("{?}", .{value}) catch return,
|
|
71
|
+
else => stderr.print("{}", .{value}) catch return,
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|