@pkmn/engine 0.1.0-dev.b01cb48c → 0.1.0-dev.b3197aa3

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 (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +54 -6
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +47 -0
  5. package/build/pkg/addon/browser.js.map +1 -0
  6. package/build/pkg/addon/node.d.ts +3 -0
  7. package/build/pkg/addon/node.js +110 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon/wasm.d.ts +2 -0
  10. package/build/pkg/addon/wasm.js +53 -0
  11. package/build/pkg/addon/wasm.js.map +1 -0
  12. package/build/pkg/addon.d.ts +25 -3
  13. package/build/pkg/addon.js +34 -65
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/layout.json +15 -2
  16. package/build/pkg/data/protocol.json +23 -9
  17. package/build/pkg/data.d.ts +23 -1
  18. package/build/pkg/data.js.map +1 -1
  19. package/build/pkg/gen1.d.ts +15 -18
  20. package/build/pkg/gen1.js +28 -19
  21. package/build/pkg/gen1.js.map +1 -1
  22. package/build/pkg/index.d.ts +40 -90
  23. package/build/pkg/index.js +23 -70
  24. package/build/pkg/index.js.map +1 -1
  25. package/build/pkg/protocol.d.ts +20 -1
  26. package/build/pkg/protocol.js +105 -103
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +52 -76
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +152 -0
  31. package/build/tools/display/debug.jsx.map +1 -0
  32. package/build/tools/display/index.js +152 -0
  33. package/build/tools/display/index.js.map +1 -0
  34. package/build/tools/display/ui.jsx +359 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +33 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +331 -85
  39. package/package.json +56 -136
  40. package/src/bin/install-pkmn-engine +10 -7
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +255 -0
  43. package/src/lib/bindings/node.zig +117 -0
  44. package/src/lib/bindings/wasm.zig +59 -0
  45. package/src/lib/common/array.zig +77 -0
  46. package/src/lib/common/data.zig +8 -8
  47. package/src/lib/common/debug.zig +39 -8
  48. package/src/lib/common/js.zig +4 -6
  49. package/src/lib/common/napi.zig +4 -0
  50. package/src/lib/common/optional.zig +13 -9
  51. package/src/lib/common/options.zig +4 -0
  52. package/src/lib/common/protocol.zig +44 -43
  53. package/src/lib/common/rational.zig +317 -304
  54. package/src/lib/common/rng.zig +5 -7
  55. package/src/lib/common/util.zig +65 -1
  56. package/src/lib/gen1/calc.zig +266 -135
  57. package/src/lib/gen1/chance.zig +469 -110
  58. package/src/lib/gen1/data/moves.zig +2 -4
  59. package/src/lib/gen1/data/species.zig +1 -3
  60. package/src/lib/gen1/data/types.zig +2 -3
  61. package/src/lib/gen1/data.zig +35 -38
  62. package/src/lib/gen1/helpers.zig +17 -27
  63. package/src/lib/gen1/mechanics.zig +213 -160
  64. package/src/lib/gen2/calc.zig +37 -27
  65. package/src/lib/gen2/chance.zig +87 -67
  66. package/src/lib/gen2/data/items.zig +1 -3
  67. package/src/lib/gen2/data/moves.zig +2 -4
  68. package/src/lib/gen2/data/species.zig +1 -3
  69. package/src/lib/gen2/data/types.zig +7 -5
  70. package/src/lib/gen2/data.zig +28 -30
  71. package/src/lib/gen2/generated.zig +18 -23
  72. package/src/lib/gen2/helpers.zig +9 -18
  73. package/src/lib/gen2/mechanics.zig +82 -82
  74. package/src/lib/node.zig +4 -121
  75. package/src/lib/pkmn.zig +14 -7
  76. package/src/lib/wasm.zig +13 -9
  77. package/src/tools/display/dom.ts +93 -0
  78. package/src/tools/display/ui.css +354 -0
  79. package/build/test/display.js +0 -405
  80. package/build/test/display.js.map +0 -1
  81. 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
 
@@ -110,19 +99,25 @@ pub const Calc = struct {
110
99
  self: Calc,
111
100
  player: Player,
112
101
  comptime field: Action.Field,
113
- ) ?std.meta.FieldType(Action, field) {
102
+ ) ?util.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
 
@@ -151,11 +146,15 @@ const Null = struct {
151
146
  self: Null,
152
147
  player: Player,
153
148
  comptime field: Action.Field,
154
- ) ?std.meta.FieldType(Action, field) {
149
+ ) ?util.FieldType(Action, field) {
155
150
  _ = .{ self, player };
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 = 83;
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
 
@@ -215,7 +216,7 @@ pub fn transitions(
215
216
  const p1 = b.side(.P1);
216
217
  const p2 = b.side(.P2);
217
218
 
218
- var p: Rational(u128) = .{ .p = 0, .q = 1 };
219
+ var p: Rational(u256) = .{ .p = 0, .q = 1 };
219
220
  try frontier.append(opts.chance.actions);
220
221
 
221
222
  // zig fmt: off
@@ -227,9 +228,12 @@ 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
+ var r: Rational(u256) = .{ .p = 0, .q = 1 };
235
+
236
+ try debug(writer, f, .{
233
237
  .shape = true,
234
238
  .color = i,
235
239
  .bold = true,
@@ -237,33 +241,44 @@ pub fn transitions(
237
241
  .indent = false,
238
242
  });
239
243
 
240
- var a: Actions = .{ .p1 = .{ .metronome = p1_move }, .p2 = .{ .metronome = p2_move } };
241
-
242
- for (Rolls.speedTie(template.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
243
- for (Rolls.confused(template.p1)) |p1_cfzd| { a.p1.confused = p1_cfzd;
244
- for (Rolls.confused(template.p2)) |p2_cfzd| { a.p2.confused = p2_cfzd;
245
- for (Rolls.paralyzed(template.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
246
- for (Rolls.paralyzed(template.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
247
- for (Rolls.duration(template.p1, p1_par)) |p1_dur| { a.p1.duration = p1_dur;
248
- for (Rolls.duration(template.p2, p2_par)) |p2_dur| { a.p2.duration = p2_dur;
249
- for (Rolls.hit(template.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
250
- for (Rolls.hit(template.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
251
- for (Rolls.psywave(template.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
252
- for (Rolls.psywave(template.p2, p2, p2_hit)) |p2_psywave| { a.p2.psywave = p2_psywave;
253
- for (Rolls.moveSlot(template.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
254
- for (Rolls.moveSlot(template.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
255
- for (Rolls.multiHit(template.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
256
- for (Rolls.multiHit(template.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
257
- for (Rolls.secondaryChance(template.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
258
- for (Rolls.secondaryChance(template.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
259
- for (Rolls.criticalHit(template.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
260
- for (Rolls.criticalHit(template.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
261
-
262
- var p1_dmg = Rolls.damage(template.p1, p1_hit);
244
+ var a: Actions = .{
245
+ .p1 = .{ .metronome = p1_move, .pp = f.p1.pp, .duration = f.p1.duration },
246
+ .p2 = .{ .metronome = p2_move, .pp = f.p2.pp, .duration = f.p2.duration },
247
+ };
248
+
249
+ for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
250
+ for (Rolls.sleep(f.p1, d.p1)) |p1_slp| { a.p1.sleep = p1_slp;
251
+ for (Rolls.sleep(f.p2, d.p2)) |p2_slp| { a.p2.sleep = p2_slp;
252
+ for (Rolls.disable(f.p1, d.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
253
+ for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
254
+ for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
255
+ for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
256
+ for (Rolls.confusion(f.p1, d.p1, tie, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
257
+ for (Rolls.confusion(f.p2, d.p2, tie, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
258
+ for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
259
+ for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
260
+ for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
261
+ for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
262
+ for (Rolls.binding(f.p1, d.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
263
+ for (Rolls.binding(f.p2, d.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
264
+ for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
265
+ for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
266
+ for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
267
+ for (Rolls.psywave(f.p2, p2, p2_hit)) |p2_psywave| { a.p2.psywave = p2_psywave;
268
+ for (Rolls.moveSlot(f.p1, p1_hit)) |p1_slot| { a.p1.move_slot = p1_slot;
269
+ for (Rolls.moveSlot(f.p2, p2_hit)) |p2_slot| { a.p2.move_slot = p2_slot;
270
+ for (Rolls.multiHit(f.p1, p1_hit)) |p1_multi| { a.p1.multi_hit = p1_multi;
271
+ for (Rolls.multiHit(f.p2, p2_hit)) |p2_multi| { a.p2.multi_hit = p2_multi;
272
+ for (Rolls.secondaryChance(f.p1, p1_hit)) |p1_sec| { a.p1.secondary_chance = p1_sec;
273
+ for (Rolls.secondaryChance(f.p2, p2_hit)) |p2_sec| { a.p2.secondary_chance = p2_sec;
274
+ for (Rolls.criticalHit(f.p1, p1_hit)) |p1_crit| { a.p1.critical_hit = p1_crit;
275
+ for (Rolls.criticalHit(f.p2, p2_hit)) |p2_crit| { a.p2.critical_hit = p2_crit;
276
+
277
+ var p1_dmg = Rolls.damage(f.p1, p1_hit);
263
278
  while (p1_dmg.min < p1_dmg.max) : (p1_dmg.min += 1) {
264
279
  a.p1.damage = @intCast(p1_dmg.min);
265
280
 
266
- var p2_dmg = Rolls.damage(template.p2, p2_hit);
281
+ var p2_dmg = Rolls.damage(f.p2, p2_hit);
267
282
 
268
283
  const p1_min: u9 = p1_dmg.min;
269
284
  const p2_min: u9 = p2_dmg.min;
@@ -271,9 +286,9 @@ pub fn transitions(
271
286
  while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
272
287
  a.p2.damage = @intCast(p2_dmg.min);
273
288
 
274
- opts.calc.overrides.actions = a;
289
+ opts.calc.overrides = a;
275
290
  opts.calc.summaries = .{};
276
- opts.chance = .{ .probability = .{}, .durations = options.durations };
291
+ opts.chance = .{ .probability = .{}, .durations = d };
277
292
  const q = &opts.chance.probability;
278
293
 
279
294
  b = battle;
@@ -285,12 +300,12 @@ pub fn transitions(
285
300
  const p1_max: u9 = if (p2_dmg.min != p2_min)
286
301
  p1_dmg.min
287
302
  else
288
- try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
303
+ Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
289
304
  const p2_max: u9 =
290
- try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
305
+ Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
291
306
 
292
- if (opts.chance.actions.matches(template)) {
293
- if (!opts.chance.actions.eql(a)) {
307
+ if (opts.chance.actions.matches(f)) {
308
+ if (!opts.chance.actions.relax().eql(a)) {
294
309
  if (!summary) {
295
310
  try debug(writer, opts.chance.actions, .{
296
311
  .p1_max = p1_max,
@@ -324,10 +339,15 @@ pub fn transitions(
324
339
  }
325
340
  }
326
341
  }
327
-
328
342
  if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
329
343
  if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
344
+
345
+ q.reduce();
330
346
  try p.add(q);
347
+ p.reduce();
348
+ try r.add(q);
349
+ r.reduce();
350
+
331
351
  stats.saved += 1;
332
352
 
333
353
  if (p.q < p.p) {
@@ -335,7 +355,7 @@ pub fn transitions(
335
355
  return error.TestUnexpectedResult;
336
356
  }
337
357
  } else {
338
- if (!matches(opts.chance.actions, i, frontier.items)) {
358
+ if (!opts.chance.actions.matchesAny(frontier.items, i)) {
339
359
  try frontier.append(opts.chance.actions);
340
360
 
341
361
  try debug(writer, opts.chance.actions, .{
@@ -365,15 +385,18 @@ pub fn transitions(
365
385
  p2_dmg.min = p2_max;
366
386
  }
367
387
 
368
- }}}}}}}}}}}}}}}}}}}}
388
+ }}}}}}}}}}}}}}}}}}}}}}}}}}}}
369
389
 
370
- if (@TypeOf(writer) != @TypeOf(std.io.null_writer)) {
371
- p.reduce();
372
- try writer.print(
373
- " = {s} ({d:.2}%)\n\n",
374
- .{p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
375
- );
376
- }
390
+ assert(stats.saved > saved);
391
+ // TODO: ziglang/zig#22667
392
+ // const old = @hasDecl(std.fmt, "formatFloatDecimal");
393
+ // if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
394
+ // try writer.print(
395
+ // " {} ({d:.2}%)\n = ──────────\n {} ({d:.2}%)\n\n",
396
+ // .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
397
+ // p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
398
+ // );
399
+ // }
377
400
 
378
401
  }
379
402
 
@@ -445,7 +468,7 @@ pub fn update(
445
468
  var override = pkmn.battle.options(
446
469
  protocol.NULL,
447
470
  Chance(Rational(u128)){ .probability = .{}, .durations = durations },
448
- Calc{ .overrides = .{ .actions = overrides } },
471
+ Calc{ .overrides = overrides },
449
472
  );
450
473
  var copy = original;
451
474
  var overridden = copy.update(c1, c2, &override);
@@ -466,8 +489,8 @@ pub fn update(
466
489
 
467
490
  override = pkmn.battle.options(
468
491
  protocol.NULL,
469
- Chance(Rational(u128)){ .probability = .{} },
470
- Calc{ .overrides = .{ .actions = overrides } },
492
+ Chance(Rational(u128)){ .probability = .{}, .durations = durations },
493
+ Calc{ .overrides = overrides },
471
494
  );
472
495
  copy = original;
473
496
  overridden = copy.update(c1, c2, &override);
@@ -503,15 +526,6 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
503
526
  };
504
527
  }
505
528
 
506
- fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
507
- for (frontier, 0..) |f, j| {
508
- // TODO: is skipping this redundant check worth it?
509
- if (i == j) continue;
510
- if (f.matches(actions)) return true;
511
- }
512
- return false;
513
- }
514
-
515
529
  fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
516
530
  const w = std.io.getStdErr().writer();
517
531
  w.print(fmt, v) catch return;
@@ -584,8 +598,6 @@ fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
584
598
  try writer.writeAll(output[0..len]);
585
599
  }
586
600
 
587
- const NONE = @intFromEnum(Optional(bool).None);
588
-
589
601
  /// Helper functions that efficiently return valid ranges for various RNG events based on the
590
602
  /// state of an `Action` and other events to be used to construct a "transitions" function.
591
603
  pub const Rolls = struct {
@@ -597,6 +609,112 @@ pub const Rolls = struct {
597
609
  return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
598
610
  }
599
611
 
612
+ const OBS_NONE = [_]Optional(Observation){.None};
613
+ const OBS_STARTED = [_]Optional(Observation){.started};
614
+ const OBS_CONTINUING = [_]Optional(Observation){.continuing};
615
+ const OBS_ENDED = [_]Optional(Observation){.ended};
616
+ const OBS_ALL = [_]Optional(Observation){ .started, .continuing, .ended };
617
+ const OBS = [_]Optional(Observation){ .continuing, .ended };
618
+
619
+ /// Returns a slice with a range of values for sleep given the `action` state and observed
620
+ /// `durations`.
621
+ pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
622
+ if (action.sleep == .None) return &OBS_NONE;
623
+ return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
624
+ &OBS_ALL
625
+ else switch (action.sleep) {
626
+ .started => &OBS_STARTED,
627
+ else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
628
+ };
629
+ }
630
+
631
+ /// Returns a slice with a range of values for disable given the `action`, observed `durations`,
632
+ /// and the state of the `parent` (observation of the player's Pokémon sleep status).
633
+ pub fn disable(
634
+ action: Action,
635
+ duration: Duration,
636
+ parent: Optional(Observation),
637
+ ) []const Optional(Observation) {
638
+ if (action.disable == .None) return &OBS_NONE;
639
+ return if (duration.disable == 0 and action.speed_tie != .None)
640
+ &OBS_ALL
641
+ else switch (action.disable) {
642
+ .started => &OBS_STARTED,
643
+ else => if (duration.disable >= 8)
644
+ &OBS_ENDED
645
+ else if (parent != .None and parent != .started)
646
+ &OBS_CONTINUING
647
+ else
648
+ &OBS,
649
+ };
650
+ }
651
+
652
+ /// FIXME
653
+ pub fn attacking(
654
+ action: Action,
655
+ duration: Duration,
656
+ parent: Optional(Observation),
657
+ ) []const Optional(Observation) {
658
+ return switch (action.attacking) {
659
+ .None => &OBS_NONE,
660
+ .started => &OBS_STARTED,
661
+ else => if (duration.attacking >= 3)
662
+ &OBS_ENDED
663
+ else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
664
+ &OBS_CONTINUING
665
+ else
666
+ &OBS,
667
+ };
668
+ }
669
+
670
+ const CFZ_NONE = [_]Optional(Confusion){.None};
671
+ const CFZ_STARTED = [_]Optional(Confusion){.started};
672
+ const CFZ_OVERWRITTEN = [_]Optional(Confusion){ .started, .overwritten };
673
+ const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
674
+ const CFZ_ENDED = [_]Optional(Confusion){.ended};
675
+ const CFZ = [_]Optional(Confusion){ .continuing, .ended };
676
+ const CFZ_TIE = [_]Optional(Confusion){ .started, .continuing, .ended };
677
+
678
+ /// FIXME
679
+ pub fn confusion(
680
+ action: Action,
681
+ duration: Duration,
682
+ tie: Optional(Player),
683
+ sibling: Optional(Observation),
684
+ parent: Optional(Observation),
685
+ ) []const Optional(Confusion) {
686
+ return switch (action.confusion) {
687
+ .None => &CFZ_NONE,
688
+ .started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
689
+ &CFZ_OVERWRITTEN
690
+ else
691
+ &CFZ_STARTED,
692
+ else => if (duration.confusion >= 5)
693
+ &CFZ_ENDED
694
+ else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
695
+ &CFZ_CONTINUING
696
+ else if (tie != .None)
697
+ &CFZ_TIE
698
+ else
699
+ &CFZ,
700
+ };
701
+ }
702
+
703
+ /// Returns a slice with a range of values for binding given the `action`, observed `durations`,
704
+ /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
705
+ pub fn binding(
706
+ action: Action,
707
+ duration: Duration,
708
+ parent: Optional(bool),
709
+ ) []const Optional(Observation) {
710
+ if (parent == .true) return &OBS_NONE;
711
+ return switch (action.binding) {
712
+ .None => &OBS_NONE,
713
+ .started => &OBS_STARTED,
714
+ else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
715
+ };
716
+ }
717
+
600
718
  const BOOL_NONE = [_]Optional(bool){.None};
601
719
  const BOOLS = [_]Optional(bool){ .false, .true };
602
720
 
@@ -610,7 +728,7 @@ pub const Rolls = struct {
610
728
  /// Returns a slice with the correct range of values for critical hits given the `action` state
611
729
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
612
730
  pub fn criticalHit(action: Action, parent: Optional(bool)) []const Optional(bool) {
613
- if (parent == .false) return &BOOL_NONE;
731
+ if (pkmn.options.showdown and parent == .false) return &BOOL_NONE;
614
732
  return if (action.critical_hit == .None) &BOOL_NONE else &BOOLS;
615
733
  }
616
734
 
@@ -635,7 +753,7 @@ pub const Rolls = struct {
635
753
 
636
754
  /// Returns the max damage roll which will produce the same damage as `roll`
637
755
  /// given the base damage in `summaries`.
638
- pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) !u8 {
756
+ pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
639
757
  if (roll == 0) return roll;
640
758
 
641
759
  const dmg = summaries.get(player.foe()).damage;
@@ -645,9 +763,11 @@ pub const Rolls = struct {
645
763
  return @min(255, roll + ((254 - ((@as(u32, dmg.base) * roll) % 255)) / dmg.base));
646
764
  }
647
765
 
648
- /// Returns a slice with the correct range of values for confused.
649
- pub fn confused(action: Action) []const Optional(bool) {
650
- return if (action.confused == .None) &BOOL_NONE else &BOOLS;
766
+ /// Returns a slice with the correct range of values for confused given the `action` state and
767
+ /// the state of the `parent` (observation of the player's Pokémon confusion status).
768
+ pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
769
+ const done = parent == .None or parent == .ended;
770
+ return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
651
771
  }
652
772
 
653
773
  /// Returns a slice with the correct range of values for paralysis given the `action` state
@@ -657,17 +777,6 @@ pub const Rolls = struct {
657
777
  return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
658
778
  }
659
779
 
660
- const DURATION_NONE = [_]u2{0};
661
- // NB: DURATION must be at least 2 because that is the maximum of the minimums for all rolls
662
- const DURATION = [_]u2{2};
663
-
664
- /// Returns a slice with a range of values for duration given the `action` state
665
- /// and the state of the `parent` (whether the player's Pokémon's move hit).
666
- pub fn duration(action: Action, parent: Optional(bool)) []const u2 {
667
- if (parent == .false) return &DURATION_NONE;
668
- return if (action.duration == 0) &DURATION_NONE else &DURATION;
669
- }
670
-
671
780
  const SLOT_NONE = [_]u4{0};
672
781
  const SLOT = [_]u4{ 1, 2, 3, 4 };
673
782
 
@@ -725,6 +834,26 @@ test "Rolls.speedTie" {
725
834
  try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
726
835
  }
727
836
 
837
+ test "Rolls.sleep" {
838
+ return error.SkipZigTest; // TODO
839
+ }
840
+
841
+ test "Rolls.disable" {
842
+ return error.SkipZigTest; // TODO
843
+ }
844
+
845
+ test "Rolls.confusion" {
846
+ return error.SkipZigTest; // TODO
847
+ }
848
+
849
+ test "Rolls.atacking" {
850
+ return error.SkipZigTest; // TODO
851
+ }
852
+
853
+ test "Rolls.binding" {
854
+ return error.SkipZigTest; // TODO
855
+ }
856
+
728
857
  test "Rolls.damage" {
729
858
  const actions: Actions = .{ .p2 = .{ .damage = 221 } };
730
859
  try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
@@ -734,12 +863,12 @@ test "Rolls.damage" {
734
863
 
735
864
  test "Rolls.coalesce" {
736
865
  var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
737
- try expectEqual(0, try Rolls.coalesce(.P2, 0, &summaries, false));
738
- try expectEqual(241, try Rolls.coalesce(.P2, 238, &summaries, false));
739
- try expectEqual(255, try Rolls.coalesce(.P2, 238, &summaries, true));
866
+ try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
867
+ try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
868
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
740
869
  summaries.p1.damage.final = 74;
741
- try expectEqual(217, try Rolls.coalesce(.P2, 217, &summaries, false));
742
- try expectEqual(255, try Rolls.coalesce(.P2, 217, &summaries, true));
870
+ try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
871
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
743
872
  }
744
873
 
745
874
  test "Rolls.hit" {
@@ -768,14 +897,23 @@ test "Rolls.criticalHit" {
768
897
  &.{ .false, .true },
769
898
  Rolls.criticalHit(actions.p1, .None),
770
899
  );
771
- try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p1, .false));
900
+ try expectEqualSlices(
901
+ Optional(bool),
902
+ if (pkmn.options.showdown) &.{.None} else &.{ .false, .true },
903
+ Rolls.criticalHit(actions.p1, .false),
904
+ );
772
905
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.criticalHit(actions.p2, .None));
773
906
  }
774
907
 
775
908
  test "Rolls.confused" {
776
909
  const actions: Actions = .{ .p2 = .{ .confused = .true } };
777
- try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1));
778
- try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2));
910
+ try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
911
+ try expectEqualSlices(
912
+ Optional(bool),
913
+ &.{ .false, .true },
914
+ Rolls.confused(actions.p2, .continuing),
915
+ );
916
+ try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
779
917
  }
780
918
 
781
919
  test "Rolls.paralyzed" {
@@ -786,13 +924,6 @@ test "Rolls.paralyzed" {
786
924
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.paralyzed(actions.p2, .true));
787
925
  }
788
926
 
789
- test "Rolls.duration" {
790
- const actions: Actions = .{ .p2 = .{ .duration = 3 } };
791
- try expectEqualSlices(u2, &.{0}, Rolls.duration(actions.p1, .None));
792
- try expectEqualSlices(u2, &.{2}, Rolls.duration(actions.p2, .None));
793
- try expectEqualSlices(u2, &.{0}, Rolls.duration(actions.p2, .false));
794
- }
795
-
796
927
  test "Rolls.moveSlot" {
797
928
  const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
798
929
  try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
@@ -823,9 +954,9 @@ test "Rolls.psywave" {
823
954
 
824
955
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p1, &side, .None));
825
956
  var rolls = Rolls.psywave(actions.p2, &side, .None);
826
- try expectEqual(150, rolls[rolls.len - 1]);
957
+ try expectEqual(@as(u8, 150), rolls[rolls.len - 1]);
827
958
  side.stored().level = 81;
828
959
  rolls = Rolls.psywave(actions.p2, &side, .None);
829
- try expectEqual(121, rolls[rolls.len - 1]);
960
+ try expectEqual(@as(u8, 121), rolls[rolls.len - 1]);
830
961
  try expectEqualSlices(u8, &.{0}, Rolls.psywave(actions.p2, &side, .false));
831
962
  }