@pkmn/engine 0.1.0-dev.565de616 → 0.1.0-dev.587596a6

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 (57) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +21 -13
  3. package/build/pkg/addon/browser.js +4 -1
  4. package/build/pkg/addon/browser.js.map +1 -1
  5. package/build/pkg/addon/node.js +20 -7
  6. package/build/pkg/addon/node.js.map +1 -1
  7. package/build/pkg/addon/wasm.js +41 -9
  8. package/build/pkg/addon/wasm.js.map +1 -1
  9. package/build/pkg/addon.d.ts +7 -7
  10. package/build/pkg/addon.js +8 -17
  11. package/build/pkg/addon.js.map +1 -1
  12. package/build/pkg/gen1.d.ts +5 -5
  13. package/build/pkg/gen1.js +28 -19
  14. package/build/pkg/gen1.js.map +1 -1
  15. package/build/pkg/index.d.ts +6 -97
  16. package/build/pkg/index.js +23 -75
  17. package/build/pkg/index.js.map +1 -1
  18. package/build/tools/debug.js +36 -9
  19. package/build/tools/debug.js.map +1 -1
  20. package/build/tools/display/debug.jsx +18 -7
  21. package/build/tools/display/debug.jsx.map +1 -1
  22. package/build/tools/display/index.js +18 -7
  23. package/build/tools/display/index.js.map +1 -1
  24. package/build/tools/display/ui.jsx +18 -8
  25. package/build/tools/display/ui.jsx.map +1 -1
  26. package/build/tools/display/util.js +16 -1
  27. package/build/tools/display/util.js.map +1 -1
  28. package/build.zig +181 -198
  29. package/package.json +31 -130
  30. package/src/bin/install-pkmn-engine +24 -8
  31. package/src/lib/bindings/c.zig +200 -0
  32. package/src/lib/bindings/node.zig +117 -0
  33. package/src/lib/bindings/wasm.zig +50 -0
  34. package/src/lib/common/array.zig +5 -12
  35. package/src/lib/common/debug.zig +11 -21
  36. package/src/lib/common/js.zig +3 -9
  37. package/src/lib/common/optional.zig +11 -21
  38. package/src/lib/common/protocol.zig +176 -163
  39. package/src/lib/common/rational.zig +275 -284
  40. package/src/lib/common/util.zig +14 -31
  41. package/src/lib/gen1/calc.zig +65 -62
  42. package/src/lib/gen1/chance.zig +17 -32
  43. package/src/lib/gen1/data/types.zig +2 -2
  44. package/src/lib/gen1/data.zig +5 -5
  45. package/src/lib/gen1/helpers.zig +8 -10
  46. package/src/lib/gen1/mechanics.zig +32 -25
  47. package/src/lib/gen2/calc.zig +3 -6
  48. package/src/lib/gen2/chance.zig +12 -18
  49. package/src/lib/gen2/data/types.zig +6 -3
  50. package/src/lib/gen2/data.zig +8 -7
  51. package/src/lib/gen2/helpers.zig +4 -6
  52. package/src/lib/gen2/mechanics.zig +28 -17
  53. package/src/lib/napi.h +2 -0
  54. package/src/lib/node.zig +3 -118
  55. package/src/lib/pkmn.zig +94 -19
  56. package/src/lib/wasm.zig +10 -9
  57. package/src/tools/display/dom.ts +5 -8
@@ -3,10 +3,8 @@ const std = @import("std");
3
3
  const expect = std.testing.expect;
4
4
  const expectEqual = std.testing.expectEqual;
5
5
 
6
- const Pointer = if (@hasField(std.builtin.Type, "pointer")) .pointer else .Pointer;
7
-
8
6
  pub fn PointerType(comptime P: type, comptime C: type) type {
9
- return if (@field(@typeInfo(P), @tagName(Pointer)).is_const) *const C else *C;
7
+ return if (@typeInfo(P).pointer.is_const) *const C else *C;
10
8
  }
11
9
 
12
10
  test PointerType {
@@ -17,7 +15,7 @@ test PointerType {
17
15
  pub fn isPointerTo(p: anytype, comptime P: type) bool {
18
16
  const info = @typeInfo(@TypeOf(p));
19
17
  return switch (info) {
20
- Pointer => @field(info, @tagName(Pointer)).child == P,
18
+ .pointer => info.pointer.child == P,
21
19
  else => false,
22
20
  };
23
21
  }
@@ -29,33 +27,18 @@ test isPointerTo {
29
27
  try expect(isPointerTo(&s, S));
30
28
  }
31
29
 
32
- // NOTE: std.mem.bytesAsValue backported from ziglang/zig#18061
33
- pub fn bytesAsValue(comptime T: type, bytes: anytype) BytesAsValueReturnType(T, @TypeOf(bytes)) {
34
- return @as(BytesAsValueReturnType(T, @TypeOf(bytes)), @ptrCast(bytes));
35
- }
30
+ pub fn repeat(comptime xs: anytype, comptime n: usize) [xs.len * n]@TypeOf(xs[0]) {
31
+ comptime {
32
+ const T = @TypeOf(xs[0]);
33
+ var ts: [xs.len * n]T = undefined;
36
34
 
37
- fn BytesAsValueReturnType(comptime T: type, comptime B: type) type {
38
- return CopyPtrAttrs(B, .One, T);
39
- }
35
+ var i: usize = 0;
36
+ while (i < n) : (i += 1) {
37
+ for (xs, 0..) |v, j| {
38
+ ts[i * xs.len + j] = v;
39
+ }
40
+ }
40
41
 
41
- fn CopyPtrAttrs(
42
- comptime source: type,
43
- comptime size: std.builtin.Type.Pointer.Size,
44
- comptime child: type,
45
- ) type {
46
- const info = @field(@typeInfo(source), @tagName(Pointer));
47
- const args = .{
48
- .size = size,
49
- .is_const = info.is_const,
50
- .is_volatile = info.is_volatile,
51
- .is_allowzero = info.is_allowzero,
52
- .alignment = info.alignment,
53
- .address_space = info.address_space,
54
- .child = child,
55
- .sentinel = null,
56
- };
57
- return @Type(if (@hasField(std.builtin.Type, "pointer"))
58
- .{ .pointer = args }
59
- else
60
- .{ .Pointer = args });
42
+ return ts;
43
+ }
61
44
  }
@@ -35,9 +35,6 @@ const Sleeps = chance.Sleeps;
35
35
  const tty = true; // DEBUG
36
36
  const summary = false; // DEBUG
37
37
 
38
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
39
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
40
-
41
38
  /// Information relevant to damage calculation that occured during a Generation I battle `update`.
42
39
  pub const Summaries = extern struct {
43
40
  /// Relevant information for Player 1.
@@ -99,13 +96,13 @@ pub const Calc = struct {
99
96
  self: Calc,
100
97
  player: Player,
101
98
  comptime field: Action.Field,
102
- ) ?std.meta.FieldType(Action, field) {
99
+ ) ?@FieldType(Action, @tagName(field)) {
103
100
  if (!enabled) return null;
104
101
 
105
102
  const val = @field(self.overrides.get(player), @tagName(field));
106
103
  return if (switch (@typeInfo(@TypeOf(val))) {
107
- Enum => val != .None,
108
- Int => val != 0,
104
+ .@"enum" => val != .None,
105
+ .int => val != 0,
109
106
  else => unreachable,
110
107
  }) val else null;
111
108
  }
@@ -146,7 +143,7 @@ const Null = struct {
146
143
  self: Null,
147
144
  player: Player,
148
145
  comptime field: Action.Field,
149
- ) ?std.meta.FieldType(Action, field) {
146
+ ) ?@FieldType(Action, @tagName(field)) {
150
147
  _ = .{ self, player };
151
148
  return null;
152
149
  }
@@ -182,14 +179,15 @@ pub const Options = struct {
182
179
  metronome: bool = false,
183
180
  };
184
181
 
185
- pub const MAX_FRONTIER = 81;
182
+ pub const MAX_FRONTIER = 83;
186
183
 
187
184
  pub fn transitions(
188
185
  battle: anytype,
189
186
  c1: Choice,
190
187
  c2: Choice,
191
188
  allocator: std.mem.Allocator,
192
- writer: anytype,
189
+ out: *std.Io.Writer,
190
+ err: *std.Io.Writer,
193
191
  options: Options,
194
192
  ) !?Stats {
195
193
  var stats: Stats = .{};
@@ -198,8 +196,8 @@ pub fn transitions(
198
196
 
199
197
  var seen = std.AutoHashMap(Actions, void).init(allocator);
200
198
  defer seen.deinit();
201
- var frontier = std.ArrayList(Actions).init(allocator);
202
- defer frontier.deinit();
199
+ var frontier: std.array_list.Aligned(Actions, null) = .empty;
200
+ defer frontier.deinit(allocator);
203
201
 
204
202
  const d = options.durations;
205
203
 
@@ -216,8 +214,8 @@ pub fn transitions(
216
214
  const p1 = b.side(.P1);
217
215
  const p2 = b.side(.P2);
218
216
 
219
- var p: Rational(u128) = .{ .p = 0, .q = 1 };
220
- try frontier.append(opts.chance.actions);
217
+ var p: Rational(u256) = .{ .p = 0, .q = 1 };
218
+ try frontier.append(allocator, opts.chance.actions);
221
219
 
222
220
  // zig fmt: off
223
221
  for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
@@ -231,7 +229,9 @@ pub fn transitions(
231
229
  const f = frontier.items[i];
232
230
  const saved = stats.saved;
233
231
 
234
- try debug(writer, f, .{
232
+ var r: Rational(u256) = .{ .p = 0, .q = 1 };
233
+
234
+ try debug(out, f, .{
235
235
  .shape = true,
236
236
  .color = i,
237
237
  .bold = true,
@@ -251,8 +251,8 @@ pub fn transitions(
251
251
  for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
252
252
  for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
253
253
  for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
254
- for (Rolls.confusion(f.p1, d.p1, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
255
- for (Rolls.confusion(f.p2, d.p2, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
254
+ for (Rolls.confusion(f.p1, d.p1, tie, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
255
+ for (Rolls.confusion(f.p2, d.p2, tie, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
256
256
  for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
257
257
  for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
258
258
  for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
@@ -278,8 +278,8 @@ pub fn transitions(
278
278
 
279
279
  var p2_dmg = Rolls.damage(f.p2, p2_hit);
280
280
 
281
- const p1_min: u9 = p1_dmg.min;
282
- const p2_min: u9 = p2_dmg.min;
281
+ const p1_min: u9 = @intCast(p1_dmg.min);
282
+ const p2_min: u9 = @intCast(p2_dmg.min);
283
283
 
284
284
  while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
285
285
  a.p2.damage = @intCast(p2_dmg.min);
@@ -296,16 +296,16 @@ pub fn transitions(
296
296
 
297
297
  const summaries = &opts.calc.summaries;
298
298
  const p1_max: u9 = if (p2_dmg.min != p2_min)
299
- p1_dmg.min
299
+ @intCast(p1_dmg.min)
300
300
  else
301
- try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
301
+ Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
302
302
  const p2_max: u9 =
303
- try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
303
+ Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
304
304
 
305
305
  if (opts.chance.actions.matches(f)) {
306
306
  if (!opts.chance.actions.relax().eql(a)) {
307
307
  if (!summary) {
308
- try debug(writer, opts.chance.actions, .{
308
+ try debug(out, opts.chance.actions, .{
309
309
  .p1_max = p1_max,
310
310
  .p2_max = p2_max,
311
311
  .color = i,
@@ -319,7 +319,7 @@ pub fn transitions(
319
319
  }
320
320
 
321
321
  if (!summary) {
322
- try debug(writer, opts.chance.actions, .{
322
+ try debug(out, opts.chance.actions, .{
323
323
  .p1_max = p1_max,
324
324
  .p2_max = p2_max,
325
325
  .color = i,
@@ -332,7 +332,7 @@ pub fn transitions(
332
332
  acts.p1.damage = @intCast(p1d);
333
333
  acts.p2.damage = @intCast(p2d);
334
334
  if ((try seen.getOrPut(acts)).found_existing) {
335
- err("already seen {}", .{acts}, options.seed);
335
+ die(err, "already seen {f}", .{acts}, options.seed);
336
336
  return error.TestUnexpectedResult;
337
337
  }
338
338
  }
@@ -343,25 +343,27 @@ pub fn transitions(
343
343
  q.reduce();
344
344
  try p.add(q);
345
345
  p.reduce();
346
+ try r.add(q);
347
+ r.reduce();
346
348
 
347
349
  stats.saved += 1;
348
350
 
349
351
  if (p.q < p.p) {
350
- err("improper fraction {}", .{p}, options.seed);
352
+ die(err, "improper fraction {f}", .{p}, options.seed);
351
353
  return error.TestUnexpectedResult;
352
354
  }
353
355
  } else {
354
356
  if (!opts.chance.actions.matchesAny(frontier.items, i)) {
355
- try frontier.append(opts.chance.actions);
357
+ try frontier.append(allocator, opts.chance.actions);
356
358
 
357
- try debug(writer, opts.chance.actions, .{
359
+ try debug(out, opts.chance.actions, .{
358
360
  .p1_max = p1_max,
359
361
  .p2_max = p2_max,
360
362
  .dim = true,
361
363
  .newline = false,
362
364
  });
363
- try writer.writeAll(" → ");
364
- try debug(writer, opts.chance.actions, .{
365
+ try out.writeAll(" → ");
366
+ try debug(out, opts.chance.actions, .{
365
367
  .shape = true,
366
368
  .color = frontier.items.len - 1,
367
369
  .dim = true,
@@ -369,7 +371,7 @@ pub fn transitions(
369
371
  .indent = false,
370
372
  });
371
373
  } else if (!summary) {
372
- try debug(writer, opts.chance.actions, .{
374
+ try debug(out, opts.chance.actions, .{
373
375
  .p1_max = p1_max,
374
376
  .p2_max = p2_max,
375
377
  .dim = true,
@@ -384,14 +386,12 @@ pub fn transitions(
384
386
  }}}}}}}}}}}}}}}}}}}}}}}}}}}}
385
387
 
386
388
  assert(stats.saved > saved);
387
- const old = @hasDecl(std.fmt, "formatFloatDecimal");
388
- if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
389
- p.reduce();
390
- try writer.print(
391
- " = {s} ({d:.2}%)\n\n",
392
- .{p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
393
- );
394
- }
389
+ // TODO: LLVM - float_from_int from 'u256' without intrinsics
390
+ // try out.print(
391
+ // " {f} ({d:.2}%)\n = ──────────\n {f} ({d:.2}%)\n\n",
392
+ // .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
393
+ // p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
394
+ // );
395
395
 
396
396
  }
397
397
 
@@ -406,7 +406,7 @@ pub fn transitions(
406
406
 
407
407
  p.reduce();
408
408
  if (p.p != 1 or p.q != 1) {
409
- err("expected 1, found {}", .{p}, options.seed);
409
+ die(err, "expected 1, found {f}", .{p}, options.seed);
410
410
  return error.TestExpectedEqual;
411
411
  }
412
412
 
@@ -419,7 +419,8 @@ pub fn update(
419
419
  c2: Choice,
420
420
  options: anytype,
421
421
  allocator: std.mem.Allocator,
422
- writer: anytype,
422
+ out: *std.Io.Writer,
423
+ err: *std.Io.Writer,
423
424
  transition: bool,
424
425
  ) !Result {
425
426
  const durations = options.chance.durations;
@@ -437,8 +438,8 @@ pub fn update(
437
438
  if (@TypeOf(battle.rng) == data.PRNG) {
438
439
  // Ensure we can encode the diffs in less than MAX_DIFFS bytes.
439
440
  var buf: [helpers.MAX_DIFFS]u8 = undefined;
440
- var stream: protocol.ByteStream = .{ .buffer = &buf };
441
- const n = try helpers.diff(&original, battle, stream.writer());
441
+ var pw: protocol.Writer = .{ .buffer = &buf };
442
+ const n = try helpers.diff(&original, battle, &pw);
442
443
 
443
444
  // Applying the diff to the battle should take us back to the original copy
444
445
  // of the battle (ignoring the RNG).
@@ -452,7 +453,7 @@ pub fn update(
452
453
  // Ensure we can generate all transitions from the same original state
453
454
  // (we must change the battle's RNG from a FixedRNG to a PRNG because
454
455
  // the transitions function relies on RNG for discovery of states)
455
- if (try transitions(unfix(original), c1, c2, allocator, writer, .{
456
+ if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
456
457
  .durations = durations,
457
458
  .cap = true,
458
459
  })) |stats| try expect(stats.frontier <= MAX_FRONTIER);
@@ -499,10 +500,9 @@ pub fn update(
499
500
  fn expectEqualActions(expected: Actions, actual: Actions) !void {
500
501
  return expectEqual(expected, actual) catch |e| switch (e) {
501
502
  error.TestExpectedEqual => {
502
- std.debug.print("expected {}, found {}\n", .{ expected, actual });
503
+ std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
503
504
  return e;
504
505
  },
505
- else => return e,
506
506
  };
507
507
  }
508
508
 
@@ -521,10 +521,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
521
521
  };
522
522
  }
523
523
 
524
- fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
525
- const w = std.io.getStdErr().writer();
524
+ fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
526
525
  w.print(fmt, v) catch return;
527
- if (seed) |s| return w.print("{}\n", .{s}) catch return;
526
+ if (seed) |s| return w.print("{d}\n", .{s}) catch return;
528
527
  return w.writeByte('\n') catch return;
529
528
  }
530
529
 
@@ -540,7 +539,7 @@ const Style = struct {
540
539
  indent: bool = true,
541
540
  };
542
541
 
543
- fn debug(writer: anytype, actions: Actions, style: Style) !void {
542
+ fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
544
543
  if (style.indent) try writer.writeAll(" ");
545
544
  if (tty) {
546
545
  const mod: usize = if (style.dim) 2 else 1;
@@ -565,13 +564,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
565
564
  if (style.newline) try writer.writeByte('\n');
566
565
  }
567
566
 
568
- fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
567
+ fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
569
568
  var input: [1024]u8 = undefined;
570
569
  var output: [1024]u8 = undefined;
571
570
 
572
- var stream = std.io.fixedBufferStream(&input);
573
- try actions.fmt(&stream.writer(), false);
574
- var len = stream.getWritten().len;
571
+ var w = std.Io.Writer.fixed(&input);
572
+ try actions.fmt(&w, false);
573
+ var len = w.buffered().len;
575
574
  @memcpy(output[0..len], input[0..len]);
576
575
 
577
576
  var find: [16]u8 = undefined;
@@ -644,7 +643,7 @@ pub const Rolls = struct {
644
643
  };
645
644
  }
646
645
 
647
- /// FIXME
646
+ /// FIXME document
648
647
  pub fn attacking(
649
648
  action: Action,
650
649
  duration: Duration,
@@ -668,11 +667,13 @@ pub const Rolls = struct {
668
667
  const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
669
668
  const CFZ_ENDED = [_]Optional(Confusion){.ended};
670
669
  const CFZ = [_]Optional(Confusion){ .continuing, .ended };
670
+ const CFZ_TIE = [_]Optional(Confusion){ .started, .continuing, .ended };
671
671
 
672
- /// FIXME
672
+ /// FIXME document
673
673
  pub fn confusion(
674
674
  action: Action,
675
675
  duration: Duration,
676
+ tie: Optional(Player),
676
677
  sibling: Optional(Observation),
677
678
  parent: Optional(Observation),
678
679
  ) []const Optional(Confusion) {
@@ -686,6 +687,8 @@ pub const Rolls = struct {
686
687
  &CFZ_ENDED
687
688
  else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
688
689
  &CFZ_CONTINUING
690
+ else if (tie != .None)
691
+ &CFZ_TIE
689
692
  else
690
693
  &CFZ,
691
694
  };
@@ -731,7 +734,7 @@ pub const Rolls = struct {
731
734
  }
732
735
 
733
736
  /// The min and max bounds on iteration over damage rolls.
734
- pub const Range = struct { min: u9, max: u9 };
737
+ pub const Range = struct { min: usize, max: usize };
735
738
 
736
739
  /// Returns the range bounding damage rolls given the `action` state and the state of
737
740
  /// the `parent` (whether the player's Pokémon's move hit).
@@ -744,7 +747,7 @@ pub const Rolls = struct {
744
747
 
745
748
  /// Returns the max damage roll which will produce the same damage as `roll`
746
749
  /// given the base damage in `summaries`.
747
- pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) !u8 {
750
+ pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
748
751
  if (roll == 0) return roll;
749
752
 
750
753
  const dmg = summaries.get(player.foe()).damage;
@@ -854,12 +857,12 @@ test "Rolls.damage" {
854
857
 
855
858
  test "Rolls.coalesce" {
856
859
  var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
857
- try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
858
- try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
859
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
860
+ try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
861
+ try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
862
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
860
863
  summaries.p1.damage.final = 74;
861
- try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
862
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
864
+ try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
865
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
863
866
  }
864
867
 
865
868
  test "Rolls.hit" {
@@ -23,10 +23,6 @@ const PointerType = util.PointerType;
23
23
  const print = std.debug.print;
24
24
  const showdown = options.showdown;
25
25
 
26
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
27
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
28
- const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
29
-
30
26
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
31
27
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
32
28
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -56,15 +52,15 @@ pub const Actions = extern struct {
56
52
  /// same shape if they have the same fields set (though those fields need not necessarily be
57
53
  /// set to the same value).
58
54
  pub fn matches(a: Actions, b: Actions) bool {
59
- inline for (@field(@typeInfo(Actions), @tagName(Struct)).fields) |player| {
60
- inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
55
+ inline for (@typeInfo(Actions).@"struct".fields) |player| {
56
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
61
57
  const a_val = @field(@field(a, player.name), field.name);
62
58
  const b_val = @field(@field(b, player.name), field.name);
63
59
 
64
60
  switch (@typeInfo(@TypeOf(a_val))) {
65
- Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
61
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
66
62
  return false,
67
- Int => if ((a_val > 0) != (b_val > 0)) return false,
63
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
68
64
  else => unreachable,
69
65
  }
70
66
  }
@@ -91,7 +87,7 @@ pub const Actions = extern struct {
91
87
  return a;
92
88
  }
93
89
 
94
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
90
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
95
91
  try writer.writeAll("<P1 = ");
96
92
  try self.p1.fmt(writer, shape);
97
93
  try writer.writeAll(", P2 = ");
@@ -99,8 +95,7 @@ pub const Actions = extern struct {
99
95
  try writer.writeAll(">");
100
96
  }
101
97
 
102
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
103
- _ = .{ f, o };
98
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
104
99
  try fmt(a, w, false);
105
100
  }
106
101
  };
@@ -191,18 +186,17 @@ pub const Action = packed struct(u64) {
191
186
 
192
187
  pub const Field = std.meta.FieldEnum(Action);
193
188
 
194
- pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
195
- _ = .{ f, o };
189
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
196
190
  try fmt(a, w, false);
197
191
  }
198
192
 
199
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
193
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
200
194
  try writer.writeByte('(');
201
195
  var printed = false;
202
- inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
196
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
203
197
  const val = @field(self, field.name);
204
198
  switch (@typeInfo(@TypeOf(val))) {
205
- Enum => if (val != .None) {
199
+ .@"enum" => if (val != .None) {
206
200
  if (printed) try writer.writeAll(", ");
207
201
  if (shape) {
208
202
  try writer.print("{s}:?", .{field.name});
@@ -223,7 +217,7 @@ pub const Action = packed struct(u64) {
223
217
  }
224
218
  printed = true;
225
219
  },
226
- Int => if (val != 0) {
220
+ .int => if (val != 0) {
227
221
  if (printed) try writer.writeAll(", ");
228
222
  if (shape) {
229
223
  try writer.print("{s}:?", .{field.name});
@@ -256,14 +250,8 @@ pub const Durations = extern struct {
256
250
  return if (player == .P1) &self.p1 else &self.p2;
257
251
  }
258
252
 
259
- pub fn format(
260
- self: Durations,
261
- comptime fmt: []const u8,
262
- opts: std.fmt.FormatOptions,
263
- writer: anytype,
264
- ) !void {
265
- _ = .{ fmt, opts };
266
- try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
253
+ pub fn format(self: Durations, writer: *std.Io.Writer) !void {
254
+ try writer.print("[P1 = {f}, P2 = {f}]", .{ self.p1, self.p2 });
267
255
  }
268
256
  };
269
257
 
@@ -281,17 +269,14 @@ pub const Duration = packed struct(u32) {
281
269
 
282
270
  pub fn format(
283
271
  self: Duration,
284
- comptime fmt: []const u8,
285
- opts: std.fmt.FormatOptions,
286
- writer: anytype,
272
+ writer: *std.Io.Writer,
287
273
  ) !void {
288
- _ = .{ fmt, opts };
289
274
  try writer.writeByte('(');
290
275
  var printed = false;
291
- inline for (@field(@typeInfo(Duration), @tagName(Struct)).fields) |field| {
276
+ inline for (@typeInfo(Duration).@"struct".fields) |field| {
292
277
  const val = @field(self, field.name);
293
278
  switch (@typeInfo(@TypeOf(val))) {
294
- Int => if (val != 0) {
279
+ .int => if (val != 0) {
295
280
  if (printed) try writer.writeAll(", ");
296
281
  if (comptime std.mem.eql(u8, field.name, "sleeps")) {
297
282
  try writer.print("{s}:[", .{field.name});
@@ -1067,7 +1052,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
1067
1052
 
1068
1053
  r.reduce();
1069
1054
  if (r.p != p or r.q != q) {
1070
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
1055
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
1071
1056
  return error.TestExpectedEqual;
1072
1057
  }
1073
1058
  }
@@ -98,7 +98,7 @@ pub const Type = enum(u4) {
98
98
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
99
99
  }
100
100
 
101
- /// The precedence order of Type `t2` vs. Type `t1`.
101
+ /// The precedence order of Type `t1` vs. Type `t2`.
102
102
  pub fn precedence(t1: Type, t2: Type) u8 {
103
103
  for (PRECEDENCE, 0..) |matchup, i| {
104
104
  if (matchup.type1 == t1 and matchup.type2 == t2) return @intCast(i);
@@ -108,7 +108,7 @@ pub const Type = enum(u4) {
108
108
  };
109
109
 
110
110
  /// Representation of a Pokémon's typing.
111
- pub const Types = packed struct {
111
+ pub const Types = packed struct(u8) {
112
112
  /// A Pokémon's primary type.
113
113
  type1: Type = .Normal,
114
114
  /// A Pokémon's secondary type (may be identical to its primary type).
@@ -62,7 +62,7 @@ pub fn Battle(comptime RNG: anytype) type {
62
62
  /// The last damage dealt by either side.
63
63
  last_damage: u16 = 0,
64
64
  /// Details about the last move selected/used by each player required to detect desyncs.
65
- last_moves: [2]MoveDetails = [_]MoveDetails{.{}} ** 2,
65
+ last_moves: [2]MoveDetails = @splat(.{}),
66
66
  /// The pseudo random number generator.
67
67
  rng: RNG,
68
68
 
@@ -122,12 +122,12 @@ test Battle {
122
122
  /// Representation of one side of a Generation I Pokémon battle.
123
123
  pub const Side = extern struct {
124
124
  /// The player's party in its original order
125
- pokemon: [6]Pokemon = [_]Pokemon{.{}} ** 6,
125
+ pokemon: [6]Pokemon = @splat(.{}),
126
126
  /// The active Pokémon for the side, zero initialized if the battle has yet to start.
127
127
  /// Note that fainted Pokémon are still consider "active" until their replacement switches in.
128
128
  active: ActivePokemon = .{},
129
129
  /// One-based slot indexes reflecting the current order of the player's party.
130
- order: [6]u8 = [_]u8{0} ** 6,
130
+ order: [6]u8 = @splat(0),
131
131
  /// The last move the player selected.
132
132
  last_selected_move: Move = .None,
133
133
  /// The last move the player used.
@@ -166,7 +166,7 @@ pub const ActivePokemon = extern struct {
166
166
  /// The active Pokémon's volatile statuses and associated data.
167
167
  volatiles: Volatiles = .{},
168
168
  /// The active Pokémon's current move slots.
169
- moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
169
+ moves: [4]MoveSlot = @splat(.{}),
170
170
 
171
171
  comptime {
172
172
  assert(@sizeOf(ActivePokemon) == 32);
@@ -186,7 +186,7 @@ pub const Pokemon = extern struct {
186
186
  /// The Pokémon's unmodified stats.
187
187
  stats: Stats(u16) = .{},
188
188
  /// The Pokémon's original stored move slots.
189
- moves: [4]MoveSlot = [_]MoveSlot{.{}} ** 4,
189
+ moves: [4]MoveSlot = @splat(.{}),
190
190
  /// The Pokémon's current HP.
191
191
  hp: u16 = 0,
192
192
  /// The Pokémon's current status.