@pkmn/engine 0.1.0-dev.9f75c7ae → 0.1.0-dev.a0167a9d

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 (42) 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.d.ts +4 -4
  6. package/build/pkg/data.js.map +1 -1
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/index.d.ts +2 -1
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.js +5 -5
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/test/display.js +2 -2
  13. package/build/test/display.js.map +1 -1
  14. package/build/tools/debug.js +5 -4
  15. package/build/tools/debug.js.map +1 -1
  16. package/build.zig +82 -90
  17. package/package.json +31 -23
  18. package/src/bin/install-pkmn-engine +26 -18
  19. package/src/lib/common/data.zig +4 -4
  20. package/src/lib/common/debug.zig +2 -2
  21. package/src/lib/common/optional.zig +1 -1
  22. package/src/lib/common/options.zig +2 -0
  23. package/src/lib/common/protocol.zig +393 -396
  24. package/src/lib/common/rational.zig +109 -15
  25. package/src/lib/common/rng.zig +11 -11
  26. package/src/lib/gen1/calc.zig +269 -97
  27. package/src/lib/gen1/chance.zig +126 -160
  28. package/src/lib/gen1/data/species.zig +1 -1
  29. package/src/lib/gen1/data.zig +21 -21
  30. package/src/lib/gen1/helpers.zig +191 -206
  31. package/src/lib/gen1/mechanics.zig +308 -285
  32. package/src/lib/gen2/chance.zig +211 -121
  33. package/src/lib/gen2/data.zig +13 -11
  34. package/src/lib/gen2/generated.zig +15 -24
  35. package/src/lib/gen2/helpers.zig +37 -7
  36. package/src/lib/gen2/mechanics.zig +855 -413
  37. package/src/lib/{binding/node.zig → node.zig} +9 -12
  38. package/src/lib/pkmn.zig +27 -12
  39. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  40. package/src/test/display/pkmn.html.tmpl +5 -5
  41. package/src/lib/gen1/README.md +0 -894
  42. package/src/lib/gen2/README.md +0 -330
@@ -14,7 +14,7 @@ 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 data = @import("./data.zig");
17
+ const data = @import("data.zig");
18
18
 
19
19
  const enabled = options.chance;
20
20
  const showdown = options.showdown;
@@ -42,12 +42,6 @@ pub const Actions = extern struct {
42
42
  return if (player == .P1) &self.p1 else &self.p2;
43
43
  }
44
44
 
45
- /// Perform a reset by clearing fields which should not persist across updates.
46
- pub fn reset(self: *Actions) void {
47
- self.p1.reset();
48
- self.p2.reset();
49
- }
50
-
51
45
  /// Returns true if `a` is equal to `b`.
52
46
  pub fn eql(a: Actions, b: Actions) bool {
53
47
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
@@ -63,11 +57,6 @@ pub const Actions = extern struct {
63
57
  const b_val = @field(@field(b, player.name), field.name);
64
58
 
65
59
  switch (@typeInfo(@TypeOf(a_val))) {
66
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
67
- if ((@field(a_val, f.name) > 0) != (@field(b_val, f.name) > 0)) {
68
- return false;
69
- }
70
- },
71
60
  .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
72
61
  return false,
73
62
  .Int => if ((a_val > 0) != (b_val > 0)) return false,
@@ -96,9 +85,6 @@ test Actions {
96
85
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
97
86
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
98
87
  const c: Actions = .{ .p1 = .{ .hit = .true } };
99
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
100
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
101
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
102
88
 
103
89
  try expect(a.eql(a));
104
90
  try expect(!a.eql(b));
@@ -111,8 +97,6 @@ test Actions {
111
97
  try expect(b.matches(a));
112
98
  try expect(!a.matches(c));
113
99
  try expect(!c.matches(a));
114
- try expect(d.matches(e));
115
- try expect(!d.matches(f));
116
100
  }
117
101
 
118
102
  /// Information about the RNG that was observed during a Generation I battle `update` for a
@@ -139,8 +123,7 @@ pub const Action = packed struct(u64) {
139
123
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
140
124
  duration: u4 = 0,
141
125
 
142
- /// Observed values of various durations. Does not influence future RNG calls. TODO
143
- durations: Duration = .{},
126
+ _: u16 = 0,
144
127
 
145
128
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
146
129
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
@@ -154,15 +137,8 @@ pub const Action = packed struct(u64) {
154
137
  /// If not None, the Move to return for Rolls.metronome.
155
138
  metronome: Move = .None,
156
139
 
157
- pub const DURATIONS: u64 = 0x000000FFFF000000;
158
-
159
140
  pub const Field = std.meta.FieldEnum(Action);
160
141
 
161
- /// Perform a reset by clearing fields which should not persist across updates.
162
- pub fn reset(self: *Action) void {
163
- self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
164
- }
165
-
166
142
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
167
143
  _ = .{ f, o };
168
144
  try fmt(a, w, false);
@@ -174,18 +150,6 @@ pub const Action = packed struct(u64) {
174
150
  inline for (@typeInfo(Action).Struct.fields) |field| {
175
151
  const val = @field(self, field.name);
176
152
  switch (@typeInfo(@TypeOf(val))) {
177
- .Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
178
- const v = @field(val, f.name);
179
- if (v != 0) {
180
- if (printed) try writer.writeAll(", ");
181
- if (shape) {
182
- try writer.print("{s}:?", .{f.name});
183
- } else {
184
- try writer.print("{s}:{d}", .{ f.name, v });
185
- }
186
- printed = true;
187
- }
188
- },
189
153
  .Enum => if (val != .None) {
190
154
  if (printed) try writer.writeAll(", ");
191
155
  if (shape) {
@@ -216,13 +180,6 @@ pub const Action = packed struct(u64) {
216
180
  }
217
181
  };
218
182
 
219
- test Action {
220
- var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
221
- a.reset();
222
-
223
- try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
224
- }
225
-
226
183
  /// TODO
227
184
  pub const Durations = extern struct {
228
185
  /// Information about the durations for Player 1.
@@ -233,6 +190,12 @@ pub const Durations = extern struct {
233
190
  comptime {
234
191
  assert(@sizeOf(Durations) == 4);
235
192
  }
193
+
194
+ /// Returns the `Duration` for the given `player`.
195
+ pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Duration) {
196
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Durations);
197
+ return if (player == .P1) &self.p1 else &self.p2;
198
+ }
236
199
  };
237
200
 
238
201
  /// Observed values for various durations that need to be tracked in order to properly
@@ -254,6 +217,8 @@ pub const Duration = packed struct(u16) {
254
217
 
255
218
  pub const Commit = enum { hit, miss, binding };
256
219
 
220
+ const uN = if (options.miss) u8 else u9;
221
+
257
222
  /// Tracks chance actions and their associated probability during a Generation I battle update when
258
223
  /// `options.chance` is enabled.
259
224
  pub fn Chance(comptime Rational: type) type {
@@ -265,6 +230,8 @@ pub fn Chance(comptime Rational: type) type {
265
230
  /// The actions taken by a hypothetical "chance player" that convey information about which
266
231
  /// RNG events were observed during a battle `update`.
267
232
  actions: Actions = .{},
233
+ /// TODO
234
+ durations: Durations = .{},
268
235
 
269
236
  // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
270
237
  // other durations which are all tied to volatiles, sleep's counter persists through
@@ -290,15 +257,12 @@ pub fn Chance(comptime Rational: type) type {
290
257
  //
291
258
  // Finally, because each player's actions are processed sequentially we only need a single
292
259
  // shared structure to track this information (though it needs to be cleared appropriately)
293
- pending: if (showdown) struct {
294
- hit: bool = false,
295
- hit_probablity: u8 = 0,
296
- } else struct {
260
+ pending: if (showdown) struct {} else struct {
297
261
  crit: bool = false,
298
262
  crit_probablity: u8 = 0,
299
263
  damage_roll: u8 = 0,
300
264
  hit: bool = false,
301
- hit_probablity: u8 = 0,
265
+ hit_probablity: uN = 0,
302
266
  binding: u8 = 0,
303
267
  } = .{},
304
268
 
@@ -307,25 +271,31 @@ pub fn Chance(comptime Rational: type) type {
307
271
 
308
272
  /// Convenience helper to clear fields which typically should be cleared between updates.
309
273
  pub fn reset(self: *Self) void {
274
+ if (!enabled) return;
275
+
310
276
  self.probability.reset();
311
- self.actions.reset();
277
+ self.actions = .{};
312
278
  self.pending = .{};
313
279
  }
314
280
 
315
281
  pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
282
+ if (!enabled) return;
283
+
284
+ assert(!showdown);
285
+
316
286
  var action = self.actions.get(player);
317
287
 
318
288
  // We need to handle binding specially because we still need to commit it when attacking
319
289
  // into a Pokémon that is immune
320
- if (!showdown) {
321
- if (kind != .miss and self.pending.binding != 0) {
322
- assert(action.duration == 0);
323
- action.duration = @intCast(self.pending.binding);
324
- assert(action.durations.binding == 0);
325
- action.durations.binding = 1;
326
- }
327
- if (kind == .binding) return;
328
- } else assert(kind != .binding);
290
+ if (kind != .miss and self.pending.binding != 0) {
291
+ // assert(action.duration == 0); FIXME
292
+ action.duration = @intCast(self.pending.binding);
293
+
294
+ var durations = self.durations.get(player);
295
+ assert(durations.binding == 0);
296
+ durations.binding = 1;
297
+ }
298
+ if (kind == .binding) return;
329
299
 
330
300
  // Always commit the hit result if we make it here (commit won't be called at all if the
331
301
  // target is immune/behind a sub/etc)
@@ -334,19 +304,19 @@ pub fn Chance(comptime Rational: type) type {
334
304
  action.hit = if (self.pending.hit) .true else .false;
335
305
  }
336
306
 
337
- if (showdown) return;
307
+ // If the move actually lands we can commit any past critical hit / damage rolls. Note
308
+ // we can't rely on the presence or absence of a damage roll to determine whether a
309
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
310
+ // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
311
+ // passed to the critical hit helper)
312
+ if (kind != .hit) return assert(!self.pending.crit or self.pending.crit_probablity > 0);
338
313
 
339
- // If the move actually lands we can commit any past critical hit / damage rolls. We
340
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
341
- // to a damage roll is actually a no-op
342
- if (kind == .hit and self.pending.damage_roll > 0) {
343
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
344
-
345
- if (self.pending.crit_probablity != 0) {
346
- try self.probability.update(self.pending.crit_probablity, 256);
347
- action.critical_hit = if (self.pending.crit) .true else .false;
348
- }
314
+ if (self.pending.crit_probablity != 0) {
315
+ try self.probability.update(self.pending.crit_probablity, 256);
316
+ action.critical_hit = if (self.pending.crit) .true else .false;
317
+ }
349
318
 
319
+ if (self.pending.damage_roll > 0) {
350
320
  try self.probability.update(1, 39);
351
321
  action.damage = self.pending.damage_roll;
352
322
  }
@@ -361,14 +331,14 @@ pub fn Chance(comptime Rational: type) type {
361
331
  pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
362
332
  if (!enabled) return;
363
333
 
364
- var action = self.actions.get(player);
334
+ var durations = self.durations.get(player);
365
335
  if (haze) |status| {
366
- if (status) action.durations.sleep = 0;
367
- action.durations.confusion = 0;
368
- action.durations.disable = 0;
336
+ if (status) durations.sleep = 0;
337
+ durations.confusion = 0;
338
+ durations.disable = 0;
369
339
  } else {
370
- action.durations.attacking = 0;
371
- action.durations.binding = 0;
340
+ durations.attacking = 0;
341
+ durations.binding = 0;
372
342
  }
373
343
  }
374
344
 
@@ -378,15 +348,15 @@ pub fn Chance(comptime Rational: type) type {
378
348
  assert(in >= 1 and in <= 6);
379
349
  assert(out >= 1 and out <= 6);
380
350
 
381
- var action = self.actions.get(player);
351
+ var durations = self.durations.get(player);
382
352
 
383
- const slp = action.durations.sleep;
384
- action.durations = .{};
353
+ const slp = durations.sleep;
354
+ durations.* = .{};
385
355
 
386
356
  self.sleeps[@intFromEnum(player)][out - 1] = slp;
387
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
357
+ durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
388
358
 
389
- self.actions.get(player.foe()).durations.binding = 0;
359
+ self.durations.get(player.foe()).binding = 0;
390
360
  }
391
361
 
392
362
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -397,12 +367,17 @@ pub fn Chance(comptime Rational: type) type {
397
367
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
398
368
  }
399
369
 
400
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
370
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
401
371
  if (!enabled) return;
402
372
 
403
373
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
404
- self.pending.hit = ok;
405
- self.pending.hit_probablity = p;
374
+ if (showdown) {
375
+ try self.probability.update(p, 256);
376
+ self.actions.get(player).hit = if (ok) .true else .false;
377
+ } else {
378
+ self.pending.hit = ok;
379
+ self.pending.hit_probablity = p;
380
+ }
406
381
  }
407
382
 
408
383
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -492,10 +467,11 @@ pub fn Chance(comptime Rational: type) type {
492
467
  if (!showdown and field == .binding) {
493
468
  self.pending.binding = if (options.key) 1 else turns;
494
469
  } else {
495
- var durations = &self.actions.get(target).durations;
470
+ var durations = self.durations.get(target);
496
471
  assert(@field(durations, @tagName(field)) == 0 or
497
472
  (field == .confusion and player == target));
498
473
  @field(durations, @tagName(field)) = 1;
474
+
499
475
  self.actions.get(player).duration = if (options.key) 1 else turns;
500
476
  }
501
477
  }
@@ -503,7 +479,7 @@ pub fn Chance(comptime Rational: type) type {
503
479
  pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
504
480
  if (!enabled) return;
505
481
 
506
- var durations = &self.actions.get(player).durations;
482
+ var durations = self.durations.get(player);
507
483
  const n = durations.sleep;
508
484
  if (turns == 0) {
509
485
  assert(n >= 1 and n <= 7);
@@ -519,7 +495,7 @@ pub fn Chance(comptime Rational: type) type {
519
495
  pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
520
496
  if (!enabled) return;
521
497
 
522
- var durations = &self.actions.get(player).durations;
498
+ var durations = self.durations.get(player);
523
499
  const n = durations.confusion;
524
500
  if (turns == 0) {
525
501
  assert(n >= 2 and n <= 5);
@@ -535,7 +511,7 @@ pub fn Chance(comptime Rational: type) type {
535
511
  pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
536
512
  if (!enabled) return;
537
513
 
538
- var durations = &self.actions.get(player).durations;
514
+ var durations = self.durations.get(player);
539
515
  const n = durations.disable;
540
516
  if (turns == 0) {
541
517
  assert(n >= 1 and n <= 8);
@@ -551,7 +527,7 @@ pub fn Chance(comptime Rational: type) type {
551
527
  pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
552
528
  if (!enabled) return;
553
529
 
554
- var durations = &self.actions.get(player).durations;
530
+ var durations = self.durations.get(player);
555
531
  const n = durations.attacking;
556
532
  if (turns == 0) {
557
533
  assert(n >= 2 and n <= 3);
@@ -567,7 +543,7 @@ pub fn Chance(comptime Rational: type) type {
567
543
  pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
568
544
  if (!enabled) return;
569
545
 
570
- var durations = &self.actions.get(player).durations;
546
+ var durations = self.durations.get(player);
571
547
  const n = durations.binding;
572
548
 
573
549
  assert(n > 0);
@@ -620,19 +596,25 @@ test "Chance.speedTie" {
620
596
  test "Chance.hit" {
621
597
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
622
598
 
623
- chance.hit(true, 229);
624
- try expectProbability(&chance.probability, 1, 1);
625
- try expectValue(Optional(bool).None, chance.actions.p1.hit);
626
- try chance.commit(.P1, .hit);
599
+ try chance.hit(.P1, true, 229);
600
+ if (!showdown) {
601
+ try expectProbability(&chance.probability, 1, 1);
602
+ try expectValue(Optional(bool).None, chance.actions.p1.hit);
603
+
604
+ try chance.commit(.P1, .hit);
605
+ }
627
606
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
628
607
  try expectProbability(&chance.probability, 229, 256);
629
608
 
630
609
  chance.reset();
631
610
 
632
- chance.hit(false, 255);
633
- try expectProbability(&chance.probability, 1, 1);
634
- try expectValue(Optional(bool).None, chance.actions.p2.hit);
635
- try chance.commit(.P2, .miss);
611
+ try chance.hit(.P2, false, 255);
612
+ if (!showdown) {
613
+ try expectProbability(&chance.probability, 1, 1);
614
+ try expectValue(Optional(bool).None, chance.actions.p2.hit);
615
+
616
+ try chance.commit(.P2, .miss);
617
+ }
636
618
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
637
619
  try expectProbability(&chance.probability, 1, 256);
638
620
  }
@@ -648,26 +630,18 @@ test "Chance.criticalHit" {
648
630
  try expectProbability(&chance.probability, 1, 1);
649
631
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
650
632
 
651
- try chance.commit(.P1, .hit);
652
- try expectProbability(&chance.probability, 1, 1);
653
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
654
-
655
- chance.hit(true, 128);
633
+ try chance.hit(.P1, true, 128);
656
634
 
657
- try chance.commit(.P1, .hit);
658
- try expectProbability(&chance.probability, 1, 2);
635
+ try expectProbability(&chance.probability, 1, 1);
659
636
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
660
637
 
661
- chance.probability.reset();
662
638
  try chance.damage(.P1, 217);
663
639
 
664
- try chance.commit(.P1, .miss);
665
- try expectProbability(&chance.probability, 1, 2);
640
+ try expectProbability(&chance.probability, 1, 1);
666
641
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
667
642
 
668
- chance.probability.reset();
669
-
670
643
  try chance.commit(.P1, .hit);
644
+
671
645
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
672
646
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
673
647
  }
@@ -682,20 +656,14 @@ test "Chance.criticalHit" {
682
656
  try expectProbability(&chance.probability, 1, 1);
683
657
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
684
658
 
685
- try chance.commit(.P1, .hit);
686
- try expectProbability(&chance.probability, 1, 1);
687
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
688
-
689
- chance.hit(true, 128);
659
+ try chance.hit(.P1, true, 128);
690
660
 
691
- try chance.commit(.P1, .hit);
692
- try expectProbability(&chance.probability, 1, 2);
661
+ try expectProbability(&chance.probability, 1, 1);
693
662
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
694
663
 
695
- chance.probability.reset();
696
664
  try chance.damage(.P1, 217);
697
-
698
665
  try chance.commit(.P1, .hit);
666
+
699
667
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
700
668
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
701
669
  }
@@ -706,19 +674,16 @@ test "Chance.damage" {
706
674
 
707
675
  try chance.damage(.P1, 217);
708
676
  if (showdown) {
709
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
677
+ try expectValue(217, chance.actions.p1.damage);
710
678
  try expectProbability(&chance.probability, 1, 39);
711
679
  } else {
712
680
  try expectProbability(&chance.probability, 1, 1);
713
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
714
-
715
- try chance.commit(.P1, .miss);
716
- try expectProbability(&chance.probability, 1, 1);
717
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
681
+ try expectValue(0, chance.actions.p1.damage);
718
682
 
719
683
  try chance.commit(.P1, .hit);
684
+
720
685
  try expectProbability(&chance.probability, 1, 39);
721
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
686
+ try expectValue(217, chance.actions.p1.damage);
722
687
  }
723
688
  }
724
689
 
@@ -770,13 +735,13 @@ test "Chance.moveSlot" {
770
735
 
771
736
  try chance.moveSlot(.P2, 2, &ms, 2);
772
737
  try expectProbability(&chance.probability, 1, 2);
773
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
738
+ try expectValue(2, chance.actions.p2.move_slot);
774
739
 
775
740
  chance.reset();
776
741
 
777
742
  try chance.moveSlot(.P1, 1, &ms, 0);
778
743
  try expectProbability(&chance.probability, 1, 3);
779
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
744
+ try expectValue(1, chance.actions.p1.move_slot);
780
745
  }
781
746
 
782
747
  test "Chance.multiHit" {
@@ -784,50 +749,51 @@ test "Chance.multiHit" {
784
749
 
785
750
  try chance.multiHit(.P1, 3);
786
751
  try expectProbability(&chance.probability, 3, 8);
787
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
752
+ try expectValue(3, chance.actions.p1.multi_hit);
788
753
 
789
754
  chance.reset();
790
755
 
791
756
  try chance.multiHit(.P2, 5);
792
757
  try expectProbability(&chance.probability, 1, 8);
793
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
758
+ try expectValue(5, chance.actions.p2.multi_hit);
794
759
  }
795
760
 
796
761
  test "Chance.duration" {
797
762
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
798
763
 
799
764
  chance.duration(.sleep, .P1, .P2, 2);
800
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
801
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
765
+ try expectValue(2, chance.actions.p1.duration);
766
+ try expectValue(1, chance.durations.p2.sleep);
802
767
 
803
768
  chance.reset();
804
769
 
805
770
  chance.duration(.binding, .P2, .P2, 4);
806
771
  if (!showdown) {
807
- try expectValue(@as(u4, 0), chance.actions.p2.duration);
808
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
772
+ try expectValue(0, chance.actions.p2.duration);
773
+ try expectValue(0, chance.durations.p2.binding);
774
+
809
775
  try chance.commit(.P2, .hit);
810
776
  }
811
- try expectValue(@as(u4, 4), chance.actions.p2.duration);
812
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
777
+ try expectValue(4, chance.actions.p2.duration);
778
+ try expectValue(1, chance.durations.p2.binding);
813
779
  }
814
780
 
815
781
  test "Chance.sleep" {
816
782
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
817
783
 
818
784
  for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
819
- chance.actions.p1.durations.sleep = @intCast(i);
785
+ chance.durations.p1.sleep = @intCast(i);
820
786
  try chance.sleep(.P1, 0);
821
787
  try expectProbability(&chance.probability, 1, d);
822
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
788
+ try expectValue(0, chance.durations.p1.sleep);
823
789
 
824
790
  chance.reset();
825
791
 
826
792
  if (i < 7) {
827
- chance.actions.p1.durations.sleep = @intCast(i);
793
+ chance.durations.p1.sleep = @intCast(i);
828
794
  try chance.sleep(.P1, 1);
829
795
  try expectProbability(&chance.probability, d - 1, d);
830
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
796
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.sleep);
831
797
 
832
798
  chance.reset();
833
799
  }
@@ -839,19 +805,19 @@ test "Chance.confusion" {
839
805
 
840
806
  for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
841
807
  if (i > 1) {
842
- chance.actions.p2.durations.confusion = @intCast(i);
808
+ chance.durations.p2.confusion = @intCast(i);
843
809
  try chance.confusion(.P2, 0);
844
810
  try expectProbability(&chance.probability, 1, d);
845
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
811
+ try expectValue(0, chance.durations.p2.confusion);
846
812
 
847
813
  chance.reset();
848
814
  }
849
815
 
850
816
  if (i < 5) {
851
- chance.actions.p2.durations.confusion = @intCast(i);
817
+ chance.durations.p2.confusion = @intCast(i);
852
818
  try chance.confusion(.P2, 1);
853
819
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
854
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
820
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
855
821
 
856
822
  chance.reset();
857
823
  }
@@ -862,18 +828,18 @@ test "Chance.disable" {
862
828
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
863
829
 
864
830
  for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
865
- chance.actions.p1.durations.disable = @intCast(i);
831
+ chance.durations.p1.disable = @intCast(i);
866
832
  try chance.disable(.P1, 0);
867
833
  try expectProbability(&chance.probability, 1, d);
868
- try expectValue(@as(u4, 0), chance.actions.p1.durations.disable);
834
+ try expectValue(0, chance.durations.p1.disable);
869
835
 
870
836
  chance.reset();
871
837
 
872
838
  if (i < 8) {
873
- chance.actions.p1.durations.disable = @intCast(i);
839
+ chance.durations.p1.disable = @intCast(i);
874
840
  try chance.disable(.P1, 1);
875
841
  try expectProbability(&chance.probability, d - 1, d);
876
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
842
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
877
843
 
878
844
  chance.reset();
879
845
  }
@@ -885,19 +851,19 @@ test "Chance.attacking" {
885
851
 
886
852
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
887
853
  if (i > 1) {
888
- chance.actions.p2.durations.attacking = @intCast(i);
854
+ chance.durations.p2.attacking = @intCast(i);
889
855
  try chance.attacking(.P2, 0);
890
856
  try expectProbability(&chance.probability, 1, d);
891
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
857
+ try expectValue(0, chance.durations.p2.attacking);
892
858
 
893
859
  chance.reset();
894
860
  }
895
861
 
896
862
  if (i < 3) {
897
- chance.actions.p2.durations.attacking = @intCast(i);
863
+ chance.durations.p2.attacking = @intCast(i);
898
864
  try chance.attacking(.P2, 1);
899
865
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
900
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
866
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
901
867
 
902
868
  chance.reset();
903
869
  }
@@ -911,18 +877,18 @@ test "Chance.binding" {
911
877
  const qs = [_]u8{ 8, 5, 2, 1 };
912
878
 
913
879
  for (ps, qs, 1..5) |p, q, i| {
914
- chance.actions.p1.durations.binding = @intCast(i);
880
+ chance.durations.p1.binding = @intCast(i);
915
881
  try chance.binding(.P1, 0);
916
882
  try expectProbability(&chance.probability, p, q);
917
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
883
+ try expectValue(0, chance.durations.p1.binding);
918
884
 
919
885
  chance.reset();
920
886
 
921
887
  if (i < 4) {
922
- chance.actions.p1.durations.binding = @intCast(i);
888
+ chance.durations.p1.binding = @intCast(i);
923
889
  try chance.binding(.P1, 1);
924
890
  try expectProbability(&chance.probability, q - p, q);
925
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
891
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
926
892
 
927
893
  chance.reset();
928
894
  }
@@ -934,7 +900,7 @@ test "Chance.psywave" {
934
900
 
935
901
  try chance.psywave(.P2, 100, 150);
936
902
  try expectProbability(&chance.probability, 1, 150);
937
- try expectValue(@as(u8, 101), chance.actions.p2.psywave);
903
+ try expectValue(101, chance.actions.p2.psywave);
938
904
  }
939
905
 
940
906
  test "Chance.metronome" {
@@ -988,8 +954,8 @@ const Null = struct {
988
954
  _ = .{ self, p1 };
989
955
  }
990
956
 
991
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
992
- _ = .{ self, ok, accuracy };
957
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
958
+ _ = .{ self, player, ok, accuracy };
993
959
  }
994
960
 
995
961
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -1091,7 +1091,7 @@ pub const Species = enum(u8) {
1091
1091
  /// The number of Pokémon species in this generation.
1092
1092
  pub const size = 151;
1093
1093
 
1094
- /// The Pokémon's critical hit rate=io out of 256.
1094
+ /// The Pokémon's critical hit ratio out of 256.
1095
1095
  pub fn chance(id: Species) u8 {
1096
1096
  assert(id != .None);
1097
1097
  return CHANCES[@intFromEnum(id) - 1];