@pkmn/engine 0.1.0-dev.3393edb3 → 0.1.0-dev.3846c8e6

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 (72) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +28 -13
  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/gen1.d.ts +15 -18
  17. package/build/pkg/gen1.js +28 -19
  18. package/build/pkg/gen1.js.map +1 -1
  19. package/build/pkg/index.d.ts +37 -100
  20. package/build/pkg/index.js +23 -75
  21. package/build/pkg/index.js.map +1 -1
  22. package/build/pkg/protocol.d.ts +11 -2
  23. package/build/pkg/protocol.js.map +1 -1
  24. package/build/tools/debug.js +55 -12
  25. package/build/tools/debug.js.map +1 -1
  26. package/build/tools/display/debug.jsx +19 -94
  27. package/build/tools/display/debug.jsx.map +1 -1
  28. package/build/tools/display/index.js +35 -15
  29. package/build/tools/display/index.js.map +1 -1
  30. package/build/tools/display/ui.jsx +20 -9
  31. package/build/tools/display/ui.jsx.map +1 -1
  32. package/build/tools/display/util.js +140 -1
  33. package/build/tools/display/util.js.map +1 -1
  34. package/build.zig +236 -216
  35. package/package.json +35 -131
  36. package/src/bin/install-pkmn-engine +25 -9
  37. package/src/lib/bindings/c.zig +200 -0
  38. package/src/lib/bindings/node.zig +117 -0
  39. package/src/lib/bindings/wasm.zig +50 -0
  40. package/src/lib/common/array.zig +5 -12
  41. package/src/lib/common/debug.zig +33 -54
  42. package/src/lib/common/js.zig +3 -9
  43. package/src/lib/common/optional.zig +11 -21
  44. package/src/lib/common/options.zig +2 -0
  45. package/src/lib/common/protocol.zig +228 -197
  46. package/src/lib/common/rational.zig +278 -285
  47. package/src/lib/common/rng.zig +1 -3
  48. package/src/lib/common/util.zig +14 -31
  49. package/src/lib/gen1/calc.zig +205 -189
  50. package/src/lib/gen1/chance.zig +146 -93
  51. package/src/lib/gen1/data/moves.zig +2 -4
  52. package/src/lib/gen1/data/species.zig +1 -3
  53. package/src/lib/gen1/data/types.zig +3 -4
  54. package/src/lib/gen1/data.zig +12 -16
  55. package/src/lib/gen1/helpers.zig +12 -21
  56. package/src/lib/gen1/mechanics.zig +112 -93
  57. package/src/lib/gen2/calc.zig +12 -23
  58. package/src/lib/gen2/chance.zig +12 -18
  59. package/src/lib/gen2/data/items.zig +1 -3
  60. package/src/lib/gen2/data/moves.zig +2 -4
  61. package/src/lib/gen2/data/species.zig +1 -3
  62. package/src/lib/gen2/data/types.zig +7 -5
  63. package/src/lib/gen2/data.zig +19 -22
  64. package/src/lib/gen2/generated.zig +18 -23
  65. package/src/lib/gen2/helpers.zig +9 -18
  66. package/src/lib/gen2/mechanics.zig +58 -55
  67. package/src/lib/napi.h +2 -0
  68. package/src/lib/node.zig +4 -121
  69. package/src/lib/pkmn.zig +98 -19
  70. package/src/lib/wasm.zig +9 -9
  71. package/src/tools/display/dom.ts +46 -34
  72. package/src/tools/display/ui.css +8 -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,17 +95,25 @@ 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
  }
121
108
 
109
+ pub fn confusion(self: *Calc, player: Player) void {
110
+ if (!enabled) return;
111
+
112
+ if (self.overrides.get(player).confusion == .started) {
113
+ self.overrides.get(player).confusion = .None;
114
+ }
115
+ }
116
+
122
117
  pub fn base(self: *Calc, player: Player, val: u16) void {
123
118
  if (!enabled) return;
124
119
 
@@ -147,11 +142,15 @@ const Null = struct {
147
142
  self: Null,
148
143
  player: Player,
149
144
  comptime field: Action.Field,
150
- ) ?std.meta.FieldType(Action, field) {
145
+ ) ?@FieldType(Action, @tagName(field)) {
151
146
  _ = .{ self, player };
152
147
  return null;
153
148
  }
154
149
 
150
+ pub fn confusion(self: Null, player: Player) void {
151
+ _ = .{ self, player };
152
+ }
153
+
155
154
  pub fn base(self: Null, player: Player, val: u16) void {
156
155
  _ = .{ self, player, val };
157
156
  }
@@ -179,30 +178,31 @@ pub const Options = struct {
179
178
  metronome: bool = false,
180
179
  };
181
180
 
182
- pub const MAX_FRONTIER = 49;
181
+ pub const MAX_FRONTIER = 86;
183
182
 
184
183
  pub fn transitions(
185
184
  battle: anytype,
186
185
  c1: Choice,
187
186
  c2: Choice,
188
187
  allocator: std.mem.Allocator,
189
- writer: anytype,
188
+ out: *std.Io.Writer,
189
+ err: *std.Io.Writer,
190
190
  options: Options,
191
191
  ) !?Stats {
192
192
  var stats: Stats = .{};
193
193
 
194
194
  const cap = options.cap;
195
195
 
196
- var seen = std.AutoHashMap(Actions, void).init(allocator);
196
+ var seen: std.AutoHashMap(Actions, void) = .init(allocator);
197
197
  defer seen.deinit();
198
- var frontier = std.ArrayList(Actions).init(allocator);
199
- defer frontier.deinit();
198
+ var frontier: std.array_list.Aligned(Actions, null) = .empty;
199
+ defer frontier.deinit(allocator);
200
200
 
201
- const durations = options.durations;
201
+ const d = options.durations;
202
202
 
203
203
  var opts = pkmn.battle.options(
204
204
  protocol.NULL,
205
- Chance(Rational(u128)){ .probability = .{}, .durations = durations },
205
+ Chance(Rational(u128)){ .probability = .{}, .durations = d },
206
206
  Calc{},
207
207
  );
208
208
 
@@ -213,8 +213,8 @@ pub fn transitions(
213
213
  const p1 = b.side(.P1);
214
214
  const p2 = b.side(.P2);
215
215
 
216
- var p: Rational(u128) = .{ .p = 0, .q = 1 };
217
- try frontier.append(opts.chance.actions);
216
+ var p: Rational(u256) = .{ .p = 0, .q = 1 };
217
+ try frontier.append(allocator, opts.chance.actions);
218
218
 
219
219
  // zig fmt: off
220
220
  for (Rolls.metronome(frontier.items[0].p1)) |p1_move| {
@@ -228,7 +228,9 @@ pub fn transitions(
228
228
  const f = frontier.items[i];
229
229
  const saved = stats.saved;
230
230
 
231
- try debug(writer, f, .{
231
+ var r: Rational(u256) = .{ .p = 0, .q = 1 };
232
+
233
+ try debug(out, f, .{
232
234
  .shape = true,
233
235
  .color = i,
234
236
  .bold = true,
@@ -242,20 +244,20 @@ pub fn transitions(
242
244
  };
243
245
 
244
246
  for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
245
- for (Rolls.sleep(f.p1, durations.p1)) |p1_slp| { a.p1.sleep = p1_slp;
246
- for (Rolls.sleep(f.p2, durations.p2)) |p2_slp| { a.p2.sleep = p2_slp;
247
- for (Rolls.disable(f.p1, durations.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
248
- for (Rolls.disable(f.p2, durations.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
249
- for (Rolls.confusion(f.p1, durations.p1, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
250
- for (Rolls.confusion(f.p2, durations.p2, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
247
+ for (Rolls.sleep(f.p1, d.p1)) |p1_slp| { a.p1.sleep = p1_slp;
248
+ for (Rolls.sleep(f.p2, d.p2)) |p2_slp| { a.p2.sleep = p2_slp;
249
+ for (Rolls.disable(f.p1, d.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
250
+ for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
251
+ for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
252
+ for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
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;
251
255
  for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
252
256
  for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
253
257
  for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
254
258
  for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
255
- for (Rolls.attacking(f.p1, durations.p1, p1_par)) |p1_atk| { a.p1.attacking = p1_atk;
256
- for (Rolls.attacking(f.p2, durations.p2, p2_par)) |p2_atk| { a.p2.attacking = p2_atk;
257
- for (Rolls.binding(f.p1, durations.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
258
- for (Rolls.binding(f.p2, durations.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
259
+ for (Rolls.binding(f.p1, d.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
260
+ for (Rolls.binding(f.p2, d.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
259
261
  for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
260
262
  for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
261
263
  for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
@@ -275,15 +277,15 @@ pub fn transitions(
275
277
 
276
278
  var p2_dmg = Rolls.damage(f.p2, p2_hit);
277
279
 
278
- const p1_min: u9 = p1_dmg.min;
279
- 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);
280
282
 
281
283
  while (p2_dmg.min < p2_dmg.max) : (p2_dmg.min += 1) {
282
284
  a.p2.damage = @intCast(p2_dmg.min);
283
285
 
284
286
  opts.calc.overrides = a;
285
287
  opts.calc.summaries = .{};
286
- opts.chance = .{ .probability = .{}, .durations = durations };
288
+ opts.chance = .{ .probability = .{}, .durations = d };
287
289
  const q = &opts.chance.probability;
288
290
 
289
291
  b = battle;
@@ -293,16 +295,16 @@ pub fn transitions(
293
295
 
294
296
  const summaries = &opts.calc.summaries;
295
297
  const p1_max: u9 = if (p2_dmg.min != p2_min)
296
- p1_dmg.min
298
+ @intCast(p1_dmg.min)
297
299
  else
298
- try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
300
+ Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
299
301
  const p2_max: u9 =
300
- try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
302
+ Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
301
303
 
302
304
  if (opts.chance.actions.matches(f)) {
303
- if (!map(opts.chance.actions).eql(a)) {
305
+ if (!opts.chance.actions.relax().eql(a)) {
304
306
  if (!summary) {
305
- try debug(writer, opts.chance.actions, .{
307
+ try debug(out, opts.chance.actions, .{
306
308
  .p1_max = p1_max,
307
309
  .p2_max = p2_max,
308
310
  .color = i,
@@ -316,7 +318,7 @@ pub fn transitions(
316
318
  }
317
319
 
318
320
  if (!summary) {
319
- try debug(writer, opts.chance.actions, .{
321
+ try debug(out, opts.chance.actions, .{
320
322
  .p1_max = p1_max,
321
323
  .p2_max = p2_max,
322
324
  .color = i,
@@ -329,33 +331,38 @@ pub fn transitions(
329
331
  acts.p1.damage = @intCast(p1d);
330
332
  acts.p2.damage = @intCast(p2d);
331
333
  if ((try seen.getOrPut(acts)).found_existing) {
332
- err("already seen {}", .{acts}, options.seed);
334
+ die(err, "already seen {f}", .{acts}, options.seed);
333
335
  return error.TestUnexpectedResult;
334
336
  }
335
337
  }
336
338
  }
337
-
338
339
  if (p1_max != p1_min) try q.update(p1_max - p1_min + 1, 1);
339
340
  if (p2_max != p2_dmg.min) try q.update(p2_max - p2_dmg.min + 1, 1);
341
+
342
+ q.reduce();
340
343
  try p.add(q);
344
+ p.reduce();
345
+ try r.add(q);
346
+ r.reduce();
347
+
341
348
  stats.saved += 1;
342
349
 
343
350
  if (p.q < p.p) {
344
- err("improper fraction {}", .{p}, options.seed);
351
+ die(err, "improper fraction {f}", .{p}, options.seed);
345
352
  return error.TestUnexpectedResult;
346
353
  }
347
354
  } else {
348
- if (!matches(opts.chance.actions, i, frontier.items)) {
349
- try frontier.append(opts.chance.actions);
355
+ if (!opts.chance.actions.matchesAny(frontier.items, i)) {
356
+ try frontier.append(allocator, opts.chance.actions);
350
357
 
351
- try debug(writer, opts.chance.actions, .{
358
+ try debug(out, opts.chance.actions, .{
352
359
  .p1_max = p1_max,
353
360
  .p2_max = p2_max,
354
361
  .dim = true,
355
362
  .newline = false,
356
363
  });
357
- try writer.writeAll(" → ");
358
- try debug(writer, opts.chance.actions, .{
364
+ try out.writeAll(" → ");
365
+ try debug(out, opts.chance.actions, .{
359
366
  .shape = true,
360
367
  .color = frontier.items.len - 1,
361
368
  .dim = true,
@@ -363,7 +370,7 @@ pub fn transitions(
363
370
  .indent = false,
364
371
  });
365
372
  } else if (!summary) {
366
- try debug(writer, opts.chance.actions, .{
373
+ try debug(out, opts.chance.actions, .{
367
374
  .p1_max = p1_max,
368
375
  .p2_max = p2_max,
369
376
  .dim = true,
@@ -378,14 +385,12 @@ pub fn transitions(
378
385
  }}}}}}}}}}}}}}}}}}}}}}}}}}}}
379
386
 
380
387
  assert(stats.saved > saved);
381
- const old = @hasDecl(std.fmt, "formatFloatDecimal");
382
- if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
383
- p.reduce();
384
- try writer.print(
385
- " = {s} ({d:.2}%)\n\n",
386
- .{p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
387
- );
388
- }
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
+ // );
389
394
 
390
395
  }
391
396
 
@@ -400,7 +405,7 @@ pub fn transitions(
400
405
 
401
406
  p.reduce();
402
407
  if (p.p != 1 or p.q != 1) {
403
- err("expected 1, found {}", .{p}, options.seed);
408
+ die(err, "expected 1, found {f}", .{p}, options.seed);
404
409
  return error.TestExpectedEqual;
405
410
  }
406
411
 
@@ -413,7 +418,8 @@ pub fn update(
413
418
  c2: Choice,
414
419
  options: anytype,
415
420
  allocator: std.mem.Allocator,
416
- writer: anytype,
421
+ out: *std.Io.Writer,
422
+ err: *std.Io.Writer,
417
423
  transition: bool,
418
424
  ) !Result {
419
425
  const durations = options.chance.durations;
@@ -431,8 +437,8 @@ pub fn update(
431
437
  if (@TypeOf(battle.rng) == data.PRNG) {
432
438
  // Ensure we can encode the diffs in less than MAX_DIFFS bytes.
433
439
  var buf: [helpers.MAX_DIFFS]u8 = undefined;
434
- var stream: protocol.ByteStream = .{ .buffer = &buf };
435
- 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);
436
442
 
437
443
  // Applying the diff to the battle should take us back to the original copy
438
444
  // of the battle (ignoring the RNG).
@@ -446,7 +452,7 @@ pub fn update(
446
452
  // Ensure we can generate all transitions from the same original state
447
453
  // (we must change the battle's RNG from a FixedRNG to a PRNG because
448
454
  // the transitions function relies on RNG for discovery of states)
449
- if (try transitions(unfix(original), c1, c2, allocator, writer, .{
455
+ if (try transitions(unfix(original), c1, c2, allocator, out, err, .{
450
456
  .durations = durations,
451
457
  .cap = true,
452
458
  })) |stats| try expect(stats.frontier <= MAX_FRONTIER);
@@ -493,10 +499,9 @@ pub fn update(
493
499
  fn expectEqualActions(expected: Actions, actual: Actions) !void {
494
500
  return expectEqual(expected, actual) catch |e| switch (e) {
495
501
  error.TestExpectedEqual => {
496
- std.debug.print("expected {}, found {}\n", .{ expected, actual });
502
+ std.debug.print("expected {f}, found {f}\n", .{ expected, actual });
497
503
  return e;
498
504
  },
499
- else => return e,
500
505
  };
501
506
  }
502
507
 
@@ -515,27 +520,9 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
515
520
  };
516
521
  }
517
522
 
518
- fn map(actions: Actions) Actions {
519
- if (!pkmn.options.overwrite) return actions;
520
- var a = actions;
521
- if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
522
- if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
523
- return a;
524
- }
525
-
526
- fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
527
- for (frontier, 0..) |f, j| {
528
- // TODO: is skipping this redundant check worth it?
529
- if (i == j) continue;
530
- if (f.matches(actions)) return true;
531
- }
532
- return false;
533
- }
534
-
535
- fn err(comptime fmt: []const u8, v: anytype, seed: ?u64) void {
536
- const w = std.io.getStdErr().writer();
523
+ fn die(w: *std.Io.Writer, comptime fmt: []const u8, v: anytype, seed: ?u64) void {
537
524
  w.print(fmt, v) catch return;
538
- if (seed) |s| return w.print("{}\n", .{s}) catch return;
525
+ if (seed) |s| return w.print("{d}\n", .{s}) catch return;
539
526
  return w.writeByte('\n') catch return;
540
527
  }
541
528
 
@@ -551,7 +538,7 @@ const Style = struct {
551
538
  indent: bool = true,
552
539
  };
553
540
 
554
- fn debug(writer: anytype, actions: Actions, style: Style) !void {
541
+ fn debug(writer: *std.Io.Writer, actions: Actions, style: Style) !void {
555
542
  if (style.indent) try writer.writeAll(" ");
556
543
  if (tty) {
557
544
  const mod: usize = if (style.dim) 2 else 1;
@@ -576,13 +563,13 @@ fn debug(writer: anytype, actions: Actions, style: Style) !void {
576
563
  if (style.newline) try writer.writeByte('\n');
577
564
  }
578
565
 
579
- 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 {
580
567
  var input: [1024]u8 = undefined;
581
568
  var output: [1024]u8 = undefined;
582
569
 
583
- var stream = std.io.fixedBufferStream(&input);
584
- try actions.fmt(&stream.writer(), false);
585
- 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;
586
573
  @memcpy(output[0..len], input[0..len]);
587
574
 
588
575
  var find: [16]u8 = undefined;
@@ -615,91 +602,113 @@ pub const Rolls = struct {
615
602
  return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
616
603
  }
617
604
 
618
- const OBS_NONE = [_]Optional(Observation){.None};
619
- const OBS_STARTED = [_]Optional(Observation){.started};
620
- const OBS_CONTINUING = [_]Optional(Observation){.continuing};
621
- const OBS_ENDED = [_]Optional(Observation){.ended};
622
- const OBS_ALL = [_]Optional(Observation){ .started, .continuing, .ended };
623
- 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 };
624
610
 
625
611
  /// Returns a slice with a range of values for sleep given the `action` state and observed
626
612
  /// `durations`.
627
- pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
628
- 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;
629
615
  return if (Sleeps.get(duration.sleeps, 0) == 0 and action.speed_tie != .None)
630
- &OBS_ALL
616
+ &SLP_ALL
631
617
  else switch (action.sleep) {
632
- .started => &OBS_STARTED,
633
- 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,
634
620
  };
635
621
  }
636
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
+
637
630
  /// Returns a slice with a range of values for disable given the `action`, observed `durations`,
638
631
  /// and the state of the `parent` (observation of the player's Pokémon sleep status).
639
632
  pub fn disable(
640
633
  action: Action,
641
634
  duration: Duration,
642
- parent: Optional(Observation),
643
- ) []const Optional(Observation) {
644
- 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;
645
638
  return if (duration.disable == 0 and action.speed_tie != .None)
646
- &OBS_ALL
639
+ &DIS_ALL
647
640
  else switch (action.disable) {
648
- .started => &OBS_STARTED,
641
+ .started => &DIS_STARTED,
649
642
  else => if (duration.disable >= 8)
650
- &OBS_ENDED
643
+ &DIS_ENDED
651
644
  else if (parent != .None and parent != .started)
652
- &OBS_CONTINUING
645
+ &DIS_CONTINUING
653
646
  else
654
- &OBS,
647
+ &DIS,
655
648
  };
656
649
  }
657
650
 
658
- const CFZ_NONE = [_]Optional(Confusion){.None};
659
- const CFZ_STARTED = [_]Optional(Confusion){.started};
660
- const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
661
- const CFZ_ENDED = [_]Optional(Confusion){.ended};
662
- const CFZ = [_]Optional(Confusion){ .continuing, .ended };
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 };
663
656
 
664
- /// Returns a slice with a range of values for confusion given the `action`, observed `durations`,
665
- /// and the state of the `parent` (observation of the player's Pokémon sleep status).
657
+ /// FIXME document
658
+ pub fn attacking(
659
+ action: Action,
660
+ duration: Duration,
661
+ parent: Optional(Observation(.sleep)),
662
+ ) []const Optional(Observation(.attacking)) {
663
+ return switch (action.attacking) {
664
+ .None => &ATK_NONE,
665
+ .started => &ATK_STARTED,
666
+ else => if (duration.attacking >= 3)
667
+ &ATK_ENDED
668
+ else if ((parent == .continuing and parent == .ended) or duration.attacking < 2)
669
+ &ATK_CONTINUING
670
+ else
671
+ &ATK,
672
+ };
673
+ }
674
+
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 };
682
+
683
+ /// FIXME document
666
684
  pub fn confusion(
667
685
  action: Action,
668
686
  duration: Duration,
669
- parent: Optional(Observation),
670
- ) []const Optional(Confusion) {
687
+ tie: Optional(Player),
688
+ sibling: Optional(Observation(.attacking)),
689
+ parent: Optional(Observation(.sleep)),
690
+ ) []const Optional(Observation(.confusion)) {
671
691
  return switch (action.confusion) {
672
692
  .None => &CFZ_NONE,
673
- .started => &CFZ_STARTED,
693
+ .started => if (pkmn.options.overwrite and sibling == .ended and duration.confusion < 5)
694
+ &CFZ_OVERWRITTEN
695
+ else
696
+ &CFZ_STARTED,
674
697
  else => if (duration.confusion >= 5)
675
698
  &CFZ_ENDED
676
- else if ((parent != .None and parent != .started) or duration.confusion < 2)
699
+ else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
677
700
  &CFZ_CONTINUING
701
+ else if (tie != .None)
702
+ &CFZ_TIE
678
703
  else
679
704
  &CFZ,
680
705
  };
681
706
  }
682
707
 
683
- /// Returns a slice with a range of values for attacking given the `action`, observed
684
- /// `durations`, and the state of the `parent` (whether the player's Pokémon was fully
685
- /// paralyzed).
686
- pub fn attacking(
687
- action: Action,
688
- duration: Duration,
689
- parent: Optional(bool),
690
- ) []const Optional(Observation) {
691
- if (parent == .true) return &OBS_NONE;
692
- return switch (action.attacking) {
693
- .None => &OBS_NONE,
694
- .started => &OBS_STARTED,
695
- else => if (duration.attacking >= 3)
696
- &OBS_ENDED
697
- else if (duration.attacking < 2)
698
- &OBS_CONTINUING
699
- else
700
- &OBS,
701
- };
702
- }
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 };
703
712
 
704
713
  /// Returns a slice with a range of values for binding given the `action`, observed `durations`,
705
714
  /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
@@ -707,12 +716,12 @@ pub const Rolls = struct {
707
716
  action: Action,
708
717
  duration: Duration,
709
718
  parent: Optional(bool),
710
- ) []const Optional(Observation) {
711
- if (parent == .true) return &OBS_NONE;
719
+ ) []const Optional(Observation(.binding)) {
720
+ if (parent == .true) return &BND_NONE;
712
721
  return switch (action.binding) {
713
- .None => &OBS_NONE,
714
- .started => &OBS_STARTED,
715
- 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,
716
725
  };
717
726
  }
718
727
 
@@ -741,7 +750,7 @@ pub const Rolls = struct {
741
750
  }
742
751
 
743
752
  /// The min and max bounds on iteration over damage rolls.
744
- pub const Range = struct { min: u9, max: u9 };
753
+ pub const Range = struct { min: usize, max: usize };
745
754
 
746
755
  /// Returns the range bounding damage rolls given the `action` state and the state of
747
756
  /// the `parent` (whether the player's Pokémon's move hit).
@@ -754,7 +763,7 @@ pub const Rolls = struct {
754
763
 
755
764
  /// Returns the max damage roll which will produce the same damage as `roll`
756
765
  /// given the base damage in `summaries`.
757
- 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 {
758
767
  if (roll == 0) return roll;
759
768
 
760
769
  const dmg = summaries.get(player.foe()).damage;
@@ -766,7 +775,10 @@ pub const Rolls = struct {
766
775
 
767
776
  /// Returns a slice with the correct range of values for confused given the `action` state and
768
777
  /// the state of the `parent` (observation of the player's Pokémon confusion status).
769
- 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) {
770
782
  const done = parent == .None or parent == .ended;
771
783
  return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
772
784
  }
@@ -778,24 +790,24 @@ pub const Rolls = struct {
778
790
  return if (action.paralyzed == .None) &BOOL_NONE else &BOOLS;
779
791
  }
780
792
 
781
- const SLOT_NONE = [_]u4{0};
782
- const SLOT = [_]u4{ 1, 2, 3, 4 };
793
+ const SLOT_NONE = [_]u3{0};
794
+ const SLOT = [_]u3{ 1, 2, 3, 4 };
783
795
 
784
796
  /// Returns a slice with a range of values for move slots given the `action` state
785
797
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
786
798
  ///
787
799
  /// These slots may or **may not be valid** as slots may be unset / have 0 PP.
788
- pub fn moveSlot(action: Action, parent: Optional(bool)) []const u4 {
800
+ pub fn moveSlot(action: Action, parent: Optional(bool)) []const u3 {
789
801
  if (parent == .false) return &SLOT_NONE;
790
802
  return if (action.move_slot == 0) &SLOT_NONE else &SLOT;
791
803
  }
792
804
 
793
- const MULTI_NONE = [_]u4{0};
794
- const MULTI = [_]u4{ 2, 3, 4, 5 };
805
+ const MULTI_NONE = [_]u3{0};
806
+ const MULTI = [_]u3{ 2, 3, 4, 5 };
795
807
 
796
808
  /// Returns a slice with the correct range of values for multi hit given the `action` state
797
809
  /// and the state of the `parent` (whether the player's Pokémon's move hit).
798
- pub fn multiHit(action: Action, parent: Optional(bool)) []const u4 {
810
+ pub fn multiHit(action: Action, parent: Optional(bool)) []const u3 {
799
811
  if (parent == .false) return &MULTI_NONE;
800
812
  return if (action.multi_hit == 0) &MULTI_NONE else &MULTI;
801
813
  }
@@ -864,12 +876,12 @@ test "Rolls.damage" {
864
876
 
865
877
  test "Rolls.coalesce" {
866
878
  var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
867
- try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
868
- try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
869
- 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));
870
882
  summaries.p1.damage.final = 74;
871
- try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
872
- 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));
873
885
  }
874
886
 
875
887
  test "Rolls.hit" {
@@ -909,7 +921,11 @@ test "Rolls.criticalHit" {
909
921
  test "Rolls.confused" {
910
922
  const actions: Actions = .{ .p2 = .{ .confused = .true } };
911
923
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
912
- try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2, .continuing));
924
+ try expectEqualSlices(
925
+ Optional(bool),
926
+ &.{ .false, .true },
927
+ Rolls.confused(actions.p2, .continuing),
928
+ );
913
929
  try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
914
930
  }
915
931
 
@@ -923,16 +939,16 @@ test "Rolls.paralyzed" {
923
939
 
924
940
  test "Rolls.moveSlot" {
925
941
  const actions: Actions = .{ .p2 = .{ .move_slot = 3 } };
926
- try expectEqualSlices(u4, &.{0}, Rolls.moveSlot(actions.p1, .None));
927
- try expectEqualSlices(u4, &.{ 1, 2, 3, 4 }, Rolls.moveSlot(actions.p2, .None));
928
- 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));
929
945
  }
930
946
 
931
947
  test "Rolls.multiHit" {
932
948
  const actions: Actions = .{ .p2 = .{ .multi_hit = 3 } };
933
- try expectEqualSlices(u4, &.{0}, Rolls.multiHit(actions.p1, .None));
934
- try expectEqualSlices(u4, &.{ 2, 3, 4, 5 }, Rolls.multiHit(actions.p2, .None));
935
- 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));
936
952
  }
937
953
 
938
954
  test "Rolls.metronome" {