@pkmn/engine 0.1.0-dev.9a0c07c3 → 0.1.0-dev.a324c50f

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.
Files changed (41) hide show
  1. package/README.md +32 -29
  2. package/build/pkg/addon.d.ts +7 -0
  3. package/build/pkg/addon.js +112 -0
  4. package/build/pkg/addon.js.map +1 -0
  5. package/build/pkg/data/layout.json +25 -3
  6. package/build/pkg/gen1.d.ts +5 -4
  7. package/build/pkg/gen1.js +47 -31
  8. package/build/pkg/gen1.js.map +1 -1
  9. package/build/pkg/index.d.ts +15 -12
  10. package/build/pkg/index.js.map +1 -1
  11. package/build/pkg/protocol.d.ts +4 -2
  12. package/build/pkg/protocol.js +4 -2
  13. package/build/pkg/protocol.js.map +1 -1
  14. package/build/test/display.js +31 -13
  15. package/build/test/display.js.map +1 -1
  16. package/build.zig +49 -39
  17. package/package.json +18 -14
  18. package/src/bin/install-pkmn-engine +11 -6
  19. package/src/bin/pkmn-debug +34 -7
  20. package/src/lib/binding/node.zig +46 -141
  21. package/src/lib/binding/wasm.zig +6 -4
  22. package/src/lib/common/data.zig +11 -11
  23. package/src/lib/common/debug.zig +21 -11
  24. package/src/lib/common/options.zig +12 -3
  25. package/src/lib/common/protocol.zig +239 -178
  26. package/src/lib/common/rng.zig +10 -10
  27. package/src/lib/gen1/README.md +111 -81
  28. package/src/lib/gen1/data/moves.zig +10 -10
  29. package/src/lib/gen1/data/species.zig +2 -2
  30. package/src/lib/gen1/data/types.zig +3 -3
  31. package/src/lib/gen1/data.zig +64 -43
  32. package/src/lib/gen1/helpers.zig +439 -32
  33. package/src/lib/gen1/mechanics.zig +987 -612
  34. package/src/lib/gen2/data/items.zig +3 -3
  35. package/src/lib/gen2/data/moves.zig +1 -1
  36. package/src/lib/gen2/data/species.zig +1 -1
  37. package/src/lib/gen2/data/types.zig +3 -3
  38. package/src/lib/gen2/data.zig +37 -26
  39. package/src/lib/gen2/helpers.zig +9 -11
  40. package/src/lib/gen2/mechanics.zig +5 -4
  41. package/src/lib/pkmn.zig +68 -9
package/build.zig CHANGED
@@ -1,13 +1,20 @@
1
1
  const std = @import("std");
2
2
  const builtin = @import("builtin");
3
3
 
4
- pub const Options = struct { showdown: ?bool = null, trace: ?bool = null };
4
+ pub const Options = struct {
5
+ showdown: ?bool = null,
6
+ log: ?bool = null,
7
+ chance: ?bool = null,
8
+ calc: ?bool = null,
9
+ };
5
10
 
6
11
  pub fn module(b: *std.Build, options: Options) *std.Build.Module {
7
12
  const dirname = comptime std.fs.path.dirname(@src().file) orelse ".";
8
13
  const build_options = b.addOptions();
9
14
  build_options.addOption(?bool, "showdown", options.showdown);
10
- build_options.addOption(?bool, "trace", options.trace);
15
+ build_options.addOption(?bool, "log", options.log);
16
+ build_options.addOption(?bool, "chance", options.chance);
17
+ build_options.addOption(?bool, "calc", options.calc);
11
18
  return b.createModule(.{
12
19
  .source_file = .{ .path = dirname ++ "/src/lib/pkmn.zig" },
13
20
  .dependencies = &.{.{ .name = "build_options", .module = build_options.createModule() }},
@@ -27,25 +34,29 @@ pub fn build(b: *std.Build) !void {
27
34
  const dynamic = b.option(bool, "dynamic", "Build a dynamic library") orelse false;
28
35
  const strip = b.option(bool, "strip", "Strip debugging symbols from binary") orelse false;
29
36
  const pic = b.option(bool, "pic", "Force position independent code") orelse false;
37
+ const emit_asm = b.option(bool, "emit-asm", "Output .s (assembly code)") orelse false;
30
38
 
31
39
  const cmd = b.findProgram(&.{"strip"}, &.{}) catch null;
32
40
 
33
- var parser = std.json.Parser.init(b.allocator, false);
34
- defer parser.deinit();
35
- var tree = try parser.parse(@embedFile("package.json"));
36
- defer tree.deinit();
37
- const version = tree.root.Object.get("version").?.String;
38
- const description = tree.root.Object.get("description").?.String;
39
- var repository = std.mem.split(u8, tree.root.Object.get("repository").?.String, ":");
41
+ const json = @embedFile("package.json");
42
+ var parsed = try std.json.parseFromSlice(std.json.Value, b.allocator, json, .{});
43
+ defer parsed.deinit();
44
+ const version = parsed.value.object.get("version").?.string;
45
+ const description = parsed.value.object.get("description").?.string;
46
+ var repository = std.mem.split(u8, parsed.value.object.get("repository").?.string, ":");
40
47
  std.debug.assert(std.mem.eql(u8, repository.first(), "github"));
41
48
 
42
49
  const showdown =
43
50
  b.option(bool, "showdown", "Enable Pokémon Showdown compatibility mode") orelse false;
44
- const trace = b.option(bool, "trace", "Enable trace logs") orelse false;
51
+ const log = b.option(bool, "log", "Enable protocol message logging") orelse false;
52
+ const chance = b.option(bool, "chance", "Enable update probability tracking") orelse false;
53
+ const calc = b.option(bool, "calc", "Enable damage calculator support") orelse false;
45
54
 
46
55
  const options = b.addOptions();
47
56
  options.addOption(?bool, "showdown", showdown);
48
- options.addOption(?bool, "trace", trace);
57
+ options.addOption(?bool, "log", log);
58
+ options.addOption(?bool, "chance", chance);
59
+ options.addOption(?bool, "calc", calc);
49
60
 
50
61
  const name = if (showdown) "pkmn-showdown" else "pkmn";
51
62
 
@@ -79,7 +90,7 @@ pub fn build(b: *std.Build) !void {
79
90
  //
80
91
  // But ziglang/zig#14647 broke this so we now need to do an install() and then manually
81
92
  // rename the file ourself in install-pkmn-engine
82
- lib.install();
93
+ b.installArtifact(lib);
83
94
  } else if (wasm) {
84
95
  const lib = b.addSharedLibrary(.{
85
96
  .name = name,
@@ -105,12 +116,12 @@ pub fn build(b: *std.Build) !void {
105
116
  sh.addFileSourceArg(.{ .path = out });
106
117
  b.getInstallStep().dependOn(&sh.step);
107
118
  }
108
- lib.install();
119
+ b.installArtifact(lib);
109
120
  } else if (dynamic) {
110
121
  const lib = b.addSharedLibrary(.{
111
122
  .name = name,
112
123
  .root_source_file = .{ .path = "src/lib/binding/c.zig" },
113
- .version = try std.builtin.Version.parse(version),
124
+ .version = try std.SemanticVersion.parse(version),
114
125
  .optimize = optimize,
115
126
  .target = target,
116
127
  });
@@ -119,7 +130,7 @@ pub fn build(b: *std.Build) !void {
119
130
  lib.addIncludePath("src/include");
120
131
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
121
132
  if (pic) lib.force_pic = pic;
122
- lib.install();
133
+ b.installArtifact(lib);
123
134
  c = true;
124
135
  } else {
125
136
  const lib = b.addStaticLibrary(.{
@@ -134,7 +145,8 @@ pub fn build(b: *std.Build) !void {
134
145
  lib.bundle_compiler_rt = true;
135
146
  maybeStrip(b, lib, b.getInstallStep(), strip, cmd);
136
147
  if (pic) lib.force_pic = pic;
137
- lib.install();
148
+ if (emit_asm) lib.emit_asm = .emit;
149
+ b.installArtifact(lib);
138
150
  c = true;
139
151
  }
140
152
 
@@ -176,14 +188,20 @@ pub fn build(b: *std.Build) !void {
176
188
  .strip = strip,
177
189
  .cmd = cmd,
178
190
  };
191
+
192
+ // TODO: tests can be run multiple times due to @imports
179
193
  const tests = TestStep.create(b, options, config);
180
194
 
181
195
  var exes = std.ArrayList(*std.Build.CompileStep).init(b.allocator);
182
196
  const tools: ToolConfig = .{
197
+ .options = .{
198
+ .showdown = showdown,
199
+ .log = log,
200
+ .chance = chance,
201
+ .calc = calc,
202
+ },
183
203
  .general = config,
184
204
  .tool = .{
185
- .showdown = showdown,
186
- .trace = trace,
187
205
  .tests = if (tests.build) tests else null,
188
206
  .exes = &exes,
189
207
  },
@@ -197,23 +215,25 @@ pub fn build(b: *std.Build) !void {
197
215
  var fuzz_config = tools;
198
216
  fuzz_config.general.strip = false;
199
217
  fuzz_config.tool.name = "fuzz";
200
- const fuzz = tool(b, "src/test/benchmark.zig", fuzz_config);
218
+ const fuzz = tool(b, "src/test/fuzz.zig", fuzz_config);
201
219
 
220
+ const dump = tool(b, "src/tools/dump.zig", tools);
202
221
  const serde = tool(b, "src/tools/serde.zig", tools);
203
- const protocol = tool(b, "src/tools/protocol.zig", tools);
222
+ const transitions = tool(b, "src/tools/transitions.zig", tools);
204
223
 
205
224
  const lint_exe =
206
225
  b.addExecutable(.{ .name = "lint", .root_source_file = .{ .path = "src/tools/lint.zig" } });
207
226
  if (tests.build) tests.step.dependOn(&lint_exe.step);
208
- const lint = lint_exe.run();
227
+ const lint = b.addRunArtifact(lint_exe);
209
228
 
210
229
  b.step("benchmark", "Run benchmark code").dependOn(&benchmark.step);
230
+ b.step("dump", "Run protocol dump tool").dependOn(&dump.step);
211
231
  b.step("fuzz", "Run fuzz tester").dependOn(&fuzz.step);
212
232
  b.step("lint", "Lint source files").dependOn(&lint.step);
213
- b.step("protocol", "Run protocol dump tool").dependOn(&protocol.step);
214
233
  b.step("serde", "Run serialization/deserialization tool").dependOn(&serde.step);
215
234
  b.step("test", "Run all tests").dependOn(&tests.step);
216
235
  b.step("tools", "Install tools").dependOn(&ToolsStep.create(b, &exes).step);
236
+ b.step("transitions", "Visualize transitions algorithm search").dependOn(&transitions.step);
217
237
  }
218
238
 
219
239
  fn maybeStrip(
@@ -256,11 +276,8 @@ const TestStep = struct {
256
276
  pub fn create(b: *std.Build, options: *std.Build.OptionsStep, config: Config) *TestStep {
257
277
  const coverage = b.option([]const u8, "test-coverage", "Generate test coverage");
258
278
  const test_file = b.option([]const u8, "test-file", "Input file for test");
259
- const test_bin = b.option([]const u8, "test-bin", "Emit test binary to");
260
279
  const test_filter =
261
280
  b.option([]const u8, "test-filter", "Skip tests that do not match filter");
262
- const test_no_exec =
263
- b.option(bool, "test-no-exec", "Compiles test binary without running it") orelse false;
264
281
 
265
282
  const self = b.allocator.create(TestStep) catch @panic("OOM");
266
283
  const step = std.Build.Step.init(.{ .id = .custom, .name = "Run all tests", .owner = b });
@@ -274,31 +291,26 @@ const TestStep = struct {
274
291
  .root_source_file = .{ .path = path },
275
292
  .optimize = config.optimize,
276
293
  .target = config.target,
294
+ .filter = test_filter,
277
295
  });
278
296
  tests.setMainPkgPath("./");
279
- tests.setFilter(test_filter);
280
297
  tests.addOptions("build_options", options);
281
298
  tests.single_threaded = true;
282
299
  maybeStrip(b, tests, &tests.step, config.strip, config.cmd);
283
300
  if (config.pic) tests.force_pic = config.pic;
284
- if (test_bin) |bin| {
285
- tests.name = std.fs.path.basename(bin);
286
- if (std.fs.path.dirname(bin)) |dir| tests.setOutputDir(dir);
287
- }
288
301
  if (coverage) |c| {
289
302
  tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
290
303
  }
291
- self.step.dependOn(if (test_no_exec) &tests.step else &tests.run().step);
304
+ self.step.dependOn(&b.addRunArtifact(tests).step);
292
305
  }
293
306
  return self;
294
307
  }
295
308
  };
296
309
 
297
310
  const ToolConfig = struct {
311
+ options: Options,
298
312
  general: Config,
299
313
  tool: struct {
300
- showdown: bool,
301
- trace: bool,
302
314
  tests: ?*TestStep,
303
315
  name: ?[]const u8 = null,
304
316
  exes: *std.ArrayList(*std.Build.CompileStep),
@@ -309,7 +321,7 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) *std.Build.RunStep
309
321
  var name = config.tool.name orelse std.fs.path.basename(path);
310
322
  const index = std.mem.lastIndexOfScalar(u8, name, '.');
311
323
  if (index) |i| name = name[0..i];
312
- if (config.tool.showdown) name = b.fmt("{s}-showdown", .{name});
324
+ if (config.options.showdown orelse false) name = b.fmt("{s}-showdown", .{name});
313
325
 
314
326
  const exe = b.addExecutable(.{
315
327
  .name = name,
@@ -317,16 +329,14 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) *std.Build.RunStep
317
329
  .target = config.general.target,
318
330
  .optimize = config.general.optimize,
319
331
  });
320
- exe.addModule("pkmn", module(b, .{
321
- .showdown = config.tool.showdown,
322
- .trace = config.tool.trace,
323
- }));
332
+ exe.addModule("pkmn", module(b, config.options));
333
+
324
334
  exe.single_threaded = true;
325
335
  if (config.general.pic) exe.force_pic = config.general.pic;
326
336
  if (config.tool.tests) |ts| ts.step.dependOn(&exe.step);
327
337
  config.tool.exes.append(exe) catch @panic("OOM");
328
338
 
329
- const run = exe.run();
339
+ const run = b.addRunArtifact(exe);
330
340
  maybeStrip(b, exe, &run.step, config.general.strip, config.general.cmd);
331
341
  if (b.args) |args| run.addArgs(args);
332
342
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/engine",
3
- "version": "0.1.0-dev.9a0c07c3",
3
+ "version": "0.1.0-dev.a324c50f",
4
4
  "description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
5
5
  "repository": "github:pkmn/engine",
6
6
  "license": "MIT",
@@ -11,6 +11,9 @@
11
11
  "build.zig",
12
12
  "build/pkg/data.d.ts",
13
13
  "build/pkg/data.js.map",
14
+ "build/pkg/addon.d.ts",
15
+ "build/pkg/addon.js.map",
16
+ "build/pkg/addon.js",
14
17
  "build/pkg/data.js",
15
18
  "build/pkg/data/ids.json",
16
19
  "build/pkg/data/layout.json",
@@ -63,30 +66,30 @@
63
66
  "access": "public"
64
67
  },
65
68
  "dependencies": {
66
- "@pkmn/data": "^0.7.36",
67
- "@pkmn/protocol": "^0.6.11"
69
+ "@pkmn/data": "^0.7.44",
70
+ "@pkmn/protocol": "^0.6.12"
68
71
  },
69
72
  "devDependencies": {
70
- "@pkmn/eslint-config": "^3.0.0",
71
- "@pkmn/img": "^0.2.8",
73
+ "@pkmn/eslint-config": "^3.2.0",
74
+ "@pkmn/img": "^0.2.12",
72
75
  "@pkmn/sets": "^5.0.4",
73
- "@pkmn/sim": "0.7.36",
76
+ "@pkmn/sim": "0.7.44",
74
77
  "@types/html-minifier": "^4.0.2",
75
- "@types/jest": "^29.5.0",
78
+ "@types/jest": "^29.5.2",
76
79
  "@types/minimist": "^1.2.2",
77
80
  "@types/mustache": "^4.2.2",
78
- "@types/node": "^18.15.11",
79
- "@vitest/coverage-c8": "^0.29.8",
80
- "binaryen": "^112.0.0",
81
- "eslint": "^8.37.0",
81
+ "@types/node": "^20.3.1",
82
+ "@vitest/coverage-c8": "^0.32.2",
83
+ "binaryen": "^113.0.0",
84
+ "eslint": "^8.43.0",
82
85
  "html-minifier": "^4.0.0",
83
86
  "json-stringify-pretty-compact": "3.0.0",
84
87
  "minimist": "^1.2.8",
85
88
  "mustache": "^4.2.0",
86
89
  "source-map-support": "^0.5.21",
87
90
  "trakr": "^0.2.0",
88
- "typescript": "^5.0.3",
89
- "vitest": "^0.29.8"
91
+ "typescript": "^5.1.3",
92
+ "vitest": "^0.32.2"
90
93
  },
91
94
  "scripts": {
92
95
  "lint": "eslint --cache src --ext ts,js && eslint src/bin/*",
@@ -158,7 +161,8 @@
158
161
  },
159
162
  {
160
163
  "files": [
161
- "src/test/*.test.ts"
164
+ "src/test/*.test.ts",
165
+ "src/test/regression/*.test.ts"
162
166
  ],
163
167
  "rules": {
164
168
  "jest/no-standalone-expect": "off"
@@ -10,7 +10,7 @@ const path = require('path');
10
10
  const {pipeline} = require('stream/promises');
11
11
  const tty = require('tty');
12
12
 
13
- const ZIG_VERSION = {major: 0, minor: 11, patch: 0, dev: 2168};
13
+ const ZIG_VERSION = {major: 0, minor: 11, patch: 0, dev: 3859};
14
14
  const DEV = ZIG_VERSION.dev ? `-dev.${ZIG_VERSION.dev}` : '';
15
15
  const VERSION = `${ZIG_VERSION.major}.${ZIG_VERSION.minor}.${ZIG_VERSION.patch}${DEV}`;
16
16
  const INDEX = 'https://ziglang.org/download/index.json';
@@ -336,14 +336,18 @@ const downloadImportLib = async () => {
336
336
  node = true;
337
337
  if (!options) {
338
338
  const addon = require(copy);
339
- if (addon.options.showdown) opts.push('-Dshowdown');
340
- if (addon.options.trace) opts.push('-Dtrace');
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');
341
343
  }
342
344
  } else if (!options) {
343
345
  const addon = await WebAssembly.instantiate(fs.readFileSync(copy));
344
346
  const values = new Uint8Array(addon.instance.exports.memory.buffer);
345
347
  if (values[addon.instance.exports.SHOWDOWN.value]) opts.push('-Dshowdown');
346
- if (values[addon.instance.exports.TRACE.value]) opts.push('-Dtrace');
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');
347
351
  }
348
352
  const key = `${type} ${opts.sort().join(' ')}`;
349
353
  if (!configs.has(key)) configs.set(key, {type, options: opts});
@@ -354,8 +358,9 @@ const downloadImportLib = async () => {
354
358
  // either the default options or whatever was passed in
355
359
  if (!configs.size) {
356
360
  node = true;
357
- configs.set('node', {type: 'node', options: options || ['-Dshowdown']});
358
- configs.set('wasm', {type: 'wasm', options: options || ['-Dshowdown']});
361
+ const DEFAULT = ['-Dshowdown', '-Dlog', '-Dchance', '-Dcalc'];
362
+ configs.set('node', {type: 'node', options: options || DEFAULT});
363
+ configs.set('wasm', {type: 'wasm', options: options || DEFAULT});
359
364
  }
360
365
 
361
366
  const headers = node ? await downloadHeaders() : undefined;
@@ -1,34 +1,61 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict';
3
3
 
4
- require('source-map-support').install();
4
+ try {
5
+ require('source-map-support').install();
6
+ } catch (err) {
7
+ if (err.code !== 'MODULE_NOT_FOUND') throw err;
8
+ }
5
9
 
6
10
  const {execFileSync} = require('child_process');
11
+ const {readFileSync} = require('fs');
12
+ const {join, resolve} = require('path');
7
13
 
8
- const {Generations} = require('@pkmn/data');
14
+ const ROOT = resolve(__dirname, '..', '..');
15
+ const packageJSON = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8'));
9
16
 
10
- const {run} = require('../../build/tools/debug');
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
+ }
11
26
 
12
- let Dex;
27
+ let Dex = undefined;
13
28
  try {
14
29
  Dex = require('@pkmn/sim').Dex;
15
30
  } catch (err) {
16
31
  if (err.code !== 'MODULE_NOT_FOUND') throw err;
17
32
  try {
18
- require.resolve('@pkmn/dex');
33
+ Dex = require('@pkmn/dex').Dex;
19
34
  } catch (e) {
20
35
  if (e.code !== 'MODULE_NOT_FOUND') throw e;
21
- execFileSync('npm', ['install', '@pkmn/dex', '--global', '--silent']);
36
+ missing.push(`@pkmn/dex@${packageJSON.dependencies['@pkmn/data']}`);
22
37
  }
38
+ }
23
39
 
40
+ if (missing.length) {
24
41
  if (+/^v(\d+)/.exec(process.version)[1] < 16) {
25
42
  console.error('pkmn-debug requires Node v16+');
26
43
  process.exit(1);
27
44
  }
28
45
 
29
- Dex = require('@pkmn/dex').Dex;
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
+ }
30
53
  }
31
54
 
55
+ const {Generations} = require('@pkmn/data');
56
+
57
+ const {run} = require('../../build/tools/debug');
58
+
32
59
  run(new Generations(Dex)).catch(err => {
33
60
  console.error(err.message);
34
61
  process.exit(1);
@@ -1,6 +1,8 @@
1
1
  const std = @import("std");
2
2
  const pkmn = @import("../pkmn.zig");
3
3
 
4
+ const js = @import("../common/js.zig");
5
+
4
6
  const assert = std.debug.assert;
5
7
 
6
8
  const c = @cImport({
@@ -10,38 +12,49 @@ const c = @cImport({
10
12
 
11
13
  export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
12
14
  const properties = [_]c.napi_property_descriptor{
13
- Property.init("options", .{ .value = options(env) }),
14
- Property.init("bindings", .{ .value = bindings(env) }),
15
+ js.Property.init("engine", .{ .value = register(env) }),
15
16
  };
16
17
  assert(c.napi_define_properties(env, exports, properties.len, &properties) == c.napi_ok);
17
18
  return exports;
18
19
  }
19
20
 
21
+ pub fn register(env: c.napi_env) c.napi_value {
22
+ var object = js.Object.init(env);
23
+ const properties = [_]c.napi_property_descriptor{
24
+ js.Property.init("options", .{ .value = options(env) }),
25
+ js.Property.init("bindings", .{ .value = bindings(env) }),
26
+ };
27
+ assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
28
+ return object;
29
+ }
30
+
20
31
  fn options(env: c.napi_env) c.napi_value {
21
- var object = Object.init(env);
32
+ var object = js.Object.init(env);
22
33
  const properties = [_]c.napi_property_descriptor{
23
- Property.init("showdown", .{ .value = Boolean.init(env, pkmn.options.showdown) }),
24
- Property.init("trace", .{ .value = Boolean.init(env, pkmn.options.trace) }),
34
+ js.Property.init("showdown", .{ .value = js.Boolean.init(env, pkmn.options.showdown) }),
35
+ js.Property.init("log", .{ .value = js.Boolean.init(env, pkmn.options.log) }),
36
+ js.Property.init("chance", .{ .value = js.Boolean.init(env, pkmn.options.chance) }),
37
+ js.Property.init("calc", .{ .value = js.Boolean.init(env, pkmn.options.calc) }),
25
38
  };
26
39
  assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
27
40
  return object;
28
41
  }
29
42
 
30
43
  fn bindings(env: c.napi_env) c.napi_value {
31
- var array = Array.init(env, .{ .length = 1 });
32
- Array.set(env, array, 0, bind(env, pkmn.gen1));
44
+ var array = js.Array.init(env, .{ .length = 1 });
45
+ js.Array.set(env, array, 0, bind(env, pkmn.gen1));
33
46
  return array;
34
47
  }
35
48
 
36
49
  fn bind(env: c.napi_env, gen: anytype) c.napi_value {
37
- const options_size = @intCast(u32, gen.OPTIONS_SIZE);
38
- const logs_size = @intCast(u32, gen.LOGS_SIZE);
39
- var object = Object.init(env);
50
+ const choices_size: u32 = @intCast(gen.CHOICES_SIZE);
51
+ const logs_size: u32 = @intCast(gen.LOGS_SIZE);
52
+ var object = js.Object.init(env);
40
53
  const properties = [_]c.napi_property_descriptor{
41
- Property.init("OPTIONS_SIZE", .{ .value = Number.init(env, options_size) }),
42
- Property.init("LOGS_SIZE", .{ .value = Number.init(env, logs_size) }),
43
- Property.init("update", .{ .method = update(gen) }),
44
- Property.init("choices", .{ .method = choices(gen) }),
54
+ js.Property.init("CHOICES_SIZE", .{ .value = js.Number.init(env, choices_size) }),
55
+ js.Property.init("LOGS_SIZE", .{ .value = js.Number.init(env, logs_size) }),
56
+ js.Property.init("update", .{ .method = update(gen) }),
57
+ js.Property.init("choices", .{ .method = choices(gen) }),
45
58
  };
46
59
  assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
47
60
  return object;
@@ -61,28 +74,32 @@ fn update(gen: anytype) c.napi_callback {
61
74
  assert(len == @sizeOf(gen.Battle(gen.PRNG)));
62
75
  assert(data != null);
63
76
 
64
- var aligned = @alignCast(@alignOf(*gen.Battle(gen.PRNG)), data.?);
65
- var battle = @ptrCast(*gen.Battle(gen.PRNG), aligned);
66
- const c1 = @bitCast(pkmn.Choice, Number.get(env, argv[1], u8));
67
- const c2 = @bitCast(pkmn.Choice, Number.get(env, argv[2], u8));
77
+ var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
78
+ const c1: pkmn.Choice = @bitCast(js.Number.get(env, argv[1], u8));
79
+ const c2: pkmn.Choice = @bitCast(js.Number.get(env, argv[2], u8));
68
80
 
69
81
  var vtype: c.napi_valuetype = undefined;
70
82
  assert(c.napi_typeof(env, argv[3], &vtype) == c.napi_ok);
71
83
  const result = switch (vtype) {
72
- c.napi_undefined, c.napi_null => battle.update(c1, c2, pkmn.protocol.NULL),
84
+ c.napi_undefined, c.napi_null => battle.update(c1, c2, &gen.NULL),
73
85
  else => result: {
74
86
  assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
75
87
  assert(len == gen.LOGS_SIZE);
76
88
  assert(data != null);
77
89
 
78
- var buf = @ptrCast([*]u8, data.?)[0..gen.LOGS_SIZE];
90
+ var buf = @as([*]u8, @ptrCast(data.?))[0..gen.LOGS_SIZE];
79
91
  var stream = pkmn.protocol.ByteStream{ .buffer = buf };
80
- var log = pkmn.protocol.FixedLog{ .writer = stream.writer() };
81
- break :result battle.update(c1, c2, log);
92
+ // TODO: extract out
93
+ var opts = pkmn.battle.options(
94
+ pkmn.protocol.FixedLog{ .writer = stream.writer() },
95
+ gen.chance.NULL,
96
+ gen.calc.NULL,
97
+ );
98
+ break :result battle.update(c1, c2, &opts);
82
99
  },
83
100
  } catch unreachable;
84
101
 
85
- return Number.init(env, @bitCast(u8, result));
102
+ return js.Number.init(env, @as(u8, @bitCast(result)));
86
103
  }
87
104
  }.call;
88
105
  }
@@ -101,129 +118,17 @@ fn choices(gen: anytype) c.napi_callback {
101
118
  assert(len == @sizeOf(gen.Battle(gen.PRNG)));
102
119
  assert(data != null);
103
120
 
104
- var aligned = @alignCast(@alignOf(*gen.Battle(gen.PRNG)), data.?);
105
- var battle = @ptrCast(*gen.Battle(gen.PRNG), aligned);
106
-
107
- const player = @intToEnum(pkmn.Player, Number.get(env, argv[1], u8));
108
- const request = @intToEnum(pkmn.Choice.Type, Number.get(env, argv[2], u8));
121
+ var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
122
+ const player: pkmn.Player = @enumFromInt(js.Number.get(env, argv[1], u8));
123
+ const request: pkmn.Choice.Type = @enumFromInt(js.Number.get(env, argv[2], u8));
109
124
 
110
125
  assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
111
- assert(len == gen.OPTIONS_SIZE);
126
+ assert(len == gen.CHOICES_SIZE);
112
127
  assert(data != null);
113
128
 
114
- var out = @ptrCast([*]pkmn.Choice, data.?)[0..gen.OPTIONS_SIZE];
129
+ var out = @as([*]pkmn.Choice, @ptrCast(data.?))[0..gen.CHOICES_SIZE];
115
130
  const n = battle.choices(player, request, out);
116
- return Number.init(env, @bitCast(u8, n));
131
+ return js.Number.init(env, @as(u8, @bitCast(n)));
117
132
  }
118
133
  }.call;
119
134
  }
120
-
121
- const Array = struct {
122
- fn init(env: c.napi_env, o: struct { length: ?usize }) c.napi_value {
123
- var result: c.napi_value = undefined;
124
- assert(c.napi_ok == if (o.length) |n|
125
- c.napi_create_array_with_length(env, n, &result)
126
- else
127
- c.napi_create_array(env, &result));
128
- return result;
129
- }
130
-
131
- fn set(env: c.napi_env, array: c.napi_value, index: u32, value: c.napi_value) void {
132
- assert(c.napi_set_element(env, array, index, value) == c.napi_ok);
133
- }
134
- };
135
-
136
- const Boolean = struct {
137
- fn init(env: c.napi_env, value: bool) c.napi_value {
138
- var result: c.napi_value = undefined;
139
- assert(c.napi_get_boolean(env, value, &result) == c.napi_ok);
140
- return result;
141
- }
142
- };
143
-
144
- const Number = struct {
145
- fn init(env: c.napi_env, value: anytype) c.napi_value {
146
- const T = @TypeOf(value);
147
- var result: c.napi_value = undefined;
148
- assert(c.napi_ok == switch (@typeInfo(T)) {
149
- .Int => |info| switch (info.bits) {
150
- 0...32 => switch (info.signedness) {
151
- .signed => c.napi_create_int32(env, @as(i32, value), &result),
152
- .unsigned => c.napi_create_uint32(env, @as(u32, value), &result),
153
- },
154
- 33...52 => c.napi_create_int64(env, @as(i64, value), &result),
155
- else => @compileError("int can't be represented as JS number"),
156
- },
157
- else => @compileError("expected number, got: " ++ @typeName(T)),
158
- });
159
- return result;
160
- }
161
-
162
- fn get(env: c.napi_env, value: c.napi_value, comptime T: type) T {
163
- switch (@typeInfo(T)) {
164
- .Int => |info| switch (info.bits) {
165
- 0...32 => switch (info.signedness) {
166
- .signed => {
167
- var result: i32 = undefined;
168
- assert(c.napi_get_value_int32(env, value, &result) == c.napi_ok);
169
- return if (info.bits == 32) result else @intCast(T, result);
170
- },
171
- .unsigned => {
172
- var result: u32 = undefined;
173
- assert(c.napi_get_value_uint32(env, value, &result) == c.napi_ok);
174
- return if (info.bits == 32) result else @intCast(T, result);
175
- },
176
- },
177
- 33...63 => {
178
- var result: i64 = undefined;
179
- assert(c.napi_get_value_int64(env, value, &result) == c.napi_ok);
180
- return @intCast(T, result);
181
- },
182
- else => {
183
- var result: i64 = undefined;
184
- assert(c.napi_get_value_int64(env, value, &result) == c.napi_ok);
185
- return switch (info.signedness) {
186
- .signed => @as(T, value),
187
- .unsigned => if (0 <= value) @intCast(T, value) else unreachable,
188
- };
189
- },
190
- },
191
- else => @compileError("expected number, got: " ++ @typeName(T)),
192
- }
193
- }
194
- };
195
-
196
- const Object = struct {
197
- fn init(env: c.napi_env) c.napi_value {
198
- var result: c.napi_value = undefined;
199
- assert(c.napi_create_object(env, &result) == c.napi_ok);
200
- return result;
201
- }
202
- };
203
-
204
- const Property = union(enum) {
205
- method: c.napi_callback,
206
- value: c.napi_value,
207
-
208
- fn init(comptime name: [:0]const u8, property: Property) c.napi_property_descriptor {
209
- return .{
210
- .utf8name = name,
211
- .name = null,
212
- .method = switch (property) {
213
- .method => |m| m,
214
- .value => null,
215
- },
216
- .getter = null,
217
- .setter = null,
218
- .value = switch (property) {
219
- .method => null,
220
- .value => |v| v,
221
- },
222
- .attributes = switch (property) {
223
- .method => c.napi_default,
224
- .value => c.napi_enumerable,
225
- },
226
- .data = null,
227
- };
228
- }
229
- };