@pkmn/engine 0.1.0-dev.8b192003 → 0.1.0-dev.8cf7e679

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 (76) hide show
  1. package/README.md +9 -6
  2. package/build/pkg/addon/browser.d.ts +3 -0
  3. package/build/pkg/addon/browser.js +44 -0
  4. package/build/pkg/addon/browser.js.map +1 -0
  5. package/build/pkg/addon/node.d.ts +3 -0
  6. package/build/pkg/addon/node.js +97 -0
  7. package/build/pkg/addon/node.js.map +1 -0
  8. package/build/pkg/addon/wasm.d.ts +2 -0
  9. package/build/pkg/addon/wasm.js +44 -0
  10. package/build/pkg/addon/wasm.js.map +1 -0
  11. package/build/pkg/addon.d.ts +25 -3
  12. package/build/pkg/addon.js +34 -65
  13. package/build/pkg/addon.js.map +1 -1
  14. package/build/pkg/data/layout.json +15 -2
  15. package/build/pkg/data/protocol.json +23 -9
  16. package/build/pkg/data.d.ts +23 -1
  17. package/build/pkg/data.js.map +1 -1
  18. package/build/pkg/gen1.d.ts +13 -17
  19. package/build/pkg/gen1.js +6 -10
  20. package/build/pkg/gen1.js.map +1 -1
  21. package/build/pkg/index.d.ts +38 -90
  22. package/build/pkg/index.js +6 -63
  23. package/build/pkg/index.js.map +1 -1
  24. package/build/pkg/protocol.d.ts +20 -1
  25. package/build/pkg/protocol.js +105 -103
  26. package/build/pkg/protocol.js.map +1 -1
  27. package/build/tools/debug.js +35 -69
  28. package/build/tools/debug.js.map +1 -1
  29. package/build/tools/display/debug.jsx +141 -0
  30. package/build/tools/display/debug.jsx.map +1 -0
  31. package/build/tools/display/index.js +141 -0
  32. package/build/tools/display/index.js.map +1 -0
  33. package/build/tools/display/ui.jsx +349 -0
  34. package/build/tools/display/ui.jsx.map +1 -0
  35. package/build/tools/display/util.js +18 -0
  36. package/build/tools/display/util.js.map +1 -0
  37. package/build.zig +254 -77
  38. package/package.json +50 -134
  39. package/src/bin/install-pkmn-engine +9 -7
  40. package/src/bin/pkmn-debug +1 -1
  41. package/src/lib/common/array.zig +77 -0
  42. package/src/lib/common/data.zig +8 -8
  43. package/src/lib/common/debug.zig +39 -8
  44. package/src/lib/common/js.zig +4 -2
  45. package/src/lib/common/optional.zig +13 -9
  46. package/src/lib/common/options.zig +4 -0
  47. package/src/lib/common/protocol.zig +45 -44
  48. package/src/lib/common/rational.zig +53 -51
  49. package/src/lib/common/rng.zig +5 -7
  50. package/src/lib/common/util.zig +50 -1
  51. package/src/lib/gen1/calc.zig +295 -154
  52. package/src/lib/gen1/chance.zig +477 -119
  53. package/src/lib/gen1/data/moves.zig +2 -4
  54. package/src/lib/gen1/data/species.zig +1 -3
  55. package/src/lib/gen1/data/types.zig +1 -2
  56. package/src/lib/gen1/data.zig +35 -38
  57. package/src/lib/gen1/helpers.zig +17 -27
  58. package/src/lib/gen1/mechanics.zig +248 -148
  59. package/src/lib/gen2/calc.zig +37 -27
  60. package/src/lib/gen2/chance.zig +87 -67
  61. package/src/lib/gen2/data/items.zig +1 -3
  62. package/src/lib/gen2/data/moves.zig +2 -4
  63. package/src/lib/gen2/data/species.zig +1 -3
  64. package/src/lib/gen2/data/types.zig +1 -2
  65. package/src/lib/gen2/data.zig +26 -29
  66. package/src/lib/gen2/generated.zig +18 -23
  67. package/src/lib/gen2/helpers.zig +9 -18
  68. package/src/lib/gen2/mechanics.zig +81 -81
  69. package/src/lib/node.zig +2 -4
  70. package/src/lib/pkmn.zig +11 -5
  71. package/src/lib/wasm.zig +36 -1
  72. package/src/tools/display/dom.ts +96 -0
  73. package/src/tools/display/ui.css +354 -0
  74. package/build/test/display.js +0 -400
  75. package/build/test/display.js.map +0 -1
  76. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,26 +1,31 @@
1
- const std = @import("std");
2
-
3
- const assert = std.debug.assert;
4
- const print = std.debug.print;
5
-
6
- const expect = std.testing.expect;
7
- const expectEqual = std.testing.expectEqual;
8
-
9
1
  const DEBUG = @import("../common/debug.zig").print;
2
+
3
+ const array = @import("../common/array.zig");
4
+ const common = @import("../common/data.zig");
5
+ const data = @import("data.zig");
6
+ const optional = @import("../common/optional.zig");
10
7
  const options = @import("../common/options.zig");
11
8
  const rational = @import("../common/rational.zig");
9
+ const std = @import("std");
12
10
  const util = @import("../common/util.zig");
13
11
 
14
- const Player = @import("../common/data.zig").Player;
15
- const Optional = @import("../common/optional.zig").Optional;
16
-
17
- const data = @import("data.zig");
18
-
12
+ const Array = array.Array;
13
+ const assert = std.debug.assert;
19
14
  const enabled = options.chance;
20
- const showdown = options.showdown;
21
-
15
+ const expect = std.testing.expect;
16
+ const expectEqual = std.testing.expectEqual;
17
+ const isPointerTo = util.isPointerTo;
22
18
  const Move = data.Move;
23
19
  const MoveSlot = data.MoveSlot;
20
+ const Optional = optional.Optional;
21
+ const Player = common.Player;
22
+ const PointerType = util.PointerType;
23
+ const print = std.debug.print;
24
+ const showdown = options.showdown;
25
+
26
+ const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
27
+ const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
28
+ const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
24
29
 
25
30
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
26
31
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
@@ -37,8 +42,8 @@ pub const Actions = extern struct {
37
42
  }
38
43
 
39
44
  /// Returns the `Action` for the given `player`.
40
- pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
41
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
45
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
46
+ assert(isPointerTo(self, Actions));
42
47
  return if (player == .P1) &self.p1 else &self.p2;
43
48
  }
44
49
 
@@ -51,15 +56,15 @@ pub const Actions = extern struct {
51
56
  /// same shape if they have the same fields set (though those fields need not necessarily be
52
57
  /// set to the same value).
53
58
  pub fn matches(a: Actions, b: Actions) bool {
54
- inline for (@typeInfo(Actions).Struct.fields) |player| {
55
- inline for (@typeInfo(Action).Struct.fields) |field| {
59
+ inline for (@field(@typeInfo(Actions), @tagName(Struct)).fields) |player| {
60
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
56
61
  const a_val = @field(@field(a, player.name), field.name);
57
62
  const b_val = @field(@field(b, player.name), field.name);
58
63
 
59
64
  switch (@typeInfo(@TypeOf(a_val))) {
60
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
65
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
61
66
  return false,
62
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
67
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
63
68
  else => unreachable,
64
69
  }
65
70
  }
@@ -67,6 +72,25 @@ pub const Actions = extern struct {
67
72
  return true;
68
73
  }
69
74
 
75
+ /// TODO
76
+ pub fn matchesAny(a: Actions, bs: []Actions, i: usize) bool {
77
+ for (bs, 0..) |b, j| {
78
+ // TODO: is skipping this redundant check worth it?
79
+ if (i == j) continue;
80
+ if (b.matches(a)) return true;
81
+ }
82
+ return false;
83
+ }
84
+
85
+ /// TODO
86
+ pub fn relax(actions: Actions) Actions {
87
+ if (!options.overwrite) return actions;
88
+ var a = actions;
89
+ if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
90
+ if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
91
+ return a;
92
+ }
93
+
70
94
  pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
71
95
  try writer.writeAll("<P1 = ");
72
96
  try self.p1.fmt(writer, shape);
@@ -99,7 +123,19 @@ test Actions {
99
123
  try expect(!c.matches(a));
100
124
  }
101
125
 
126
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
102
127
  pub const Observation = enum { started, continuing, ended };
128
+ /// An extension of Observation to account for "hidden" confusion.
129
+ pub const Confusion = enum {
130
+ started,
131
+ continuing,
132
+ ended,
133
+ /// An indicator that an existing confusion duration has been overwritten by a new confusion
134
+ /// duration due to a Thrashing move ending. This is not immediately observable to an opponent,
135
+ /// to compute probabilities from an opponent's point of view based solely on public information
136
+ /// extra work must be done to correct for the information leak
137
+ overwritten,
138
+ };
103
139
 
104
140
  /// Information about the RNG that was observed during a Generation I battle `update` for a
105
141
  /// single player.
@@ -125,19 +161,25 @@ pub const Action = packed struct(u64) {
125
161
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
126
162
  duration: u4 = 0,
127
163
 
128
- // TODO
164
+ /// TODO
129
165
  sleep: Optional(Observation) = .None,
130
- confusion: Optional(Observation) = .None,
166
+ /// TODO
167
+ confusion: Optional(Confusion) = .None,
168
+ /// TODO
131
169
  disable: Optional(Observation) = .None,
132
- bide: Optional(Observation) = .None,
170
+ /// TODO
171
+ attacking: Optional(Observation) = .None,
172
+ /// TODO
133
173
  binding: Optional(Observation) = .None,
134
- thrashing: Optional(Observation) = .None,
135
174
 
136
- _: u4 = 0, // TODO
175
+ _: u1 = 0,
137
176
 
138
177
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
139
178
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
140
179
  move_slot: u4 = 0,
180
+
181
+ /// TODO
182
+ pp: u4 = 0,
141
183
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
142
184
  multi_hit: u4 = 0,
143
185
 
@@ -157,10 +199,10 @@ pub const Action = packed struct(u64) {
157
199
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
158
200
  try writer.writeByte('(');
159
201
  var printed = false;
160
- inline for (@typeInfo(Action).Struct.fields) |field| {
202
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
161
203
  const val = @field(self, field.name);
162
204
  switch (@typeInfo(@TypeOf(val))) {
163
- .Enum => if (val != .None) {
205
+ Enum => if (val != .None) {
164
206
  if (printed) try writer.writeAll(", ");
165
207
  if (shape) {
166
208
  try writer.print("{s}:?", .{field.name});
@@ -169,17 +211,11 @@ pub const Action = packed struct(u64) {
169
211
  if (val == .false) "!" else "",
170
212
  field.name,
171
213
  });
172
- } else if (@TypeOf(val) == Optional(Observation)) {
214
+ } else if (@TypeOf(val) == Optional(Observation) or
215
+ @TypeOf(val) == Optional(Confusion))
216
+ {
173
217
  try writer.print("{s}{s}", .{
174
- switch (val) {
175
- .started => "+",
176
- .continuing => "",
177
- .ended => if (std.mem.eql(u8, field.name, "thrashing"))
178
- "~"
179
- else
180
- "-",
181
- else => unreachable,
182
- },
218
+ ([_][]const u8{ "+", "", "-", "#" })[@intFromEnum(val) - 1],
183
219
  field.name,
184
220
  });
185
221
  } else {
@@ -187,7 +223,7 @@ pub const Action = packed struct(u64) {
187
223
  }
188
224
  printed = true;
189
225
  },
190
- .Int => if (val != 0) {
226
+ Int => if (val != 0) {
191
227
  if (printed) try writer.writeAll(", ");
192
228
  if (shape) {
193
229
  try writer.print("{s}:?", .{field.name});
@@ -204,47 +240,20 @@ pub const Action = packed struct(u64) {
204
240
  };
205
241
 
206
242
  /// TODO
207
- pub const Durations = struct {
243
+ pub const Durations = extern struct {
208
244
  /// TODO
209
245
  p1: Duration = .{},
210
246
  /// TODO
211
247
  p2: Duration = .{},
212
248
 
213
- /// TODO
214
- pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Duration) {
215
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Durations);
216
- return if (player == .P1) &self.p1 else &self.p2;
249
+ comptime {
250
+ assert(@sizeOf(Durations) == 8);
217
251
  }
218
252
 
219
253
  /// TODO
220
- pub fn update(self: *Durations, battle: anytype, q: anytype, actions: Actions) !void {
221
- for (&battle.sides, 0..) |*s, i| {
222
- const p: Player = @enumFromInt(i);
223
- const action = actions.get(p);
224
- var duration = self.get(p);
225
-
226
- // TODO sleeps
227
-
228
- duration.confusion =
229
- if (@intFromEnum(action.confusion) > 1) duration.confusion + 1 else 0;
230
- duration.disable =
231
- if (@intFromEnum(action.disable) > 1) duration.disable + 1 else 0;
232
- duration.bide =
233
- if (@intFromEnum(action.bide) > 1) duration.bide + 1 else 0;
234
- duration.binding =
235
- if (@intFromEnum(action.binding) > 1) duration.binding + 1 else 0;
236
-
237
- // NOTE: this relies on private information!
238
- if (action.thrashing == .ended) {
239
- duration.thrashing =
240
- if (s.active.volatiles.Thrashing) duration.thrashing + 1 else 0;
241
- } else {
242
- duration.thrashing =
243
- if (@intFromEnum(action.thrashing) > 1) duration.thrashing + 1 else 0;
244
- }
245
-
246
- _ = q;
247
- }
254
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
255
+ assert(isPointerTo(self, Durations));
256
+ return if (player == .P1) &self.p1 else &self.p2;
248
257
  }
249
258
 
250
259
  pub fn format(
@@ -258,15 +267,18 @@ pub const Durations = struct {
258
267
  }
259
268
  };
260
269
 
270
+ pub const Sleeps = Array(6, u3);
271
+
261
272
  /// TODO
262
- pub const Duration = struct {
263
- sleeps: [6]u3 = .{0} ** 6,
273
+ pub const Duration = packed struct(u32) {
274
+ sleeps: Sleeps.T = 0,
264
275
  confusion: u3 = 0,
265
276
  disable: u4 = 0,
266
- bide: u3 = 0,
267
- thrashing: u3 = 0,
277
+ attacking: u3 = 0,
268
278
  binding: u3 = 0,
269
279
 
280
+ _: u1 = 0,
281
+
270
282
  pub fn format(
271
283
  self: Duration,
272
284
  comptime fmt: []const u8,
@@ -276,27 +288,22 @@ pub const Duration = struct {
276
288
  _ = .{ fmt, opts };
277
289
  try writer.writeByte('(');
278
290
  var printed = false;
279
- inline for (@typeInfo(Duration).Struct.fields) |field| {
291
+ inline for (@field(@typeInfo(Duration), @tagName(Struct)).fields) |field| {
280
292
  const val = @field(self, field.name);
281
293
  switch (@typeInfo(@TypeOf(val))) {
282
- .Int => if (val != 0) {
294
+ Int => if (val != 0) {
283
295
  if (printed) try writer.writeAll(", ");
284
- try writer.print("{s}:{d}", .{ field.name, val });
285
- printed = true;
286
- },
287
- .Array => {
288
- var total: usize = 0;
289
- for (val) |v| total += v;
290
- if (total > 0) {
291
- if (printed) try writer.writeAll(", ");
296
+ if (comptime std.mem.eql(u8, field.name, "sleeps")) {
292
297
  try writer.print("{s}:[", .{field.name});
293
- for (val, 0..) |v, i| {
298
+ for (0..6) |i| {
294
299
  if (i != 0) try writer.writeByte(',');
295
- try writer.print("{d}", .{v});
300
+ try writer.print("{d}", .{Sleeps.get(val, i)});
296
301
  }
297
302
  try writer.writeAll("]");
298
- printed = true;
303
+ } else {
304
+ try writer.print("{s}:{d}", .{ field.name, val });
299
305
  }
306
+ printed = true;
300
307
  },
301
308
  else => unreachable,
302
309
  }
@@ -305,7 +312,7 @@ pub const Duration = struct {
305
312
  }
306
313
  };
307
314
 
308
- pub const Commit = enum { hit, miss };
315
+ pub const Commit = enum { hit, miss, binding, err, glitch };
309
316
 
310
317
  const uN = if (options.miss) u8 else u9;
311
318
 
@@ -320,6 +327,8 @@ pub fn Chance(comptime Rational: type) type {
320
327
  /// The actions taken by a hypothetical "chance player" that convey information about which
321
328
  /// RNG events were observed during a battle `update`.
322
329
  actions: Actions = .{},
330
+ /// TODO
331
+ durations: Durations = .{},
323
332
 
324
333
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
325
334
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -343,6 +352,7 @@ pub fn Chance(comptime Rational: type) type {
343
352
  damage_roll: u8 = 0,
344
353
  hit: bool = false,
345
354
  hit_probablity: uN = 0,
355
+ glitch: bool = false,
346
356
  } = .{},
347
357
 
348
358
  /// Possible error returned by operations tracking chance probability.
@@ -362,10 +372,18 @@ pub fn Chance(comptime Rational: type) type {
362
372
 
363
373
  assert(!showdown);
364
374
 
375
+ if (kind == .glitch and !self.pending.glitch) return;
376
+
365
377
  var action = self.actions.get(player);
366
378
 
379
+ if (kind == .err and self.pending.crit_probablity != 0) {
380
+ try self.probability.update(self.pending.crit_probablity, 256);
381
+ action.critical_hit = if (self.pending.crit) .true else .false;
382
+ return;
383
+ }
384
+
367
385
  // Always commit the hit result if we make it here (commit won't be called at all if the
368
- // target is immune/behind a sub/etc)
386
+ // target is immune*/behind a sub/etc)
369
387
  if (self.pending.hit_probablity != 0) {
370
388
  try self.probability.update(self.pending.hit_probablity, 256);
371
389
  action.hit = if (self.pending.hit) .true else .false;
@@ -373,28 +391,61 @@ pub fn Chance(comptime Rational: type) type {
373
391
 
374
392
  // If the move actually lands we can commit any past critical hit / damage rolls. Note
375
393
  // we can't rely on the presence or absence of a damage roll to determine whether a
376
- // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
394
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
377
395
  // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
378
- // passed to the critical hit helper)
379
- if (kind != .hit) return assert(!self.pending.crit or self.pending.crit_probablity > 0);
396
+ // passed to the critical hit helper).
397
+ //
398
+ // There is a special edge case with the division-by-zero freeze where we need to commit
399
+ // the status of critical hit roll the occured before the move missed to avoid missing
400
+ // out on the possibility that the alternative branch encountered an error
401
+ if (kind != .hit and !self.pending.glitch) {
402
+ return assert(!self.pending.crit or self.pending.crit_probablity > 0);
403
+ }
380
404
 
381
405
  if (self.pending.crit_probablity != 0) {
382
406
  try self.probability.update(self.pending.crit_probablity, 256);
383
407
  action.critical_hit = if (self.pending.crit) .true else .false;
384
- }
408
+ } else assert(!self.pending.glitch);
385
409
 
386
- if (self.pending.damage_roll > 0) {
410
+ if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
387
411
  try self.probability.update(1, 39);
388
412
  action.damage = self.pending.damage_roll;
389
413
  }
390
414
  }
391
415
 
416
+ pub fn glitch(self: *Self) void {
417
+ if (!enabled) return;
418
+
419
+ assert(!showdown);
420
+ self.pending.glitch = true;
421
+ }
422
+
392
423
  pub fn clearPending(self: *Self) void {
393
424
  if (!enabled) return;
394
425
 
426
+ assert(!showdown);
395
427
  self.pending = .{};
396
428
  }
397
429
 
430
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
431
+ if (!enabled) return;
432
+
433
+ assert(slot >= 1 and slot <= 6);
434
+
435
+ var d = self.durations.get(player);
436
+
437
+ const out = Sleeps.get(d.sleeps, 0);
438
+ d.sleeps = Sleeps.set(d.sleeps, 0, Sleeps.get(d.sleeps, slot - 1));
439
+ d.sleeps = Sleeps.set(d.sleeps, slot - 1, out);
440
+
441
+ d.confusion = 0;
442
+ d.disable = 0;
443
+ d.attacking = 0;
444
+ d.binding = 0;
445
+
446
+ self.durations.get(player.foe()).binding = 0;
447
+ }
448
+
398
449
  pub fn speedTie(self: *Self, p1: bool) Error!void {
399
450
  if (!enabled) return;
400
451
 
@@ -494,18 +545,170 @@ pub fn Chance(comptime Rational: type) type {
494
545
  pub fn duration(self: *Self, player: Player, turns: u4) void {
495
546
  if (!enabled) return;
496
547
 
497
- self.actions.get(player).duration = if (options.key) 1 else turns;
548
+ self.actions.get(player).duration = if (options.key) 2 else turns;
498
549
  }
499
550
 
500
- pub fn durations(
551
+ pub fn observe(
501
552
  self: *Self,
502
- comptime f: Action.Field,
553
+ comptime field: Action.Field,
503
554
  player: Player,
504
- observation: Optional(Observation),
555
+ obs: Optional(Confusion),
505
556
  ) void {
506
557
  if (!enabled) return;
507
558
 
508
- @field(self.actions.get(player), @tagName(f)) = observation;
559
+ var a = self.actions.get(player);
560
+ const val = @field(a, @tagName(field));
561
+ switch (obs) {
562
+ .overwritten => {
563
+ assert(field == .confusion);
564
+ assert(val == .started or val == .continuing);
565
+ assert(a.duration > 0);
566
+ a.confusion = obs;
567
+ self.durations.get(player).confusion = 1;
568
+ },
569
+ .None => {
570
+ @field(a, @tagName(field)) = .None;
571
+ if (field == .sleep) {
572
+ var d = self.durations.get(player);
573
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
574
+ } else {
575
+ @field(self.durations.get(player), @tagName(field)) = 0;
576
+ }
577
+ },
578
+ .started => {
579
+ assert(val == .None or val == .ended);
580
+ assert(switch (field) {
581
+ .confusion => a.duration > 0 or self.actions.get(player.foe()).duration > 0,
582
+ .sleep, .disable => self.actions.get(player.foe()).duration > 0,
583
+ else => a.duration > 0,
584
+ });
585
+ @field(a, @tagName(field)) = .started;
586
+ if (field == .sleep) {
587
+ var d = self.durations.get(player);
588
+ d.sleeps = Sleeps.set(d.sleeps, 0, 1);
589
+ } else {
590
+ @field(self.durations.get(player), @tagName(field)) = 1;
591
+ }
592
+ },
593
+ else => unreachable,
594
+ }
595
+ }
596
+
597
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
598
+ if (!enabled) return;
599
+
600
+ var a = self.actions.get(player);
601
+ assert(a.sleep == .None or a.sleep == .started);
602
+ a.sleep = obs;
603
+
604
+ var d = self.durations.get(player);
605
+ const n = Sleeps.get(d.sleeps, 0);
606
+
607
+ if (obs == .None) {
608
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
609
+ } else if (obs == .ended) {
610
+ assert(n >= 1 and n <= 7);
611
+ if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
612
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
613
+ } else {
614
+ assert(obs == .continuing);
615
+ assert(n >= 1 and n < 7);
616
+ try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
617
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
618
+ }
619
+ }
620
+
621
+ pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
622
+ if (!enabled) return;
623
+
624
+ var a = self.actions.get(player);
625
+ assert(a.confusion == .None or a.confusion == .started);
626
+ a.confusion = obs;
627
+
628
+ var d = self.durations.get(player);
629
+ const n = d.confusion;
630
+
631
+ if (obs == .ended) {
632
+ assert(n >= 2 and n <= 5);
633
+ if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
634
+ d.confusion = 0;
635
+ } else {
636
+ assert(obs == .continuing);
637
+ assert(n >= 1 and n < 5);
638
+ if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
639
+ d.confusion += 1;
640
+ }
641
+ }
642
+
643
+ pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
644
+ if (!enabled) return;
645
+
646
+ var a = self.actions.get(player);
647
+ assert(a.disable == .None or a.disable == .started);
648
+ a.disable = obs;
649
+
650
+ var d = self.durations.get(player);
651
+ const n = d.disable;
652
+
653
+ if (obs == .ended) {
654
+ assert(n >= 1 and n <= 8);
655
+ if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
656
+ d.disable = 0;
657
+ } else {
658
+ assert(obs == .continuing);
659
+ assert(n >= 1 and n < 8);
660
+ try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
661
+ d.disable += 1;
662
+ }
663
+ }
664
+
665
+ pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
666
+ if (!enabled) return;
667
+
668
+ var a = self.actions.get(player);
669
+ assert(a.attacking == .None or a.attacking == .started);
670
+ a.attacking = obs;
671
+
672
+ var d = self.durations.get(player);
673
+ const n = d.attacking;
674
+ if (obs == .ended) {
675
+ // "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
676
+ assert(n >= 2 and n <= 3);
677
+ if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
678
+ d.attacking = 0;
679
+ } else {
680
+ assert(obs == .continuing);
681
+ assert(n >= 1 and n < 3);
682
+ if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
683
+ d.attacking += 1;
684
+ }
685
+ }
686
+
687
+ pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
688
+ if (!enabled) return;
689
+
690
+ var a = self.actions.get(player);
691
+ assert(a.binding == .None or a.binding == .started);
692
+ a.binding = obs;
693
+
694
+ var d = self.durations.get(player);
695
+ const n = d.binding;
696
+
697
+ assert(n > 0);
698
+ const p: u4 = if (n < 3) 3 else 1;
699
+ const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
700
+
701
+ if (obs == .ended) {
702
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
703
+ assert(n >= 1 and n <= 4);
704
+ if (n != 4) try self.probability.update(p, q);
705
+ d.binding = 0;
706
+ } else {
707
+ assert(obs == .continuing);
708
+ assert(n >= 1 and n < 4);
709
+ try self.probability.update(q - p, q);
710
+ d.binding += 1;
711
+ }
509
712
  }
510
713
 
511
714
  pub fn psywave(self: *Self, player: Player, power: u8, max: u8) Error!void {
@@ -521,6 +724,15 @@ pub fn Chance(comptime Rational: type) type {
521
724
  try self.probability.update(1, Move.METRONOME.len);
522
725
  self.actions.get(player).metronome = move;
523
726
  }
727
+
728
+ pub fn pp(self: *Self, player: Player, n: u4) bool {
729
+ if (!enabled) return true;
730
+
731
+ var a = self.actions.get(player);
732
+ if (n > 1 and a.pp == n) return false;
733
+ if (a.metronome != .None) a.pp += n;
734
+ return true;
735
+ }
524
736
  };
525
737
  }
526
738
 
@@ -621,16 +833,16 @@ test "Chance.damage" {
621
833
 
622
834
  try chance.damage(.P1, 217);
623
835
  if (showdown) {
624
- try expectValue(217, chance.actions.p1.damage);
836
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
625
837
  try expectProbability(&chance.probability, 1, 39);
626
838
  } else {
627
839
  try expectProbability(&chance.probability, 1, 1);
628
- try expectValue(0, chance.actions.p1.damage);
840
+ try expectValue(@as(u8, 0), chance.actions.p1.damage);
629
841
 
630
842
  try chance.commit(.P1, .hit);
631
843
 
632
844
  try expectProbability(&chance.probability, 1, 39);
633
- try expectValue(217, chance.actions.p1.damage);
845
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
634
846
  }
635
847
  }
636
848
 
@@ -682,13 +894,13 @@ test "Chance.moveSlot" {
682
894
 
683
895
  try chance.moveSlot(.P2, 2, &ms, 2);
684
896
  try expectProbability(&chance.probability, 1, 2);
685
- try expectValue(2, chance.actions.p2.move_slot);
897
+ try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
686
898
 
687
899
  chance.reset();
688
900
 
689
901
  try chance.moveSlot(.P1, 1, &ms, 0);
690
902
  try expectProbability(&chance.probability, 1, 3);
691
- try expectValue(1, chance.actions.p1.move_slot);
903
+ try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
692
904
  }
693
905
 
694
906
  test "Chance.multiHit" {
@@ -696,25 +908,142 @@ test "Chance.multiHit" {
696
908
 
697
909
  try chance.multiHit(.P1, 3);
698
910
  try expectProbability(&chance.probability, 3, 8);
699
- try expectValue(3, chance.actions.p1.multi_hit);
911
+ try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
700
912
 
701
913
  chance.reset();
702
914
 
703
915
  try chance.multiHit(.P2, 5);
704
916
  try expectProbability(&chance.probability, 1, 8);
705
- try expectValue(5, chance.actions.p2.multi_hit);
917
+ try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
706
918
  }
707
919
 
708
920
  test "Chance.duration" {
709
921
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
710
922
 
711
923
  chance.duration(.P1, 2);
712
- try expectValue(2, chance.actions.p1.duration);
924
+ try expectValue(@as(u4, 2), chance.actions.p1.duration);
713
925
 
714
926
  chance.reset();
715
927
 
716
928
  chance.duration(.P2, 4);
717
- try expectValue(4, chance.actions.p2.duration);
929
+ try expectValue(@as(u4, 4), chance.actions.p2.duration);
930
+ }
931
+
932
+ test "Chance.sleep" {
933
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
934
+
935
+ for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
936
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
937
+ try chance.sleep(.P1, .ended);
938
+ try expectProbability(&chance.probability, 1, d);
939
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
940
+
941
+ chance.reset();
942
+
943
+ if (i < 7) {
944
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
945
+ try chance.sleep(.P1, .continuing);
946
+ try expectProbability(&chance.probability, d - 1, d);
947
+ try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
948
+
949
+ chance.reset();
950
+ }
951
+ }
952
+ }
953
+
954
+ test "Chance.confusion" {
955
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
956
+
957
+ for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
958
+ if (i > 1) {
959
+ chance.durations.p2.confusion = @intCast(i);
960
+ try chance.confusion(.P2, .ended);
961
+ try expectProbability(&chance.probability, 1, d);
962
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
963
+
964
+ chance.reset();
965
+ }
966
+
967
+ if (i < 5) {
968
+ chance.durations.p2.confusion = @intCast(i);
969
+ try chance.confusion(.P2, .continuing);
970
+ try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
971
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
972
+
973
+ chance.reset();
974
+ }
975
+ }
976
+ }
977
+
978
+ test "Chance.disable" {
979
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
980
+
981
+ for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
982
+ chance.durations.p1.disable = @intCast(i);
983
+ try chance.disable(.P1, .ended);
984
+ try expectProbability(&chance.probability, 1, d);
985
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
986
+
987
+ chance.reset();
988
+
989
+ if (i < 8) {
990
+ chance.durations.p1.disable = @intCast(i);
991
+ try chance.disable(.P1, .continuing);
992
+ try expectProbability(&chance.probability, d - 1, d);
993
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
994
+
995
+ chance.reset();
996
+ }
997
+ }
998
+ }
999
+
1000
+ test "Chance.attacking" {
1001
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
1002
+
1003
+ for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
1004
+ if (i > 1) {
1005
+ chance.durations.p2.attacking = @intCast(i);
1006
+ try chance.attacking(.P2, .ended);
1007
+ try expectProbability(&chance.probability, 1, d);
1008
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
1009
+
1010
+ chance.reset();
1011
+ }
1012
+
1013
+ if (i < 3) {
1014
+ chance.durations.p2.attacking = @intCast(i);
1015
+ try chance.attacking(.P2, .continuing);
1016
+ try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1017
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
1018
+
1019
+ chance.reset();
1020
+ }
1021
+ }
1022
+ }
1023
+
1024
+ test "Chance.binding" {
1025
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
1026
+
1027
+ const ps = [_]u8{ 3, 3, 1, 1 };
1028
+ const qs = [_]u8{ 8, 5, 2, 1 };
1029
+
1030
+ for (ps, qs, 1..5) |p, q, i| {
1031
+ chance.durations.p1.binding = @intCast(i);
1032
+ try chance.binding(.P1, .ended);
1033
+ try expectProbability(&chance.probability, p, q);
1034
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
1035
+
1036
+ chance.reset();
1037
+
1038
+ if (i < 4) {
1039
+ chance.durations.p1.binding = @intCast(i);
1040
+ try chance.binding(.P1, .continuing);
1041
+ try expectProbability(&chance.probability, q - p, q);
1042
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
1043
+
1044
+ chance.reset();
1045
+ }
1046
+ }
718
1047
  }
719
1048
 
720
1049
  test "Chance.psywave" {
@@ -722,7 +1051,7 @@ test "Chance.psywave" {
722
1051
 
723
1052
  try chance.psywave(.P2, 100, 150);
724
1053
  try expectProbability(&chance.probability, 1, 150);
725
- try expectValue(101, chance.actions.p2.psywave);
1054
+ try expectValue(@as(u8, 101), chance.actions.p2.psywave);
726
1055
  }
727
1056
 
728
1057
  test "Chance.metronome" {
@@ -760,12 +1089,16 @@ const Null = struct {
760
1089
  _ = .{ self, player, kind };
761
1090
  }
762
1091
 
1092
+ pub fn glitch(self: Null) void {
1093
+ _ = .{self};
1094
+ }
1095
+
763
1096
  pub fn clearPending(self: Null) void {
764
1097
  _ = .{self};
765
1098
  }
766
1099
 
767
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
768
- _ = .{ self, player, in, out };
1100
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1101
+ _ = .{ self, player, slot };
769
1102
  }
770
1103
 
771
1104
  pub fn speedTie(self: Null, p1: bool) Error!void {
@@ -804,17 +1137,37 @@ const Null = struct {
804
1137
  _ = .{ self, player, n };
805
1138
  }
806
1139
 
807
- pub fn durations(
1140
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1141
+ _ = .{ self, player, turns };
1142
+ }
1143
+
1144
+ pub fn observe(
808
1145
  self: Null,
809
- comptime f: Action.Field,
1146
+ comptime field: Action.Field,
810
1147
  player: Player,
811
- observation: Optional(Observation),
1148
+ obs: Optional(Confusion),
812
1149
  ) void {
813
- _ = .{ self, f, player, observation };
1150
+ _ = .{ self, field, player, obs };
814
1151
  }
815
1152
 
816
- pub fn duration(self: Null, player: Player, turns: u4) void {
817
- _ = .{ self, player, turns };
1153
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1154
+ _ = .{ self, player, obs };
1155
+ }
1156
+
1157
+ pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
1158
+ _ = .{ self, player, obs };
1159
+ }
1160
+
1161
+ pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1162
+ _ = .{ self, player, obs };
1163
+ }
1164
+
1165
+ pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1166
+ _ = .{ self, player, obs };
1167
+ }
1168
+
1169
+ pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1170
+ _ = .{ self, player, obs };
818
1171
  }
819
1172
 
820
1173
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -824,4 +1177,9 @@ const Null = struct {
824
1177
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
825
1178
  _ = .{ self, player, move };
826
1179
  }
1180
+
1181
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1182
+ _ = .{ self, player, n };
1183
+ return true;
1184
+ }
827
1185
  };