@pkmn/engine 0.1.0-dev.3de821b9 → 0.1.0-dev.3f3af84c

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 (79) hide show
  1. package/README.md +9 -6
  2. package/build/pkg/addon/browser.d.ts +3 -0
  3. package/build/pkg/addon/browser.js +47 -0
  4. package/build/pkg/addon/browser.js.map +1 -0
  5. package/build/pkg/addon/node.d.ts +3 -0
  6. package/build/pkg/addon/node.js +100 -0
  7. package/build/pkg/addon/node.js.map +1 -0
  8. package/build/pkg/addon/wasm.d.ts +2 -0
  9. package/build/pkg/addon/wasm.js +53 -0
  10. package/build/pkg/addon/wasm.js.map +1 -0
  11. package/build/pkg/addon.d.ts +25 -3
  12. package/build/pkg/addon.js +34 -65
  13. package/build/pkg/addon.js.map +1 -1
  14. package/build/pkg/data/layout.json +15 -2
  15. package/build/pkg/data/protocol.json +23 -9
  16. package/build/pkg/data.d.ts +23 -1
  17. package/build/pkg/data.js.map +1 -1
  18. package/build/pkg/gen1.d.ts +15 -18
  19. package/build/pkg/gen1.js +11 -12
  20. package/build/pkg/gen1.js.map +1 -1
  21. package/build/pkg/index.d.ts +40 -90
  22. package/build/pkg/index.js +6 -63
  23. package/build/pkg/index.js.map +1 -1
  24. package/build/pkg/protocol.d.ts +20 -1
  25. package/build/pkg/protocol.js +105 -103
  26. package/build/pkg/protocol.js.map +1 -1
  27. package/build/tools/debug.js +35 -69
  28. package/build/tools/debug.js.map +1 -1
  29. package/build/tools/display/debug.jsx +141 -0
  30. package/build/tools/display/debug.jsx.map +1 -0
  31. package/build/tools/display/index.js +142 -0
  32. package/build/tools/display/index.js.map +1 -0
  33. package/build/tools/display/ui.jsx +349 -0
  34. package/build/tools/display/ui.jsx.map +1 -0
  35. package/build/tools/display/util.js +33 -0
  36. package/build/tools/display/util.js.map +1 -0
  37. package/build.zig +330 -85
  38. package/package.json +53 -134
  39. package/src/bin/install-pkmn-engine +10 -7
  40. package/src/bin/pkmn-debug +1 -1
  41. package/src/lib/bindings/c.zig +255 -0
  42. package/src/lib/bindings/node.zig +121 -0
  43. package/src/lib/bindings/wasm.zig +59 -0
  44. package/src/lib/common/array.zig +77 -0
  45. package/src/lib/common/data.zig +8 -8
  46. package/src/lib/common/debug.zig +39 -8
  47. package/src/lib/common/js.zig +4 -2
  48. package/src/lib/common/optional.zig +13 -9
  49. package/src/lib/common/options.zig +4 -0
  50. package/src/lib/common/protocol.zig +44 -43
  51. package/src/lib/common/rational.zig +53 -51
  52. package/src/lib/common/rng.zig +5 -7
  53. package/src/lib/common/util.zig +50 -1
  54. package/src/lib/gen1/calc.zig +250 -111
  55. package/src/lib/gen1/chance.zig +130 -121
  56. package/src/lib/gen1/data/moves.zig +2 -4
  57. package/src/lib/gen1/data/species.zig +1 -3
  58. package/src/lib/gen1/data/types.zig +1 -2
  59. package/src/lib/gen1/data.zig +35 -38
  60. package/src/lib/gen1/helpers.zig +15 -27
  61. package/src/lib/gen1/mechanics.zig +118 -95
  62. package/src/lib/gen2/calc.zig +37 -27
  63. package/src/lib/gen2/chance.zig +86 -66
  64. package/src/lib/gen2/data/items.zig +1 -3
  65. package/src/lib/gen2/data/moves.zig +2 -4
  66. package/src/lib/gen2/data/species.zig +1 -3
  67. package/src/lib/gen2/data/types.zig +1 -2
  68. package/src/lib/gen2/data.zig +26 -29
  69. package/src/lib/gen2/generated.zig +18 -23
  70. package/src/lib/gen2/helpers.zig +9 -18
  71. package/src/lib/gen2/mechanics.zig +81 -81
  72. package/src/lib/node.zig +3 -116
  73. package/src/lib/pkmn.zig +14 -7
  74. package/src/lib/wasm.zig +13 -9
  75. package/src/tools/display/dom.ts +96 -0
  76. package/src/tools/display/ui.css +354 -0
  77. package/build/test/display.js +0 -405
  78. package/build/test/display.js.map +0 -1
  79. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,44 +1,43 @@
1
- const std = @import("std");
2
-
3
- const pkmn = @import("../pkmn.zig");
1
+ const DEBUG = @import("../common/debug.zig").print;
4
2
 
3
+ const chance = @import("chance.zig");
5
4
  const common = @import("../common/data.zig");
6
- const DEBUG = @import("../common/debug.zig").print;
5
+ const data = @import("data.zig");
6
+ const helpers = @import("helpers.zig");
7
7
  const optional = @import("../common/optional.zig");
8
+ const pkmn = @import("../pkmn.zig");
8
9
  const protocol = @import("../common/protocol.zig");
9
10
  const rational = @import("../common/rational.zig");
11
+ const std = @import("std");
10
12
  const util = @import("../common/util.zig");
11
13
 
12
- const chance = @import("chance.zig");
13
- const data = @import("data.zig");
14
- const helpers = @import("helpers.zig");
15
-
14
+ const Action = chance.Action;
15
+ const Actions = chance.Actions;
16
16
  const assert = std.debug.assert;
17
-
17
+ const Chance = chance.Chance;
18
+ const Choice = common.Choice;
19
+ const Confusion = chance.Confusion;
20
+ const Duration = chance.Duration;
21
+ const Durations = chance.Durations;
22
+ const enabled = pkmn.options.calc;
18
23
  const expect = std.testing.expect;
19
24
  const expectEqual = std.testing.expectEqual;
20
25
  const expectEqualSlices = std.testing.expectEqualSlices;
21
-
22
- const enabled = pkmn.options.calc;
23
-
24
- const Player = common.Player;
25
- const Result = common.Result;
26
- const Choice = common.Choice;
27
-
26
+ const isPointerTo = util.isPointerTo;
27
+ const Observation = chance.Observation;
28
28
  const Optional = optional.Optional;
29
-
30
- const Rational = rational.Rational;
31
-
29
+ const Player = common.Player;
32
30
  const PointerType = util.PointerType;
33
-
34
- const Actions = chance.Actions;
35
- const Action = chance.Action;
36
- const Chance = chance.Chance;
37
- const Durations = chance.Durations;
31
+ const Rational = rational.Rational;
32
+ const Result = common.Result;
33
+ const Sleeps = chance.Sleeps;
38
34
 
39
35
  const tty = true; // DEBUG
40
36
  const summary = false; // DEBUG
41
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
+
42
41
  /// Information relevant to damage calculation that occured during a Generation I battle `update`.
43
42
  pub const Summaries = extern struct {
44
43
  /// Relevant information for Player 1.
@@ -52,7 +51,7 @@ pub const Summaries = extern struct {
52
51
 
53
52
  /// Returns the `Summary` for the given `player`.
54
53
  pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Summary) {
55
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Summaries);
54
+ assert(isPointerTo(self, Summaries));
56
55
  return if (player == .P1) &self.p1 else &self.p2;
57
56
  }
58
57
  };
@@ -88,21 +87,11 @@ pub const Summary = extern struct {
88
87
  }
89
88
  };
90
89
 
91
- /// TODO
92
- pub const Overrides = extern struct {
93
- /// TODO
94
- actions: Actions = .{},
95
-
96
- comptime {
97
- assert(@sizeOf(Overrides) == 16);
98
- }
99
- };
100
-
101
90
  /// Allows for forcing the value of specific RNG events during a Generation I battle `update` via
102
91
  /// `overrides` and tracks `summaries` of information relevant to damage calculation.
103
92
  pub const Calc = struct {
104
93
  /// Overrides the normal behavior of the RNG during an `update` to force specific outcomes.
105
- overrides: Overrides = .{},
94
+ overrides: Actions = .{},
106
95
  /// Information relevant to damage calculation.
107
96
  summaries: Summaries = .{},
108
97
 
@@ -113,16 +102,22 @@ pub const Calc = struct {
113
102
  ) ?std.meta.FieldType(Action, field) {
114
103
  if (!enabled) return null;
115
104
 
116
- const overrides =
117
- if (player == .P1) self.overrides.actions.p1 else self.overrides.actions.p2;
118
- const val = @field(overrides, @tagName(field));
105
+ const val = @field(self.overrides.get(player), @tagName(field));
119
106
  return if (switch (@typeInfo(@TypeOf(val))) {
120
- .Enum => val != .None,
121
- .Int => val != 0,
107
+ Enum => val != .None,
108
+ Int => val != 0,
122
109
  else => unreachable,
123
110
  }) val else null;
124
111
  }
125
112
 
113
+ pub fn confusion(self: *Calc, player: Player) void {
114
+ if (!enabled) return;
115
+
116
+ if (self.overrides.get(player).confusion == .started) {
117
+ self.overrides.get(player).confusion = .None;
118
+ }
119
+ }
120
+
126
121
  pub fn base(self: *Calc, player: Player, val: u16) void {
127
122
  if (!enabled) return;
128
123
 
@@ -156,6 +151,10 @@ const Null = struct {
156
151
  return null;
157
152
  }
158
153
 
154
+ pub fn confusion(self: Null, player: Player) void {
155
+ _ = .{ self, player };
156
+ }
157
+
159
158
  pub fn base(self: Null, player: Player, val: u16) void {
160
159
  _ = .{ self, player, val };
161
160
  }
@@ -183,7 +182,7 @@ pub const Options = struct {
183
182
  metronome: bool = false,
184
183
  };
185
184
 
186
- pub const MAX_FRONTIER = 19;
185
+ pub const MAX_FRONTIER = 81;
187
186
 
188
187
  pub fn transitions(
189
188
  battle: anytype,
@@ -202,9 +201,11 @@ pub fn transitions(
202
201
  var frontier = std.ArrayList(Actions).init(allocator);
203
202
  defer frontier.deinit();
204
203
 
204
+ const d = options.durations;
205
+
205
206
  var opts = pkmn.battle.options(
206
207
  protocol.NULL,
207
- Chance(Rational(u128)){ .probability = .{}, .durations = options.durations },
208
+ Chance(Rational(u128)){ .probability = .{}, .durations = d },
208
209
  Calc{},
209
210
  );
210
211
 
@@ -227,9 +228,10 @@ pub fn transitions(
227
228
  var i: usize = 0;
228
229
  assert(frontier.items.len == 1);
229
230
  while (i < frontier.items.len) : (i += 1) {
230
- const template = frontier.items[i];
231
+ const f = frontier.items[i];
232
+ const saved = stats.saved;
231
233
 
232
- try debug(writer, template, .{
234
+ try debug(writer, f, .{
233
235
  .shape = true,
234
236
  .color = i,
235
237
  .bold = true,
@@ -238,33 +240,43 @@ pub fn transitions(
238
240
  });
239
241
 
240
242
  var a: Actions = .{
241
- .p1 = .{ .metronome = p1_move, .duration = template.p1.duration },
242
- .p2 = .{ .metronome = p2_move, .duration = template.p2.duration },
243
+ .p1 = .{ .metronome = p1_move, .pp = f.p1.pp, .duration = f.p1.duration },
244
+ .p2 = .{ .metronome = p2_move, .pp = f.p2.pp, .duration = f.p2.duration },
243
245
  };
244
246
 
245
- for (Rolls.speedTie(template.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
246
- for (Rolls.confused(template.p1)) |p1_cfzd| { a.p1.confused = p1_cfzd;
247
- for (Rolls.confused(template.p2)) |p2_cfzd| { a.p2.confused = p2_cfzd;
248
- for (Rolls.paralyzed(template.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
249
- for (Rolls.paralyzed(template.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
250
- for (Rolls.hit(template.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
251
- for (Rolls.hit(template.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
252
- for (Rolls.psywave(template.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
253
- for (Rolls.psywave(template.p2, p2, p2_hit)) |p2_psywave| { a.p2.psywave = p2_psywave;
254
- for (Rolls.moveSlot(template.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
255
- for (Rolls.moveSlot(template.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
256
- for (Rolls.multiHit(template.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
257
- for (Rolls.multiHit(template.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
258
- for (Rolls.secondaryChance(template.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
259
- for (Rolls.secondaryChance(template.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
260
- for (Rolls.criticalHit(template.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
261
- for (Rolls.criticalHit(template.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
262
-
263
- var p1_dmg = Rolls.damage(template.p1, p1_hit);
247
+ for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
248
+ for (Rolls.sleep(f.p1, d.p1)) |p1_slp| { a.p1.sleep = p1_slp;
249
+ for (Rolls.sleep(f.p2, d.p2)) |p2_slp| { a.p2.sleep = p2_slp;
250
+ for (Rolls.disable(f.p1, d.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
251
+ for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
252
+ for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
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;
256
+ for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
257
+ for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
258
+ for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
259
+ for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
260
+ for (Rolls.binding(f.p1, d.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
261
+ for (Rolls.binding(f.p2, d.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
262
+ for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
263
+ for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
264
+ for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
265
+ for (Rolls.psywave(f.p2, p2, p2_hit)) |p2_psywave| { a.p2.psywave = p2_psywave;
266
+ for (Rolls.moveSlot(f.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
267
+ for (Rolls.moveSlot(f.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
268
+ for (Rolls.multiHit(f.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
269
+ for (Rolls.multiHit(f.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
270
+ for (Rolls.secondaryChance(f.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
271
+ for (Rolls.secondaryChance(f.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
272
+ for (Rolls.criticalHit(f.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
273
+ for (Rolls.criticalHit(f.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
274
+
275
+ var p1_dmg = Rolls.damage(f.p1, p1_hit);
264
276
  while (p1_dmg.min < p1_dmg.max) : (p1_dmg.min += 1) {
265
277
  a.p1.damage = @intCast(p1_dmg.min);
266
278
 
267
- var p2_dmg = Rolls.damage(template.p2, p2_hit);
279
+ var p2_dmg = Rolls.damage(f.p2, p2_hit);
268
280
 
269
281
  const p1_min: u9 = p1_dmg.min;
270
282
  const p2_min: u9 = p2_dmg.min;
@@ -272,9 +284,9 @@ pub fn transitions(
272
284
  while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
273
285
  a.p2.damage = @intCast(p2_dmg.min);
274
286
 
275
- opts.calc.overrides.actions = a;
287
+ opts.calc.overrides = a;
276
288
  opts.calc.summaries = .{};
277
- opts.chance = .{ .probability = .{}, .durations = options.durations };
289
+ opts.chance = .{ .probability = .{}, .durations = d };
278
290
  const q = &opts.chance.probability;
279
291
 
280
292
  b = battle;
@@ -286,12 +298,12 @@ pub fn transitions(
286
298
  const p1_max: u9 = if (p2_dmg.min != p2_min)
287
299
  p1_dmg.min
288
300
  else
289
- try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
301
+ Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
290
302
  const p2_max: u9 =
291
- try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
303
+ Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
292
304
 
293
- if (opts.chance.actions.matches(template)) {
294
- if (!opts.chance.actions.eql(a)) {
305
+ if (opts.chance.actions.matches(f)) {
306
+ if (!opts.chance.actions.relax().eql(a)) {
295
307
  if (!summary) {
296
308
  try debug(writer, opts.chance.actions, .{
297
309
  .p1_max = p1_max,
@@ -325,10 +337,13 @@ pub fn transitions(
325
337
  }
326
338
  }
327
339
  }
328
-
329
340
  if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
330
341
  if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
342
+
343
+ q.reduce();
331
344
  try p.add(q);
345
+ p.reduce();
346
+
332
347
  stats.saved += 1;
333
348
 
334
349
  if (p.q < p.p) {
@@ -336,7 +351,7 @@ pub fn transitions(
336
351
  return error.TestUnexpectedResult;
337
352
  }
338
353
  } else {
339
- if (!matches(opts.chance.actions, i, frontier.items)) {
354
+ if (!opts.chance.actions.matchesAny(frontier.items, i)) {
340
355
  try frontier.append(opts.chance.actions);
341
356
 
342
357
  try debug(writer, opts.chance.actions, .{
@@ -366,9 +381,11 @@ pub fn transitions(
366
381
  p2_dmg.min = p2_max;
367
382
  }
368
383
 
369
- }}}}}}}}}}}}}}}}}}
384
+ }}}}}}}}}}}}}}}}}}}}}}}}}}}}
370
385
 
371
- if (@TypeOf(writer) != @TypeOf(std.io.null_writer)) {
386
+ assert(stats.saved > saved);
387
+ const old = @hasDecl(std.fmt, "formatFloatDecimal");
388
+ if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
372
389
  p.reduce();
373
390
  try writer.print(
374
391
  " = {s} ({d:.2}%)\n\n",
@@ -446,7 +463,7 @@ pub fn update(
446
463
  var override = pkmn.battle.options(
447
464
  protocol.NULL,
448
465
  Chance(Rational(u128)){ .probability = .{}, .durations = durations },
449
- Calc{ .overrides = .{ .actions = overrides } },
466
+ Calc{ .overrides = overrides },
450
467
  );
451
468
  var copy = original;
452
469
  var overridden = copy.update(c1, c2, &override);
@@ -468,7 +485,7 @@ pub fn update(
468
485
  override = pkmn.battle.options(
469
486
  protocol.NULL,
470
487
  Chance(Rational(u128)){ .probability = .{}, .durations = durations },
471
- Calc{ .overrides = .{ .actions = overrides } },
488
+ Calc{ .overrides = overrides },
472
489
  );
473
490
  copy = original;
474
491
  overridden = copy.update(c1, c2, &override);
@@ -504,15 +521,6 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
504
521
  };
505
522
  }
506
523
 
507
- fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
508
- for (frontier, 0..) |f, j| {
509
- // TODO: is skipping this redundant check worth it?
510
- if (i == j) continue;
511
- if (f.matches(actions)) return true;
512
- }
513
- return false;
514
- }
515
-
516
524
  fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
517
525
  const w = std.io.getStdErr().writer();
518
526
  w.print(fmt, v) catch return;
@@ -585,8 +593,6 @@ fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
585
593
  try writer.writeAll(output[0..len]);
586
594
  }
587
595
 
588
- const NONE = @intFromEnum(Optional(bool).None);
589
-
590
596
  /// Helper functions that efficiently return valid ranges for various RNG events based on the
591
597
  /// state of an `Action` and other events to be used to construct a "transitions" function.
592
598
  pub const Rolls = struct {
@@ -598,6 +604,108 @@ pub const Rolls = struct {
598
604
  return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
599
605
  }
600
606
 
607
+ const OBS_NONE = [_]Optional(Observation){.None};
608
+ const OBS_STARTED = [_]Optional(Observation){.started};
609
+ const OBS_CONTINUING = [_]Optional(Observation){.continuing};
610
+ const OBS_ENDED = [_]Optional(Observation){.ended};
611
+ const OBS_ALL = [_]Optional(Observation){ .started, .continuing, .ended };
612
+ const OBS = [_]Optional(Observation){ .continuing, .ended };
613
+
614
+ /// Returns a slice with a range of values for sleep given the `action` state and observed
615
+ /// `durations`.
616
+ pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
617
+ if (action.sleep == .None) return &OBS_NONE;
618
+ return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
619
+ &OBS_ALL
620
+ else switch (action.sleep) {
621
+ .started => &OBS_STARTED,
622
+ else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
623
+ };
624
+ }
625
+
626
+ /// Returns a slice with a range of values for disable given the `action`, observed `durations`,
627
+ /// and the state of the `parent` (observation of the player's Pokémon sleep status).
628
+ pub fn disable(
629
+ action: Action,
630
+ duration: Duration,
631
+ parent: Optional(Observation),
632
+ ) []const Optional(Observation) {
633
+ if (action.disable == .None) return &OBS_NONE;
634
+ return if (duration.disable == 0 and action.speed_tie != .None)
635
+ &OBS_ALL
636
+ else switch (action.disable) {
637
+ .started => &OBS_STARTED,
638
+ else => if (duration.disable >= 8)
639
+ &OBS_ENDED
640
+ else if (parent != .None and parent != .started)
641
+ &OBS_CONTINUING
642
+ else
643
+ &OBS,
644
+ };
645
+ }
646
+
647
+ /// FIXME
648
+ pub fn attacking(
649
+ action: Action,
650
+ duration: Duration,
651
+ parent: Optional(Observation),
652
+ ) []const Optional(Observation) {
653
+ return switch (action.attacking) {
654
+ .None => &OBS_NONE,
655
+ .started => &OBS_STARTED,
656
+ else => if (duration.attacking >= 3)
657
+ &OBS_ENDED
658
+ else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
659
+ &OBS_CONTINUING
660
+ else
661
+ &OBS,
662
+ };
663
+ }
664
+
665
+ const CFZ_NONE = [_]Optional(Confusion){.None};
666
+ const CFZ_STARTED = [_]Optional(Confusion){.started};
667
+ const CFZ_OVERWRITTEN = [_]Optional(Confusion){ .started, .overwritten };
668
+ const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
669
+ const CFZ_ENDED = [_]Optional(Confusion){.ended};
670
+ const CFZ = [_]Optional(Confusion){ .continuing, .ended };
671
+
672
+ /// FIXME
673
+ pub fn confusion(
674
+ action: Action,
675
+ duration: Duration,
676
+ sibling: Optional(Observation),
677
+ parent: Optional(Observation),
678
+ ) []const Optional(Confusion) {
679
+ return switch (action.confusion) {
680
+ .None => &CFZ_NONE,
681
+ .started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
682
+ &CFZ_OVERWRITTEN
683
+ else
684
+ &CFZ_STARTED,
685
+ else => if (duration.confusion >= 5)
686
+ &CFZ_ENDED
687
+ else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
688
+ &CFZ_CONTINUING
689
+ else
690
+ &CFZ,
691
+ };
692
+ }
693
+
694
+ /// Returns a slice with a range of values for binding given the `action`, observed `durations`,
695
+ /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
696
+ pub fn binding(
697
+ action: Action,
698
+ duration: Duration,
699
+ parent: Optional(bool),
700
+ ) []const Optional(Observation) {
701
+ if (parent == .true) return &OBS_NONE;
702
+ return switch (action.binding) {
703
+ .None => &OBS_NONE,
704
+ .started => &OBS_STARTED,
705
+ else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
706
+ };
707
+ }
708
+
601
709
  const BOOL_NONE = [_]Optional(bool){.None};
602
710
  const BOOLS = [_]Optional(bool){ .false, .true };
603
711
 
@@ -611,7 +719,7 @@ pub const Rolls = struct {
611
719
  /// Returns a slice with the correct range of values for critical hits given the `action` state
612
720
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
613
721
  pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
614
- if (parent == .false) return &BOOL_NONE;
722
+ if (pkmn.options.showdown and parent == .false) return &BOOL_NONE;
615
723
  return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
616
724
  }
617
725
 
@@ -636,7 +744,7 @@ pub const Rolls = struct {
636
744
 
637
745
  /// Returns the max damage roll which will produce the same damage as `roll`
638
746
  /// given the base damage in `summaries`.
639
- pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) !u8 {
747
+ pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
640
748
  if (roll == 0) return roll;
641
749
 
642
750
  const dmg = summaries.get(player.foe()).damage;
@@ -646,9 +754,11 @@ pub const Rolls = struct {
646
754
  return @min(255, roll + ((254 - ((@as(u32, dmg.base) * roll) % 255)) / dmg.base));
647
755
  }
648
756
 
649
- /// Returns a slice with the correct range of values for confused.
650
- pub fn confused(action: Action) []const Optional(bool) {
651
- return if (action.confused == .None) &BOOL_NONE else &BOOLS;
757
+ /// Returns a slice with the correct range of values for confused given the `action` state and
758
+ /// the state of the `parent` (observation of the player's Pokémon confusion status).
759
+ pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
760
+ const done = parent == .None or parent == .ended;
761
+ return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
652
762
  }
653
763
 
654
764
  /// Returns a slice with the correct range of values for paralysis given the `action` state
@@ -658,14 +768,14 @@ pub const Rolls = struct {
658
768
  return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
659
769
  }
660
770
 
661
- const SLOT_NONE = [_]u3{0};
662
- const SLOT = [_]u3{ 1, 2, 3, 4 };
771
+ const SLOT_NONE = [_]u4{0};
772
+ const SLOT = [_]u4{ 1, 2, 3, 4 };
663
773
 
664
774
  /// Returns a slice with a range of values for move slots given the `action` state
665
775
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
666
776
  ///
667
777
  /// These slots may or **may not be valid** as slots may be unset / have 0 PP.
668
- pub fn moveSlot(action: Action, parent: Optional(bool)) []const u3 {
778
+ pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
669
779
  if (parent == .false) return &SLOT_NONE;
670
780
  return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
671
781
  }
@@ -715,6 +825,26 @@ test "Rolls.speedTie" {
715
825
  try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
716
826
  }
717
827
 
828
+ test "Rolls.sleep" {
829
+ return error.SkipZigTest; // TODO
830
+ }
831
+
832
+ test "Rolls.disable" {
833
+ return error.SkipZigTest; // TODO
834
+ }
835
+
836
+ test "Rolls.confusion" {
837
+ return error.SkipZigTest; // TODO
838
+ }
839
+
840
+ test "Rolls.atacking" {
841
+ return error.SkipZigTest; // TODO
842
+ }
843
+
844
+ test "Rolls.binding" {
845
+ return error.SkipZigTest; // TODO
846
+ }
847
+
718
848
  test "Rolls.damage" {
719
849
  const actions: Actions = .{ .p2 = .{ .damage = 221 } };
720
850
  try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
@@ -724,12 +854,12 @@ test "Rolls.damage" {
724
854
 
725
855
  test "Rolls.coalesce" {
726
856
  var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
727
- try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
728
- try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
729
- try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
857
+ try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
858
+ try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
859
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
730
860
  summaries.p1.damage.final = 74;
731
- try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
732
- try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
861
+ try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
862
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
733
863
  }
734
864
 
735
865
  test "Rolls.hit" {
@@ -758,14 +888,23 @@ test "Rolls.criticalHit" {
758
888
  &.{ .false, .true },
759
889
  Rolls.criticalHit(actions.p1, .None),
760
890
  );
761
- try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p1, .false));
891
+ try expectEqualSlices(
892
+ Optional(bool),
893
+ if (pkmn.options.showdown) &.{.None} else &.{ .false, .true },
894
+ Rolls.criticalHit(actions.p1, .false),
895
+ );
762
896
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p2, .None));
763
897
  }
764
898
 
765
899
  test "Rolls.confused" {
766
900
  const actions: Actions = .{ .p2 = .{ .confused = .true } };
767
- try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1));
768
- try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2));
901
+ try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
902
+ try expectEqualSlices(
903
+ Optional(bool),
904
+ &.{ .false, .true },
905
+ Rolls.confused(actions.p2, .continuing),
906
+ );
907
+ try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
769
908
  }
770
909
 
771
910
  test "Rolls.paralyzed" {
@@ -778,9 +917,9 @@ test "Rolls.paralyzed" {
778
917
 
779
918
  test "Rolls.moveSlot" {
780
919
  const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
781
- try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p1, .None));
782
- try expectEqualSlices(u3, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
783
- try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p2, .false));
920
+ try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
921
+ try expectEqualSlices(u4, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
922
+ try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p2, .false));
784
923
  }
785
924
 
786
925
  test "Rolls.multiHit" {
@@ -806,9 +945,9 @@ test "Rolls.psywave" {
806
945
 
807
946
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
808
947
  var rolls = Rolls.psywave(actions.p2, &side, .None);
809
- try expectEqual(150, rolls[rolls.len - 1]);
948
+ try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
810
949
  side.stored().level = 81;
811
950
  rolls = Rolls.psywave(actions.p2, &side, .None);
812
- try expectEqual(121, rolls[rolls.len - 1]);
951
+ try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
813
952
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
814
953
  }