@pkmn/engine 0.1.0-dev.a0447192 → 0.1.0-dev.a15b6c17

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 (73) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +22 -12
  3. package/build/pkg/addon/browser.d.ts +1 -1
  4. package/build/pkg/addon/browser.js +6 -2
  5. package/build/pkg/addon/browser.js.map +1 -1
  6. package/build/pkg/addon/node.js +22 -8
  7. package/build/pkg/addon/node.js.map +1 -1
  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 -5
  12. package/build/pkg/addon.js +8 -17
  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.js.map +1 -1
  17. package/build/pkg/gen1.d.ts +15 -18
  18. package/build/pkg/gen1.js +28 -19
  19. package/build/pkg/gen1.js.map +1 -1
  20. package/build/pkg/index.d.ts +37 -100
  21. package/build/pkg/index.js +23 -75
  22. package/build/pkg/index.js.map +1 -1
  23. package/build/pkg/protocol.d.ts +11 -2
  24. package/build/pkg/protocol.js.map +1 -1
  25. package/build/tools/debug.js +55 -12
  26. package/build/tools/debug.js.map +1 -1
  27. package/build/tools/display/debug.jsx +19 -94
  28. package/build/tools/display/debug.jsx.map +1 -1
  29. package/build/tools/display/index.js +35 -15
  30. package/build/tools/display/index.js.map +1 -1
  31. package/build/tools/display/ui.jsx +20 -9
  32. package/build/tools/display/ui.jsx.map +1 -1
  33. package/build/tools/display/util.js +140 -1
  34. package/build/tools/display/util.js.map +1 -1
  35. package/build.zig +239 -217
  36. package/package.json +34 -130
  37. package/src/bin/install-pkmn-engine +25 -9
  38. package/src/lib/bindings/c.zig +200 -0
  39. package/src/lib/bindings/node.zig +117 -0
  40. package/src/lib/bindings/wasm.zig +50 -0
  41. package/src/lib/common/array.zig +5 -12
  42. package/src/lib/common/debug.zig +33 -59
  43. package/src/lib/common/js.zig +3 -9
  44. package/src/lib/common/optional.zig +11 -21
  45. package/src/lib/common/options.zig +2 -0
  46. package/src/lib/common/protocol.zig +228 -197
  47. package/src/lib/common/rational.zig +275 -284
  48. package/src/lib/common/rng.zig +1 -3
  49. package/src/lib/common/util.zig +14 -31
  50. package/src/lib/gen1/calc.zig +159 -163
  51. package/src/lib/gen1/chance.zig +146 -93
  52. package/src/lib/gen1/data/moves.zig +2 -4
  53. package/src/lib/gen1/data/species.zig +1 -3
  54. package/src/lib/gen1/data/types.zig +3 -4
  55. package/src/lib/gen1/data.zig +12 -16
  56. package/src/lib/gen1/helpers.zig +12 -20
  57. package/src/lib/gen1/mechanics.zig +103 -78
  58. package/src/lib/gen2/calc.zig +12 -23
  59. package/src/lib/gen2/chance.zig +12 -18
  60. package/src/lib/gen2/data/items.zig +1 -3
  61. package/src/lib/gen2/data/moves.zig +2 -4
  62. package/src/lib/gen2/data/species.zig +1 -3
  63. package/src/lib/gen2/data/types.zig +7 -5
  64. package/src/lib/gen2/data.zig +19 -22
  65. package/src/lib/gen2/generated.zig +18 -23
  66. package/src/lib/gen2/helpers.zig +9 -18
  67. package/src/lib/gen2/mechanics.zig +58 -55
  68. package/src/lib/napi.h +2 -0
  69. package/src/lib/node.zig +4 -121
  70. package/src/lib/pkmn.zig +98 -19
  71. package/src/lib/wasm.zig +9 -9
  72. package/src/tools/display/dom.ts +43 -31
  73. package/src/tools/display/ui.css +0 -5
@@ -1,52 +1,39 @@
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 Duration = chance.Duration;
20
+ const Durations = chance.Durations;
21
+ const enabled = pkmn.options.calc;
18
22
  const expect = std.testing.expect;
19
23
  const expectEqual = std.testing.expectEqual;
20
24
  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
-
28
- const Optional = optional.Optional;
29
-
30
- const Rational = rational.Rational;
31
-
32
- const PointerType = util.PointerType;
33
25
  const isPointerTo = util.isPointerTo;
34
-
35
- const Actions = chance.Actions;
36
- const Action = chance.Action;
37
- const Chance = chance.Chance;
38
- const Confusion = chance.Confusion;
39
- const Duration = chance.Duration;
40
- const Durations = chance.Durations;
41
26
  const Observation = chance.Observation;
27
+ const Optional = optional.Optional;
28
+ const Player = common.Player;
29
+ const PointerType = util.PointerType;
30
+ const Rational = rational.Rational;
31
+ const Result = common.Result;
42
32
  const Sleeps = chance.Sleeps;
43
33
 
44
34
  const tty = true; // DEBUG
45
35
  const summary = false; // DEBUG
46
36
 
47
- const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
48
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
49
-
50
37
  /// Information relevant to damage calculation that occured during a Generation I battle `update`.
51
38
  pub const Summaries = extern struct {
52
39
  /// Relevant information for Player 1.
@@ -108,13 +95,13 @@ pub const Calc = struct {
108
95
  self: Calc,
109
96
  player: Player,
110
97
  comptime field: Action.Field,
111
- ) ?std.meta.FieldType(Action, field) {
98
+ ) ?@FieldType(Action, @tagName(field)) {
112
99
  if (!enabled) return null;
113
100
 
114
101
  const val = @field(self.overrides.get(player), @tagName(field));
115
102
  return if (switch (@typeInfo(@TypeOf(val))) {
116
- Enum => val != .None,
117
- Int => val != 0,
103
+ .@"enum" => val != .None,
104
+ .int => val != 0,
118
105
  else => unreachable,
119
106
  }) val else null;
120
107
  }
@@ -155,7 +142,7 @@ const Null = struct {
155
142
  self: Null,
156
143
  player: Player,
157
144
  comptime field: Action.Field,
158
- ) ?std.meta.FieldType(Action, field) {
145
+ ) ?@FieldType(Action, @tagName(field)) {
159
146
  _ = .{ self, player };
160
147
  return null;
161
148
  }
@@ -191,24 +178,25 @@ pub const Options = struct {
191
178
  metronome: bool = false,
192
179
  };
193
180
 
194
- pub const MAX_FRONTIER = 67;
181
+ pub const MAX_FRONTIER = 86;
195
182
 
196
183
  pub fn transitions(
197
184
  battle: anytype,
198
185
  c1: Choice,
199
186
  c2: Choice,
200
187
  allocator: std.mem.Allocator,
201
- writer: anytype,
188
+ out: *std.Io.Writer,
189
+ err: *std.Io.Writer,
202
190
  options: Options,
203
191
  ) !?Stats {
204
192
  var stats: Stats = .{};
205
193
 
206
194
  const cap = options.cap;
207
195
 
208
- var seen = std.AutoHashMap(Actions, void).init(allocator);
196
+ var seen: std.AutoHashMap(Actions, void) = .init(allocator);
209
197
  defer seen.deinit();
210
- var frontier = std.ArrayList(Actions).init(allocator);
211
- defer frontier.deinit();
198
+ var frontier: std.array_list.Aligned(Actions, null) = .empty;
199
+ defer frontier.deinit(allocator);
212
200
 
213
201
  const d = options.durations;
214
202
 
@@ -225,8 +213,8 @@ pub fn transitions(
225
213
  const p1 = b.side(.P1);
226
214
  const p2 = b.side(.P2);
227
215
 
228
- var p: Rational(u128) = .{ .p = 0, .q = 1 };
229
- try frontier.append(opts.chance.actions);
216
+ var p: Rational(u256) = .{ .p = 0, .q = 1 };
217
+ try frontier.append(allocator, opts.chance.actions);
230
218
 
231
219
  // zig fmt: off
232
220
  for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
@@ -240,7 +228,9 @@ pub fn transitions(
240
228
  const f = frontier.items[i];
241
229
  const saved = stats.saved;
242
230
 
243
- try debug(writer, f, .{
231
+ var r: Rational(u256) = .{ .p = 0, .q = 1 };
232
+
233
+ try debug(out, f, .{
244
234
  .shape = true,
245
235
  .color = i,
246
236
  .bold = true,
@@ -260,8 +250,8 @@ pub fn transitions(
260
250
  for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
261
251
  for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
262
252
  for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
263
- for (Rolls.confusion(f.p1, d.p1, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
264
- for (Rolls.confusion(f.p2, d.p2, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
253
+ for (Rolls.confusion(f.p1, d.p1, tie, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
254
+ for (Rolls.confusion(f.p2, d.p2, tie, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
265
255
  for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
266
256
  for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
267
257
  for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
@@ -287,8 +277,8 @@ pub fn transitions(
287
277
 
288
278
  var p2_dmg = Rolls.damage(f.p2, p2_hit);
289
279
 
290
- const p1_min: u9 = p1_dmg.min;
291
- const p2_min: u9 = p2_dmg.min;
280
+ const p1_min: u9 = @intCast(p1_dmg.min);
281
+ const p2_min: u9 = @intCast(p2_dmg.min);
292
282
 
293
283
  while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
294
284
  a.p2.damage = @intCast(p2_dmg.min);
@@ -305,16 +295,16 @@ pub fn transitions(
305
295
 
306
296
  const summaries = &opts.calc.summaries;
307
297
  const p1_max: u9 = if (p2_dmg.min != p2_min)
308
- p1_dmg.min
298
+ @intCast(p1_dmg.min)
309
299
  else
310
- try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
300
+ Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
311
301
  const p2_max: u9 =
312
- try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
302
+ Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
313
303
 
314
304
  if (opts.chance.actions.matches(f)) {
315
- if (!map(opts.chance.actions).eql(a)) {
305
+ if (!opts.chance.actions.relax().eql(a)) {
316
306
  if (!summary) {
317
- try debug(writer, opts.chance.actions, .{
307
+ try debug(out, opts.chance.actions, .{
318
308
  .p1_max = p1_max,
319
309
  .p2_max = p2_max,
320
310
  .color = i,
@@ -328,7 +318,7 @@ pub fn transitions(
328
318
  }
329
319
 
330
320
  if (!summary) {
331
- try debug(writer, opts.chance.actions, .{
321
+ try debug(out, opts.chance.actions, .{
332
322
  .p1_max = p1_max,
333
323
  .p2_max = p2_max,
334
324
  .color = i,
@@ -341,7 +331,7 @@ pub fn transitions(
341
331
  acts.p1.damage = @intCast(p1d);
342
332
  acts.p2.damage = @intCast(p2d);
343
333
  if ((try seen.getOrPut(acts)).found_existing) {
344
- err("already seen {}", .{acts}, options.seed);
334
+ die(err, "already seen {f}", .{acts}, options.seed);
345
335
  return error.TestUnexpectedResult;
346
336
  }
347
337
  }
@@ -352,25 +342,27 @@ pub fn transitions(
352
342
  q.reduce();
353
343
  try p.add(q);
354
344
  p.reduce();
345
+ try r.add(q);
346
+ r.reduce();
355
347
 
356
348
  stats.saved += 1;
357
349
 
358
350
  if (p.q < p.p) {
359
- err("improper fraction {}", .{p}, options.seed);
351
+ die(err, "improper fraction {f}", .{p}, options.seed);
360
352
  return error.TestUnexpectedResult;
361
353
  }
362
354
  } else {
363
- if (!matches(opts.chance.actions, i, frontier.items)) {
364
- try frontier.append(opts.chance.actions);
355
+ if (!opts.chance.actions.matchesAny(frontier.items, i)) {
356
+ try frontier.append(allocator, opts.chance.actions);
365
357
 
366
- try debug(writer, opts.chance.actions, .{
358
+ try debug(out, opts.chance.actions, .{
367
359
  .p1_max = p1_max,
368
360
  .p2_max = p2_max,
369
361
  .dim = true,
370
362
  .newline = false,
371
363
  });
372
- try writer.writeAll(" → ");
373
- try debug(writer, opts.chance.actions, .{
364
+ try out.writeAll(" → ");
365
+ try debug(out, opts.chance.actions, .{
374
366
  .shape = true,
375
367
  .color = frontier.items.len - 1,
376
368
  .dim = true,
@@ -378,7 +370,7 @@ pub fn transitions(
378
370
  .indent = false,
379
371
  });
380
372
  } else if (!summary) {
381
- try debug(writer, opts.chance.actions, .{
373
+ try debug(out, opts.chance.actions, .{
382
374
  .p1_max = p1_max,
383
375
  .p2_max = p2_max,
384
376
  .dim = true,
@@ -393,14 +385,12 @@ pub fn transitions(
393
385
  }}}}}}}}}}}}}}}}}}}}}}}}}}}}
394
386
 
395
387
  assert(stats.saved > saved);
396
- const old = @hasDecl(std.fmt, "formatFloatDecimal");
397
- if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
398
- p.reduce();
399
- try writer.print(
400
- " = {s} ({d:.2}%)\n\n",
401
- .{p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
402
- );
403
- }
388
+ // TODO: LLVM - float_from_int from 'u256' without intrinsics
389
+ // try out.print(
390
+ // " {f} ({d:.2}%)\n = ──────────\n {f} ({d:.2}%)\n\n",
391
+ // .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
392
+ // p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
393
+ // );
404
394
 
405
395
  }
406
396
 
@@ -415,7 +405,7 @@ pub fn transitions(
415
405
 
416
406
  p.reduce();
417
407
  if (p.p != 1 or p.q != 1) {
418
- err("expected 1, found {}", .{p}, options.seed);
408
+ die(err, "expected 1, found {f}", .{p}, options.seed);
419
409
  return error.TestExpectedEqual;
420
410
  }
421
411
 
@@ -428,7 +418,8 @@ pub fn update(
428
418
  c2: Choice,
429
419
  options: anytype,
430
420
  allocator: std.mem.Allocator,
431
- writer: anytype,
421
+ out: *std.Io.Writer,
422
+ err: *std.Io.Writer,
432
423
  transition: bool,
433
424
  ) !Result {
434
425
  const durations = options.chance.durations;
@@ -446,8 +437,8 @@ pub fn update(
446
437
  if (@TypeOf(battle.rng) == data.PRNG) {
447
438
  // Ensure we can encode the diffs in less than MAX_DIFFS bytes.
448
439
  var buf: [helpers.MAX_DIFFS]u8 = undefined;
449
- var stream: protocol.ByteStream = .{ .buffer = &buf };
450
- const n = try helpers.diff(&original, battle, stream.writer());
440
+ var pw: protocol.Writer = .{ .buffer = &buf };
441
+ const n = try helpers.diff(&original, battle, &pw);
451
442
 
452
443
  // Applying the diff to the battle should take us back to the original copy
453
444
  // of the battle (ignoring the RNG).
@@ -461,7 +452,7 @@ pub fn update(
461
452
  // Ensure we can generate all transitions from the same original state
462
453
  // (we must change the battle's RNG from a FixedRNG to a PRNG because
463
454
  // the transitions function relies on RNG for discovery of states)
464
- if (try transitions(unfix(original), c1, c2, allocator, writer, .{
455
+ if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
465
456
  .durations = durations,
466
457
  .cap = true,
467
458
  })) |stats| try expect(stats.frontier <= MAX_FRONTIER);
@@ -508,10 +499,9 @@ pub fn update(
508
499
  fn expectEqualActions(expected: Actions, actual: Actions) !void {
509
500
  return expectEqual(expected, actual) catch |e| switch (e) {
510
501
  error.TestExpectedEqual => {
511
- std.debug.print("expected {}, found {}\n", .{ expected, actual });
502
+ std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
512
503
  return e;
513
504
  },
514
- else => return e,
515
505
  };
516
506
  }
517
507
 
@@ -530,27 +520,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
530
520
  };
531
521
  }
532
522
 
533
- fn map(actions: Actions) Actions {
534
- if (!pkmn.options.overwrite) return actions;
535
- var a = actions;
536
- if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
537
- if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
538
- return a;
539
- }
540
-
541
- fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
542
- for (frontier, 0..) |f, j| {
543
- // TODO: is skipping this redundant check worth it?
544
- if (i == j) continue;
545
- if (f.matches(actions)) return true;
546
- }
547
- return false;
548
- }
549
-
550
- fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
551
- const w = std.io.getStdErr().writer();
523
+ fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
552
524
  w.print(fmt, v) catch return;
553
- if (seed) |s| return w.print("{}\n", .{s}) catch return;
525
+ if (seed) |s| return w.print("{d}\n", .{s}) catch return;
554
526
  return w.writeByte('\n') catch return;
555
527
  }
556
528
 
@@ -566,7 +538,7 @@ const Style = struct {
566
538
  indent: bool = true,
567
539
  };
568
540
 
569
- fn debug(writer: anytype, actions: Actions, style: Style) !void {
541
+ fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
570
542
  if (style.indent) try writer.writeAll(" ");
571
543
  if (tty) {
572
544
  const mod: usize = if (style.dim) 2 else 1;
@@ -591,13 +563,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
591
563
  if (style.newline) try writer.writeByte('\n');
592
564
  }
593
565
 
594
- fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
566
+ fn format(writer: *std.Io.Writer, actions: Actions, p1: u9, p2: u9) !void {
595
567
  var input: [1024]u8 = undefined;
596
568
  var output: [1024]u8 = undefined;
597
569
 
598
- var stream = std.io.fixedBufferStream(&input);
599
- try actions.fmt(&stream.writer(), false);
600
- var len = stream.getWritten().len;
570
+ var w = std.Io.Writer.fixed(&input);
571
+ try actions.fmt(&w, false);
572
+ var len = w.buffered().len;
601
573
  @memcpy(output[0..len], input[0..len]);
602
574
 
603
575
  var find: [16]u8 = undefined;
@@ -630,78 +602,92 @@ pub const Rolls = struct {
630
602
  return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
631
603
  }
632
604
 
633
- const OBS_NONE = [_]Optional(Observation){.None};
634
- const OBS_STARTED = [_]Optional(Observation){.started};
635
- const OBS_CONTINUING = [_]Optional(Observation){.continuing};
636
- const OBS_ENDED = [_]Optional(Observation){.ended};
637
- const OBS_ALL = [_]Optional(Observation){ .started, .continuing, .ended };
638
- const OBS = [_]Optional(Observation){ .continuing, .ended };
605
+ const SLP_NONE = [_]Optional(Observation(.sleep)){.None};
606
+ const SLP_STARTED = [_]Optional(Observation(.sleep)){.started};
607
+ const SLP_ENDED = [_]Optional(Observation(.sleep)){.ended};
608
+ const SLP_ALL = [_]Optional(Observation(.sleep)){ .started, .continuing, .ended };
609
+ const SLP = [_]Optional(Observation(.sleep)){ .continuing, .ended };
639
610
 
640
611
  /// Returns a slice with a range of values for sleep given the `action` state and observed
641
612
  /// `durations`.
642
- pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
643
- if (action.sleep == .None) return &OBS_NONE;
613
+ pub fn sleep(action: Action, duration: Duration) []const Optional(Observation(.sleep)) {
614
+ if (action.sleep == .None) return &SLP_NONE;
644
615
  return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
645
- &OBS_ALL
616
+ &SLP_ALL
646
617
  else switch (action.sleep) {
647
- .started => &OBS_STARTED,
648
- else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
618
+ .started => &SLP_STARTED,
619
+ else => if (Sleeps.get(duration.sleeps, 0) >= 7) &SLP_ENDED else &SLP,
649
620
  };
650
621
  }
651
622
 
623
+ const DIS_NONE = [_]Optional(Observation(.disable)){.None};
624
+ const DIS_STARTED = [_]Optional(Observation(.disable)){.started};
625
+ const DIS_CONTINUING = [_]Optional(Observation(.disable)){.continuing};
626
+ const DIS_ENDED = [_]Optional(Observation(.disable)){.ended};
627
+ const DIS_ALL = [_]Optional(Observation(.disable)){ .started, .continuing, .ended };
628
+ const DIS = [_]Optional(Observation(.disable)){ .continuing, .ended };
629
+
652
630
  /// Returns a slice with a range of values for disable given the `action`, observed `durations`,
653
631
  /// and the state of the `parent` (observation of the player's Pokémon sleep status).
654
632
  pub fn disable(
655
633
  action: Action,
656
634
  duration: Duration,
657
- parent: Optional(Observation),
658
- ) []const Optional(Observation) {
659
- if (action.disable == .None) return &OBS_NONE;
635
+ parent: Optional(Observation(.sleep)),
636
+ ) []const Optional(Observation(.disable)) {
637
+ if (action.disable == .None) return &DIS_NONE;
660
638
  return if (duration.disable == 0 and action.speed_tie != .None)
661
- &OBS_ALL
639
+ &DIS_ALL
662
640
  else switch (action.disable) {
663
- .started => &OBS_STARTED,
641
+ .started => &DIS_STARTED,
664
642
  else => if (duration.disable >= 8)
665
- &OBS_ENDED
643
+ &DIS_ENDED
666
644
  else if (parent != .None and parent != .started)
667
- &OBS_CONTINUING
645
+ &DIS_CONTINUING
668
646
  else
669
- &OBS,
647
+ &DIS,
670
648
  };
671
649
  }
672
650
 
673
- /// FIXME
651
+ const ATK_NONE = [_]Optional(Observation(.attacking)){.None};
652
+ const ATK_STARTED = [_]Optional(Observation(.attacking)){.started};
653
+ const ATK_CONTINUING = [_]Optional(Observation(.attacking)){.continuing};
654
+ const ATK_ENDED = [_]Optional(Observation(.attacking)){.ended};
655
+ const ATK = [_]Optional(Observation(.attacking)){ .continuing, .ended };
656
+
657
+ /// FIXME document
674
658
  pub fn attacking(
675
659
  action: Action,
676
660
  duration: Duration,
677
- parent: Optional(Observation),
678
- ) []const Optional(Observation) {
661
+ parent: Optional(Observation(.sleep)),
662
+ ) []const Optional(Observation(.attacking)) {
679
663
  return switch (action.attacking) {
680
- .None => &OBS_NONE,
681
- .started => &OBS_STARTED,
664
+ .None => &ATK_NONE,
665
+ .started => &ATK_STARTED,
682
666
  else => if (duration.attacking >= 3)
683
- &OBS_ENDED
667
+ &ATK_ENDED
684
668
  else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
685
- &OBS_CONTINUING
669
+ &ATK_CONTINUING
686
670
  else
687
- &OBS,
671
+ &ATK,
688
672
  };
689
673
  }
690
674
 
691
- const CFZ_NONE = [_]Optional(Confusion){.None};
692
- const CFZ_STARTED = [_]Optional(Confusion){.started};
693
- const CFZ_OVERWRITTEN = [_]Optional(Confusion){ .started, .overwritten };
694
- const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
695
- const CFZ_ENDED = [_]Optional(Confusion){.ended};
696
- const CFZ = [_]Optional(Confusion){ .continuing, .ended };
675
+ const CFZ_NONE = [_]Optional(Observation(.confusion)){.None};
676
+ const CFZ_STARTED = [_]Optional(Observation(.confusion)){.started};
677
+ const CFZ_OVERWRITTEN = [_]Optional(Observation(.confusion)){ .started, .overwritten };
678
+ const CFZ_CONTINUING = [_]Optional(Observation(.confusion)){.continuing};
679
+ const CFZ_ENDED = [_]Optional(Observation(.confusion)){.ended};
680
+ const CFZ = [_]Optional(Observation(.confusion)){ .continuing, .ended };
681
+ const CFZ_TIE = [_]Optional(Observation(.confusion)){ .started, .continuing, .ended };
697
682
 
698
- /// FIXME
683
+ /// FIXME document
699
684
  pub fn confusion(
700
685
  action: Action,
701
686
  duration: Duration,
702
- sibling: Optional(Observation),
703
- parent: Optional(Observation),
704
- ) []const Optional(Confusion) {
687
+ tie: Optional(Player),
688
+ sibling: Optional(Observation(.attacking)),
689
+ parent: Optional(Observation(.sleep)),
690
+ ) []const Optional(Observation(.confusion)) {
705
691
  return switch (action.confusion) {
706
692
  .None => &CFZ_NONE,
707
693
  .started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
@@ -712,23 +698,30 @@ pub const Rolls = struct {
712
698
  &CFZ_ENDED
713
699
  else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
714
700
  &CFZ_CONTINUING
701
+ else if (tie != .None)
702
+ &CFZ_TIE
715
703
  else
716
704
  &CFZ,
717
705
  };
718
706
  }
719
707
 
708
+ const BND_NONE = [_]Optional(Observation(.binding)){.None};
709
+ const BND_STARTED = [_]Optional(Observation(.binding)){.started};
710
+ const BND_ENDED = [_]Optional(Observation(.binding)){.ended};
711
+ const BND = [_]Optional(Observation(.binding)){ .continuing, .ended };
712
+
720
713
  /// Returns a slice with a range of values for binding given the `action`, observed `durations`,
721
714
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
722
715
  pub fn binding(
723
716
  action: Action,
724
717
  duration: Duration,
725
718
  parent: Optional(bool),
726
- ) []const Optional(Observation) {
727
- if (parent == .true) return &OBS_NONE;
719
+ ) []const Optional(Observation(.binding)) {
720
+ if (parent == .true) return &BND_NONE;
728
721
  return switch (action.binding) {
729
- .None => &OBS_NONE,
730
- .started => &OBS_STARTED,
731
- else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
722
+ .None => &BND_NONE,
723
+ .started => &BND_STARTED,
724
+ else => if (duration.binding >= 4) &BND_ENDED else &BND,
732
725
  };
733
726
  }
734
727
 
@@ -757,7 +750,7 @@ pub const Rolls = struct {
757
750
  }
758
751
 
759
752
  /// The min and max bounds on iteration over damage rolls.
760
- pub const Range = struct { min: u9, max: u9 };
753
+ pub const Range = struct { min: usize, max: usize };
761
754
 
762
755
  /// Returns the range bounding damage rolls given the `action` state and the state of
763
756
  /// the `parent` (whether the player's Pokémon's move hit).
@@ -770,7 +763,7 @@ pub const Rolls = struct {
770
763
 
771
764
  /// Returns the max damage roll which will produce the same damage as `roll`
772
765
  /// given the base damage in `summaries`.
773
- pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) !u8 {
766
+ pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
774
767
  if (roll == 0) return roll;
775
768
 
776
769
  const dmg = summaries.get(player.foe()).damage;
@@ -782,7 +775,10 @@ pub const Rolls = struct {
782
775
 
783
776
  /// Returns a slice with the correct range of values for confused given the `action` state and
784
777
  /// the state of the `parent` (observation of the player's Pokémon confusion status).
785
- pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
778
+ pub fn confused(
779
+ action: Action,
780
+ parent: Optional(Observation(.confusion)),
781
+ ) []const Optional(bool) {
786
782
  const done = parent == .None or parent == .ended;
787
783
  return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
788
784
  }
@@ -794,24 +790,24 @@ pub const Rolls = struct {
794
790
  return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
795
791
  }
796
792
 
797
- const SLOT_NONE = [_]u4{0};
798
- const SLOT = [_]u4{ 1, 2, 3, 4 };
793
+ const SLOT_NONE = [_]u3{0};
794
+ const SLOT = [_]u3{ 1, 2, 3, 4 };
799
795
 
800
796
  /// Returns a slice with a range of values for move slots given the `action` state
801
797
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
802
798
  ///
803
799
  /// These slots may or **may not be valid** as slots may be unset / have 0 PP.
804
- pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
800
+ pub fn moveSlot(action: Action, parent: Optional(bool)) []const u3 {
805
801
  if (parent == .false) return &SLOT_NONE;
806
802
  return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
807
803
  }
808
804
 
809
- const MULTI_NONE = [_]u4{0};
810
- const MULTI = [_]u4{ 2, 3, 4, 5 };
805
+ const MULTI_NONE = [_]u3{0};
806
+ const MULTI = [_]u3{ 2, 3, 4, 5 };
811
807
 
812
808
  /// Returns a slice with the correct range of values for multi hit given the `action` state
813
809
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
814
- pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
810
+ pub fn multiHit(action: Action, parent: Optional(bool)) []const u3 {
815
811
  if (parent == .false) return &MULTI_NONE;
816
812
  return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
817
813
  }
@@ -880,12 +876,12 @@ test "Rolls.damage" {
880
876
 
881
877
  test "Rolls.coalesce" {
882
878
  var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
883
- try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
884
- try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
885
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
879
+ try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
880
+ try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
881
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
886
882
  summaries.p1.damage.final = 74;
887
- try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
888
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
883
+ try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
884
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
889
885
  }
890
886
 
891
887
  test "Rolls.hit" {
@@ -943,16 +939,16 @@ test "Rolls.paralyzed" {
943
939
 
944
940
  test "Rolls.moveSlot" {
945
941
  const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
946
- try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
947
- try expectEqualSlices(u4, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
948
- try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p2, .false));
942
+ try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p1, .None));
943
+ try expectEqualSlices(u3, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
944
+ try expectEqualSlices(u3, &.{0}, Rolls.moveSlot(actions.p2, .false));
949
945
  }
950
946
 
951
947
  test "Rolls.multiHit" {
952
948
  const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
953
- try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p1, .None));
954
- try expectEqualSlices(u4, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
955
- try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p2, .false));
949
+ try expectEqualSlices(u3, &.{0}, Rolls.multiHit(actions.p1, .None));
950
+ try expectEqualSlices(u3, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
951
+ try expectEqualSlices(u3, &.{0}, Rolls.multiHit(actions.p2, .false));
956
952
  }
957
953
 
958
954
  test "Rolls.metronome" {