@pkmn/engine 0.1.0-dev.724afcb1 → 0.1.0-dev.72514829

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,7 +13,7 @@
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
19
  > initial v0.1 release** before depending on this project.
@@ -57,11 +57,7 @@ $ zig build --prefix /usr/local -Doptimize=ReleaseFast
57
57
  The Zig website has [installation instructions](https://ziglang.org/learn/getting-started/) which
58
58
  walk through how to install Zig on each platform - the engine code should work on Zig v0.16.0,
59
59
  though tracks Zig's master branch so this may change in the future if breaking language changes are
60
- introduced. Note that **due to a [bug in the Zig
61
- compiler](https://github.com/ziglang/zig/issues/17768), building a version of [Zig from
62
- source](https://codeberg.org/ziglang/zig#building-from-source) after having applied a
63
- [patch](zig.patch) to revert [ziglang/zig#17391](https://github.com/ziglang/zig/pull/17391) is
64
- recommended for performance**.
60
+ introduced.
65
61
 
66
62
  `libpkmn` can be built with `-Dshowdown` to instead produce the Pokémon Showdown compatible
67
63
  `libpkmn-showdown` library. Furthermore, protocol message logging can be enabled through `-Dlog`.
@@ -84,8 +80,11 @@ lifecycle script](https://docs.npmjs.com/cli/v8/using-npm/scripts) runs
84
80
  it can't find one, as well as looking for (and downloading, if necessary) the required Node headers
85
81
  needed to successfully build the addons natively.
86
82
 
87
- **If you have configured `npm` to `--ignore-scripts` you must either run `npx install-pkmn-engine`
88
- directly or build the addons manually and place the artifacts in the expected paths.**
83
+ > [!IMPORTANT]
84
+ > If you have configured `npm` to `--ignore-scripts` (or are using [npm v12+ and have not
85
+ > approved](https://github.blog/changelog/2026-06-09-upcoming-breaking-changes-for-npm-v12/)
86
+ > `@pkmn/engine` via `npm approve-scripts @pkmn/engine`) **you must either run `npx install-pkmn-engine`
87
+ > directly or build the addons manually and place the artifacts in the expected paths.**
89
88
 
90
89
  ### `pkmn`
91
90
 
package/build.zig CHANGED
@@ -55,7 +55,10 @@ pub fn build(b: *std.Build) !void {
55
55
  const emit_asm = b.option(bool, "emit-asm", "Output .s (assembly code)") orelse false;
56
56
  const emit_ll = b.option(bool, "emit-ll", "Output .ll (LLVM IR)") orelse false;
57
57
 
58
- 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;
59
62
 
60
63
  const json = @embedFile("package.json");
61
64
  var parsed = try std.json.parseFromSlice(std.json.Value, b.allocator, json, .{});
@@ -109,7 +112,8 @@ pub fn build(b: *std.Build) !void {
109
112
  const translate_c = b.addTranslateC(.{
110
113
  .root_source_file = b.path("src/lib/napi.h"),
111
114
  .target = target,
112
- .optimize = optimize,
115
+ // TODO: workaround for ziglang/zig#35515
116
+ .optimize = .Debug,
113
117
  });
114
118
  translate_c.addSystemIncludePath(b.path(headers));
115
119
  const addon = b.fmt("{s}.node", .{name});
@@ -205,7 +209,9 @@ pub fn build(b: *std.Build) !void {
205
209
  b.fmt("{s}.ll", .{name}),
206
210
  ).step);
207
211
  }
208
- b.installArtifact(lib);
212
+ const install = b.addInstallArtifact(lib, .{});
213
+ b.getInstallStep().dependOn(&install.step);
214
+ maybeRanlib(b, lib, &install.step);
209
215
  c = true;
210
216
  }
211
217
 
@@ -214,34 +220,45 @@ pub fn build(b: *std.Build) !void {
214
220
  const header = b.addInstallFileWithDir(path, .header, "pkmn.h");
215
221
  b.getInstallStep().dependOn(&header.step);
216
222
 
217
- const pc = b.fmt("lib{s}.pc", .{name});
218
- const cwd = try std.process.currentPathAlloc(b.graph.io, b.allocator);
219
- const file = try std.Io.Dir.path.relative(
220
- b.allocator,
221
- cwd,
222
- &b.graph.environ_map,
223
- cwd,
224
- try b.cache_root.join(b.allocator, &.{pc}),
225
- );
226
- const pkgconfig_file = try std.Io.Dir.cwd().createFile(b.graph.io, file, .{});
227
- defer pkgconfig_file.close(b.graph.io);
228
-
229
- const dirname = comptime std.fs.path.dirname(@src().file) orelse ".";
230
- var writer = pkgconfig_file.writer(b.graph.io, &.{});
231
- try writer.interface.print(
232
- \\prefix={0s}/{1s}
223
+ const content = try std.fmt.allocPrint(b.allocator,
224
+ \\prefix=${{pcfiledir}}/../..
233
225
  \\includedir=${{prefix}}/include
234
226
  \\libdir=${{prefix}}/lib
235
227
  \\
236
- \\Name: lib{2s}
237
- \\URL: https://github.com/{3s}
238
- \\Description: {4s}
239
- \\Version: {5s}
228
+ \\Name: lib{0s}
229
+ \\URL: https://github.com/{1s}
230
+ \\Description: {2s}
231
+ \\Version: {3s}
240
232
  \\Cflags: -I${{includedir}}
241
- \\Libs: -L${{libdir}} -l{2s}
242
- , .{ dirname, b.install_path, name, repository.next().?, description, version });
233
+ \\Libs: -L${{libdir}} -l{0s}
234
+ , .{ name, repository.next().?, description, version });
243
235
 
244
- 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
+ }
245
262
  }
246
263
 
247
264
  const config: Config = .{
@@ -294,8 +311,8 @@ pub fn build(b: *std.Build) !void {
294
311
  if (dump) |t| b.step("dump", "Run protocol dump tool").dependOn(&t.step);
295
312
  if (fuzz) |t| b.step("fuzz", "Run fuzz tester").dependOn(&t.step);
296
313
  if (serde) |t| b.step("serde", "Run serialization/deserialization tool").dependOn(&t.step);
297
- b.step("test", "Run all tests").dependOn(&tests.step);
298
- 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);
299
316
  if (transitions) |t| {
300
317
  b.step("transitions", "Visualize transitions algorithm search").dependOn(&t.step);
301
318
  }
@@ -365,7 +382,12 @@ fn buildWasm(
365
382
  exe.stack_size = wasm_stack_size;
366
383
  exe.root_module.addOptions("build_options", options);
367
384
 
368
- 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;
369
391
  if (optimize != .Debug and opt != null) {
370
392
  const out = b.fmt("build/lib/{s}.wasm", .{name});
371
393
  const sh = b.addSystemCommand(&.{ opt.?, "--enable-bulk-memory", "--enable-simd", "-O4" });
@@ -407,7 +429,7 @@ const Config = struct {
407
429
  };
408
430
 
409
431
  const TestStep = struct {
410
- step: std.Build.Step,
432
+ step: *std.Build.Step,
411
433
  build: bool,
412
434
 
413
435
  pub fn create(b: *std.Build, options: *std.Build.Step.Options, config: Config) *TestStep {
@@ -416,8 +438,6 @@ const TestStep = struct {
416
438
  b.option([]const u8, "test-filter", "Skip tests that do not match filter");
417
439
 
418
440
  const self = b.allocator.create(TestStep) catch @panic("OOM");
419
- const step = std.Build.Step.init(.{ .id = .custom, .name = "Run all tests", .owner = b });
420
- self.* = .{ .step = step, .build = test_filter == null };
421
441
 
422
442
  const path = b.path("src/lib/test.zig");
423
443
  const tests = b.addTest(.{
@@ -437,7 +457,20 @@ const TestStep = struct {
437
457
  if (coverage) |c| {
438
458
  tests.setExecCmd(&.{ "kcov", "--include-pattern=src/lib", c, null });
439
459
  }
440
- 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
+ }
441
474
 
442
475
  return self;
443
476
  }
@@ -482,7 +515,11 @@ fn tool(b: *std.Build, path: []const u8, config: ToolConfig) !?*std.Build.Step.R
482
515
 
483
516
  const run = b.addRunArtifact(exe);
484
517
  maybeStrip(b, exe, &run.step, config.general.strip, config.general.cmd);
485
- 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
+ }
486
523
 
487
524
  return run;
488
525
  }
@@ -496,13 +533,33 @@ fn exists(b: *std.Build, path: []const u8) !bool {
496
533
  }
497
534
 
498
535
  const ToolsStep = struct {
499
- step: std.Build.Step,
536
+ step: *std.Build.Step,
500
537
 
501
538
  pub fn create(b: *std.Build, exes: *ArrayList(*std.Build.Step.Compile)) *ToolsStep {
502
539
  const self = b.allocator.create(ToolsStep) catch @panic("OOM");
503
- const step = std.Build.Step.init(.{ .id = .custom, .name = "Install tools", .owner = b });
504
- 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
+ }
505
561
  for (exes.items) |t| self.step.dependOn(&b.addInstallArtifact(t, .{}).step);
562
+
506
563
  return self;
507
564
  }
508
565
  };
@@ -524,3 +581,21 @@ pub fn exports(b: *std.Build, bytes: []const u8) ![][]const u8 {
524
581
 
525
582
  return symbols.items;
526
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.724afcb1",
3
+ "version": "0.1.0-dev.72514829",
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) {
@@ -45,17 +45,31 @@ pub const Actions = extern struct {
45
45
 
46
46
  /// Returns true if `a` is equal to `b`.
47
47
  pub fn eql(a: Actions, b: Actions) bool {
48
- return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
48
+ return @as(u64, @bitCast(a.p1)) == @as(u64, @bitCast(b.p1)) and
49
+ @as(u64, @bitCast(a.p2)) == @as(u64, @bitCast(b.p2));
49
50
  }
50
51
 
51
52
  /// Returns true if `a` has the same "shape" as `b`, where `Actions` are defined to have the
52
53
  /// same shape if they have the same fields set (though those fields need not necessarily be
53
54
  /// set to the same value).
54
55
  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);
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);
59
73
 
60
74
  switch (@typeInfo(@TypeOf(a_val))) {
61
75
  .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
@@ -216,31 +230,36 @@ pub const Action = packed struct(u64) {
216
230
  pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
217
231
  try writer.writeByte('(');
218
232
  var printed = false;
219
- inline for (@typeInfo(Action).@"struct".fields) |field| {
220
- const val = @field(self, field.name);
233
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
234
+ @typeInfo(Action).@"struct".fields
235
+ else
236
+ @typeInfo(Action).@"struct".field_names) |field|
237
+ {
238
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
239
+ const val = @field(self, field_name);
221
240
  switch (@typeInfo(@TypeOf(val))) {
222
241
  .@"enum" => if (val != .None) {
223
242
  if (printed) try writer.writeAll(", ");
224
243
  if (shape) {
225
- try writer.print("{s}:?", .{field.name});
244
+ try writer.print("{s}:?", .{field_name});
226
245
  } else if (@TypeOf(val) == Optional(bool)) {
227
246
  try writer.print("{s}{s}", .{
228
247
  if (val == .false) "!" else "",
229
- field.name,
248
+ field_name,
230
249
  });
231
250
  } else if (@TypeOf(val) != Move and @TypeOf(val) != Optional(Player)) {
232
- try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field.name });
251
+ try writer.print("{s}{s}", .{ SYMBOLS[@intFromEnum(val) - 1], field_name });
233
252
  } else {
234
- try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
253
+ try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
235
254
  }
236
255
  printed = true;
237
256
  },
238
257
  .int => if (val != 0) {
239
258
  if (printed) try writer.writeAll(", ");
240
259
  if (shape) {
241
- try writer.print("{s}:?", .{field.name});
260
+ try writer.print("{s}:?", .{field_name});
242
261
  } else {
243
- try writer.print("{s}:{d}", .{ field.name, val });
262
+ try writer.print("{s}:{d}", .{ field_name, val });
244
263
  }
245
264
  printed = true;
246
265
  },
@@ -293,20 +312,25 @@ pub const Duration = packed struct(u32) {
293
312
  ) !void {
294
313
  try writer.writeByte('(');
295
314
  var printed = false;
296
- inline for (@typeInfo(Duration).@"struct".fields) |field| {
297
- const val = @field(self, field.name);
315
+ inline for (if (@hasField(@TypeOf(@typeInfo(Duration).@"struct"), "fields"))
316
+ @typeInfo(Duration).@"struct".fields
317
+ else
318
+ @typeInfo(Duration).@"struct".field_names) |field|
319
+ {
320
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
321
+ const val = @field(self, field_name);
298
322
  switch (@typeInfo(@TypeOf(val))) {
299
323
  .int => if (val != 0) {
300
324
  if (printed) try writer.writeAll(", ");
301
- if (comptime std.mem.eql(u8, field.name, "sleeps")) {
302
- try writer.print("{s}:[", .{field.name});
325
+ if (comptime std.mem.eql(u8, field_name, "sleeps")) {
326
+ try writer.print("{s}:[", .{field_name});
303
327
  for (0..6) |i| {
304
328
  if (i != 0) try writer.writeByte(',');
305
329
  try writer.print("{d}", .{Sleeps.get(val, i)});
306
330
  }
307
331
  try writer.writeAll("]");
308
332
  } else {
309
- try writer.print("{s}:{d}", .{ field.name, val });
333
+ try writer.print("{s}:{d}", .{ field_name, val });
310
334
  }
311
335
  printed = true;
312
336
  },
@@ -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
  );