@pkmn/engine 0.1.0-dev.a15b6c17 → 0.1.0-dev.a19bc07d

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/README.md CHANGED
@@ -13,12 +13,10 @@
13
13
  </a>
14
14
  </p>
15
15
 
16
- > [!WARNING]
16
+ > [!WARNING]
17
17
  > **This project is under heavy development** and currently the `main` branch contains numerous breaking
18
18
  > changes which may not work and which are not fully documented. Please **wait for the forthcoming
19
- > initial v0.1 release** before depending on this project (code from the
20
- > [`old`](https://github.com/pkmn/engine/releases/tag/old) tag can also be used to experiment with a
21
- > more stable version of the codebase).
19
+ > initial v0.1 release** before depending on this project.
22
20
 
23
21
  <hr />
24
22
 
@@ -86,8 +84,11 @@ lifecycle script](https://docs.npmjs.com/cli/v8/using-npm/scripts) runs
86
84
  it can't find one, as well as looking for (and downloading, if necessary) the required Node headers
87
85
  needed to successfully build the addons natively.
88
86
 
89
- **If you have configured `npm` to `--ignore-scripts` you must either run `npx install-pkmn-engine`
90
- directly or build the addons manually and place the artifacts in the expected paths.**
87
+ > [!IMPORTANT]
88
+ > If you have configured `npm` to `--ignore-scripts` (or are using [npm v12+ and have not
89
+ > approved](https://github.blog/changelog/2026-06-09-upcoming-breaking-changes-for-npm-v12/)
90
+ > `@pkmn/engine` via `npm approve-scripts @pkmn/engine`) **you must either run `npx install-pkmn-engine`
91
+ > directly or build the addons manually and place the artifacts in the expected paths.**
91
92
 
92
93
  ### `pkmn`
93
94
 
package/build.zig CHANGED
@@ -12,7 +12,12 @@ pub const Options = struct {
12
12
  calc: ?bool = null,
13
13
  };
14
14
 
15
- pub fn module(b: *std.Build, options: Options) *std.Build.Module {
15
+ pub fn module(
16
+ b: *std.Build,
17
+ options: Options,
18
+ target: std.Build.ResolvedTarget,
19
+ optimize: std.builtin.OptimizeMode,
20
+ ) *std.Build.Module {
16
21
  const build_options = b.addOptions();
17
22
  build_options.addOption(?bool, "showdown", options.showdown);
18
23
  build_options.addOption(?bool, "log", options.log);
@@ -22,7 +27,12 @@ pub fn module(b: *std.Build, options: Options) *std.Build.Module {
22
27
  const root_source_file = b.path("src/lib/pkmn.zig");
23
28
  const imports: []const std.Build.Module.Import =
24
29
  &.{.{ .name = "build_options", .module = build_options.createModule() }};
25
- return b.createModule(.{ .root_source_file = root_source_file, .imports = imports });
30
+ return b.createModule(.{
31
+ .root_source_file = root_source_file,
32
+ .target = target,
33
+ .optimize = optimize,
34
+ .imports = imports,
35
+ });
26
36
  }
27
37
 
28
38
  pub fn build(b: *std.Build) !void {
@@ -45,7 +55,10 @@ pub fn build(b: *std.Build) !void {
45
55
  const emit_asm = b.option(bool, "emit-asm", "Output .s (assembly code)") orelse false;
46
56
  const emit_ll = b.option(bool, "emit-ll", "Output .ll (LLVM IR)") orelse false;
47
57
 
48
- const cmd = b.findProgram(&.{"strip"}, &.{}) catch null;
58
+ const cmd = if (@hasDecl(std.Build, "FindProgramOptions"))
59
+ b.findProgram(.{ .names = &.{"strip"} })
60
+ else
61
+ b.findProgram(&.{"strip"}, &.{}) catch null;
49
62
 
50
63
  const json = @embedFile("package.json");
51
64
  var parsed = try std.json.parseFromSlice(std.json.Value, b.allocator, json, .{});
@@ -99,7 +112,8 @@ pub fn build(b: *std.Build) !void {
99
112
  const translate_c = b.addTranslateC(.{
100
113
  .root_source_file = b.path("src/lib/napi.h"),
101
114
  .target = target,
102
- .optimize = optimize,
115
+ // TODO: workaround for ziglang/zig#35515
116
+ .optimize = .Debug,
103
117
  });
104
118
  translate_c.addSystemIncludePath(b.path(headers));
105
119
  const addon = b.fmt("{s}.node", .{name});
@@ -195,7 +209,9 @@ pub fn build(b: *std.Build) !void {
195
209
  b.fmt("{s}.ll", .{name}),
196
210
  ).step);
197
211
  }
198
- b.installArtifact(lib);
212
+ const install = b.addInstallArtifact(lib, .{});
213
+ b.getInstallStep().dependOn(&install.step);
214
+ maybeRanlib(b, lib, &install.step);
199
215
  c = true;
200
216
  }
201
217
 
@@ -204,34 +220,45 @@ pub fn build(b: *std.Build) !void {
204
220
  const header = b.addInstallFileWithDir(path, .header, "pkmn.h");
205
221
  b.getInstallStep().dependOn(&header.step);
206
222
 
207
- const pc = b.fmt("lib{s}.pc", .{name});
208
- const cwd = try std.process.currentPathAlloc(b.graph.io, b.allocator);
209
- const file = try std.Io.Dir.path.relative(
210
- b.allocator,
211
- cwd,
212
- &b.graph.environ_map,
213
- cwd,
214
- try b.cache_root.join(b.allocator, &.{pc}),
215
- );
216
- const pkgconfig_file = try std.Io.Dir.cwd().createFile(b.graph.io, file, .{});
217
- defer pkgconfig_file.close(b.graph.io);
218
-
219
- const dirname = comptime std.fs.path.dirname(@src().file) orelse ".";
220
- var writer = pkgconfig_file.writer(b.graph.io, &.{});
221
- try writer.interface.print(
222
- \\prefix={0s}/{1s}
223
+ const content = try std.fmt.allocPrint(b.allocator,
224
+ \\prefix=${{pcfiledir}}/../..
223
225
  \\includedir=${{prefix}}/include
224
226
  \\libdir=${{prefix}}/lib
225
227
  \\
226
- \\Name: lib{2s}
227
- \\URL: https://github.com/{3s}
228
- \\Description: {4s}
229
- \\Version: {5s}
228
+ \\Name: lib{0s}
229
+ \\URL: https://github.com/{1s}
230
+ \\Description: {2s}
231
+ \\Version: {3s}
230
232
  \\Cflags: -I${{includedir}}
231
- \\Libs: -L${{libdir}} -l{2s}
232
- , .{ dirname, b.install_path, name, repository.next().?, description, version });
233
+ \\Libs: -L${{libdir}} -l{0s}
234
+ , .{ name, repository.next().?, description, version });
233
235
 
234
- b.installFile(file, b.fmt("share/pkgconfig/{s}", .{pc}));
236
+ const pc = b.fmt("lib{s}.pc", .{name});
237
+ if (@hasDecl(std.Build, "FindProgramOptions")) {
238
+ const write_file = b.addWriteFiles();
239
+ const pkgconfig = write_file.add(pc, content);
240
+ b.getInstallStep().dependOn(&b.addInstallFileWithDir(
241
+ pkgconfig,
242
+ .prefix,
243
+ b.fmt("share/pkgconfig/{s}", .{pc}),
244
+ ).step);
245
+ } else {
246
+ const cwd = try std.process.currentPathAlloc(b.graph.io, b.allocator);
247
+ const file = try std.Io.Dir.path.relative(
248
+ b.allocator,
249
+ cwd,
250
+ &b.graph.environ_map,
251
+ cwd,
252
+ try b.cache_root.join(b.allocator, &.{pc}),
253
+ );
254
+ const pkgconfig = try std.Io.Dir.cwd().createFile(b.graph.io, file, .{});
255
+ defer pkgconfig.close(b.graph.io);
256
+
257
+ var writer = pkgconfig.writer(b.graph.io, &.{});
258
+ try writer.interface.writeAll(content);
259
+
260
+ b.installFile(file, b.fmt("share/pkgconfig/{s}", .{pc}));
261
+ }
235
262
  }
236
263
 
237
264
  const config: Config = .{
@@ -253,7 +280,6 @@ pub fn build(b: *std.Build) !void {
253
280
  .chance = chance,
254
281
  .calc = calc,
255
282
  },
256
- .module = pkmn,
257
283
  .general = config,
258
284
  .tool = .{
259
285
  .tests = if (tests.build) tests else null,
@@ -285,8 +311,8 @@ pub fn build(b: *std.Build) !void {
285
311
  if (dump) |t| b.step("dump", "Run protocol dump tool").dependOn(&t.step);
286
312
  if (fuzz) |t| b.step("fuzz", "Run fuzz tester").dependOn(&t.step);
287
313
  if (serde) |t| b.step("serde", "Run serialization/deserialization tool").dependOn(&t.step);
288
- b.step("test", "Run all tests").dependOn(&tests.step);
289
- b.step("tools", "Install tools").dependOn(&ToolsStep.create(b, &exes).step);
314
+ b.step("test", "Run all tests").dependOn(tests.step);
315
+ b.step("tools", "Install tools").dependOn(ToolsStep.create(b, &exes).step);
290
316
  if (transitions) |t| {
291
317
  b.step("transitions", "Visualize transitions algorithm search").dependOn(&t.step);
292
318
  }
@@ -356,7 +382,12 @@ fn buildWasm(
356
382
  exe.stack_size = wasm_stack_size;
357
383
  exe.root_module.addOptions("build_options", options);
358
384
 
359
- const opt = b.findProgram(&.{"wasm-opt"}, &.{"./node_modules/.bin"}) catch null;
385
+ const opt = if (@hasDecl(std.Build, "FindProgramOptions")) blk: {
386
+ if (exists(b, "./node_modules/.bin/wasm-opt") catch false) {
387
+ break :blk "./node_modules/.bin/wasm-opt";
388
+ }
389
+ break :blk b.findProgram(.{ .names = &.{"wasm-opt"} });
390
+ } else b.findProgram(&.{"wasm-opt"}, &.{"./node_modules/.bin"}) catch null;
360
391
  if (optimize != .Debug and opt != null) {
361
392
  const out = b.fmt("build/lib/{s}.wasm", .{name});
362
393
  const sh = b.addSystemCommand(&.{ opt.?, "--enable-bulk-memory", "--enable-simd", "-O4" });
@@ -398,7 +429,7 @@ const Config = struct {
398
429
  };
399
430
 
400
431
  const TestStep = struct {
401
- step: std.Build.Step,
432
+ step: *std.Build.Step,
402
433
  build: bool,
403
434
 
404
435
  pub fn create(b: *std.Build, options: *std.Build.Step.Options, config: Config) *TestStep {
@@ -407,8 +438,6 @@ const TestStep = struct {
407
438
  b.option([]const u8, "test-filter", "Skip tests that do not match filter");
408
439
 
409
440
  const self = b.allocator.create(TestStep) catch @panic("OOM");
410
- const step = std.Build.Step.init(.{ .id = .custom, .name = "Run all tests", .owner = b });
411
- self.* = .{ .step = step, .build = test_filter == null };
412
441
 
413
442
  const path = b.path("src/lib/test.zig");
414
443
  const tests = b.addTest(.{
@@ -428,7 +457,20 @@ const TestStep = struct {
428
457
  if (coverage) |c| {
429
458
  tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
430
459
  }
431
- self.step.dependOn(&b.addRunArtifact(tests).step);
460
+
461
+ if (@hasDecl(std.Build, "FindProgramOptions")) {
462
+ const run_step = b.addRunArtifact(tests);
463
+ self.* = .{ .step = &run_step.step, .build = test_filter == null };
464
+ } else {
465
+ const custom_step = b.allocator.create(std.Build.Step) catch @panic("OOM");
466
+ custom_step.* = std.Build.Step.init(.{
467
+ .id = .custom,
468
+ .name = "Run all tests",
469
+ .owner = b,
470
+ });
471
+ custom_step.dependOn(&b.addRunArtifact(tests).step);
472
+ self.* = .{ .step = custom_step, .build = test_filter == null };
473
+ }
432
474
 
433
475
  return self;
434
476
  }
@@ -436,7 +478,6 @@ const TestStep = struct {
436
478
 
437
479
  const ToolConfig = struct {
438
480
  options: Options,
439
- module: ?*std.Build.Module,
440
481
  general: Config,
441
482
  tool: struct {
442
483
  tests: ?*TestStep,
@@ -463,7 +504,7 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.R
463
504
  }),
464
505
  });
465
506
 
466
- const import = if (config.module) |m| m else module(b, config.options);
507
+ const import = module(b, config.options, config.general.target, config.general.optimize);
467
508
  exe.root_module.addImport("pkmn", import);
468
509
  if (config.general.target.result.os.tag == .windows) {
469
510
  exe.root_module.linkSystemLibrary("advapi32", .{});
@@ -474,7 +515,11 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.R
474
515
 
475
516
  const run = b.addRunArtifact(exe);
476
517
  maybeStrip(b, exe, &run.step, config.general.strip, config.general.cmd);
477
- if (b.args) |args| run.addArgs(args);
518
+ if (@hasDecl(std.Build, "FindProgramOptions")) {
519
+ run.addPassthruArgs();
520
+ } else {
521
+ if (b.args) |args| run.addArgs(args);
522
+ }
478
523
 
479
524
  return run;
480
525
  }
@@ -488,13 +533,33 @@ fn exists(b: *std.Build, path: []const u8) !bool {
488
533
  }
489
534
 
490
535
  const ToolsStep = struct {
491
- step: std.Build.Step,
536
+ step: *std.Build.Step,
492
537
 
493
538
  pub fn create(b: *std.Build, exes: *ArrayList(*std.Build.Step.Compile)) *ToolsStep {
494
539
  const self = b.allocator.create(ToolsStep) catch @panic("OOM");
495
- const step = std.Build.Step.init(.{ .id = .custom, .name = "Install tools", .owner = b });
496
- self.* = .{ .step = step };
540
+
541
+ if (@hasDecl(std.Build, "FindProgramOptions")) {
542
+ const step = b.allocator.create(std.Build.Step.TopLevel) catch @panic("OOM");
543
+ step.* = .{
544
+ .step = std.Build.Step.init(.{
545
+ .tag = .top_level,
546
+ .name = "Install tools",
547
+ .owner = b,
548
+ }),
549
+ .description = "Install tools",
550
+ };
551
+ self.* = .{ .step = &step.step };
552
+ } else {
553
+ const step = b.allocator.create(std.Build.Step) catch @panic("OOM");
554
+ step.* = std.Build.Step.init(.{
555
+ .id = .custom,
556
+ .name = "Install tools",
557
+ .owner = b,
558
+ });
559
+ self.* = .{ .step = step };
560
+ }
497
561
  for (exes.items) |t| self.step.dependOn(&b.addInstallArtifact(t, .{}).step);
562
+
498
563
  return self;
499
564
  }
500
565
  };
@@ -516,3 +581,21 @@ pub fn exports(b: *std.Build, bytes: []const u8) ![][]const u8 {
516
581
 
517
582
  return symbols.items;
518
583
  }
584
+
585
+ fn maybeRanlib(
586
+ b: *std.Build,
587
+ artifact: *std.Build.Step.Compile,
588
+ install_step: *std.Build.Step,
589
+ ) void {
590
+ if (builtin.os.tag != .macos) return;
591
+ if (artifact.linkage != .static) return;
592
+
593
+ const ranlib = if (comptime @hasDecl(std.Build, "FindProgramOptions"))
594
+ b.findProgram(.{ .names = &.{"ranlib"} }) orelse return
595
+ else
596
+ b.findProgram(&[_][]const u8{"ranlib"}, &[_][]const u8{}) catch return;
597
+
598
+ const sh = b.addSystemCommand(&[_][]const u8{ranlib});
599
+ sh.addFileArg(artifact.getEmittedBin());
600
+ install_step.dependOn(&sh.step);
601
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pkmn/engine",
3
- "version": "0.1.0-dev.a15b6c17",
3
+ "version": "0.1.0-dev.a19bc07d",
4
4
  "description": "A minimal, complete, Pokémon battle simulation engine optimized for performance",
5
5
  "repository": "github:pkmn/engine",
6
6
  "license": "MIT",
@@ -100,12 +100,12 @@
100
100
  },
101
101
  "dependencies": {
102
102
  "@pkmn/data": "0.9.35",
103
- "@pkmn/protocol": "^0.7.2"
103
+ "@pkmn/protocol": "^0.7.3"
104
104
  },
105
105
  "devDependencies": {
106
106
  "@pkmn/dex-types": "0.9.35",
107
- "@pkmn/eslint-config": "^9.27.0",
108
- "@pkmn/img": "^0.3.3",
107
+ "@pkmn/eslint-config": "^9.28.0",
108
+ "@pkmn/img": "^0.3.4",
109
109
  "@pkmn/sets": "^5.2.0",
110
110
  "@pkmn/sim": "0.9.31",
111
111
  "@pkmn/smogon": "0.5.21",
@@ -113,25 +113,25 @@
113
113
  "@types/jest": "^30.0.0",
114
114
  "@types/minimist": "^1.2.5",
115
115
  "@types/mustache": "^4.2.6",
116
- "@types/node": "^25.6.0",
117
- "@types/react": "^19.2.14",
116
+ "@types/node": "^26.0.0",
117
+ "@types/react": "^19.2.17",
118
118
  "@types/semver": "^7.7.1",
119
- "@types/vscode": "^1.118.0",
120
- "@vitest/coverage-v8": "^4.1.5",
121
- "@vscode/vsce": "^3.9.1",
122
- "binaryen": "^129.0.0",
123
- "esbuild": "^0.28.0",
119
+ "@types/vscode": "^1.125.0",
120
+ "@vitest/coverage-v8": "^4.1.9",
121
+ "@vscode/vsce": "^3.9.2",
122
+ "binaryen": "^130.0.0",
123
+ "esbuild": "^0.28.1",
124
124
  "eslint": "<10",
125
125
  "html-minifier": "^4.0.0",
126
126
  "json-stringify-pretty-compact": "3.0.0",
127
127
  "minimist": "^1.2.8",
128
128
  "mustache": "^4.2.0",
129
- "semver": "^7.7.4",
129
+ "semver": "^7.8.5",
130
130
  "source-map-support": "^0.5.21",
131
131
  "table": "^6.9.0",
132
132
  "trakr": "^0.2.0",
133
133
  "typescript": "^6.0.3",
134
- "vitest": "^4.1.5"
134
+ "vitest": "^4.1.9"
135
135
  },
136
136
  "scripts": {
137
137
  "lint": "eslint --cache src",
@@ -1,5 +1,6 @@
1
1
  const pkmn = @import("../pkmn.zig");
2
2
  const std = @import("std");
3
+ const util = @import("../common/util.zig");
3
4
 
4
5
  const assert = std.debug.assert;
5
6
 
@@ -23,7 +24,7 @@ pub const MAX_LOGS = pkmn.MAX_LOGS;
23
24
  pub const LOGS_SIZE = pkmn.LOGS_SIZE;
24
25
 
25
26
  pub fn choice_init(choice: u8, data: u8) callconv(.c) u8 {
26
- assert(choice <= @typeInfo(pkmn.Choice.Type).@"enum".fields.len);
27
+ assert(choice <= util.EnumFieldsLen(pkmn.Choice.Type));
27
28
  assert(data <= 6);
28
29
  return @bitCast(pkmn.Choice{ .type = @enumFromInt(choice), .data = @intCast(data) });
29
30
  }
@@ -190,8 +191,8 @@ pub fn gen(comptime num: comptime_int) type {
190
191
  out: [*]u8,
191
192
  n: usize,
192
193
  ) callconv(.c) u8 {
193
- assert(player <= @typeInfo(pkmn.Player).@"enum".fields.len);
194
- assert(request <= @typeInfo(pkmn.Choice.Type).@"enum".fields.len);
194
+ assert(player <= util.EnumFieldsLen(pkmn.Player));
195
+ assert(request <= util.EnumFieldsLen(pkmn.Choice.Type));
195
196
 
196
197
  assert(!pkmn.options.showdown or n > 0);
197
198
  return battle.choices(@enumFromInt(player), @enumFromInt(request), @ptrCast(out[0..n]));
@@ -1,5 +1,6 @@
1
1
  const pkmn = @import("../pkmn.zig");
2
2
  const std = @import("std");
3
+ const util = @import("../common/util.zig");
3
4
 
4
5
  const assert = std.debug.assert;
5
6
 
@@ -36,8 +37,8 @@ pub fn gen(comptime num: comptime_int) type {
36
37
  request: u8,
37
38
  buf: [*]u8,
38
39
  ) callconv(.c) u8 {
39
- assert(player <= @typeInfo(pkmn.Player).@"enum".fields.len);
40
- assert(request <= @typeInfo(pkmn.Choice.Type).@"enum".fields.len);
40
+ assert(player <= util.EnumFieldsLen(pkmn.Player));
41
+ assert(request <= util.EnumFieldsLen(pkmn.Choice.Type));
41
42
 
42
43
  const n = CHOICES_SIZE;
43
44
  return battle.choices(@enumFromInt(player), @enumFromInt(request), @ptrCast(buf[0..n]));
@@ -24,9 +24,20 @@ pub fn print(value: anytype) void {
24
24
  switch (@typeInfo(@TypeOf(value))) {
25
25
  .@"struct" => |info| {
26
26
  if (info.is_tuple) {
27
- inline for (info.fields, 0..) |f, i| {
28
- inspect(fw, @field(value, f.name));
29
- if (i < info.fields.len - 1) fw.interface.writeAll(" ") catch return;
27
+ if (@hasField(@TypeOf(info), "fields")) {
28
+ inline for (info.fields, 0..) |f, i| {
29
+ inspect(fw, @field(value, f.name));
30
+ if (i < info.fields.len - 1) {
31
+ fw.interface.writeAll(" ") catch return;
32
+ }
33
+ }
34
+ } else {
35
+ inline for (info.field_names, 0..) |name, i| {
36
+ inspect(fw, @field(value, name));
37
+ if (i < info.field_names.len - 1) {
38
+ fw.interface.writeAll(" ") catch return;
39
+ }
40
+ }
30
41
  }
31
42
  } else {
32
43
  inspect(fw, value);
@@ -6,21 +6,31 @@ const assert = std.debug.assert;
6
6
  /// Optimized optional representation which stores the empty None value as a sentinel.
7
7
  /// NOTE: ziglang/zig#104
8
8
  pub fn Optional(comptime T: type) type {
9
- const fields = std.meta.fields(switch (@typeInfo(T)) {
10
- .bool => enum { false, true },
11
- else => T,
12
- });
9
+ const names = if (@hasDecl(std.meta, "fieldNames"))
10
+ std.meta.fieldNames(switch (@typeInfo(T)) {
11
+ .bool => enum { false, true },
12
+ else => T,
13
+ })
14
+ else blk: {
15
+ const fields = std.meta.fields(switch (@typeInfo(T)) {
16
+ .bool => enum { false, true },
17
+ else => T,
18
+ });
19
+ var ns: [fields.len][:0]const u8 = undefined;
20
+ inline for (fields, 0..) |f, i| ns[i] = f.name;
21
+ break :blk &ns;
22
+ };
13
23
 
14
- const TagType = std.math.IntFittingRange(0, fields.len);
15
- var fieldNames: [fields.len + 1][]const u8 = undefined;
16
- var fieldValues: [fields.len + 1]TagType = undefined;
24
+ const TagType = std.math.IntFittingRange(0, names.len);
25
+ var fieldNames: [names.len + 1][]const u8 = undefined;
26
+ var fieldValues: [names.len + 1]TagType = undefined;
17
27
 
18
28
  fieldNames[0] = "None";
19
29
  fieldValues[0] = 0;
20
30
 
21
- inline for (fields, 1..) |field, i| {
22
- assert(!std.mem.eql(u8, field.name, "None"));
23
- fieldNames[i] = field.name;
31
+ inline for (names, 1..) |name, i| {
32
+ assert(!std.mem.eql(u8, name, "None"));
33
+ fieldNames[i] = name;
24
34
  fieldValues[i] = i;
25
35
  }
26
36
 
@@ -4,7 +4,12 @@ const expect = std.testing.expect;
4
4
  const expectEqual = std.testing.expectEqual;
5
5
 
6
6
  pub fn PointerType(comptime P: type, comptime C: type) type {
7
- return if (@typeInfo(P).pointer.is_const) *const C else *C;
7
+ const ptr_info = @typeInfo(P).pointer;
8
+ const is_const = if (@hasField(@TypeOf(ptr_info), "attrs"))
9
+ ptr_info.attrs.@"const"
10
+ else
11
+ ptr_info.is_const;
12
+ return if (is_const) *const C else *C;
8
13
  }
9
14
 
10
15
  test PointerType {
@@ -12,6 +17,21 @@ test PointerType {
12
17
  try expectEqual(*const f64, PointerType(*const i32, f64));
13
18
  }
14
19
 
20
+ pub fn EnumFieldsLen(comptime E: type) usize {
21
+ const info = @typeInfo(E).@"enum";
22
+ return if (@hasField(@TypeOf(info), "fields"))
23
+ info.fields.len
24
+ else
25
+ info.field_names.len;
26
+ }
27
+
28
+ test EnumFieldsLen {
29
+ const E1 = enum { A, B, C };
30
+ const E2 = enum(u8) { A = 0, B = 10, C = 20, D = 30 };
31
+ try expectEqual(3, EnumFieldsLen(E1));
32
+ try expectEqual(4, EnumFieldsLen(E2));
33
+ }
34
+
15
35
  pub fn isPointerTo(p: anytype, comptime P: type) bool {
16
36
  const info = @typeInfo(@TypeOf(p));
17
37
  return switch (info) {
@@ -52,10 +52,23 @@ pub const Actions = extern struct {
52
52
  /// same shape if they have the same fields set (though those fields need not necessarily be
53
53
  /// set to the same value).
54
54
  pub fn matches(a: Actions, b: Actions) bool {
55
- inline for (@typeInfo(Actions).@"struct".fields) |player| {
56
- inline for (@typeInfo(Action).@"struct".fields) |field| {
57
- const a_val = @field(@field(a, player.name), field.name);
58
- const b_val = @field(@field(b, player.name), field.name);
55
+ inline for (if (@hasField(@TypeOf(@typeInfo(Actions).@"struct"), "fields"))
56
+ @typeInfo(Actions).@"struct".fields
57
+ else
58
+ @typeInfo(Actions).@"struct".field_names) |player|
59
+ {
60
+ const player_name = if (@hasField(@TypeOf(player), "name"))
61
+ player.name
62
+ else
63
+ player;
64
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
65
+ @typeInfo(Action).@"struct".fields
66
+ else
67
+ @typeInfo(Action).@"struct".field_names) |field|
68
+ {
69
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
70
+ const a_val = @field(@field(a, player_name), field_name);
71
+ const b_val = @field(@field(b, player_name), field_name);
59
72
 
60
73
  switch (@typeInfo(@TypeOf(a_val))) {
61
74
  .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
@@ -216,31 +229,36 @@ pub const Action = packed struct(u64) {
216
229
  pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
217
230
  try writer.writeByte('(');
218
231
  var printed = false;
219
- inline for (@typeInfo(Action).@"struct".fields) |field| {
220
- const val = @field(self, field.name);
232
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
233
+ @typeInfo(Action).@"struct".fields
234
+ else
235
+ @typeInfo(Action).@"struct".field_names) |field|
236
+ {
237
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
238
+ const val = @field(self, field_name);
221
239
  switch (@typeInfo(@TypeOf(val))) {
222
240
  .@"enum" => if (val != .None) {
223
241
  if (printed) try writer.writeAll(", ");
224
242
  if (shape) {
225
- try writer.print("{s}:?", .{field.name});
243
+ try writer.print("{s}:?", .{field_name});
226
244
  } else if (@TypeOf(val) == Optional(bool)) {
227
245
  try writer.print("{s}{s}", .{
228
246
  if (val == .false) "!" else "",
229
- field.name,
247
+ field_name,
230
248
  });
231
249
  } else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
232
- try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field.name });
250
+ try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field_name });
233
251
  } else {
234
- try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
252
+ try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
235
253
  }
236
254
  printed = true;
237
255
  },
238
256
  .int => if (val != 0) {
239
257
  if (printed) try writer.writeAll(", ");
240
258
  if (shape) {
241
- try writer.print("{s}:?", .{field.name});
259
+ try writer.print("{s}:?", .{field_name});
242
260
  } else {
243
- try writer.print("{s}:{d}", .{ field.name, val });
261
+ try writer.print("{s}:{d}", .{ field_name, val });
244
262
  }
245
263
  printed = true;
246
264
  },
@@ -293,20 +311,25 @@ pub const Duration = packed struct(u32) {
293
311
  ) !void {
294
312
  try writer.writeByte('(');
295
313
  var printed = false;
296
- inline for (@typeInfo(Duration).@"struct".fields) |field| {
297
- const val = @field(self, field.name);
314
+ inline for (if (@hasField(@TypeOf(@typeInfo(Duration).@"struct"), "fields"))
315
+ @typeInfo(Duration).@"struct".fields
316
+ else
317
+ @typeInfo(Duration).@"struct".field_names) |field|
318
+ {
319
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
320
+ const val = @field(self, field_name);
298
321
  switch (@typeInfo(@TypeOf(val))) {
299
322
  .int => if (val != 0) {
300
323
  if (printed) try writer.writeAll(", ");
301
- if (comptime std.mem.eql(u8, field.name, "sleeps")) {
302
- try writer.print("{s}:[", .{field.name});
324
+ if (comptime std.mem.eql(u8, field_name, "sleeps")) {
325
+ try writer.print("{s}:[", .{field_name});
303
326
  for (0..6) |i| {
304
327
  if (i != 0) try writer.writeByte(',');
305
328
  try writer.print("{d}", .{Sleeps.get(val, i)});
306
329
  }
307
330
  try writer.writeAll("]");
308
331
  } else {
309
- try writer.print("{s}:{d}", .{ field.name, val });
332
+ try writer.print("{s}:{d}", .{ field_name, val });
310
333
  }
311
334
  printed = true;
312
335
  },
@@ -161,15 +161,20 @@ pub const Pokemon = struct {
161
161
  var pokemon = data.Pokemon{};
162
162
  pokemon.species = p.species;
163
163
  const species = Species.get(p.species);
164
- inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
165
- const hp = comptime std.mem.eql(u8, field.name, "hp");
164
+ inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(pokemon.stats)).@"struct"), "fields"))
165
+ @typeInfo(@TypeOf(pokemon.stats)).@"struct".fields
166
+ else
167
+ @typeInfo(@TypeOf(pokemon.stats)).@"struct".field_names) |field|
168
+ {
169
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
170
+ const hp = comptime std.mem.eql(u8, field_name, "hp");
166
171
  const spc =
167
- comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
168
- @field(pokemon.stats, field.name) = Stats(u16).calc(
169
- field.name,
170
- @field(species.stats, field.name),
171
- if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field.name),
172
- @field(p.stats, field.name),
172
+ comptime std.mem.eql(u8, field_name, "spa") or std.mem.eql(u8, field_name, "spd");
173
+ @field(pokemon.stats, field_name) = Stats(u16).calc(
174
+ field_name,
175
+ @field(species.stats, field_name),
176
+ if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field_name),
177
+ @field(p.stats, field_name),
173
178
  p.level,
174
179
  );
175
180
  }
@@ -197,14 +202,19 @@ pub const Pokemon = struct {
197
202
  const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
198
203
  var stats: Stats(u16) = .{};
199
204
  const dvs = DVs.random(rand);
200
- inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
201
- @field(stats, field.name) = Stats(u16).calc(
202
- field.name,
203
- @field(species.stats, field.name),
204
- if (comptime std.mem.eql(u8, field.name, "hp"))
205
+ inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(stats)).@"struct"), "fields"))
206
+ @typeInfo(@TypeOf(stats)).@"struct".fields
207
+ else
208
+ @typeInfo(@TypeOf(stats)).@"struct".field_names) |field|
209
+ {
210
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
211
+ @field(stats, field_name) = Stats(u16).calc(
212
+ field_name,
213
+ @field(species.stats, field_name),
214
+ if (comptime std.mem.eql(u8, field_name, "hp"))
205
215
  dvs.hp()
206
216
  else
207
- @field(dvs, field.name),
217
+ @field(dvs, field_name),
208
218
  if (rand.chance(u8, 1, 20)) rand.range(u16, 0, EXP + 1) else EXP,
209
219
  lvl,
210
220
  );
@@ -53,10 +53,23 @@ pub const Actions = extern struct {
53
53
  /// same shape if they have the same fields set (though those fields need not necessarily be
54
54
  /// set to the same value).
55
55
  pub fn matches(a: Actions, b: Actions) bool {
56
- inline for (@typeInfo(Actions).@"struct".fields) |player| {
57
- inline for (@typeInfo(Action).@"struct".fields) |field| {
58
- const a_val = @field(@field(a, player.name), field.name);
59
- const b_val = @field(@field(b, player.name), field.name);
56
+ inline for (if (@hasField(@TypeOf(@typeInfo(Actions).@"struct"), "fields"))
57
+ @typeInfo(Actions).@"struct".fields
58
+ else
59
+ @typeInfo(Actions).@"struct".field_names) |player|
60
+ {
61
+ const player_name = if (@hasField(@TypeOf(player), "name"))
62
+ player.name
63
+ else
64
+ player;
65
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
66
+ @typeInfo(Action).@"struct".fields
67
+ else
68
+ @typeInfo(Action).@"struct".field_names) |field|
69
+ {
70
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
71
+ const a_val = @field(@field(a, player_name), field_name);
72
+ const b_val = @field(@field(b, player_name), field_name);
60
73
 
61
74
  switch (@typeInfo(@TypeOf(a_val))) {
62
75
  .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
@@ -188,29 +201,34 @@ pub const Action = packed struct(u128) {
188
201
  pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
189
202
  try writer.writeByte('(');
190
203
  var printed = false;
191
- inline for (@typeInfo(Action).@"struct".fields) |field| {
192
- const val = @field(self, field.name);
204
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
205
+ @typeInfo(Action).@"struct".fields
206
+ else
207
+ @typeInfo(Action).@"struct".field_names) |field|
208
+ {
209
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
210
+ const val = @field(self, field_name);
193
211
  switch (@typeInfo(@TypeOf(val))) {
194
212
  .@"enum" => if (val != .None) {
195
213
  if (printed) try writer.writeAll(", ");
196
214
  if (shape) {
197
- try writer.print("{s}:?", .{field.name});
215
+ try writer.print("{s}:?", .{field_name});
198
216
  } else if (@TypeOf(val) == Optional(bool)) {
199
217
  try writer.print("{s}{s}", .{
200
218
  if (val == .false) "!" else "",
201
- field.name,
219
+ field_name,
202
220
  });
203
221
  } else {
204
- try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
222
+ try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
205
223
  }
206
224
  printed = true;
207
225
  },
208
226
  .int => if (val != 0) {
209
227
  if (printed) try writer.writeAll(", ");
210
228
  if (shape) {
211
- try writer.print("{s}:?", .{field.name});
229
+ try writer.print("{s}:?", .{field_name});
212
230
  } else {
213
- try writer.print("{s}:{d}", .{ field.name, val });
231
+ try writer.print("{s}:{d}", .{ field_name, val });
214
232
  }
215
233
  printed = true;
216
234
  },
@@ -140,15 +140,20 @@ pub const Pokemon = struct {
140
140
  var pokemon = data.Pokemon{};
141
141
  pokemon.species = p.species;
142
142
  const species = Species.get(p.species);
143
- inline for (@typeInfo(@TypeOf(pokemon.stats)).@"struct".fields) |field| {
144
- const hp = comptime std.mem.eql(u8, field.name, "hp");
143
+ inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(pokemon.stats)).@"struct"), "fields"))
144
+ @typeInfo(@TypeOf(pokemon.stats)).@"struct".fields
145
+ else
146
+ @typeInfo(@TypeOf(pokemon.stats)).@"struct".field_names) |field|
147
+ {
148
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
149
+ const hp = comptime std.mem.eql(u8, field_name, "hp");
145
150
  const spc =
146
- comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
147
- @field(pokemon.stats, field.name) = Stats(u16).calc(
148
- field.name,
149
- @field(species.stats, field.name),
150
- if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field.name),
151
- @field(p.stats, field.name),
151
+ comptime std.mem.eql(u8, field_name, "spa") or std.mem.eql(u8, field_name, "spd");
152
+ @field(pokemon.stats, field_name) = Stats(u16).calc(
153
+ field_name,
154
+ @field(species.stats, field_name),
155
+ if (hp) p.dvs.hp() else if (spc) p.dvs.spc else @field(p.dvs, field_name),
156
+ @field(p.stats, field_name),
152
157
  p.level,
153
158
  );
154
159
  }
@@ -181,13 +186,18 @@ pub const Pokemon = struct {
181
186
 
182
187
  var stats: Stats(u16) = .{};
183
188
  const dvs = gen1.DVs.random(rand);
184
- inline for (@typeInfo(@TypeOf(stats)).@"struct".fields) |field| {
185
- const hp = std.mem.eql(u8, field.name, "hp");
186
- const spc = std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
187
- @field(stats, field.name) = Stats(u16).calc(
188
- field.name,
189
- @field(species.stats, field.name),
190
- if (hp) dvs.hp() else if (spc) dvs.spc else @field(dvs, field.name),
189
+ inline for (if (@hasField(@TypeOf(@typeInfo(@TypeOf(stats)).@"struct"), "fields"))
190
+ @typeInfo(@TypeOf(stats)).@"struct".fields
191
+ else
192
+ @typeInfo(@TypeOf(stats)).@"struct".field_names) |field|
193
+ {
194
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
195
+ const hp = std.mem.eql(u8, field_name, "hp");
196
+ const spc = std.mem.eql(u8, field_name, "spa") or std.mem.eql(u8, field_name, "spd");
197
+ @field(stats, field_name) = Stats(u16).calc(
198
+ field_name,
199
+ @field(species.stats, field_name),
200
+ if (hp) dvs.hp() else if (spc) dvs.spc else @field(dvs, field_name),
191
201
  if (rand.chance(u8, 1, 20)) rand.range(u8, 0, 255 + 1) else 255,
192
202
  lvl,
193
203
  );