@pkmn/engine 0.1.0-dev.5570b5c5 → 0.1.0-dev.59567a0b

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.
@@ -11,6 +11,7 @@ const options = @import("../common/options.zig");
11
11
  const rational = @import("../common/rational.zig");
12
12
  const util = @import("../common/util.zig");
13
13
 
14
+ const Array = @import("../common/array.zig").Array;
14
15
  const Player = @import("../common/data.zig").Player;
15
16
  const Optional = @import("../common/optional.zig").Optional;
16
17
 
@@ -19,9 +20,16 @@ const data = @import("data.zig");
19
20
  const enabled = options.chance;
20
21
  const showdown = options.showdown;
21
22
 
23
+ const PointerType = util.PointerType;
24
+ const isPointerTo = util.isPointerTo;
25
+
22
26
  const Move = data.Move;
23
27
  const MoveSlot = data.MoveSlot;
24
28
 
29
+ const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
30
+ const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
31
+ const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
32
+
25
33
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
26
34
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
27
35
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -37,17 +45,11 @@ pub const Actions = extern struct {
37
45
  }
38
46
 
39
47
  /// 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);
48
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
49
+ assert(isPointerTo(self, Actions));
42
50
  return if (player == .P1) &self.p1 else &self.p2;
43
51
  }
44
52
 
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
53
  /// Returns true if `a` is equal to `b`.
52
54
  pub fn eql(a: Actions, b: Actions) bool {
53
55
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
@@ -57,20 +59,15 @@ pub const Actions = extern struct {
57
59
  /// same shape if they have the same fields set (though those fields need not necessarily be
58
60
  /// set to the same value).
59
61
  pub fn matches(a: Actions, b: Actions) bool {
60
- inline for (@typeInfo(Actions).Struct.fields) |player| {
61
- inline for (@typeInfo(Action).Struct.fields) |field| {
62
+ inline for (@field(@typeInfo(Actions), @tagName(Struct)).fields) |player| {
63
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
62
64
  const a_val = @field(@field(a, player.name), field.name);
63
65
  const b_val = @field(@field(b, player.name), field.name);
64
66
 
65
67
  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))
68
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
72
69
  return false,
73
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
70
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
74
71
  else => unreachable,
75
72
  }
76
73
  }
@@ -92,14 +89,10 @@ pub const Actions = extern struct {
92
89
  }
93
90
  };
94
91
 
95
- // TODO: ziglang/zig#19730
96
92
  test Actions {
97
93
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
98
94
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
99
95
  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
96
 
104
97
  try expect(a.eql(a));
105
98
  try expect(!a.eql(b));
@@ -112,10 +105,22 @@ test Actions {
112
105
  try expect(b.matches(a));
113
106
  try expect(!a.matches(c));
114
107
  try expect(!c.matches(a));
115
- // try expect(d.matches(e));
116
- // try expect(!d.matches(f));
117
108
  }
118
109
 
110
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
111
+ pub const Observation = enum { started, continuing, ended };
112
+ /// An extension of Observation to account for "hidden" confusion.
113
+ pub const Confusion = enum {
114
+ started,
115
+ continuing,
116
+ ended,
117
+ /// An indicator that an existing confusion duration has been overwritten by a new confusion
118
+ /// duration due to a Thrashing move ending. This is not immediately observable to an opponent,
119
+ /// to compute probabilities from an opponent's point of view based solely on public information
120
+ /// extra work must be done to correct for the information leak
121
+ overwritten,
122
+ };
123
+
119
124
  /// Information about the RNG that was observed during a Generation I battle `update` for a
120
125
  /// single player.
121
126
  pub const Action = packed struct(u64) {
@@ -140,12 +145,25 @@ pub const Action = packed struct(u64) {
140
145
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
141
146
  duration: u4 = 0,
142
147
 
143
- /// Observed values of various durations. Does not influence future RNG calls. TODO
144
- durations: Duration = .{},
148
+ /// TODO
149
+ sleep: Optional(Observation) = .None,
150
+ /// TODO
151
+ confusion: Optional(Confusion) = .None,
152
+ /// TODO
153
+ disable: Optional(Observation) = .None,
154
+ /// TODO
155
+ attacking: Optional(Observation) = .None,
156
+ /// TODO
157
+ binding: Optional(Observation) = .None,
158
+
159
+ _: u1 = 0,
145
160
 
146
161
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
147
162
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
148
163
  move_slot: u4 = 0,
164
+
165
+ /// TODO
166
+ pp: u4 = 0,
149
167
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
150
168
  multi_hit: u4 = 0,
151
169
 
@@ -155,15 +173,8 @@ pub const Action = packed struct(u64) {
155
173
  /// If not None, the Move to return for Rolls.metronome.
156
174
  metronome: Move = .None,
157
175
 
158
- pub const DURATIONS: u64 = 0x000000FFFF000000;
159
-
160
176
  pub const Field = std.meta.FieldEnum(Action);
161
177
 
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
178
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
168
179
  _ = .{ f, o };
169
180
  try fmt(a, w, false);
@@ -172,22 +183,10 @@ pub const Action = packed struct(u64) {
172
183
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
173
184
  try writer.writeByte('(');
174
185
  var printed = false;
175
- inline for (@typeInfo(Action).Struct.fields) |field| {
186
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
176
187
  const val = @field(self, field.name);
177
188
  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) {
189
+ Enum => if (val != .None) {
191
190
  if (printed) try writer.writeAll(", ");
192
191
  if (shape) {
193
192
  try writer.print("{s}:?", .{field.name});
@@ -196,12 +195,19 @@ pub const Action = packed struct(u64) {
196
195
  if (val == .false) "!" else "",
197
196
  field.name,
198
197
  });
198
+ } else if (@TypeOf(val) == Optional(Observation) or
199
+ @TypeOf(val) == Optional(Confusion))
200
+ {
201
+ try writer.print("{s}{s}", .{
202
+ ([_][]const u8{ "+", "", "-", "#" })[@intFromEnum(val) - 1],
203
+ field.name,
204
+ });
199
205
  } else {
200
206
  try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
201
207
  }
202
208
  printed = true;
203
209
  },
204
- .Int => if (val != 0) {
210
+ Int => if (val != 0) {
205
211
  if (printed) try writer.writeAll(", ");
206
212
  if (shape) {
207
213
  try writer.print("{s}:?", .{field.name});
@@ -217,43 +223,82 @@ pub const Action = packed struct(u64) {
217
223
  }
218
224
  };
219
225
 
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
226
  /// TODO
228
- pub const Durations = extern struct {
229
- /// Information about the durations for Player 1.
227
+ pub const Durations = struct {
228
+ /// TODO
230
229
  p1: Duration = .{},
231
- /// Information about the durations for Player 2.
230
+ /// TODO
232
231
  p2: Duration = .{},
233
232
 
234
233
  comptime {
235
- assert(@sizeOf(Durations) == 4);
234
+ assert(@sizeOf(Durations) == 8);
235
+ }
236
+
237
+ /// TODO
238
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
239
+ assert(isPointerTo(self, Durations));
240
+ return if (player == .P1) &self.p1 else &self.p2;
241
+ }
242
+
243
+ pub fn format(
244
+ self: Durations,
245
+ comptime fmt: []const u8,
246
+ opts: std.fmt.FormatOptions,
247
+ writer: anytype,
248
+ ) !void {
249
+ _ = .{ fmt, opts };
250
+ try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
236
251
  }
237
252
  };
238
253
 
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.
254
+ const Sleeps = Array(6, u3);
255
+
256
+ /// TODO
257
+ pub const Duration = packed struct(u32) {
258
+ sleeps: Sleeps.T = 0,
245
259
  confusion: u3 = 0,
246
- /// The number of turns a Pokémon has been observed to be disabled.
247
260
  disable: u4 = 0,
248
- /// The number of turns a Pokémon has been observed to be attacking.
249
261
  attacking: u3 = 0,
250
- /// The number of turns a Pokémon has been observed to be binding their opponent.
251
262
  binding: u3 = 0,
252
263
 
253
- pub const Field = std.meta.FieldEnum(Duration);
264
+ _: u1 = 0,
265
+
266
+ pub fn format(
267
+ self: Duration,
268
+ comptime fmt: []const u8,
269
+ opts: std.fmt.FormatOptions,
270
+ writer: anytype,
271
+ ) !void {
272
+ _ = .{ fmt, opts };
273
+ try writer.writeByte('(');
274
+ var printed = false;
275
+ inline for (@field(@typeInfo(Duration), @tagName(Struct)).fields) |field| {
276
+ const val = @field(self, field.name);
277
+ switch (@typeInfo(@TypeOf(val))) {
278
+ Int => if (val != 0) {
279
+ if (printed) try writer.writeAll(", ");
280
+ if (comptime std.mem.eql(u8, field.name, "sleeps")) {
281
+ try writer.print("{s}:[", .{field.name});
282
+ for (0..6) |i| {
283
+ if (i != 0) try writer.writeByte(',');
284
+ try writer.print("{d}", .{Sleeps.get(val, i)});
285
+ }
286
+ try writer.writeAll("]");
287
+ } else {
288
+ try writer.print("{s}:{d}", .{ field.name, val });
289
+ }
290
+ printed = true;
291
+ },
292
+ else => unreachable,
293
+ }
294
+ }
295
+ try writer.writeByte(')');
296
+ }
254
297
  };
255
298
 
256
- pub const Commit = enum { hit, miss, binding };
299
+ pub const Commit = enum { hit, miss, binding, err, glitch };
300
+
301
+ const uN = if (options.miss) u8 else u9;
257
302
 
258
303
  /// Tracks chance actions and their associated probability during a Generation I battle update when
259
304
  /// `options.chance` is enabled.
@@ -266,14 +311,8 @@ pub fn Chance(comptime Rational: type) type {
266
311
  /// The actions taken by a hypothetical "chance player" that convey information about which
267
312
  /// RNG events were observed during a battle `update`.
268
313
  actions: Actions = .{},
269
-
270
- // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
271
- // other durations which are all tied to volatiles, sleep's counter persists through
272
- // switching and so we must store it here. The indices of these arrays correspond to the
273
- // `order` field of a Side. This information could be stored in actions but size is a
274
- // concern so this is tracked separately as actions only purports to track RNG events
275
- // observed during a single `update` (not across updates).
276
- sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
314
+ /// TODO
315
+ durations: Durations = .{},
277
316
 
278
317
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
279
318
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -291,16 +330,13 @@ pub fn Chance(comptime Rational: type) type {
291
330
  //
292
331
  // Finally, because each player's actions are processed sequentially we only need a single
293
332
  // shared structure to track this information (though it needs to be cleared appropriately)
294
- pending: if (showdown) struct {
295
- hit: bool = false,
296
- hit_probablity: u8 = 0,
297
- } else struct {
333
+ pending: if (showdown) struct {} else struct {
298
334
  crit: bool = false,
299
335
  crit_probablity: u8 = 0,
300
336
  damage_roll: u8 = 0,
301
337
  hit: bool = false,
302
- hit_probablity: u8 = 0,
303
- binding: u8 = 0,
338
+ hit_probablity: uN = 0,
339
+ glitch: bool = false,
304
340
  } = .{},
305
341
 
306
342
  /// Possible error returned by operations tracking chance probability.
@@ -308,86 +344,90 @@ pub fn Chance(comptime Rational: type) type {
308
344
 
309
345
  /// Convenience helper to clear fields which typically should be cleared between updates.
310
346
  pub fn reset(self: *Self) void {
347
+ if (!enabled) return;
348
+
311
349
  self.probability.reset();
312
- self.actions.reset();
350
+ self.actions = .{};
313
351
  self.pending = .{};
314
352
  }
315
353
 
316
354
  pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
355
+ if (!enabled) return;
356
+
357
+ assert(!showdown);
358
+
359
+ if (kind == .glitch and !self.pending.glitch) return;
360
+
317
361
  var action = self.actions.get(player);
318
362
 
319
- // We need to handle binding specially because we still need to commit it when attacking
320
- // into a Pokémon that is immune
321
- if (!showdown) {
322
- if (kind != .miss and self.pending.binding != 0) {
323
- assert(action.duration == 0);
324
- action.duration = @intCast(self.pending.binding);
325
- assert(action.durations.binding == 0);
326
- action.durations.binding = 1;
327
- }
328
- if (kind == .binding) return;
329
- } else assert(kind != .binding);
363
+ if (kind == .err and self.pending.crit_probablity != 0) {
364
+ try self.probability.update(self.pending.crit_probablity, 256);
365
+ action.critical_hit = if (self.pending.crit) .true else .false;
366
+ return;
367
+ }
330
368
 
331
369
  // Always commit the hit result if we make it here (commit won't be called at all if the
332
- // target is immune/behind a sub/etc)
370
+ // target is immune*/behind a sub/etc)
333
371
  if (self.pending.hit_probablity != 0) {
334
372
  try self.probability.update(self.pending.hit_probablity, 256);
335
373
  action.hit = if (self.pending.hit) .true else .false;
336
374
  }
337
375
 
338
- if (showdown) return;
376
+ // If the move actually lands we can commit any past critical hit / damage rolls. Note
377
+ // we can't rely on the presence or absence of a damage roll to determine whether a
378
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
379
+ // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
380
+ // passed to the critical hit helper).
381
+ //
382
+ // There is a special edge case with the division-by-zero freeze where we need to commit
383
+ // the status of critical hit roll the occured before the move missed to avoid missing
384
+ // out on the possibility that the alternative branch encountered an error
385
+ if (kind != .hit and !self.pending.glitch) {
386
+ return assert(!self.pending.crit or self.pending.crit_probablity > 0);
387
+ }
339
388
 
340
- // If the move actually lands we can commit any past critical hit / damage rolls. We
341
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
342
- // to a damage roll is actually a no-op
343
- if (kind == .hit and self.pending.damage_roll > 0) {
344
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
345
-
346
- if (self.pending.crit_probablity != 0) {
347
- try self.probability.update(self.pending.crit_probablity, 256);
348
- action.critical_hit = if (self.pending.crit) .true else .false;
349
- }
389
+ if (self.pending.crit_probablity != 0) {
390
+ try self.probability.update(self.pending.crit_probablity, 256);
391
+ action.critical_hit = if (self.pending.crit) .true else .false;
392
+ } else assert(!self.pending.glitch);
350
393
 
394
+ if (self.pending.damage_roll > 0) {
351
395
  try self.probability.update(1, 39);
352
396
  action.damage = self.pending.damage_roll;
353
397
  }
354
398
  }
355
399
 
356
- pub fn clearPending(self: *Self) void {
400
+ pub fn glitch(self: *Self) void {
357
401
  if (!enabled) return;
358
402
 
359
- self.pending = .{};
403
+ assert(!showdown);
404
+ self.pending.glitch = true;
360
405
  }
361
406
 
362
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
407
+ pub fn clearPending(self: *Self) void {
363
408
  if (!enabled) return;
364
409
 
365
- var action = self.actions.get(player);
366
- if (haze) |status| {
367
- if (status) action.durations.sleep = 0;
368
- action.durations.confusion = 0;
369
- action.durations.disable = 0;
370
- } else {
371
- action.durations.attacking = 0;
372
- action.durations.binding = 0;
373
- }
410
+ assert(!showdown);
411
+ self.pending = .{};
374
412
  }
375
413
 
376
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
414
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
377
415
  if (!enabled) return;
378
416
 
379
- assert(in >= 1 and in <= 6);
380
- assert(out >= 1 and out <= 6);
417
+ assert(slot >= 1 and slot <= 6);
381
418
 
382
- var action = self.actions.get(player);
419
+ var d = self.durations.get(player);
383
420
 
384
- const slp = action.durations.sleep;
385
- action.durations = .{};
421
+ const out = Sleeps.get(d.sleeps, 0);
422
+ d.sleeps = Sleeps.set(d.sleeps, 0, Sleeps.get(d.sleeps, slot - 1));
423
+ d.sleeps = Sleeps.set(d.sleeps, slot - 1, out);
386
424
 
387
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
388
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
425
+ d.confusion = 0;
426
+ d.disable = 0;
427
+ d.attacking = 0;
428
+ d.binding = 0;
389
429
 
390
- self.actions.get(player.foe()).durations.binding = 0;
430
+ self.durations.get(player.foe()).binding = 0;
391
431
  }
392
432
 
393
433
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -398,12 +438,17 @@ pub fn Chance(comptime Rational: type) type {
398
438
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
399
439
  }
400
440
 
401
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
441
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
402
442
  if (!enabled) return;
403
443
 
404
444
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
405
- self.pending.hit = ok;
406
- self.pending.hit_probablity = p;
445
+ if (showdown) {
446
+ try self.probability.update(p, 256);
447
+ self.actions.get(player).hit = if (ok) .true else .false;
448
+ } else {
449
+ self.pending.hit = ok;
450
+ self.pending.hit_probablity = p;
451
+ }
407
452
  }
408
453
 
409
454
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -481,108 +526,170 @@ pub fn Chance(comptime Rational: type) type {
481
526
  self.actions.get(player).multi_hit = n;
482
527
  }
483
528
 
484
- pub fn duration(
529
+ pub fn duration(self: *Self, player: Player, turns: u4) void {
530
+ if (!enabled) return;
531
+
532
+ self.actions.get(player).duration = if (options.key) 2 else turns;
533
+ }
534
+
535
+ pub fn observe(
485
536
  self: *Self,
486
- comptime field: Duration.Field,
537
+ comptime field: Action.Field,
487
538
  player: Player,
488
- target: Player,
489
- turns: u4,
539
+ obs: Optional(Confusion),
490
540
  ) void {
491
541
  if (!enabled) return;
492
542
 
493
- if (!showdown and field == .binding) {
494
- self.pending.binding = if (options.key) 1 else turns;
495
- } else {
496
- var durations = &self.actions.get(target).durations;
497
- assert(@field(durations, @tagName(field)) == 0 or
498
- (field == .confusion and player == target));
499
- @field(durations, @tagName(field)) = 1;
500
- self.actions.get(player).duration = if (options.key) 1 else turns;
543
+ var a = self.actions.get(player);
544
+ const val = @field(a, @tagName(field));
545
+ switch (obs) {
546
+ .overwritten => {
547
+ assert(field == .confusion);
548
+ assert(val == .started or val == .continuing);
549
+ assert(a.duration > 0);
550
+ a.confusion = obs;
551
+ self.durations.get(player).confusion = 1;
552
+ },
553
+ .None => {
554
+ @field(a, @tagName(field)) = .None;
555
+ if (field == .sleep) {
556
+ var d = self.durations.get(player);
557
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
558
+ } else {
559
+ @field(self.durations.get(player), @tagName(field)) = 0;
560
+ }
561
+ },
562
+ .started => {
563
+ assert(val == .None or val == .ended);
564
+ assert(switch (field) {
565
+ .confusion => a.duration > 0 or self.actions.get(player.foe()).duration > 0,
566
+ .sleep, .disable => self.actions.get(player.foe()).duration > 0,
567
+ else => a.duration > 0,
568
+ });
569
+ @field(a, @tagName(field)) = .started;
570
+ if (field == .sleep) {
571
+ var d = self.durations.get(player);
572
+ d.sleeps = Sleeps.set(d.sleeps, 0, 1);
573
+ } else {
574
+ @field(self.durations.get(player), @tagName(field)) = 1;
575
+ }
576
+ },
577
+ else => unreachable,
501
578
  }
502
579
  }
503
580
 
504
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
581
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
505
582
  if (!enabled) return;
506
583
 
507
- var durations = &self.actions.get(player).durations;
508
- const n = durations.sleep;
509
- if (turns == 0) {
584
+ var a = self.actions.get(player);
585
+ assert(a.sleep == .None or a.sleep == .started);
586
+ a.sleep = obs;
587
+
588
+ var d = self.durations.get(player);
589
+ const n = Sleeps.get(d.sleeps, 0);
590
+
591
+ if (obs == .None) {
592
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
593
+ } else if (obs == .ended) {
510
594
  assert(n >= 1 and n <= 7);
511
595
  if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
512
- durations.sleep = 0;
596
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
513
597
  } else {
598
+ assert(obs == .continuing);
514
599
  assert(n >= 1 and n < 7);
515
600
  try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
516
- durations.sleep += 1;
601
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
517
602
  }
518
603
  }
519
604
 
520
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
605
+ pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
521
606
  if (!enabled) return;
522
607
 
523
- var durations = &self.actions.get(player).durations;
524
- const n = durations.confusion;
525
- if (turns == 0) {
608
+ var a = self.actions.get(player);
609
+ assert(a.confusion == .None or a.confusion == .started);
610
+ a.confusion = obs;
611
+
612
+ var d = self.durations.get(player);
613
+ const n = d.confusion;
614
+
615
+ if (obs == .ended) {
526
616
  assert(n >= 2 and n <= 5);
527
617
  if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
528
- durations.confusion = 0;
618
+ d.confusion = 0;
529
619
  } else {
620
+ assert(obs == .continuing);
530
621
  assert(n >= 1 and n < 5);
531
622
  if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
532
- durations.confusion += 1;
623
+ d.confusion += 1;
533
624
  }
534
625
  }
535
626
 
536
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
627
+ pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
537
628
  if (!enabled) return;
538
629
 
539
- var durations = &self.actions.get(player).durations;
540
- const n = durations.disable;
541
- if (turns == 0) {
630
+ var a = self.actions.get(player);
631
+ assert(a.disable == .None or a.disable == .started);
632
+ a.disable = obs;
633
+
634
+ var d = self.durations.get(player);
635
+ const n = d.disable;
636
+
637
+ if (obs == .ended) {
542
638
  assert(n >= 1 and n <= 8);
543
639
  if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
544
- durations.disable = 0;
640
+ d.disable = 0;
545
641
  } else {
642
+ assert(obs == .continuing);
546
643
  assert(n >= 1 and n < 8);
547
644
  try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
548
- durations.disable += 1;
645
+ d.disable += 1;
549
646
  }
550
647
  }
551
648
 
552
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
649
+ pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
553
650
  if (!enabled) return;
554
651
 
555
- var durations = &self.actions.get(player).durations;
556
- const n = durations.attacking;
557
- if (turns == 0) {
652
+ var a = self.actions.get(player);
653
+ assert(a.attacking == .None or a.attacking == .started);
654
+ a.attacking = obs;
655
+
656
+ var d = self.durations.get(player);
657
+ const n = d.attacking;
658
+ if (obs == .ended) {
558
659
  assert(n >= 2 and n <= 3);
559
660
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
560
- durations.attacking = 0;
661
+ d.attacking = 0;
561
662
  } else {
663
+ assert(obs == .continuing);
562
664
  assert(n >= 1 and n < 3);
563
665
  if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
564
- durations.attacking += 1;
666
+ d.attacking += 1;
565
667
  }
566
668
  }
567
669
 
568
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
670
+ pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
569
671
  if (!enabled) return;
570
672
 
571
- var durations = &self.actions.get(player).durations;
572
- const n = durations.binding;
673
+ var a = self.actions.get(player);
674
+ assert(a.binding == .None or a.binding == .started);
675
+ a.binding = obs;
676
+
677
+ var d = self.durations.get(player);
678
+ const n = d.binding;
573
679
 
574
680
  assert(n > 0);
575
681
  const p: u4 = if (n < 3) 3 else 1;
576
682
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
577
683
 
578
- if (turns == 0) {
684
+ if (obs == .ended) {
579
685
  assert(n >= 1 and n <= 4);
580
686
  if (n != 4) try self.probability.update(p, q);
581
- durations.binding = 0;
687
+ d.binding = 0;
582
688
  } else {
689
+ assert(obs == .continuing);
583
690
  assert(n >= 1 and n < 4);
584
691
  try self.probability.update(q - p, q);
585
- durations.binding += 1;
692
+ d.binding += 1;
586
693
  }
587
694
  }
588
695
 
@@ -599,6 +706,15 @@ pub fn Chance(comptime Rational: type) type {
599
706
  try self.probability.update(1, Move.METRONOME.len);
600
707
  self.actions.get(player).metronome = move;
601
708
  }
709
+
710
+ pub fn pp(self: *Self, player: Player, n: u4) bool {
711
+ if (!enabled) return true;
712
+
713
+ var a = self.actions.get(player);
714
+ if (n > 1 and a.pp == n) return false;
715
+ if (a.metronome != .None) a.pp += n;
716
+ return true;
717
+ }
602
718
  };
603
719
  }
604
720
 
@@ -621,19 +737,25 @@ test "Chance.speedTie" {
621
737
  test "Chance.hit" {
622
738
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
623
739
 
624
- chance.hit(true, 229);
625
- try expectProbability(&chance.probability, 1, 1);
626
- try expectValue(Optional(bool).None, chance.actions.p1.hit);
627
- try chance.commit(.P1, .hit);
740
+ try chance.hit(.P1, true, 229);
741
+ if (!showdown) {
742
+ try expectProbability(&chance.probability, 1, 1);
743
+ try expectValue(Optional(bool).None, chance.actions.p1.hit);
744
+
745
+ try chance.commit(.P1, .hit);
746
+ }
628
747
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
629
748
  try expectProbability(&chance.probability, 229, 256);
630
749
 
631
750
  chance.reset();
632
751
 
633
- chance.hit(false, 255);
634
- try expectProbability(&chance.probability, 1, 1);
635
- try expectValue(Optional(bool).None, chance.actions.p2.hit);
636
- try chance.commit(.P2, .miss);
752
+ try chance.hit(.P2, false, 255);
753
+ if (!showdown) {
754
+ try expectProbability(&chance.probability, 1, 1);
755
+ try expectValue(Optional(bool).None, chance.actions.p2.hit);
756
+
757
+ try chance.commit(.P2, .miss);
758
+ }
637
759
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
638
760
  try expectProbability(&chance.probability, 1, 256);
639
761
  }
@@ -649,26 +771,18 @@ test "Chance.criticalHit" {
649
771
  try expectProbability(&chance.probability, 1, 1);
650
772
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
651
773
 
652
- try chance.commit(.P1, .hit);
653
- try expectProbability(&chance.probability, 1, 1);
654
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
655
-
656
- chance.hit(true, 128);
774
+ try chance.hit(.P1, true, 128);
657
775
 
658
- try chance.commit(.P1, .hit);
659
- try expectProbability(&chance.probability, 1, 2);
776
+ try expectProbability(&chance.probability, 1, 1);
660
777
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
661
778
 
662
- chance.probability.reset();
663
779
  try chance.damage(.P1, 217);
664
780
 
665
- try chance.commit(.P1, .miss);
666
- try expectProbability(&chance.probability, 1, 2);
781
+ try expectProbability(&chance.probability, 1, 1);
667
782
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
668
783
 
669
- chance.probability.reset();
670
-
671
784
  try chance.commit(.P1, .hit);
785
+
672
786
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
673
787
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
674
788
  }
@@ -683,20 +797,14 @@ test "Chance.criticalHit" {
683
797
  try expectProbability(&chance.probability, 1, 1);
684
798
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
685
799
 
686
- try chance.commit(.P1, .hit);
687
- try expectProbability(&chance.probability, 1, 1);
688
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
689
-
690
- chance.hit(true, 128);
800
+ try chance.hit(.P1, true, 128);
691
801
 
692
- try chance.commit(.P1, .hit);
693
- try expectProbability(&chance.probability, 1, 2);
802
+ try expectProbability(&chance.probability, 1, 1);
694
803
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
695
804
 
696
- chance.probability.reset();
697
805
  try chance.damage(.P1, 217);
698
-
699
806
  try chance.commit(.P1, .hit);
807
+
700
808
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
701
809
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
702
810
  }
@@ -707,19 +815,16 @@ test "Chance.damage" {
707
815
 
708
816
  try chance.damage(.P1, 217);
709
817
  if (showdown) {
710
- try expectValue(217, chance.actions.p1.damage);
818
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
711
819
  try expectProbability(&chance.probability, 1, 39);
712
820
  } else {
713
821
  try expectProbability(&chance.probability, 1, 1);
714
- try expectValue(0, chance.actions.p1.damage);
715
-
716
- try chance.commit(.P1, .miss);
717
- try expectProbability(&chance.probability, 1, 1);
718
- try expectValue(0, chance.actions.p1.damage);
822
+ try expectValue(@as(u8, 0), chance.actions.p1.damage);
719
823
 
720
824
  try chance.commit(.P1, .hit);
825
+
721
826
  try expectProbability(&chance.probability, 1, 39);
722
- try expectValue(217, chance.actions.p1.damage);
827
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
723
828
  }
724
829
  }
725
830
 
@@ -771,13 +876,13 @@ test "Chance.moveSlot" {
771
876
 
772
877
  try chance.moveSlot(.P2, 2, &ms, 2);
773
878
  try expectProbability(&chance.probability, 1, 2);
774
- try expectValue(2, chance.actions.p2.move_slot);
879
+ try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
775
880
 
776
881
  chance.reset();
777
882
 
778
883
  try chance.moveSlot(.P1, 1, &ms, 0);
779
884
  try expectProbability(&chance.probability, 1, 3);
780
- try expectValue(1, chance.actions.p1.move_slot);
885
+ try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
781
886
  }
782
887
 
783
888
  test "Chance.multiHit" {
@@ -785,50 +890,43 @@ test "Chance.multiHit" {
785
890
 
786
891
  try chance.multiHit(.P1, 3);
787
892
  try expectProbability(&chance.probability, 3, 8);
788
- try expectValue(3, chance.actions.p1.multi_hit);
893
+ try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
789
894
 
790
895
  chance.reset();
791
896
 
792
897
  try chance.multiHit(.P2, 5);
793
898
  try expectProbability(&chance.probability, 1, 8);
794
- try expectValue(5, chance.actions.p2.multi_hit);
899
+ try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
795
900
  }
796
901
 
797
902
  test "Chance.duration" {
798
903
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
799
904
 
800
- chance.duration(.sleep, .P1, .P2, 2);
801
- try expectValue(2, chance.actions.p1.duration);
802
- try expectValue(1, chance.actions.p2.durations.sleep);
905
+ chance.duration(.P1, 2);
906
+ try expectValue(@as(u4, 2), chance.actions.p1.duration);
803
907
 
804
908
  chance.reset();
805
909
 
806
- chance.duration(.binding, .P2, .P2, 4);
807
- if (!showdown) {
808
- try expectValue(0, chance.actions.p2.duration);
809
- try expectValue(0, chance.actions.p2.durations.binding);
810
- try chance.commit(.P2, .hit);
811
- }
812
- try expectValue(4, chance.actions.p2.duration);
813
- try expectValue(1, chance.actions.p2.durations.binding);
910
+ chance.duration(.P2, 4);
911
+ try expectValue(@as(u4, 4), chance.actions.p2.duration);
814
912
  }
815
913
 
816
914
  test "Chance.sleep" {
817
915
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
818
916
 
819
917
  for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
820
- chance.actions.p1.durations.sleep = @intCast(i);
821
- try chance.sleep(.P1, 0);
918
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
919
+ try chance.sleep(.P1, .ended);
822
920
  try expectProbability(&chance.probability, 1, d);
823
- try expectValue(0, chance.actions.p1.durations.sleep);
921
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
824
922
 
825
923
  chance.reset();
826
924
 
827
925
  if (i < 7) {
828
- chance.actions.p1.durations.sleep = @intCast(i);
829
- try chance.sleep(.P1, 1);
926
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
927
+ try chance.sleep(.P1, .continuing);
830
928
  try expectProbability(&chance.probability, d - 1, d);
831
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
929
+ try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
832
930
 
833
931
  chance.reset();
834
932
  }
@@ -840,19 +938,19 @@ test "Chance.confusion" {
840
938
 
841
939
  for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
842
940
  if (i > 1) {
843
- chance.actions.p2.durations.confusion = @intCast(i);
844
- try chance.confusion(.P2, 0);
941
+ chance.durations.p2.confusion = @intCast(i);
942
+ try chance.confusion(.P2, .ended);
845
943
  try expectProbability(&chance.probability, 1, d);
846
- try expectValue(0, chance.actions.p2.durations.confusion);
944
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
847
945
 
848
946
  chance.reset();
849
947
  }
850
948
 
851
949
  if (i < 5) {
852
- chance.actions.p2.durations.confusion = @intCast(i);
853
- try chance.confusion(.P2, 1);
950
+ chance.durations.p2.confusion = @intCast(i);
951
+ try chance.confusion(.P2, .continuing);
854
952
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
855
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
953
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
856
954
 
857
955
  chance.reset();
858
956
  }
@@ -863,18 +961,18 @@ test "Chance.disable" {
863
961
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
864
962
 
865
963
  for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
866
- chance.actions.p1.durations.disable = @intCast(i);
867
- try chance.disable(.P1, 0);
964
+ chance.durations.p1.disable = @intCast(i);
965
+ try chance.disable(.P1, .ended);
868
966
  try expectProbability(&chance.probability, 1, d);
869
- try expectValue(0, chance.actions.p1.durations.disable);
967
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
870
968
 
871
969
  chance.reset();
872
970
 
873
971
  if (i < 8) {
874
- chance.actions.p1.durations.disable = @intCast(i);
875
- try chance.disable(.P1, 1);
972
+ chance.durations.p1.disable = @intCast(i);
973
+ try chance.disable(.P1, .continuing);
876
974
  try expectProbability(&chance.probability, d - 1, d);
877
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
975
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
878
976
 
879
977
  chance.reset();
880
978
  }
@@ -886,19 +984,19 @@ test "Chance.attacking" {
886
984
 
887
985
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
888
986
  if (i > 1) {
889
- chance.actions.p2.durations.attacking = @intCast(i);
890
- try chance.attacking(.P2, 0);
987
+ chance.durations.p2.attacking = @intCast(i);
988
+ try chance.attacking(.P2, .ended);
891
989
  try expectProbability(&chance.probability, 1, d);
892
- try expectValue(0, chance.actions.p2.durations.attacking);
990
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
893
991
 
894
992
  chance.reset();
895
993
  }
896
994
 
897
995
  if (i < 3) {
898
- chance.actions.p2.durations.attacking = @intCast(i);
899
- try chance.attacking(.P2, 1);
996
+ chance.durations.p2.attacking = @intCast(i);
997
+ try chance.attacking(.P2, .continuing);
900
998
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
901
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
999
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
902
1000
 
903
1001
  chance.reset();
904
1002
  }
@@ -912,18 +1010,18 @@ test "Chance.binding" {
912
1010
  const qs = [_]u8{ 8, 5, 2, 1 };
913
1011
 
914
1012
  for (ps, qs, 1..5) |p, q, i| {
915
- chance.actions.p1.durations.binding = @intCast(i);
916
- try chance.binding(.P1, 0);
1013
+ chance.durations.p1.binding = @intCast(i);
1014
+ try chance.binding(.P1, .ended);
917
1015
  try expectProbability(&chance.probability, p, q);
918
- try expectValue(0, chance.actions.p1.durations.binding);
1016
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
919
1017
 
920
1018
  chance.reset();
921
1019
 
922
1020
  if (i < 4) {
923
- chance.actions.p1.durations.binding = @intCast(i);
924
- try chance.binding(.P1, 1);
1021
+ chance.durations.p1.binding = @intCast(i);
1022
+ try chance.binding(.P1, .continuing);
925
1023
  try expectProbability(&chance.probability, q - p, q);
926
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
1024
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
927
1025
 
928
1026
  chance.reset();
929
1027
  }
@@ -935,7 +1033,7 @@ test "Chance.psywave" {
935
1033
 
936
1034
  try chance.psywave(.P2, 100, 150);
937
1035
  try expectProbability(&chance.probability, 1, 150);
938
- try expectValue(101, chance.actions.p2.psywave);
1036
+ try expectValue(@as(u8, 101), chance.actions.p2.psywave);
939
1037
  }
940
1038
 
941
1039
  test "Chance.metronome" {
@@ -973,24 +1071,24 @@ const Null = struct {
973
1071
  _ = .{ self, player, kind };
974
1072
  }
975
1073
 
976
- pub fn clearPending(self: Null) void {
1074
+ pub fn glitch(self: Null) void {
977
1075
  _ = .{self};
978
1076
  }
979
1077
 
980
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
981
- _ = .{ self, player, haze };
1078
+ pub fn clearPending(self: Null) void {
1079
+ _ = .{self};
982
1080
  }
983
1081
 
984
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
985
- _ = .{ self, player, in, out };
1082
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1083
+ _ = .{ self, player, slot };
986
1084
  }
987
1085
 
988
1086
  pub fn speedTie(self: Null, p1: bool) Error!void {
989
1087
  _ = .{ self, p1 };
990
1088
  }
991
1089
 
992
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
993
- _ = .{ self, ok, accuracy };
1090
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
1091
+ _ = .{ self, player, ok, accuracy };
994
1092
  }
995
1093
 
996
1094
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -1021,34 +1119,37 @@ const Null = struct {
1021
1119
  _ = .{ self, player, n };
1022
1120
  }
1023
1121
 
1024
- pub fn duration(
1122
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1123
+ _ = .{ self, player, turns };
1124
+ }
1125
+
1126
+ pub fn observe(
1025
1127
  self: Null,
1026
- comptime field: Duration.Field,
1128
+ comptime field: Action.Field,
1027
1129
  player: Player,
1028
- target: Player,
1029
- turns: u4,
1130
+ obs: Optional(Confusion),
1030
1131
  ) void {
1031
- _ = .{ self, field, player, target, turns };
1132
+ _ = .{ self, field, player, obs };
1032
1133
  }
1033
1134
 
1034
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1035
- _ = .{ self, player, turns };
1135
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1136
+ _ = .{ self, player, obs };
1036
1137
  }
1037
1138
 
1038
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1039
- _ = .{ self, player, turns };
1139
+ pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
1140
+ _ = .{ self, player, obs };
1040
1141
  }
1041
1142
 
1042
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1043
- _ = .{ self, player, turns };
1143
+ pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1144
+ _ = .{ self, player, obs };
1044
1145
  }
1045
1146
 
1046
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1047
- _ = .{ self, player, turns };
1147
+ pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1148
+ _ = .{ self, player, obs };
1048
1149
  }
1049
1150
 
1050
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1051
- _ = .{ self, player, turns };
1151
+ pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1152
+ _ = .{ self, player, obs };
1052
1153
  }
1053
1154
 
1054
1155
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -1058,4 +1159,9 @@ const Null = struct {
1058
1159
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
1059
1160
  _ = .{ self, player, move };
1060
1161
  }
1162
+
1163
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1164
+ _ = .{ self, player, n };
1165
+ return true;
1166
+ }
1061
1167
  };