@pkmn/engine 0.1.0-dev.dd5b9081 → 0.1.0-dev.deda8593

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 (53) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +44 -19
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data/ids.json +12 -12
  6. package/build/pkg/data/layout.json +2 -2
  7. package/build/pkg/data/protocol.json +69 -12
  8. package/build/pkg/data.d.ts +4 -4
  9. package/build/pkg/data.js.map +1 -1
  10. package/build/pkg/gen1.js +2 -2
  11. package/build/pkg/gen1.js.map +1 -1
  12. package/build/pkg/index.d.ts +2 -1
  13. package/build/pkg/index.js.map +1 -1
  14. package/build/pkg/protocol.js +61 -42
  15. package/build/pkg/protocol.js.map +1 -1
  16. package/build/test/display.js +2 -2
  17. package/build/test/display.js.map +1 -1
  18. package/build/tools/debug.js +5 -4
  19. package/build/tools/debug.js.map +1 -1
  20. package/build.zig +102 -104
  21. package/package.json +43 -23
  22. package/src/bin/install-pkmn-engine +26 -18
  23. package/src/lib/common/data.zig +9 -9
  24. package/src/lib/common/debug.zig +2 -2
  25. package/src/lib/common/optional.zig +9 -2
  26. package/src/lib/common/options.zig +2 -0
  27. package/src/lib/common/protocol.zig +739 -318
  28. package/src/lib/common/rational.zig +12 -9
  29. package/src/lib/common/rng.zig +25 -16
  30. package/src/lib/gen1/calc.zig +219 -49
  31. package/src/lib/gen1/chance.zig +138 -115
  32. package/src/lib/gen1/data/moves.zig +190 -176
  33. package/src/lib/gen1/data/species.zig +3 -3
  34. package/src/lib/gen1/data/types.zig +7 -7
  35. package/src/lib/gen1/data.zig +44 -44
  36. package/src/lib/gen1/helpers.zig +148 -82
  37. package/src/lib/gen1/mechanics.zig +392 -406
  38. package/src/lib/gen2/calc.zig +143 -0
  39. package/src/lib/gen2/chance.zig +1315 -0
  40. package/src/lib/gen2/data/items.zig +12 -18
  41. package/src/lib/gen2/data/moves.zig +377 -344
  42. package/src/lib/gen2/data/species.zig +1 -1
  43. package/src/lib/gen2/data/types.zig +134 -31
  44. package/src/lib/gen2/data.zig +52 -44
  45. package/src/lib/gen2/generated.zig +806 -0
  46. package/src/lib/gen2/helpers.zig +37 -7
  47. package/src/lib/gen2/mechanics.zig +3086 -211
  48. package/src/lib/{binding/node.zig → node.zig} +10 -13
  49. package/src/lib/pkmn.zig +27 -12
  50. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  51. package/src/test/display/pkmn.html.tmpl +5 -5
  52. package/src/lib/gen1/README.md +0 -894
  53. package/src/lib/gen2/README.md +0 -383
@@ -14,12 +14,14 @@ const util = @import("../common/util.zig");
14
14
  const Player = @import("../common/data.zig").Player;
15
15
  const Optional = @import("../common/optional.zig").Optional;
16
16
 
17
- const Move = @import("data.zig").Move;
18
- const MoveSlot = @import("data.zig").MoveSlot;
17
+ const data = @import("data.zig");
19
18
 
20
19
  const enabled = options.chance;
21
20
  const showdown = options.showdown;
22
21
 
22
+ const Move = data.Move;
23
+ const MoveSlot = data.MoveSlot;
24
+
23
25
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
24
26
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
25
27
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -35,19 +37,19 @@ pub const Actions = extern struct {
35
37
  }
36
38
 
37
39
  /// Returns the `Action` for the given `player`.
38
- pub inline fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
40
+ pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
39
41
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
40
42
  return if (player == .P1) &self.p1 else &self.p2;
41
43
  }
42
44
 
43
45
  /// Perform a reset by clearing fields which should not persist across updates.
44
- pub inline fn reset(self: *Actions) void {
46
+ pub fn reset(self: *Actions) void {
45
47
  self.p1.reset();
46
48
  self.p2.reset();
47
49
  }
48
50
 
49
51
  /// Returns true if `a` is equal to `b`.
50
- pub inline fn eql(a: Actions, b: Actions) bool {
52
+ pub fn eql(a: Actions, b: Actions) bool {
51
53
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
52
54
  }
53
55
 
@@ -90,13 +92,14 @@ pub const Actions = extern struct {
90
92
  }
91
93
  };
92
94
 
95
+ // TODO: ziglang/zig#19730
93
96
  test Actions {
94
97
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
95
98
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
96
99
  const c: Actions = .{ .p1 = .{ .hit = .true } };
97
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
98
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
99
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
100
+ // const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
101
+ // const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
102
+ // const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
100
103
 
101
104
  try expect(a.eql(a));
102
105
  try expect(!a.eql(b));
@@ -109,8 +112,8 @@ test Actions {
109
112
  try expect(b.matches(a));
110
113
  try expect(!a.matches(c));
111
114
  try expect(!c.matches(a));
112
- try expect(d.matches(e));
113
- try expect(!d.matches(f));
115
+ // try expect(d.matches(e));
116
+ // try expect(!d.matches(f));
114
117
  }
115
118
 
116
119
  /// Information about the RNG that was observed during a Generation I battle `update` for a
@@ -157,7 +160,7 @@ pub const Action = packed struct(u64) {
157
160
  pub const Field = std.meta.FieldEnum(Action);
158
161
 
159
162
  /// Perform a reset by clearing fields which should not persist across updates.
160
- pub inline fn reset(self: *Action) void {
163
+ pub fn reset(self: *Action) void {
161
164
  self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
162
165
  }
163
166
 
@@ -215,7 +218,7 @@ pub const Action = packed struct(u64) {
215
218
  };
216
219
 
217
220
  test Action {
218
- var a = Action{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
221
+ var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
219
222
  a.reset();
220
223
 
221
224
  try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
@@ -252,6 +255,8 @@ pub const Duration = packed struct(u16) {
252
255
 
253
256
  pub const Commit = enum { hit, miss, binding };
254
257
 
258
+ const uN = if (options.miss) u8 else u9;
259
+
255
260
  /// Tracks chance actions and their associated probability during a Generation I battle update when
256
261
  /// `options.chance` is enabled.
257
262
  pub fn Chance(comptime Rational: type) type {
@@ -288,15 +293,12 @@ pub fn Chance(comptime Rational: type) type {
288
293
  //
289
294
  // Finally, because each player's actions are processed sequentially we only need a single
290
295
  // shared structure to track this information (though it needs to be cleared appropriately)
291
- pending: if (showdown) struct {
292
- hit: bool = false,
293
- hit_probablity: u8 = 0,
294
- } else struct {
296
+ pending: if (showdown) struct {} else struct {
295
297
  crit: bool = false,
296
298
  crit_probablity: u8 = 0,
297
299
  damage_roll: u8 = 0,
298
300
  hit: bool = false,
299
- hit_probablity: u8 = 0,
301
+ hit_probablity: uN = 0,
300
302
  binding: u8 = 0,
301
303
  } = .{},
302
304
 
@@ -305,25 +307,29 @@ pub fn Chance(comptime Rational: type) type {
305
307
 
306
308
  /// Convenience helper to clear fields which typically should be cleared between updates.
307
309
  pub fn reset(self: *Self) void {
310
+ if (!enabled) return;
311
+
308
312
  self.probability.reset();
309
313
  self.actions.reset();
310
314
  self.pending = .{};
311
315
  }
312
316
 
313
317
  pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
318
+ if (!enabled) return;
319
+
320
+ assert(!showdown);
321
+
314
322
  var action = self.actions.get(player);
315
323
 
316
324
  // We need to handle binding specially because we still need to commit it when attacking
317
325
  // into a Pokémon that is immune
318
- if (!showdown) {
319
- if (kind != .miss and self.pending.binding != 0) {
320
- assert(action.duration == 0);
321
- action.duration = @intCast(self.pending.binding);
322
- assert(action.durations.binding == 0);
323
- action.durations.binding = 1;
324
- }
325
- if (kind == .binding) return;
326
- } else assert(kind != .binding);
326
+ if (kind != .miss and self.pending.binding != 0) {
327
+ assert(action.duration == 0);
328
+ action.duration = @intCast(self.pending.binding);
329
+ assert(action.durations.binding == 0);
330
+ action.durations.binding = 1;
331
+ }
332
+ if (kind == .binding) return;
327
333
 
328
334
  // Always commit the hit result if we make it here (commit won't be called at all if the
329
335
  // target is immune/behind a sub/etc)
@@ -332,19 +338,19 @@ pub fn Chance(comptime Rational: type) type {
332
338
  action.hit = if (self.pending.hit) .true else .false;
333
339
  }
334
340
 
335
- if (showdown) return;
341
+ // If the move actually lands we can commit any past critical hit / damage rolls. Note
342
+ // we can't rely on the presence or absence of a damage roll to determine whether a
343
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
344
+ // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
345
+ // passed to the critical hit helper)
346
+ if (kind != .hit) return assert(!self.pending.crit or self.pending.crit_probablity > 0);
336
347
 
337
- // If the move actually lands we can commit any past critical hit / damage rolls. We
338
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
339
- // to a damage roll is actually a no-op
340
- if (kind == .hit and self.pending.damage_roll > 0) {
341
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
342
-
343
- if (self.pending.crit_probablity != 0) {
344
- try self.probability.update(self.pending.crit_probablity, 256);
345
- action.critical_hit = if (self.pending.crit) .true else .false;
346
- }
348
+ if (self.pending.crit_probablity != 0) {
349
+ try self.probability.update(self.pending.crit_probablity, 256);
350
+ action.critical_hit = if (self.pending.crit) .true else .false;
351
+ }
347
352
 
353
+ if (self.pending.damage_roll > 0) {
348
354
  try self.probability.update(1, 39);
349
355
  action.damage = self.pending.damage_roll;
350
356
  }
@@ -395,12 +401,17 @@ pub fn Chance(comptime Rational: type) type {
395
401
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
396
402
  }
397
403
 
398
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
404
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
399
405
  if (!enabled) return;
400
406
 
401
407
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
402
- self.pending.hit = ok;
403
- self.pending.hit_probablity = p;
408
+ if (showdown) {
409
+ try self.probability.update(p, 256);
410
+ self.actions.get(player).hit = if (ok) .true else .false;
411
+ } else {
412
+ self.pending.hit = ok;
413
+ self.pending.hit_probablity = p;
414
+ }
404
415
  }
405
416
 
406
417
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -514,22 +525,6 @@ pub fn Chance(comptime Rational: type) type {
514
525
  }
515
526
  }
516
527
 
517
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
518
- if (!enabled) return;
519
-
520
- var durations = &self.actions.get(player).durations;
521
- const n = durations.disable;
522
- if (turns == 0) {
523
- assert(n >= 1 and n <= 8);
524
- if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
525
- durations.disable = 0;
526
- } else {
527
- assert(n >= 1 and n < 8);
528
- try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
529
- durations.disable += 1;
530
- }
531
- }
532
-
533
528
  pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
534
529
  if (!enabled) return;
535
530
 
@@ -546,6 +541,22 @@ pub fn Chance(comptime Rational: type) type {
546
541
  }
547
542
  }
548
543
 
544
+ pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
545
+ if (!enabled) return;
546
+
547
+ var durations = &self.actions.get(player).durations;
548
+ const n = durations.disable;
549
+ if (turns == 0) {
550
+ assert(n >= 1 and n <= 8);
551
+ if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
552
+ durations.disable = 0;
553
+ } else {
554
+ assert(n >= 1 and n < 8);
555
+ try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
556
+ durations.disable += 1;
557
+ }
558
+ }
559
+
549
560
  pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
550
561
  if (!enabled) return;
551
562
 
@@ -593,7 +604,7 @@ pub fn Chance(comptime Rational: type) type {
593
604
  pub fn metronome(self: *Self, player: Player, move: Move) Error!void {
594
605
  if (!enabled) return;
595
606
 
596
- try self.probability.update(1, Move.size - 2);
607
+ try self.probability.update(1, Move.METRONOME.len);
597
608
  self.actions.get(player).metronome = move;
598
609
  }
599
610
  };
@@ -618,19 +629,25 @@ test "Chance.speedTie" {
618
629
  test "Chance.hit" {
619
630
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
620
631
 
621
- chance.hit(true, 229);
622
- try expectProbability(&chance.probability, 1, 1);
623
- try expectValue(Optional(bool).None, chance.actions.p1.hit);
624
- try chance.commit(.P1, .hit);
632
+ try chance.hit(.P1, true, 229);
633
+ if (!showdown) {
634
+ try expectProbability(&chance.probability, 1, 1);
635
+ try expectValue(Optional(bool).None, chance.actions.p1.hit);
636
+
637
+ try chance.commit(.P1, .hit);
638
+ }
625
639
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
626
640
  try expectProbability(&chance.probability, 229, 256);
627
641
 
628
642
  chance.reset();
629
643
 
630
- chance.hit(false, 255);
631
- try expectProbability(&chance.probability, 1, 1);
632
- try expectValue(Optional(bool).None, chance.actions.p2.hit);
633
- try chance.commit(.P2, .miss);
644
+ try chance.hit(.P2, false, 255);
645
+ if (!showdown) {
646
+ try expectProbability(&chance.probability, 1, 1);
647
+ try expectValue(Optional(bool).None, chance.actions.p2.hit);
648
+
649
+ try chance.commit(.P2, .miss);
650
+ }
634
651
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
635
652
  try expectProbability(&chance.probability, 1, 256);
636
653
  }
@@ -646,26 +663,18 @@ test "Chance.criticalHit" {
646
663
  try expectProbability(&chance.probability, 1, 1);
647
664
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
648
665
 
649
- try chance.commit(.P1, .hit);
650
- try expectProbability(&chance.probability, 1, 1);
651
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
666
+ try chance.hit(.P1, true, 128);
652
667
 
653
- chance.hit(true, 128);
654
-
655
- try chance.commit(.P1, .hit);
656
- try expectProbability(&chance.probability, 1, 2);
668
+ try expectProbability(&chance.probability, 1, 1);
657
669
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
658
670
 
659
- chance.probability.reset();
660
671
  try chance.damage(.P1, 217);
661
672
 
662
- try chance.commit(.P1, .miss);
663
- try expectProbability(&chance.probability, 1, 2);
673
+ try expectProbability(&chance.probability, 1, 1);
664
674
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
665
675
 
666
- chance.probability.reset();
667
-
668
676
  try chance.commit(.P1, .hit);
677
+
669
678
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
670
679
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
671
680
  }
@@ -680,20 +689,14 @@ test "Chance.criticalHit" {
680
689
  try expectProbability(&chance.probability, 1, 1);
681
690
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
682
691
 
683
- try chance.commit(.P1, .hit);
684
- try expectProbability(&chance.probability, 1, 1);
685
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
686
-
687
- chance.hit(true, 128);
692
+ try chance.hit(.P1, true, 128);
688
693
 
689
- try chance.commit(.P1, .hit);
690
- try expectProbability(&chance.probability, 1, 2);
694
+ try expectProbability(&chance.probability, 1, 1);
691
695
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
692
696
 
693
- chance.probability.reset();
694
697
  try chance.damage(.P1, 217);
695
-
696
698
  try chance.commit(.P1, .hit);
699
+
697
700
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
698
701
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
699
702
  }
@@ -704,19 +707,16 @@ test "Chance.damage" {
704
707
 
705
708
  try chance.damage(.P1, 217);
706
709
  if (showdown) {
707
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
710
+ try expectValue(217, chance.actions.p1.damage);
708
711
  try expectProbability(&chance.probability, 1, 39);
709
712
  } else {
710
713
  try expectProbability(&chance.probability, 1, 1);
711
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
712
-
713
- try chance.commit(.P1, .miss);
714
- try expectProbability(&chance.probability, 1, 1);
715
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
714
+ try expectValue(0, chance.actions.p1.damage);
716
715
 
717
716
  try chance.commit(.P1, .hit);
717
+
718
718
  try expectProbability(&chance.probability, 1, 39);
719
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
719
+ try expectValue(217, chance.actions.p1.damage);
720
720
  }
721
721
  }
722
722
 
@@ -768,13 +768,13 @@ test "Chance.moveSlot" {
768
768
 
769
769
  try chance.moveSlot(.P2, 2, &ms, 2);
770
770
  try expectProbability(&chance.probability, 1, 2);
771
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
771
+ try expectValue(2, chance.actions.p2.move_slot);
772
772
 
773
773
  chance.reset();
774
774
 
775
775
  try chance.moveSlot(.P1, 1, &ms, 0);
776
776
  try expectProbability(&chance.probability, 1, 3);
777
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
777
+ try expectValue(1, chance.actions.p1.move_slot);
778
778
  }
779
779
 
780
780
  test "Chance.multiHit" {
@@ -782,32 +782,33 @@ test "Chance.multiHit" {
782
782
 
783
783
  try chance.multiHit(.P1, 3);
784
784
  try expectProbability(&chance.probability, 3, 8);
785
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
785
+ try expectValue(3, chance.actions.p1.multi_hit);
786
786
 
787
787
  chance.reset();
788
788
 
789
789
  try chance.multiHit(.P2, 5);
790
790
  try expectProbability(&chance.probability, 1, 8);
791
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
791
+ try expectValue(5, chance.actions.p2.multi_hit);
792
792
  }
793
793
 
794
794
  test "Chance.duration" {
795
795
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
796
796
 
797
797
  chance.duration(.sleep, .P1, .P2, 2);
798
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
799
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
798
+ try expectValue(2, chance.actions.p1.duration);
799
+ try expectValue(1, chance.actions.p2.durations.sleep);
800
800
 
801
801
  chance.reset();
802
802
 
803
803
  chance.duration(.binding, .P2, .P2, 4);
804
804
  if (!showdown) {
805
- try expectValue(@as(u4, 0), chance.actions.p2.duration);
806
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
805
+ try expectValue(0, chance.actions.p2.duration);
806
+ try expectValue(0, chance.actions.p2.durations.binding);
807
+
807
808
  try chance.commit(.P2, .hit);
808
809
  }
809
- try expectValue(@as(u4, 4), chance.actions.p2.duration);
810
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
810
+ try expectValue(4, chance.actions.p2.duration);
811
+ try expectValue(1, chance.actions.p2.durations.binding);
811
812
  }
812
813
 
813
814
  test "Chance.sleep" {
@@ -817,7 +818,7 @@ test "Chance.sleep" {
817
818
  chance.actions.p1.durations.sleep = @intCast(i);
818
819
  try chance.sleep(.P1, 0);
819
820
  try expectProbability(&chance.probability, 1, d);
820
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
821
+ try expectValue(0, chance.actions.p1.durations.sleep);
821
822
 
822
823
  chance.reset();
823
824
 
@@ -840,7 +841,7 @@ test "Chance.confusion" {
840
841
  chance.actions.p2.durations.confusion = @intCast(i);
841
842
  try chance.confusion(.P2, 0);
842
843
  try expectProbability(&chance.probability, 1, d);
843
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
844
+ try expectValue(0, chance.actions.p2.durations.confusion);
844
845
 
845
846
  chance.reset();
846
847
  }
@@ -856,6 +857,28 @@ test "Chance.confusion" {
856
857
  }
857
858
  }
858
859
 
860
+ test "Chance.disable" {
861
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
862
+
863
+ for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
864
+ chance.actions.p1.durations.disable = @intCast(i);
865
+ try chance.disable(.P1, 0);
866
+ try expectProbability(&chance.probability, 1, d);
867
+ try expectValue(0, chance.actions.p1.durations.disable);
868
+
869
+ chance.reset();
870
+
871
+ if (i < 8) {
872
+ chance.actions.p1.durations.disable = @intCast(i);
873
+ try chance.disable(.P1, 1);
874
+ try expectProbability(&chance.probability, d - 1, d);
875
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
876
+
877
+ chance.reset();
878
+ }
879
+ }
880
+ }
881
+
859
882
  test "Chance.attacking" {
860
883
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
861
884
 
@@ -864,7 +887,7 @@ test "Chance.attacking" {
864
887
  chance.actions.p2.durations.attacking = @intCast(i);
865
888
  try chance.attacking(.P2, 0);
866
889
  try expectProbability(&chance.probability, 1, d);
867
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
890
+ try expectValue(0, chance.actions.p2.durations.attacking);
868
891
 
869
892
  chance.reset();
870
893
  }
@@ -890,7 +913,7 @@ test "Chance.binding" {
890
913
  chance.actions.p1.durations.binding = @intCast(i);
891
914
  try chance.binding(.P1, 0);
892
915
  try expectProbability(&chance.probability, p, q);
893
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
916
+ try expectValue(0, chance.actions.p1.durations.binding);
894
917
 
895
918
  chance.reset();
896
919
 
@@ -910,7 +933,7 @@ test "Chance.psywave" {
910
933
 
911
934
  try chance.psywave(.P2, 100, 150);
912
935
  try expectProbability(&chance.probability, 1, 150);
913
- try expectValue(@as(u8, 101), chance.actions.p2.psywave);
936
+ try expectValue(101, chance.actions.p2.psywave);
914
937
  }
915
938
 
916
939
  test "Chance.metronome" {
@@ -939,7 +962,7 @@ pub fn expectValue(a: anytype, b: anytype) !void {
939
962
 
940
963
  /// Null object pattern implementation of Generation I `Chance` which does nothing, though chance
941
964
  /// tracking should additionally be turned off entirely via `options.chance`.
942
- pub const NULL = Null{};
965
+ pub const NULL: Null = .{};
943
966
 
944
967
  const Null = struct {
945
968
  pub const Error = error{};
@@ -964,8 +987,8 @@ const Null = struct {
964
987
  _ = .{ self, p1 };
965
988
  }
966
989
 
967
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
968
- _ = .{ self, ok, accuracy };
990
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
991
+ _ = .{ self, player, ok, accuracy };
969
992
  }
970
993
 
971
994
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -976,10 +999,6 @@ const Null = struct {
976
999
  _ = .{ self, player, roll };
977
1000
  }
978
1001
 
979
- pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
980
- _ = .{ self, player, proc, rate };
981
- }
982
-
983
1002
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
984
1003
  _ = .{ self, player, ok };
985
1004
  }
@@ -988,6 +1007,10 @@ const Null = struct {
988
1007
  _ = .{ self, player, ok };
989
1008
  }
990
1009
 
1010
+ pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
1011
+ _ = .{ self, player, proc, rate };
1012
+ }
1013
+
991
1014
  pub fn moveSlot(self: Null, player: Player, slot: u4, ms: []const MoveSlot, n: u4) Error!void {
992
1015
  _ = .{ self, player, slot, ms, n };
993
1016
  }