@pkmn/engine 0.1.0-dev.deda8593 → 0.1.0-dev.e0a786f8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/README.md +9 -6
  2. package/build/pkg/addon/browser.d.ts +3 -0
  3. package/build/pkg/addon/browser.js +47 -0
  4. package/build/pkg/addon/browser.js.map +1 -0
  5. package/build/pkg/addon/node.d.ts +3 -0
  6. package/build/pkg/addon/node.js +110 -0
  7. package/build/pkg/addon/node.js.map +1 -0
  8. package/build/pkg/addon/wasm.d.ts +2 -0
  9. package/build/pkg/addon/wasm.js +53 -0
  10. package/build/pkg/addon/wasm.js.map +1 -0
  11. package/build/pkg/addon.d.ts +25 -3
  12. package/build/pkg/addon.js +34 -65
  13. package/build/pkg/addon.js.map +1 -1
  14. package/build/pkg/data/layout.json +15 -2
  15. package/build/pkg/data/protocol.json +23 -9
  16. package/build/pkg/data.d.ts +23 -1
  17. package/build/pkg/data.js.map +1 -1
  18. package/build/pkg/gen1.d.ts +15 -18
  19. package/build/pkg/gen1.js +28 -19
  20. package/build/pkg/gen1.js.map +1 -1
  21. package/build/pkg/index.d.ts +40 -90
  22. package/build/pkg/index.js +23 -70
  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 +52 -76
  28. package/build/tools/debug.js.map +1 -1
  29. package/build/tools/display/debug.jsx +152 -0
  30. package/build/tools/display/debug.jsx.map +1 -0
  31. package/build/tools/display/index.js +152 -0
  32. package/build/tools/display/index.js.map +1 -0
  33. package/build/tools/display/ui.jsx +359 -0
  34. package/build/tools/display/ui.jsx.map +1 -0
  35. package/build/tools/display/util.js +33 -0
  36. package/build/tools/display/util.js.map +1 -0
  37. package/build.zig +330 -85
  38. package/package.json +54 -135
  39. package/src/bin/install-pkmn-engine +10 -7
  40. package/src/bin/pkmn-debug +1 -1
  41. package/src/lib/bindings/c.zig +255 -0
  42. package/src/lib/bindings/node.zig +121 -0
  43. package/src/lib/bindings/wasm.zig +59 -0
  44. package/src/lib/common/array.zig +77 -0
  45. package/src/lib/common/data.zig +8 -8
  46. package/src/lib/common/debug.zig +39 -8
  47. package/src/lib/common/js.zig +4 -2
  48. package/src/lib/common/optional.zig +13 -9
  49. package/src/lib/common/options.zig +4 -0
  50. package/src/lib/common/protocol.zig +45 -44
  51. package/src/lib/common/rational.zig +323 -157
  52. package/src/lib/common/rng.zig +5 -7
  53. package/src/lib/common/util.zig +60 -1
  54. package/src/lib/gen1/calc.zig +527 -161
  55. package/src/lib/gen1/chance.zig +366 -240
  56. package/src/lib/gen1/data/moves.zig +2 -4
  57. package/src/lib/gen1/data/species.zig +1 -3
  58. package/src/lib/gen1/data/types.zig +1 -2
  59. package/src/lib/gen1/data.zig +35 -38
  60. package/src/lib/gen1/helpers.zig +17 -554
  61. package/src/lib/gen1/mechanics.zig +280 -193
  62. package/src/lib/gen2/calc.zig +37 -27
  63. package/src/lib/gen2/chance.zig +90 -503
  64. package/src/lib/gen2/data/items.zig +1 -3
  65. package/src/lib/gen2/data/moves.zig +2 -4
  66. package/src/lib/gen2/data/species.zig +1 -3
  67. package/src/lib/gen2/data/types.zig +1 -2
  68. package/src/lib/gen2/data.zig +26 -29
  69. package/src/lib/gen2/generated.zig +18 -23
  70. package/src/lib/gen2/helpers.zig +9 -18
  71. package/src/lib/gen2/mechanics.zig +88 -115
  72. package/src/lib/node.zig +3 -116
  73. package/src/lib/pkmn.zig +14 -7
  74. package/src/lib/wasm.zig +13 -9
  75. package/src/tools/display/dom.ts +93 -0
  76. package/src/tools/display/ui.css +354 -0
  77. package/build/test/display.js +0 -400
  78. package/build/test/display.js.map +0 -1
  79. 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,17 +42,11 @@ 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
 
45
- /// Perform a reset by clearing fields which should not persist across updates.
46
- pub fn reset(self: *Actions) void {
47
- self.p1.reset();
48
- self.p2.reset();
49
- }
50
-
51
50
  /// Returns true if `a` is equal to `b`.
52
51
  pub fn eql(a: Actions, b: Actions) bool {
53
52
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
@@ -57,20 +56,15 @@ pub const Actions = extern struct {
57
56
  /// same shape if they have the same fields set (though those fields need not necessarily be
58
57
  /// set to the same value).
59
58
  pub fn matches(a: Actions, b: Actions) bool {
60
- inline for (@typeInfo(Actions).Struct.fields) |player| {
61
- 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| {
62
61
  const a_val = @field(@field(a, player.name), field.name);
63
62
  const b_val = @field(@field(b, player.name), field.name);
64
63
 
65
64
  switch (@typeInfo(@TypeOf(a_val))) {
66
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
67
- if ((@field(a_val, f.name) > 0) != (@field(b_val, f.name) > 0)) {
68
- return false;
69
- }
70
- },
71
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
65
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
72
66
  return false,
73
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
67
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
74
68
  else => unreachable,
75
69
  }
76
70
  }
@@ -78,6 +72,25 @@ pub const Actions = extern struct {
78
72
  return true;
79
73
  }
80
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
+
81
94
  pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
82
95
  try writer.writeAll("<P1 = ");
83
96
  try self.p1.fmt(writer, shape);
@@ -92,14 +105,10 @@ pub const Actions = extern struct {
92
105
  }
93
106
  };
94
107
 
95
- // TODO: ziglang/zig#19730
96
108
  test Actions {
97
109
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
98
110
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
99
111
  const c: Actions = .{ .p1 = .{ .hit = .true } };
100
- // const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
101
- // const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
102
- // const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
103
112
 
104
113
  try expect(a.eql(a));
105
114
  try expect(!a.eql(b));
@@ -112,10 +121,22 @@ test Actions {
112
121
  try expect(b.matches(a));
113
122
  try expect(!a.matches(c));
114
123
  try expect(!c.matches(a));
115
- // try expect(d.matches(e));
116
- // try expect(!d.matches(f));
117
124
  }
118
125
 
126
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
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
+ };
139
+
119
140
  /// Information about the RNG that was observed during a Generation I battle `update` for a
120
141
  /// single player.
121
142
  pub const Action = packed struct(u64) {
@@ -140,12 +161,25 @@ pub const Action = packed struct(u64) {
140
161
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
141
162
  duration: u4 = 0,
142
163
 
143
- /// Observed values of various durations. Does not influence future RNG calls. TODO
144
- durations: Duration = .{},
164
+ /// TODO
165
+ sleep: Optional(Observation) = .None,
166
+ /// TODO
167
+ confusion: Optional(Confusion) = .None,
168
+ /// TODO
169
+ disable: Optional(Observation) = .None,
170
+ /// TODO
171
+ attacking: Optional(Observation) = .None,
172
+ /// TODO
173
+ binding: Optional(Observation) = .None,
174
+
175
+ _: u1 = 0,
145
176
 
146
177
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
147
178
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
148
179
  move_slot: u4 = 0,
180
+
181
+ /// TODO
182
+ pp: u4 = 0,
149
183
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
150
184
  multi_hit: u4 = 0,
151
185
 
@@ -155,15 +189,8 @@ pub const Action = packed struct(u64) {
155
189
  /// If not None, the Move to return for Rolls.metronome.
156
190
  metronome: Move = .None,
157
191
 
158
- pub const DURATIONS: u64 = 0x000000FFFF000000;
159
-
160
192
  pub const Field = std.meta.FieldEnum(Action);
161
193
 
162
- /// Perform a reset by clearing fields which should not persist across updates.
163
- pub fn reset(self: *Action) void {
164
- self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
165
- }
166
-
167
194
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
168
195
  _ = .{ f, o };
169
196
  try fmt(a, w, false);
@@ -172,22 +199,10 @@ pub const Action = packed struct(u64) {
172
199
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
173
200
  try writer.writeByte('(');
174
201
  var printed = false;
175
- inline for (@typeInfo(Action).Struct.fields) |field| {
202
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
176
203
  const val = @field(self, field.name);
177
204
  switch (@typeInfo(@TypeOf(val))) {
178
- .Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
179
- const v = @field(val, f.name);
180
- if (v != 0) {
181
- if (printed) try writer.writeAll(", ");
182
- if (shape) {
183
- try writer.print("{s}:?", .{f.name});
184
- } else {
185
- try writer.print("{s}:{d}", .{ f.name, v });
186
- }
187
- printed = true;
188
- }
189
- },
190
- .Enum => if (val != .None) {
205
+ Enum => if (val != .None) {
191
206
  if (printed) try writer.writeAll(", ");
192
207
  if (shape) {
193
208
  try writer.print("{s}:?", .{field.name});
@@ -196,12 +211,19 @@ pub const Action = packed struct(u64) {
196
211
  if (val == .false) "!" else "",
197
212
  field.name,
198
213
  });
214
+ } else if (@TypeOf(val) == Optional(Observation) or
215
+ @TypeOf(val) == Optional(Confusion))
216
+ {
217
+ try writer.print("{s}{s}", .{
218
+ ([_][]const u8{ "+", "", "-", "#" })[@intFromEnum(val) - 1],
219
+ field.name,
220
+ });
199
221
  } else {
200
222
  try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
201
223
  }
202
224
  printed = true;
203
225
  },
204
- .Int => if (val != 0) {
226
+ Int => if (val != 0) {
205
227
  if (printed) try writer.writeAll(", ");
206
228
  if (shape) {
207
229
  try writer.print("{s}:?", .{field.name});
@@ -217,43 +239,80 @@ pub const Action = packed struct(u64) {
217
239
  }
218
240
  };
219
241
 
220
- test Action {
221
- var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
222
- a.reset();
223
-
224
- try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
225
- }
226
-
227
242
  /// TODO
228
243
  pub const Durations = extern struct {
229
- /// Information about the durations for Player 1.
244
+ /// TODO
230
245
  p1: Duration = .{},
231
- /// Information about the durations for Player 2.
246
+ /// TODO
232
247
  p2: Duration = .{},
233
248
 
234
249
  comptime {
235
- assert(@sizeOf(Durations) == 4);
250
+ assert(@sizeOf(Durations) == 8);
251
+ }
252
+
253
+ /// TODO
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;
257
+ }
258
+
259
+ pub fn format(
260
+ self: Durations,
261
+ comptime fmt: []const u8,
262
+ opts: std.fmt.FormatOptions,
263
+ writer: anytype,
264
+ ) !void {
265
+ _ = .{ fmt, opts };
266
+ try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
236
267
  }
237
268
  };
238
269
 
239
- /// Observed values for various durations that need to be tracked in order to properly
240
- /// deduplicate transitions with a primary key. TODO
241
- pub const Duration = packed struct(u16) {
242
- /// The number of turns a Pokémon has been observed to be sleeping.
243
- sleep: u3 = 0,
244
- /// The number of turns a Pokémon has been observed to be confused.
270
+ pub const Sleeps = Array(6, u3);
271
+
272
+ /// TODO
273
+ pub const Duration = packed struct(u32) {
274
+ sleeps: Sleeps.T = 0,
245
275
  confusion: u3 = 0,
246
- /// The number of turns a Pokémon has been observed to be disabled.
247
276
  disable: u4 = 0,
248
- /// The number of turns a Pokémon has been observed to be attacking.
249
277
  attacking: u3 = 0,
250
- /// The number of turns a Pokémon has been observed to be binding their opponent.
251
278
  binding: u3 = 0,
252
279
 
253
- pub const Field = std.meta.FieldEnum(Duration);
280
+ _: u1 = 0,
281
+
282
+ pub fn format(
283
+ self: Duration,
284
+ comptime fmt: []const u8,
285
+ opts: std.fmt.FormatOptions,
286
+ writer: anytype,
287
+ ) !void {
288
+ _ = .{ fmt, opts };
289
+ try writer.writeByte('(');
290
+ var printed = false;
291
+ inline for (@field(@typeInfo(Duration), @tagName(Struct)).fields) |field| {
292
+ const val = @field(self, field.name);
293
+ switch (@typeInfo(@TypeOf(val))) {
294
+ Int => if (val != 0) {
295
+ if (printed) try writer.writeAll(", ");
296
+ if (comptime std.mem.eql(u8, field.name, "sleeps")) {
297
+ try writer.print("{s}:[", .{field.name});
298
+ for (0..6) |i| {
299
+ if (i != 0) try writer.writeByte(',');
300
+ try writer.print("{d}", .{Sleeps.get(val, i)});
301
+ }
302
+ try writer.writeAll("]");
303
+ } else {
304
+ try writer.print("{s}:{d}", .{ field.name, val });
305
+ }
306
+ printed = true;
307
+ },
308
+ else => unreachable,
309
+ }
310
+ }
311
+ try writer.writeByte(')');
312
+ }
254
313
  };
255
314
 
256
- pub const Commit = enum { hit, miss, binding };
315
+ pub const Commit = enum { hit, miss, binding, err, glitch };
257
316
 
258
317
  const uN = if (options.miss) u8 else u9;
259
318
 
@@ -268,14 +327,8 @@ pub fn Chance(comptime Rational: type) type {
268
327
  /// The actions taken by a hypothetical "chance player" that convey information about which
269
328
  /// RNG events were observed during a battle `update`.
270
329
  actions: Actions = .{},
271
-
272
- // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
273
- // other durations which are all tied to volatiles, sleep's counter persists through
274
- // switching and so we must store it here. The indices of these arrays correspond to the
275
- // `order` field of a Side. This information could be stored in actions but size is a
276
- // concern so this is tracked separately as actions only purports to track RNG events
277
- // observed during a single `update` (not across updates).
278
- sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
330
+ /// TODO
331
+ durations: Durations = .{},
279
332
 
280
333
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
281
334
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -299,7 +352,7 @@ pub fn Chance(comptime Rational: type) type {
299
352
  damage_roll: u8 = 0,
300
353
  hit: bool = false,
301
354
  hit_probablity: uN = 0,
302
- binding: u8 = 0,
355
+ glitch: bool = false,
303
356
  } = .{},
304
357
 
305
358
  /// Possible error returned by operations tracking chance probability.
@@ -310,7 +363,7 @@ pub fn Chance(comptime Rational: type) type {
310
363
  if (!enabled) return;
311
364
 
312
365
  self.probability.reset();
313
- self.actions.reset();
366
+ self.actions = .{};
314
367
  self.pending = .{};
315
368
  }
316
369
 
@@ -319,20 +372,18 @@ pub fn Chance(comptime Rational: type) type {
319
372
 
320
373
  assert(!showdown);
321
374
 
375
+ if (kind == .glitch and !self.pending.glitch) return;
376
+
322
377
  var action = self.actions.get(player);
323
378
 
324
- // We need to handle binding specially because we still need to commit it when attacking
325
- // into a Pokémon that is immune
326
- if (kind != .miss and self.pending.binding != 0) {
327
- assert(action.duration == 0);
328
- action.duration = @intCast(self.pending.binding);
329
- assert(action.durations.binding == 0);
330
- action.durations.binding = 1;
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;
331
383
  }
332
- if (kind == .binding) return;
333
384
 
334
385
  // Always commit the hit result if we make it here (commit won't be called at all if the
335
- // target is immune/behind a sub/etc)
386
+ // target is immune*/behind a sub/etc)
336
387
  if (self.pending.hit_probablity != 0) {
337
388
  try self.probability.update(self.pending.hit_probablity, 256);
338
389
  action.hit = if (self.pending.hit) .true else .false;
@@ -340,57 +391,59 @@ pub fn Chance(comptime Rational: type) type {
340
391
 
341
392
  // If the move actually lands we can commit any past critical hit / damage rolls. Note
342
393
  // we can't rely on the presence or absence of a damage roll to determine whether a
343
- // "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
344
395
  // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
345
- // passed to the critical hit helper)
346
- 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
+ }
347
404
 
348
405
  if (self.pending.crit_probablity != 0) {
349
406
  try self.probability.update(self.pending.crit_probablity, 256);
350
407
  action.critical_hit = if (self.pending.crit) .true else .false;
351
- }
408
+ } else assert(!self.pending.glitch);
352
409
 
353
- if (self.pending.damage_roll > 0) {
410
+ if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
354
411
  try self.probability.update(1, 39);
355
412
  action.damage = self.pending.damage_roll;
356
413
  }
357
414
  }
358
415
 
359
- pub fn clearPending(self: *Self) void {
416
+ pub fn glitch(self: *Self) void {
360
417
  if (!enabled) return;
361
418
 
362
- self.pending = .{};
419
+ assert(!showdown);
420
+ self.pending.glitch = true;
363
421
  }
364
422
 
365
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
423
+ pub fn clearPending(self: *Self) void {
366
424
  if (!enabled) return;
367
425
 
368
- var action = self.actions.get(player);
369
- if (haze) |status| {
370
- if (status) action.durations.sleep = 0;
371
- action.durations.confusion = 0;
372
- action.durations.disable = 0;
373
- } else {
374
- action.durations.attacking = 0;
375
- action.durations.binding = 0;
376
- }
426
+ assert(!showdown);
427
+ self.pending = .{};
377
428
  }
378
429
 
379
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
430
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
380
431
  if (!enabled) return;
381
432
 
382
- assert(in >= 1 and in <= 6);
383
- assert(out >= 1 and out <= 6);
433
+ assert(slot >= 1 and slot <= 6);
384
434
 
385
- var action = self.actions.get(player);
435
+ var d = self.durations.get(player);
386
436
 
387
- const slp = action.durations.sleep;
388
- action.durations = .{};
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);
389
440
 
390
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
391
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
441
+ d.confusion = 0;
442
+ d.disable = 0;
443
+ d.attacking = 0;
444
+ d.binding = 0;
392
445
 
393
- self.actions.get(player.foe()).durations.binding = 0;
446
+ self.durations.get(player.foe()).binding = 0;
394
447
  }
395
448
 
396
449
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -489,108 +542,172 @@ pub fn Chance(comptime Rational: type) type {
489
542
  self.actions.get(player).multi_hit = n;
490
543
  }
491
544
 
492
- pub fn duration(
545
+ pub fn duration(self: *Self, player: Player, turns: u4) void {
546
+ if (!enabled) return;
547
+
548
+ self.actions.get(player).duration = if (options.key) 2 else turns;
549
+ }
550
+
551
+ pub fn observe(
493
552
  self: *Self,
494
- comptime field: Duration.Field,
553
+ comptime field: Action.Field,
495
554
  player: Player,
496
- target: Player,
497
- turns: u4,
555
+ obs: Optional(Confusion),
498
556
  ) void {
499
557
  if (!enabled) return;
500
558
 
501
- if (!showdown and field == .binding) {
502
- self.pending.binding = if (options.key) 1 else turns;
503
- } else {
504
- var durations = &self.actions.get(target).durations;
505
- assert(@field(durations, @tagName(field)) == 0 or
506
- (field == .confusion and player == target));
507
- @field(durations, @tagName(field)) = 1;
508
- self.actions.get(player).duration = if (options.key) 1 else turns;
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,
509
594
  }
510
595
  }
511
596
 
512
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
597
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
513
598
  if (!enabled) return;
514
599
 
515
- var durations = &self.actions.get(player).durations;
516
- const n = durations.sleep;
517
- if (turns == 0) {
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) {
518
610
  assert(n >= 1 and n <= 7);
519
611
  if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
520
- durations.sleep = 0;
612
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
521
613
  } else {
614
+ assert(obs == .continuing);
522
615
  assert(n >= 1 and n < 7);
523
616
  try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
524
- durations.sleep += 1;
617
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
525
618
  }
526
619
  }
527
620
 
528
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
621
+ pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
529
622
  if (!enabled) return;
530
623
 
531
- var durations = &self.actions.get(player).durations;
532
- const n = durations.confusion;
533
- if (turns == 0) {
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) {
534
632
  assert(n >= 2 and n <= 5);
535
633
  if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
536
- durations.confusion = 0;
634
+ d.confusion = 0;
537
635
  } else {
636
+ assert(obs == .continuing);
538
637
  assert(n >= 1 and n < 5);
539
638
  if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
540
- durations.confusion += 1;
639
+ d.confusion += 1;
541
640
  }
542
641
  }
543
642
 
544
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
643
+ pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
545
644
  if (!enabled) return;
546
645
 
547
- var durations = &self.actions.get(player).durations;
548
- const n = durations.disable;
549
- if (turns == 0) {
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) {
550
654
  assert(n >= 1 and n <= 8);
551
655
  if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
552
- durations.disable = 0;
656
+ d.disable = 0;
553
657
  } else {
658
+ assert(obs == .continuing);
554
659
  assert(n >= 1 and n < 8);
555
660
  try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
556
- durations.disable += 1;
661
+ d.disable += 1;
557
662
  }
558
663
  }
559
664
 
560
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
665
+ pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
561
666
  if (!enabled) return;
562
667
 
563
- var durations = &self.actions.get(player).durations;
564
- const n = durations.attacking;
565
- if (turns == 0) {
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
566
676
  assert(n >= 2 and n <= 3);
567
677
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
568
- durations.attacking = 0;
678
+ d.attacking = 0;
569
679
  } else {
680
+ assert(obs == .continuing);
570
681
  assert(n >= 1 and n < 3);
571
682
  if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
572
- durations.attacking += 1;
683
+ d.attacking += 1;
573
684
  }
574
685
  }
575
686
 
576
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
687
+ pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
577
688
  if (!enabled) return;
578
689
 
579
- var durations = &self.actions.get(player).durations;
580
- const n = durations.binding;
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;
581
696
 
582
697
  assert(n > 0);
583
698
  const p: u4 = if (n < 3) 3 else 1;
584
699
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
585
700
 
586
- if (turns == 0) {
701
+ if (obs == .ended) {
702
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
587
703
  assert(n >= 1 and n <= 4);
588
704
  if (n != 4) try self.probability.update(p, q);
589
- durations.binding = 0;
705
+ d.binding = 0;
590
706
  } else {
707
+ assert(obs == .continuing);
591
708
  assert(n >= 1 and n < 4);
592
709
  try self.probability.update(q - p, q);
593
- durations.binding += 1;
710
+ d.binding += 1;
594
711
  }
595
712
  }
596
713
 
@@ -607,6 +724,15 @@ pub fn Chance(comptime Rational: type) type {
607
724
  try self.probability.update(1, Move.METRONOME.len);
608
725
  self.actions.get(player).metronome = move;
609
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
+ }
610
736
  };
611
737
  }
612
738
 
@@ -707,16 +833,16 @@ test "Chance.damage" {
707
833
 
708
834
  try chance.damage(.P1, 217);
709
835
  if (showdown) {
710
- try expectValue(217, chance.actions.p1.damage);
836
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
711
837
  try expectProbability(&chance.probability, 1, 39);
712
838
  } else {
713
839
  try expectProbability(&chance.probability, 1, 1);
714
- try expectValue(0, chance.actions.p1.damage);
840
+ try expectValue(@as(u8, 0), chance.actions.p1.damage);
715
841
 
716
842
  try chance.commit(.P1, .hit);
717
843
 
718
844
  try expectProbability(&chance.probability, 1, 39);
719
- try expectValue(217, chance.actions.p1.damage);
845
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
720
846
  }
721
847
  }
722
848
 
@@ -768,13 +894,13 @@ test "Chance.moveSlot" {
768
894
 
769
895
  try chance.moveSlot(.P2, 2, &ms, 2);
770
896
  try expectProbability(&chance.probability, 1, 2);
771
- try expectValue(2, chance.actions.p2.move_slot);
897
+ try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
772
898
 
773
899
  chance.reset();
774
900
 
775
901
  try chance.moveSlot(.P1, 1, &ms, 0);
776
902
  try expectProbability(&chance.probability, 1, 3);
777
- try expectValue(1, chance.actions.p1.move_slot);
903
+ try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
778
904
  }
779
905
 
780
906
  test "Chance.multiHit" {
@@ -782,51 +908,43 @@ test "Chance.multiHit" {
782
908
 
783
909
  try chance.multiHit(.P1, 3);
784
910
  try expectProbability(&chance.probability, 3, 8);
785
- try expectValue(3, chance.actions.p1.multi_hit);
911
+ try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
786
912
 
787
913
  chance.reset();
788
914
 
789
915
  try chance.multiHit(.P2, 5);
790
916
  try expectProbability(&chance.probability, 1, 8);
791
- try expectValue(5, chance.actions.p2.multi_hit);
917
+ try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
792
918
  }
793
919
 
794
920
  test "Chance.duration" {
795
921
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
796
922
 
797
- chance.duration(.sleep, .P1, .P2, 2);
798
- try expectValue(2, chance.actions.p1.duration);
799
- try expectValue(1, chance.actions.p2.durations.sleep);
923
+ chance.duration(.P1, 2);
924
+ try expectValue(@as(u4, 2), chance.actions.p1.duration);
800
925
 
801
926
  chance.reset();
802
927
 
803
- chance.duration(.binding, .P2, .P2, 4);
804
- if (!showdown) {
805
- try expectValue(0, chance.actions.p2.duration);
806
- try expectValue(0, chance.actions.p2.durations.binding);
807
-
808
- try chance.commit(.P2, .hit);
809
- }
810
- try expectValue(4, chance.actions.p2.duration);
811
- try expectValue(1, chance.actions.p2.durations.binding);
928
+ chance.duration(.P2, 4);
929
+ try expectValue(@as(u4, 4), chance.actions.p2.duration);
812
930
  }
813
931
 
814
932
  test "Chance.sleep" {
815
933
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
816
934
 
817
935
  for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
818
- chance.actions.p1.durations.sleep = @intCast(i);
819
- try chance.sleep(.P1, 0);
936
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
937
+ try chance.sleep(.P1, .ended);
820
938
  try expectProbability(&chance.probability, 1, d);
821
- try expectValue(0, chance.actions.p1.durations.sleep);
939
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
822
940
 
823
941
  chance.reset();
824
942
 
825
943
  if (i < 7) {
826
- chance.actions.p1.durations.sleep = @intCast(i);
827
- try chance.sleep(.P1, 1);
944
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
945
+ try chance.sleep(.P1, .continuing);
828
946
  try expectProbability(&chance.probability, d - 1, d);
829
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
947
+ try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
830
948
 
831
949
  chance.reset();
832
950
  }
@@ -838,19 +956,19 @@ test "Chance.confusion" {
838
956
 
839
957
  for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
840
958
  if (i > 1) {
841
- chance.actions.p2.durations.confusion = @intCast(i);
842
- try chance.confusion(.P2, 0);
959
+ chance.durations.p2.confusion = @intCast(i);
960
+ try chance.confusion(.P2, .ended);
843
961
  try expectProbability(&chance.probability, 1, d);
844
- try expectValue(0, chance.actions.p2.durations.confusion);
962
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
845
963
 
846
964
  chance.reset();
847
965
  }
848
966
 
849
967
  if (i < 5) {
850
- chance.actions.p2.durations.confusion = @intCast(i);
851
- try chance.confusion(.P2, 1);
968
+ chance.durations.p2.confusion = @intCast(i);
969
+ try chance.confusion(.P2, .continuing);
852
970
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
853
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
971
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
854
972
 
855
973
  chance.reset();
856
974
  }
@@ -861,18 +979,18 @@ test "Chance.disable" {
861
979
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
862
980
 
863
981
  for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
864
- chance.actions.p1.durations.disable = @intCast(i);
865
- try chance.disable(.P1, 0);
982
+ chance.durations.p1.disable = @intCast(i);
983
+ try chance.disable(.P1, .ended);
866
984
  try expectProbability(&chance.probability, 1, d);
867
- try expectValue(0, chance.actions.p1.durations.disable);
985
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
868
986
 
869
987
  chance.reset();
870
988
 
871
989
  if (i < 8) {
872
- chance.actions.p1.durations.disable = @intCast(i);
873
- try chance.disable(.P1, 1);
990
+ chance.durations.p1.disable = @intCast(i);
991
+ try chance.disable(.P1, .continuing);
874
992
  try expectProbability(&chance.probability, d - 1, d);
875
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
993
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
876
994
 
877
995
  chance.reset();
878
996
  }
@@ -884,19 +1002,19 @@ test "Chance.attacking" {
884
1002
 
885
1003
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
886
1004
  if (i > 1) {
887
- chance.actions.p2.durations.attacking = @intCast(i);
888
- try chance.attacking(.P2, 0);
1005
+ chance.durations.p2.attacking = @intCast(i);
1006
+ try chance.attacking(.P2, .ended);
889
1007
  try expectProbability(&chance.probability, 1, d);
890
- try expectValue(0, chance.actions.p2.durations.attacking);
1008
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
891
1009
 
892
1010
  chance.reset();
893
1011
  }
894
1012
 
895
1013
  if (i < 3) {
896
- chance.actions.p2.durations.attacking = @intCast(i);
897
- try chance.attacking(.P2, 1);
1014
+ chance.durations.p2.attacking = @intCast(i);
1015
+ try chance.attacking(.P2, .continuing);
898
1016
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
899
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
1017
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
900
1018
 
901
1019
  chance.reset();
902
1020
  }
@@ -910,18 +1028,18 @@ test "Chance.binding" {
910
1028
  const qs = [_]u8{ 8, 5, 2, 1 };
911
1029
 
912
1030
  for (ps, qs, 1..5) |p, q, i| {
913
- chance.actions.p1.durations.binding = @intCast(i);
914
- try chance.binding(.P1, 0);
1031
+ chance.durations.p1.binding = @intCast(i);
1032
+ try chance.binding(.P1, .ended);
915
1033
  try expectProbability(&chance.probability, p, q);
916
- try expectValue(0, chance.actions.p1.durations.binding);
1034
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
917
1035
 
918
1036
  chance.reset();
919
1037
 
920
1038
  if (i < 4) {
921
- chance.actions.p1.durations.binding = @intCast(i);
922
- try chance.binding(.P1, 1);
1039
+ chance.durations.p1.binding = @intCast(i);
1040
+ try chance.binding(.P1, .continuing);
923
1041
  try expectProbability(&chance.probability, q - p, q);
924
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
1042
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
925
1043
 
926
1044
  chance.reset();
927
1045
  }
@@ -933,7 +1051,7 @@ test "Chance.psywave" {
933
1051
 
934
1052
  try chance.psywave(.P2, 100, 150);
935
1053
  try expectProbability(&chance.probability, 1, 150);
936
- try expectValue(101, chance.actions.p2.psywave);
1054
+ try expectValue(@as(u8, 101), chance.actions.p2.psywave);
937
1055
  }
938
1056
 
939
1057
  test "Chance.metronome" {
@@ -971,16 +1089,16 @@ const Null = struct {
971
1089
  _ = .{ self, player, kind };
972
1090
  }
973
1091
 
974
- pub fn clearPending(self: Null) void {
1092
+ pub fn glitch(self: Null) void {
975
1093
  _ = .{self};
976
1094
  }
977
1095
 
978
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
979
- _ = .{ self, player, haze };
1096
+ pub fn clearPending(self: Null) void {
1097
+ _ = .{self};
980
1098
  }
981
1099
 
982
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
983
- _ = .{ self, player, in, out };
1100
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1101
+ _ = .{ self, player, slot };
984
1102
  }
985
1103
 
986
1104
  pub fn speedTie(self: Null, p1: bool) Error!void {
@@ -1019,34 +1137,37 @@ const Null = struct {
1019
1137
  _ = .{ self, player, n };
1020
1138
  }
1021
1139
 
1022
- pub fn duration(
1140
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1141
+ _ = .{ self, player, turns };
1142
+ }
1143
+
1144
+ pub fn observe(
1023
1145
  self: Null,
1024
- comptime field: Duration.Field,
1146
+ comptime field: Action.Field,
1025
1147
  player: Player,
1026
- target: Player,
1027
- turns: u4,
1148
+ obs: Optional(Confusion),
1028
1149
  ) void {
1029
- _ = .{ self, field, player, target, turns };
1150
+ _ = .{ self, field, player, obs };
1030
1151
  }
1031
1152
 
1032
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1033
- _ = .{ self, player, turns };
1153
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1154
+ _ = .{ self, player, obs };
1034
1155
  }
1035
1156
 
1036
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1037
- _ = .{ self, player, turns };
1157
+ pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
1158
+ _ = .{ self, player, obs };
1038
1159
  }
1039
1160
 
1040
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1041
- _ = .{ self, player, turns };
1161
+ pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1162
+ _ = .{ self, player, obs };
1042
1163
  }
1043
1164
 
1044
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1045
- _ = .{ self, player, turns };
1165
+ pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1166
+ _ = .{ self, player, obs };
1046
1167
  }
1047
1168
 
1048
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1049
- _ = .{ self, player, turns };
1169
+ pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1170
+ _ = .{ self, player, obs };
1050
1171
  }
1051
1172
 
1052
1173
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -1056,4 +1177,9 @@ const Null = struct {
1056
1177
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
1057
1178
  _ = .{ self, player, move };
1058
1179
  }
1180
+
1181
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1182
+ _ = .{ self, player, n };
1183
+ return true;
1184
+ }
1059
1185
  };