@pkmn/engine 0.1.0-dev.90d6c14c → 0.1.0-dev.9257876b

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 (48) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +52 -21
  3. package/build/pkg/addon.js.map +1 -1
  4. package/build/pkg/data/ids.json +12 -12
  5. package/build/pkg/data/layout.json +2 -2
  6. package/build/pkg/data/protocol.json +69 -12
  7. package/build/pkg/data.d.ts +3 -2
  8. package/build/pkg/data.js.map +1 -1
  9. package/build/pkg/gen1.js +2 -2
  10. package/build/pkg/gen1.js.map +1 -1
  11. package/build/pkg/index.d.ts +2 -1
  12. package/build/pkg/index.js.map +1 -1
  13. package/build/pkg/protocol.js +57 -38
  14. package/build/pkg/protocol.js.map +1 -1
  15. package/build/test/display.js.map +1 -1
  16. package/build/tools/debug.js +3 -1
  17. package/build/tools/debug.js.map +1 -1
  18. package/build.zig +125 -102
  19. package/package.json +43 -23
  20. package/src/bin/install-pkmn-engine +27 -19
  21. package/src/lib/common/data.zig +12 -24
  22. package/src/lib/common/optional.zig +9 -2
  23. package/src/lib/common/protocol.zig +805 -327
  24. package/src/lib/common/rational.zig +12 -8
  25. package/src/lib/common/rng.zig +26 -17
  26. package/src/lib/gen1/calc.zig +9 -10
  27. package/src/lib/gen1/chance.zig +83 -66
  28. package/src/lib/gen1/data/moves.zig +192 -180
  29. package/src/lib/gen1/data/species.zig +3 -3
  30. package/src/lib/gen1/data/types.zig +7 -7
  31. package/src/lib/gen1/data.zig +60 -67
  32. package/src/lib/gen1/helpers.zig +261 -123
  33. package/src/lib/gen1/mechanics.zig +370 -403
  34. package/src/lib/gen2/calc.zig +143 -0
  35. package/src/lib/gen2/chance.zig +1315 -0
  36. package/src/lib/gen2/data/items.zig +12 -18
  37. package/src/lib/gen2/data/moves.zig +663 -569
  38. package/src/lib/gen2/data/species.zig +1 -1
  39. package/src/lib/gen2/data/types.zig +134 -31
  40. package/src/lib/gen2/data.zig +106 -79
  41. package/src/lib/gen2/generated.zig +806 -0
  42. package/src/lib/gen2/helpers.zig +8 -12
  43. package/src/lib/gen2/mechanics.zig +3587 -9
  44. package/src/lib/{binding/node.zig → node.zig} +10 -13
  45. package/src/lib/pkmn.zig +26 -3
  46. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  47. package/src/lib/gen1/README.md +0 -812
  48. package/src/lib/gen2/README.md +0 -308
@@ -44,7 +44,7 @@ pub fn Rational(comptime T: type) type {
44
44
  /// Both p and q must be >= 1, and if computable at comptime must have no common factors.
45
45
  pub fn update(r: *Self, p: anytype, q: anytype) Error!void {
46
46
  // FIXME
47
- // std.debug.print("({d}/{d}) * ", .{p, q});
47
+ // std.debug.print("({d}/{d}) * ", .{ p, q });
48
48
  assert(p >= 1);
49
49
  assert(q >= 1);
50
50
 
@@ -185,16 +185,16 @@ pub fn Rational(comptime T: type) type {
185
185
  try writer.print("{d}/{d}", .{ self.p, self.q });
186
186
  }
187
187
 
188
- inline fn multiplication(r: *Self, p: anytype, q: anytype) !void {
188
+ fn multiplication(r: *Self, p: anytype, q: anytype) !void {
189
189
  r.p = try std.math.mul(T, r.p, p);
190
190
  r.q = try std.math.mul(T, r.q, q);
191
191
  }
192
192
 
193
- inline fn addition(r: *Self, p: anytype, q: anytype) !void {
193
+ fn addition(r: *Self, p: anytype, q: anytype) !void {
194
194
  // (a/b) + (c/d) = (ad+bc)/(bd)
195
- var d = try std.math.mul(T, r.q, q);
196
- var n1 = try std.math.mul(T, r.p, q);
197
- var n2 = try std.math.mul(T, r.q, p);
195
+ const d = try std.math.mul(T, r.q, q);
196
+ const n1 = try std.math.mul(T, r.p, q);
197
+ const n2 = try std.math.mul(T, r.q, p);
198
198
 
199
199
  r.p = try std.math.add(T, n1, n2);
200
200
  r.q = d;
@@ -203,7 +203,7 @@ pub fn Rational(comptime T: type) type {
203
203
  }
204
204
 
205
205
  // https://en.wikipedia.org/wiki/Euclidean_algorithm
206
- inline fn gcd(p: anytype, q: anytype) @TypeOf(p, q) {
206
+ fn gcd(p: anytype, q: anytype) @TypeOf(p, q) {
207
207
  assert(p >= 1);
208
208
  assert(q >= 1);
209
209
 
@@ -234,6 +234,8 @@ test Rational {
234
234
  var r: Rational(t) = .{};
235
235
 
236
236
  var c: t = 128;
237
+ _ = &c;
238
+
237
239
  try r.update(c, 256);
238
240
  try doTurn(&r);
239
241
 
@@ -275,7 +277,7 @@ pub const BigRational = struct {
275
277
  /// Create a new BigRational wrapper. A small amount of memory will be allocated on
276
278
  /// initialization. Not default initialized to 1 - you must explicitly `reset` first.
277
279
  pub fn init(alloc: std.mem.Allocator) !BigRational {
278
- return BigRational{ .val = try std.math.big.Rational.init(alloc) };
280
+ return .{ .val = try std.math.big.Rational.init(alloc) };
279
281
  }
280
282
 
281
283
  /// Frees all memory associated with a rational.
@@ -320,6 +322,8 @@ test BigRational {
320
322
  try r.reset();
321
323
 
322
324
  var c: u8 = 128;
325
+ _ = &c;
326
+
323
327
  try r.update(c, 256);
324
328
  try doTurn(&r);
325
329
 
@@ -1,6 +1,6 @@
1
1
  const std = @import("std");
2
2
 
3
- const options = @import("./options.zig");
3
+ const options = @import("options.zig");
4
4
 
5
5
  const assert = std.debug.assert;
6
6
  const expectEqual = std.testing.expectEqual;
@@ -25,6 +25,11 @@ pub fn PRNG(comptime gen: comptime_int) type {
25
25
  pub fn next(self: *Self) Output(gen) {
26
26
  return self.src.next();
27
27
  }
28
+
29
+ pub fn advance(self: *Self, n: usize) void {
30
+ if (!options.advance) return;
31
+ for (0..n) |_| self.src.advance();
32
+ }
28
33
  };
29
34
  }
30
35
 
@@ -61,11 +66,15 @@ pub const PSRNG = extern struct {
61
66
  (@as(u64, self.range(u16, 0, 0x10000)) << 16) |
62
67
  (@as(u64, self.range(u16, 0, 0x10000)));
63
68
  }
69
+
70
+ pub fn Bound(comptime T: type) type {
71
+ return std.math.IntFittingRange(0, std.math.maxInt(T) + 1);
72
+ }
64
73
  };
65
74
 
66
75
  test PSRNG {
67
- var psrng = PSRNG{ .src = .{ .seed = 0x0001000200030004 } };
68
- try expectEqual(@as(u8, 121), psrng.range(u8, 0, 256));
76
+ var psrng: PSRNG = .{ .src = .{ .seed = 0x0001000200030004 } };
77
+ try expectEqual(121, psrng.range(u8, 0, 256));
69
78
  try expectEqual(false, psrng.chance(u8, 128, 256)); // 226 < 128
70
79
  }
71
80
 
@@ -79,7 +88,7 @@ pub const Gen12 = extern struct {
79
88
  assert(@sizeOf(Gen12) == 10);
80
89
  }
81
90
 
82
- pub fn percent(p: u8) u8 {
91
+ pub fn percent(comptime p: comptime_int) u8 {
83
92
  return @intCast((@as(u16, p) * 0xFF) / 100);
84
93
  }
85
94
 
@@ -89,18 +98,22 @@ pub const Gen12 = extern struct {
89
98
  self.index = (self.index + 1) % 9;
90
99
  return val;
91
100
  }
101
+
102
+ pub fn advance(self: *Gen12) void {
103
+ _ = self.next();
104
+ }
92
105
  };
93
106
 
94
107
  test Gen12 {
95
108
  const expected = [_]u8{ 6, 11, 16, 21, 26, 31, 36, 41, 46, 31, 56, 81 };
96
- var rng = Gen12{ .seed = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9 } };
109
+ var rng: Gen12 = .{ .seed = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9 } };
97
110
  for (expected) |e| {
98
111
  try expectEqual(e, rng.next());
99
112
  }
100
113
 
101
- try expectEqual(@as(u8, 16), Gen12.percent(6) + 1);
102
- try expectEqual(@as(u8, 16), Gen12.percent(7) - 1);
103
- try expectEqual(@as(u8, 128), Gen12.percent(50) + 1);
114
+ try expectEqual(16, Gen12.percent(6) + 1);
115
+ try expectEqual(16, Gen12.percent(7) - 1);
116
+ try expectEqual(128, Gen12.percent(50) + 1);
104
117
  }
105
118
 
106
119
  // https://pkmn.cc/pokeemerald/src/random.c
@@ -129,7 +142,7 @@ test Gen34 {
129
142
  .{ 0x80000000, 5, 0x0E425287 }, .{ 0x80000000, 10, 0x6F2CF4B2 },
130
143
  };
131
144
  for (data) |d| {
132
- var rng = Gen34{ .seed = d[0] };
145
+ var rng: Gen34 = .{ .seed = d[0] };
133
146
  for (0..d[1]) |_| _ = rng.next();
134
147
  try expectEqual(d[2], rng.seed);
135
148
  }
@@ -161,7 +174,7 @@ test Gen56 {
161
174
  .{ 0x8000000000000000, 10, 0xE7795501267F125A },
162
175
  };
163
176
  for (data) |d| {
164
- var rng = Gen56{ .seed = d[0] };
177
+ var rng: Gen56 = .{ .seed = d[0] };
165
178
  for (0..d[1]) |_| _ = rng.next();
166
179
  try expectEqual(d[2], rng.seed);
167
180
  }
@@ -188,7 +201,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
188
201
  for (0..n) |_| _ = self.next();
189
202
  }
190
203
 
191
- pub fn range(self: *Self, comptime T: type, from: T, to: Bound(T)) T {
204
+ pub fn range(self: *Self, comptime T: type, from: T, to: PSRNG.Bound(T)) T {
192
205
  assert(showdown);
193
206
  return @intCast(@as(u64, self.next()) * (to - from) / divisor + from);
194
207
  }
@@ -197,7 +210,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
197
210
  self: *Self,
198
211
  comptime T: type,
199
212
  numerator: T,
200
- denominator: Bound(T),
213
+ denominator: PSRNG.Bound(T),
201
214
  ) bool {
202
215
  assert(showdown);
203
216
  assert(denominator > 0);
@@ -213,7 +226,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
213
226
  test FixedRNG {
214
227
  const Type = if (showdown) u32 else u8;
215
228
  const expected = [_]Type{ 42, 255, 0 };
216
- var rng = FixedRNG(1, expected.len){ .rolls = expected };
229
+ var rng: FixedRNG(1, expected.len) = .{ .rolls = expected };
217
230
  for (expected) |e| {
218
231
  try expectEqual(e, rng.next());
219
232
  }
@@ -228,7 +241,3 @@ fn Output(comptime gen: comptime_int) type {
228
241
  else => unreachable,
229
242
  };
230
243
  }
231
-
232
- fn Bound(comptime T: type) type {
233
- return std.math.IntFittingRange(0, std.math.maxInt(T) + 1);
234
- }
@@ -29,7 +29,6 @@ const Optional = optional.Optional;
29
29
  const Rational = rational.Rational;
30
30
 
31
31
  const PointerType = util.PointerType;
32
- const FieldType = util.FieldType;
33
32
 
34
33
  const Actions = chance.Actions;
35
34
  const Action = chance.Action;
@@ -51,7 +50,7 @@ pub const Summaries = extern struct {
51
50
  }
52
51
 
53
52
  /// Returns the `Summary` for the given `player`.
54
- pub inline fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
53
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
55
54
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Summaries);
56
55
  return if (player == .P1) &self.p1 else &self.p2;
57
56
  }
@@ -158,7 +157,7 @@ pub const Calc = struct {
158
157
 
159
158
  /// Null object pattern implementation of Generation I `Calc` which does nothing, though damage
160
159
  /// calculator support should additionally be turned off entirely via `options.calc`.
161
- pub const NULL = Null{};
160
+ pub const NULL: Null = .{};
162
161
 
163
162
  const Null = struct {
164
163
  pub fn overridden(
@@ -212,8 +211,8 @@ pub fn transitions(
212
211
  ) !?Stats {
213
212
  var stats: Stats = .{};
214
213
 
215
- var actions = options.actions;
216
- var cap = options.cap;
214
+ const actions = options.actions;
215
+ const cap = options.cap;
217
216
 
218
217
  var seen = std.AutoHashMap(Actions, void).init(allocator);
219
218
  defer seen.deinit();
@@ -233,8 +232,8 @@ pub fn transitions(
233
232
  _ = try b.update(c1, c2, &opts);
234
233
  stats.updates += 1;
235
234
 
236
- var p1 = b.side(.P1);
237
- var p2 = b.side(.P2);
235
+ const p1 = b.side(.P1);
236
+ const p2 = b.side(.P2);
238
237
 
239
238
  var p: Rational(u128) = .{ .p = 0, .q = 1 };
240
239
  try frontier.append(opts.chance.actions);
@@ -248,7 +247,7 @@ pub fn transitions(
248
247
  var i: usize = 0;
249
248
  assert(frontier.items.len == 1);
250
249
  while (i < frontier.items.len) : (i += 1) {
251
- var template = frontier.items[i];
250
+ const template = frontier.items[i];
252
251
  opts.calc.overrides.durations = .{
253
252
  .p1 = template.p1.durations,
254
253
  .p2 = template.p2.durations,
@@ -256,7 +255,7 @@ pub fn transitions(
256
255
 
257
256
  try debug(writer, template, true, .{ .color = i, .bold = true, .background = true });
258
257
 
259
- var a = Actions{ .p1 = .{ .metronome = p1_move }, .p2 = .{ .metronome = p2_move } };
258
+ var a: Actions = .{ .p1 = .{ .metronome = p1_move }, .p2 = .{ .metronome = p2_move } };
260
259
 
261
260
  for (Rolls.speedTie(template.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
262
261
  for (Rolls.sleep(template.p1, actions.p1)) |p1_slp| { a.p1.durations.sleep = p1_slp;
@@ -388,7 +387,7 @@ pub fn transitions(
388
387
  return stats;
389
388
  }
390
389
 
391
- inline fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
390
+ fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
392
391
  for (frontier, 0..) |f, j| {
393
392
  // TODO: is skipping this redundant check worth it?
394
393
  if (i == j) continue;
@@ -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,13 +112,13 @@ 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
117
120
  /// single player.
118
- pub const Action = packed struct {
121
+ pub const Action = packed struct(u64) {
119
122
  /// If not 0, the roll to be returned Rolls.damage.
120
123
  damage: u8 = 0,
121
124
 
@@ -126,7 +129,7 @@ pub const Action = packed struct {
126
129
  /// If not None, the value to be returned for
127
130
  /// Rolls.{confusionChance,secondaryChance,poisonChance}.
128
131
  secondary_chance: Optional(bool) = .None,
129
- /// If not None, the Playr to be returned by Rolls.speedTie.
132
+ /// If not None, the Player to be returned by Rolls.speedTie.
130
133
  speed_tie: Optional(Player) = .None,
131
134
 
132
135
  /// If not None, the value to return for Rolls.confused.
@@ -156,12 +159,8 @@ pub const Action = packed struct {
156
159
 
157
160
  pub const Field = std.meta.FieldEnum(Action);
158
161
 
159
- comptime {
160
- assert(@sizeOf(Action) == 8);
161
- }
162
-
163
162
  /// Perform a reset by clearing fields which should not persist across updates.
164
- pub inline fn reset(self: *Action) void {
163
+ pub fn reset(self: *Action) void {
165
164
  self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
166
165
  }
167
166
 
@@ -181,9 +180,9 @@ pub const Action = packed struct {
181
180
  if (v != 0) {
182
181
  if (printed) try writer.writeAll(", ");
183
182
  if (shape) {
184
- try writer.print("{s}:?", .{field.name});
183
+ try writer.print("{s}:?", .{f.name});
185
184
  } else {
186
- try writer.print("{s}:{d}", .{ field.name, v });
185
+ try writer.print("{s}:{d}", .{ f.name, v });
187
186
  }
188
187
  printed = true;
189
188
  }
@@ -219,7 +218,7 @@ pub const Action = packed struct {
219
218
  };
220
219
 
221
220
  test Action {
222
- var a = Action{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
221
+ var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
223
222
  a.reset();
224
223
 
225
224
  try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
@@ -239,7 +238,7 @@ pub const Durations = extern struct {
239
238
 
240
239
  /// Observed values for various durations that need to be tracked in order to properly
241
240
  /// deduplicate transitions with a primary key. TODO
242
- pub const Duration = packed struct {
241
+ pub const Duration = packed struct(u16) {
243
242
  /// The number of turns a Pokémon has been observed to be sleeping.
244
243
  sleep: u3 = 0,
245
244
  /// The number of turns a Pokémon has been observed to be confused.
@@ -252,10 +251,6 @@ pub const Duration = packed struct {
252
251
  binding: u3 = 0,
253
252
 
254
253
  pub const Field = std.meta.FieldEnum(Duration);
255
-
256
- comptime {
257
- assert(@sizeOf(Duration) == 2);
258
- }
259
254
  };
260
255
 
261
256
  pub const Commit = enum { hit, miss, binding };
@@ -522,22 +517,6 @@ pub fn Chance(comptime Rational: type) type {
522
517
  }
523
518
  }
524
519
 
525
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
526
- if (!enabled) return;
527
-
528
- var durations = &self.actions.get(player).durations;
529
- const n = durations.disable;
530
- if (turns == 0) {
531
- assert(n >= 1 and n <= 8);
532
- if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
533
- durations.disable = 0;
534
- } else {
535
- assert(n >= 1 and n < 8);
536
- try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
537
- durations.disable += 1;
538
- }
539
- }
540
-
541
520
  pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
542
521
  if (!enabled) return;
543
522
 
@@ -554,6 +533,22 @@ pub fn Chance(comptime Rational: type) type {
554
533
  }
555
534
  }
556
535
 
536
+ pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
537
+ if (!enabled) return;
538
+
539
+ var durations = &self.actions.get(player).durations;
540
+ const n = durations.disable;
541
+ if (turns == 0) {
542
+ assert(n >= 1 and n <= 8);
543
+ if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
544
+ durations.disable = 0;
545
+ } else {
546
+ assert(n >= 1 and n < 8);
547
+ try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
548
+ durations.disable += 1;
549
+ }
550
+ }
551
+
557
552
  pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
558
553
  if (!enabled) return;
559
554
 
@@ -601,7 +596,7 @@ pub fn Chance(comptime Rational: type) type {
601
596
  pub fn metronome(self: *Self, player: Player, move: Move) Error!void {
602
597
  if (!enabled) return;
603
598
 
604
- try self.probability.update(1, Move.size - 2);
599
+ try self.probability.update(1, Move.METRONOME.len);
605
600
  self.actions.get(player).metronome = move;
606
601
  }
607
602
  };
@@ -712,19 +707,19 @@ test "Chance.damage" {
712
707
 
713
708
  try chance.damage(.P1, 217);
714
709
  if (showdown) {
715
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
710
+ try expectValue(217, chance.actions.p1.damage);
716
711
  try expectProbability(&chance.probability, 1, 39);
717
712
  } else {
718
713
  try expectProbability(&chance.probability, 1, 1);
719
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
714
+ try expectValue(0, chance.actions.p1.damage);
720
715
 
721
716
  try chance.commit(.P1, .miss);
722
717
  try expectProbability(&chance.probability, 1, 1);
723
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
718
+ try expectValue(0, chance.actions.p1.damage);
724
719
 
725
720
  try chance.commit(.P1, .hit);
726
721
  try expectProbability(&chance.probability, 1, 39);
727
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
722
+ try expectValue(217, chance.actions.p1.damage);
728
723
  }
729
724
  }
730
725
 
@@ -776,13 +771,13 @@ test "Chance.moveSlot" {
776
771
 
777
772
  try chance.moveSlot(.P2, 2, &ms, 2);
778
773
  try expectProbability(&chance.probability, 1, 2);
779
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
774
+ try expectValue(2, chance.actions.p2.move_slot);
780
775
 
781
776
  chance.reset();
782
777
 
783
778
  try chance.moveSlot(.P1, 1, &ms, 0);
784
779
  try expectProbability(&chance.probability, 1, 3);
785
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
780
+ try expectValue(1, chance.actions.p1.move_slot);
786
781
  }
787
782
 
788
783
  test "Chance.multiHit" {
@@ -790,32 +785,32 @@ test "Chance.multiHit" {
790
785
 
791
786
  try chance.multiHit(.P1, 3);
792
787
  try expectProbability(&chance.probability, 3, 8);
793
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
788
+ try expectValue(3, chance.actions.p1.multi_hit);
794
789
 
795
790
  chance.reset();
796
791
 
797
792
  try chance.multiHit(.P2, 5);
798
793
  try expectProbability(&chance.probability, 1, 8);
799
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
794
+ try expectValue(5, chance.actions.p2.multi_hit);
800
795
  }
801
796
 
802
797
  test "Chance.duration" {
803
798
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
804
799
 
805
800
  chance.duration(.sleep, .P1, .P2, 2);
806
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
807
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
801
+ try expectValue(2, chance.actions.p1.duration);
802
+ try expectValue(1, chance.actions.p2.durations.sleep);
808
803
 
809
804
  chance.reset();
810
805
 
811
806
  chance.duration(.binding, .P2, .P2, 4);
812
807
  if (!showdown) {
813
- try expectValue(@as(u4, 0), chance.actions.p2.duration);
814
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
808
+ try expectValue(0, chance.actions.p2.duration);
809
+ try expectValue(0, chance.actions.p2.durations.binding);
815
810
  try chance.commit(.P2, .hit);
816
811
  }
817
- try expectValue(@as(u4, 4), chance.actions.p2.duration);
818
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
812
+ try expectValue(4, chance.actions.p2.duration);
813
+ try expectValue(1, chance.actions.p2.durations.binding);
819
814
  }
820
815
 
821
816
  test "Chance.sleep" {
@@ -825,7 +820,7 @@ test "Chance.sleep" {
825
820
  chance.actions.p1.durations.sleep = @intCast(i);
826
821
  try chance.sleep(.P1, 0);
827
822
  try expectProbability(&chance.probability, 1, d);
828
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
823
+ try expectValue(0, chance.actions.p1.durations.sleep);
829
824
 
830
825
  chance.reset();
831
826
 
@@ -848,7 +843,7 @@ test "Chance.confusion" {
848
843
  chance.actions.p2.durations.confusion = @intCast(i);
849
844
  try chance.confusion(.P2, 0);
850
845
  try expectProbability(&chance.probability, 1, d);
851
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
846
+ try expectValue(0, chance.actions.p2.durations.confusion);
852
847
 
853
848
  chance.reset();
854
849
  }
@@ -864,6 +859,28 @@ test "Chance.confusion" {
864
859
  }
865
860
  }
866
861
 
862
+ test "Chance.disable" {
863
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
864
+
865
+ for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
866
+ chance.actions.p1.durations.disable = @intCast(i);
867
+ try chance.disable(.P1, 0);
868
+ try expectProbability(&chance.probability, 1, d);
869
+ try expectValue(0, chance.actions.p1.durations.disable);
870
+
871
+ chance.reset();
872
+
873
+ if (i < 8) {
874
+ chance.actions.p1.durations.disable = @intCast(i);
875
+ try chance.disable(.P1, 1);
876
+ try expectProbability(&chance.probability, d - 1, d);
877
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
878
+
879
+ chance.reset();
880
+ }
881
+ }
882
+ }
883
+
867
884
  test "Chance.attacking" {
868
885
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
869
886
 
@@ -872,7 +889,7 @@ test "Chance.attacking" {
872
889
  chance.actions.p2.durations.attacking = @intCast(i);
873
890
  try chance.attacking(.P2, 0);
874
891
  try expectProbability(&chance.probability, 1, d);
875
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
892
+ try expectValue(0, chance.actions.p2.durations.attacking);
876
893
 
877
894
  chance.reset();
878
895
  }
@@ -898,7 +915,7 @@ test "Chance.binding" {
898
915
  chance.actions.p1.durations.binding = @intCast(i);
899
916
  try chance.binding(.P1, 0);
900
917
  try expectProbability(&chance.probability, p, q);
901
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
918
+ try expectValue(0, chance.actions.p1.durations.binding);
902
919
 
903
920
  chance.reset();
904
921
 
@@ -918,7 +935,7 @@ test "Chance.psywave" {
918
935
 
919
936
  try chance.psywave(.P2, 100, 150);
920
937
  try expectProbability(&chance.probability, 1, 150);
921
- try expectValue(@as(u8, 101), chance.actions.p2.psywave);
938
+ try expectValue(101, chance.actions.p2.psywave);
922
939
  }
923
940
 
924
941
  test "Chance.metronome" {
@@ -947,7 +964,7 @@ pub fn expectValue(a: anytype, b: anytype) !void {
947
964
 
948
965
  /// Null object pattern implementation of Generation I `Chance` which does nothing, though chance
949
966
  /// tracking should additionally be turned off entirely via `options.chance`.
950
- pub const NULL = Null{};
967
+ pub const NULL: Null = .{};
951
968
 
952
969
  const Null = struct {
953
970
  pub const Error = error{};
@@ -984,10 +1001,6 @@ const Null = struct {
984
1001
  _ = .{ self, player, roll };
985
1002
  }
986
1003
 
987
- pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
988
- _ = .{ self, player, proc, rate };
989
- }
990
-
991
1004
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
992
1005
  _ = .{ self, player, ok };
993
1006
  }
@@ -996,6 +1009,10 @@ const Null = struct {
996
1009
  _ = .{ self, player, ok };
997
1010
  }
998
1011
 
1012
+ pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
1013
+ _ = .{ self, player, proc, rate };
1014
+ }
1015
+
999
1016
  pub fn moveSlot(self: Null, player: Player, slot: u4, ms: []const MoveSlot, n: u4) Error!void {
1000
1017
  _ = .{ self, player, slot, ms, n };
1001
1018
  }