@pkmn/engine 0.1.0-dev.2ad317f2 → 0.1.0-dev.2c426670

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 (53) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +44 -19
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data/ids.json +12 -12
  6. package/build/pkg/data/layout.json +2 -2
  7. package/build/pkg/data/protocol.json +69 -12
  8. package/build/pkg/data.d.ts +4 -4
  9. package/build/pkg/data.js.map +1 -1
  10. package/build/pkg/gen1.js +2 -2
  11. package/build/pkg/gen1.js.map +1 -1
  12. package/build/pkg/index.d.ts +2 -1
  13. package/build/pkg/index.js.map +1 -1
  14. package/build/pkg/protocol.js +61 -42
  15. package/build/pkg/protocol.js.map +1 -1
  16. package/build/test/display.js +8 -3
  17. package/build/test/display.js.map +1 -1
  18. package/build/tools/debug.js +5 -4
  19. package/build/tools/debug.js.map +1 -1
  20. package/build.zig +123 -109
  21. package/package.json +43 -23
  22. package/src/bin/install-pkmn-engine +26 -18
  23. package/src/lib/common/data.zig +12 -24
  24. package/src/lib/common/debug.zig +2 -2
  25. package/src/lib/common/optional.zig +9 -2
  26. package/src/lib/common/options.zig +2 -0
  27. package/src/lib/common/protocol.zig +802 -327
  28. package/src/lib/common/rational.zig +113 -19
  29. package/src/lib/common/rng.zig +26 -17
  30. package/src/lib/gen1/calc.zig +501 -97
  31. package/src/lib/gen1/chance.zig +333 -383
  32. package/src/lib/gen1/data/moves.zig +192 -180
  33. package/src/lib/gen1/data/species.zig +3 -3
  34. package/src/lib/gen1/data/types.zig +7 -7
  35. package/src/lib/gen1/data.zig +50 -62
  36. package/src/lib/gen1/helpers.zig +87 -548
  37. package/src/lib/gen1/mechanics.zig +562 -510
  38. package/src/lib/gen2/calc.zig +143 -0
  39. package/src/lib/gen2/chance.zig +882 -0
  40. package/src/lib/gen2/data/items.zig +12 -18
  41. package/src/lib/gen2/data/moves.zig +663 -569
  42. package/src/lib/gen2/data/species.zig +1 -1
  43. package/src/lib/gen2/data/types.zig +134 -31
  44. package/src/lib/gen2/data.zig +106 -79
  45. package/src/lib/gen2/generated.zig +806 -0
  46. package/src/lib/gen2/helpers.zig +45 -19
  47. package/src/lib/gen2/mechanics.zig +3560 -9
  48. package/src/lib/{binding/node.zig → node.zig} +10 -13
  49. package/src/lib/pkmn.zig +27 -12
  50. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  51. package/src/test/display/pkmn.html.tmpl +5 -5
  52. package/src/lib/gen1/README.md +0 -892
  53. package/src/lib/gen2/README.md +0 -308
@@ -14,12 +14,14 @@ const util = @import("../common/util.zig");
14
14
  const Player = @import("../common/data.zig").Player;
15
15
  const Optional = @import("../common/optional.zig").Optional;
16
16
 
17
- const Move = @import("data.zig").Move;
18
- const MoveSlot = @import("data.zig").MoveSlot;
17
+ const data = @import("data.zig");
19
18
 
20
19
  const enabled = options.chance;
21
20
  const showdown = options.showdown;
22
21
 
22
+ const Move = data.Move;
23
+ const MoveSlot = data.MoveSlot;
24
+
23
25
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
24
26
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
25
27
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -35,19 +37,13 @@ pub const Actions = extern struct {
35
37
  }
36
38
 
37
39
  /// Returns the `Action` for the given `player`.
38
- pub inline fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
40
+ pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
39
41
  assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
40
42
  return if (player == .P1) &self.p1 else &self.p2;
41
43
  }
42
44
 
43
- /// Perform a reset by clearing fields which should not persist across updates.
44
- pub inline fn reset(self: *Actions) void {
45
- self.p1.reset();
46
- self.p2.reset();
47
- }
48
-
49
45
  /// Returns true if `a` is equal to `b`.
50
- pub inline fn eql(a: Actions, b: Actions) bool {
46
+ pub fn eql(a: Actions, b: Actions) bool {
51
47
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
52
48
  }
53
49
 
@@ -61,11 +57,6 @@ pub const Actions = extern struct {
61
57
  const b_val = @field(@field(b, player.name), field.name);
62
58
 
63
59
  switch (@typeInfo(@TypeOf(a_val))) {
64
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
65
- if ((@field(a_val, f.name) > 0) != (@field(b_val, f.name) > 0)) {
66
- return false;
67
- }
68
- },
69
60
  .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
70
61
  return false,
71
62
  .Int => if ((a_val > 0) != (b_val > 0)) return false,
@@ -94,9 +85,6 @@ test Actions {
94
85
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
95
86
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
96
87
  const c: Actions = .{ .p1 = .{ .hit = .true } };
97
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
98
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
99
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
100
88
 
101
89
  try expect(a.eql(a));
102
90
  try expect(!a.eql(b));
@@ -109,13 +97,25 @@ test Actions {
109
97
  try expect(b.matches(a));
110
98
  try expect(!a.matches(c));
111
99
  try expect(!c.matches(a));
112
- try expect(d.matches(e));
113
- try expect(!d.matches(f));
114
100
  }
115
101
 
102
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
103
+ pub const Observation = enum { started, continuing, ended };
104
+ /// An extension of Observation to account for "hidden" confusion".
105
+ pub const Confusion = enum {
106
+ started,
107
+ continuing,
108
+ ended,
109
+ /// An indicator that an existing confusion duration has been overwritten by a new confusion
110
+ /// duration due to a Thrashing move ending. This is not immediately observable to an opponent,
111
+ /// to compute probabilities from an opponent's point of view based solely on public information
112
+ /// extra work must be done to correct for the information leak
113
+ overwritten,
114
+ };
115
+
116
116
  /// Information about the RNG that was observed during a Generation I battle `update` for a
117
117
  /// single player.
118
- pub const Action = packed struct {
118
+ pub const Action = packed struct(u64) {
119
119
  /// If not 0, the roll to be returned Rolls.damage.
120
120
  damage: u8 = 0,
121
121
 
@@ -137,8 +137,15 @@ pub const Action = packed struct {
137
137
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
138
138
  duration: u4 = 0,
139
139
 
140
- /// Observed values of various durations. Does not influence future RNG calls. TODO
141
- durations: Duration = .{},
140
+ // TODO
141
+ sleep: Optional(Observation) = .None,
142
+ confusion: Optional(Confusion) = .None,
143
+ disable: Optional(Observation) = .None,
144
+ bide: Optional(Observation) = .None,
145
+ binding: Optional(Observation) = .None,
146
+ thrashing: Optional(Observation) = .None,
147
+
148
+ _: u3 = 0, // TODO
142
149
 
143
150
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
144
151
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
@@ -152,19 +159,8 @@ pub const Action = packed struct {
152
159
  /// If not None, the Move to return for Rolls.metronome.
153
160
  metronome: Move = .None,
154
161
 
155
- pub const DURATIONS: u64 = 0x000000FFFF000000;
156
-
157
162
  pub const Field = std.meta.FieldEnum(Action);
158
163
 
159
- comptime {
160
- assert(@sizeOf(Action) == 8);
161
- }
162
-
163
- /// Perform a reset by clearing fields which should not persist across updates.
164
- pub inline fn reset(self: *Action) void {
165
- self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
166
- }
167
-
168
164
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
169
165
  _ = .{ f, o };
170
166
  try fmt(a, w, false);
@@ -176,18 +172,6 @@ pub const Action = packed struct {
176
172
  inline for (@typeInfo(Action).Struct.fields) |field| {
177
173
  const val = @field(self, field.name);
178
174
  switch (@typeInfo(@TypeOf(val))) {
179
- .Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
180
- const v = @field(val, f.name);
181
- if (v != 0) {
182
- if (printed) try writer.writeAll(", ");
183
- if (shape) {
184
- try writer.print("{s}:?", .{f.name});
185
- } else {
186
- try writer.print("{s}:{d}", .{ f.name, v });
187
- }
188
- printed = true;
189
- }
190
- },
191
175
  .Enum => if (val != .None) {
192
176
  if (printed) try writer.writeAll(", ");
193
177
  if (shape) {
@@ -197,6 +181,13 @@ pub const Action = packed struct {
197
181
  if (val == .false) "!" else "",
198
182
  field.name,
199
183
  });
184
+ } else if (@TypeOf(val) == Optional(Observation) or
185
+ @TypeOf(val) == Optional(Confusion))
186
+ {
187
+ try writer.print("{s}{s}", .{
188
+ ([_][]const u8{ "+", "", "-", "#" })[@intFromEnum(val) - 1],
189
+ field.name,
190
+ });
200
191
  } else {
201
192
  try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
202
193
  }
@@ -218,47 +209,80 @@ pub const Action = packed struct {
218
209
  }
219
210
  };
220
211
 
221
- test Action {
222
- var a = Action{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
223
- a.reset();
224
-
225
- try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
226
- }
227
-
228
212
  /// TODO
229
- pub const Durations = extern struct {
230
- /// Information about the durations for Player 1.
213
+ pub const Durations = struct {
214
+ /// TODO
231
215
  p1: Duration = .{},
232
- /// Information about the durations for Player 2.
216
+ /// TODO
233
217
  p2: Duration = .{},
234
218
 
235
- comptime {
236
- assert(@sizeOf(Durations) == 4);
219
+ /// TODO
220
+ pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Duration) {
221
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Durations);
222
+ return if (player == .P1) &self.p1 else &self.p2;
223
+ }
224
+
225
+ pub fn format(
226
+ self: Durations,
227
+ comptime fmt: []const u8,
228
+ opts: std.fmt.FormatOptions,
229
+ writer: anytype,
230
+ ) !void {
231
+ _ = .{ fmt, opts };
232
+ try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
237
233
  }
238
234
  };
239
235
 
240
- /// Observed values for various durations that need to be tracked in order to properly
241
- /// deduplicate transitions with a primary key. TODO
242
- pub const Duration = packed struct {
243
- /// The number of turns a Pokémon has been observed to be sleeping.
244
- sleep: u3 = 0,
245
- /// The number of turns a Pokémon has been observed to be confused.
236
+ /// TODO
237
+ pub const Duration = struct {
238
+ sleeps: [6]u3 = .{0} ** 6,
246
239
  confusion: u3 = 0,
247
- /// The number of turns a Pokémon has been observed to be disabled.
248
240
  disable: u4 = 0,
249
- /// The number of turns a Pokémon has been observed to be attacking.
250
- attacking: u3 = 0,
251
- /// The number of turns a Pokémon has been observed to be binding their opponent.
241
+ bide: u3 = 0,
242
+ thrashing: u3 = 0,
252
243
  binding: u3 = 0,
253
244
 
254
- pub const Field = std.meta.FieldEnum(Duration);
255
-
256
- comptime {
257
- assert(@sizeOf(Duration) == 2);
245
+ pub fn format(
246
+ self: Duration,
247
+ comptime fmt: []const u8,
248
+ opts: std.fmt.FormatOptions,
249
+ writer: anytype,
250
+ ) !void {
251
+ _ = .{ fmt, opts };
252
+ try writer.writeByte('(');
253
+ var printed = false;
254
+ inline for (@typeInfo(Duration).Struct.fields) |field| {
255
+ const val = @field(self, field.name);
256
+ switch (@typeInfo(@TypeOf(val))) {
257
+ .Int => if (val != 0) {
258
+ if (printed) try writer.writeAll(", ");
259
+ try writer.print("{s}:{d}", .{ field.name, val });
260
+ printed = true;
261
+ },
262
+ .Array => {
263
+ var total: usize = 0;
264
+ for (val) |v| total += v;
265
+ if (total > 0) {
266
+ if (printed) try writer.writeAll(", ");
267
+ try writer.print("{s}:[", .{field.name});
268
+ for (val, 0..) |v, i| {
269
+ if (i != 0) try writer.writeByte(',');
270
+ try writer.print("{d}", .{v});
271
+ }
272
+ try writer.writeAll("]");
273
+ printed = true;
274
+ }
275
+ },
276
+ else => unreachable,
277
+ }
278
+ }
279
+ try writer.writeByte(')');
258
280
  }
259
281
  };
260
282
 
261
- pub const Commit = enum { hit, miss, binding };
283
+ pub const Commit = enum { hit, miss, binding, err };
284
+
285
+ const uN = if (options.miss) u8 else u9;
262
286
 
263
287
  /// Tracks chance actions and their associated probability during a Generation I battle update when
264
288
  /// `options.chance` is enabled.
@@ -271,14 +295,8 @@ pub fn Chance(comptime Rational: type) type {
271
295
  /// The actions taken by a hypothetical "chance player" that convey information about which
272
296
  /// RNG events were observed during a battle `update`.
273
297
  actions: Actions = .{},
274
-
275
- // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
276
- // other durations which are all tied to volatiles, sleep's counter persists through
277
- // switching and so we must store it here. The indices of these arrays correspond to the
278
- // `order` field of a Side. This information could be stored in actions but size is a
279
- // concern so this is tracked separately as actions only purports to track RNG events
280
- // observed during a single `update` (not across updates).
281
- sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
298
+ /// TODO
299
+ durations: Durations = .{},
282
300
 
283
301
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
284
302
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -296,16 +314,13 @@ pub fn Chance(comptime Rational: type) type {
296
314
  //
297
315
  // Finally, because each player's actions are processed sequentially we only need a single
298
316
  // shared structure to track this information (though it needs to be cleared appropriately)
299
- pending: if (showdown) struct {
300
- hit: bool = false,
301
- hit_probablity: u8 = 0,
302
- } else struct {
317
+ pending: if (showdown) struct {} else struct {
303
318
  crit: bool = false,
304
319
  crit_probablity: u8 = 0,
305
320
  damage_roll: u8 = 0,
306
321
  hit: bool = false,
307
- hit_probablity: u8 = 0,
308
- binding: u8 = 0,
322
+ hit_probablity: uN = 0,
323
+ glitch: bool = false,
309
324
  } = .{},
310
325
 
311
326
  /// Possible error returned by operations tracking chance probability.
@@ -313,25 +328,25 @@ pub fn Chance(comptime Rational: type) type {
313
328
 
314
329
  /// Convenience helper to clear fields which typically should be cleared between updates.
315
330
  pub fn reset(self: *Self) void {
331
+ if (!enabled) return;
332
+
316
333
  self.probability.reset();
317
- self.actions.reset();
334
+ self.actions = .{};
318
335
  self.pending = .{};
319
336
  }
320
337
 
321
338
  pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
339
+ if (!enabled) return;
340
+
341
+ assert(!showdown);
342
+
322
343
  var action = self.actions.get(player);
323
344
 
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 (!showdown) {
327
- if (kind != .miss and self.pending.binding != 0) {
328
- assert(action.duration == 0);
329
- action.duration = @intCast(self.pending.binding);
330
- assert(action.durations.binding == 0);
331
- action.durations.binding = 1;
332
- }
333
- if (kind == .binding) return;
334
- } else assert(kind != .binding);
345
+ if (kind == .err and self.pending.crit_probablity != 0) {
346
+ try self.probability.update(self.pending.crit_probablity, 256);
347
+ action.critical_hit = if (self.pending.crit) .true else .false;
348
+ return;
349
+ }
335
350
 
336
351
  // Always commit the hit result if we make it here (commit won't be called at all if the
337
352
  // target is immune/behind a sub/etc)
@@ -340,59 +355,62 @@ pub fn Chance(comptime Rational: type) type {
340
355
  action.hit = if (self.pending.hit) .true else .false;
341
356
  }
342
357
 
343
- if (showdown) return;
344
-
345
- // If the move actually lands we can commit any past critical hit / damage rolls. We
346
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
347
- // to a damage roll is actually a no-op
348
- if (kind == .hit and self.pending.damage_roll > 0) {
349
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
358
+ // If the move actually lands we can commit any past critical hit / damage rolls. Note
359
+ // we can't rely on the presence or absence of a damage roll to determine whether a
360
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
361
+ // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
362
+ // passed to the critical hit helper).
363
+ //
364
+ // There is a special edge case with the division-by-zero freeze where we need to commit
365
+ // the status of critical hit roll the occured before the move missed to avoid missing
366
+ // out on the possibility that the alternative branch encountered an error
367
+ if (kind != .hit and !self.pending.glitch) {
368
+ return assert(!self.pending.crit or self.pending.crit_probablity > 0);
369
+ }
350
370
 
351
- if (self.pending.crit_probablity != 0) {
352
- try self.probability.update(self.pending.crit_probablity, 256);
353
- action.critical_hit = if (self.pending.crit) .true else .false;
354
- }
371
+ if (self.pending.crit_probablity != 0) {
372
+ try self.probability.update(self.pending.crit_probablity, 256);
373
+ action.critical_hit = if (self.pending.crit) .true else .false;
374
+ } else assert(!self.pending.glitch);
355
375
 
376
+ if (self.pending.damage_roll > 0) {
356
377
  try self.probability.update(1, 39);
357
378
  action.damage = self.pending.damage_roll;
358
379
  }
359
380
  }
360
381
 
361
- pub fn clearPending(self: *Self) void {
382
+ pub fn glitch(self: *Self) void {
362
383
  if (!enabled) return;
363
384
 
364
- self.pending = .{};
385
+ assert(!showdown);
386
+ self.pending.glitch = true;
365
387
  }
366
388
 
367
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
389
+ pub fn clearPending(self: *Self) void {
368
390
  if (!enabled) return;
369
391
 
370
- var action = self.actions.get(player);
371
- if (haze) |status| {
372
- if (status) action.durations.sleep = 0;
373
- action.durations.confusion = 0;
374
- action.durations.disable = 0;
375
- } else {
376
- action.durations.attacking = 0;
377
- action.durations.binding = 0;
378
- }
392
+ assert(!showdown);
393
+ self.pending = .{};
379
394
  }
380
395
 
381
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
396
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
382
397
  if (!enabled) return;
383
398
 
384
- assert(in >= 1 and in <= 6);
385
- assert(out >= 1 and out <= 6);
399
+ assert(slot >= 1 and slot <= 6);
386
400
 
387
- var action = self.actions.get(player);
401
+ var d = self.durations.get(player);
388
402
 
389
- const slp = action.durations.sleep;
390
- action.durations = .{};
403
+ const out = d.sleeps[0];
404
+ d.sleeps[0] = d.sleeps[slot - 1];
405
+ d.sleeps[slot - 1] = out;
391
406
 
392
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
393
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
407
+ d.confusion = 0;
408
+ d.disable = 0;
409
+ d.bide = 0;
410
+ d.thrashing = 0;
411
+ d.binding = 0;
394
412
 
395
- self.actions.get(player.foe()).durations.binding = 0;
413
+ self.durations.get(player.foe()).binding = 0;
396
414
  }
397
415
 
398
416
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -403,12 +421,17 @@ pub fn Chance(comptime Rational: type) type {
403
421
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
404
422
  }
405
423
 
406
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
424
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
407
425
  if (!enabled) return;
408
426
 
409
427
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
410
- self.pending.hit = ok;
411
- self.pending.hit_probablity = p;
428
+ if (showdown) {
429
+ try self.probability.update(p, 256);
430
+ self.actions.get(player).hit = if (ok) .true else .false;
431
+ } else {
432
+ self.pending.hit = ok;
433
+ self.pending.hit_probablity = p;
434
+ }
412
435
  }
413
436
 
414
437
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -486,109 +509,134 @@ pub fn Chance(comptime Rational: type) type {
486
509
  self.actions.get(player).multi_hit = n;
487
510
  }
488
511
 
489
- pub fn duration(
512
+ pub fn duration(self: *Self, player: Player, turns: u4) void {
513
+ if (!enabled) return;
514
+
515
+ self.actions.get(player).duration = if (options.key) 1 else turns;
516
+ }
517
+
518
+ pub fn observe(
490
519
  self: *Self,
491
- comptime field: Duration.Field,
520
+ comptime field: Action.Field,
492
521
  player: Player,
493
- target: Player,
494
- turns: u4,
522
+ obs: Optional(Confusion),
495
523
  ) void {
496
524
  if (!enabled) return;
497
525
 
498
- if (!showdown and field == .binding) {
499
- self.pending.binding = if (options.key) 1 else turns;
500
- } else {
501
- var durations = &self.actions.get(target).durations;
502
- assert(@field(durations, @tagName(field)) == 0 or
503
- (field == .confusion and player == target));
504
- @field(durations, @tagName(field)) = 1;
505
- self.actions.get(player).duration = if (options.key) 1 else turns;
526
+ var a = self.actions.get(player);
527
+ const val = @field(a, @tagName(field));
528
+ switch (obs) {
529
+ .overwritten => {
530
+ assert(field == .confusion);
531
+ assert(val == .started or val == .continuing);
532
+ assert(a.duration > 0);
533
+ a.confusion = obs;
534
+ self.durations.get(player).confusion = 1;
535
+ },
536
+ .None => {
537
+ @field(a, @tagName(field)) = .None;
538
+ if (field == .sleep) {
539
+ self.durations.get(player).sleeps[0] = 0;
540
+ } else {
541
+ @field(self.durations.get(player), @tagName(field)) = 0;
542
+ }
543
+ },
544
+ .started => {
545
+ assert(val == .None or val == .ended);
546
+ assert(switch (field) {
547
+ .confusion => a.duration > 0 or self.actions.get(player.foe()).duration > 0,
548
+ .sleep, .disable => self.actions.get(player.foe()).duration > 0,
549
+ else => a.duration > 0,
550
+ });
551
+ @field(a, @tagName(field)) = .started;
552
+ if (field == .sleep) {
553
+ self.durations.get(player).sleeps[0] = 1;
554
+ } else {
555
+ @field(self.durations.get(player), @tagName(field)) = 1;
556
+ }
557
+ },
558
+ else => unreachable,
506
559
  }
507
560
  }
508
561
 
509
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
562
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
510
563
  if (!enabled) return;
511
564
 
512
- var durations = &self.actions.get(player).durations;
513
- const n = durations.sleep;
514
- if (turns == 0) {
515
- assert(n >= 1 and n <= 7);
516
- if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
517
- durations.sleep = 0;
518
- } else {
519
- assert(n >= 1 and n < 7);
520
- try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
521
- durations.sleep += 1;
522
- }
565
+ var a = self.actions.get(player);
566
+ var d = self.durations.get(player);
567
+
568
+ assert(a.sleep == .None or a.sleep == .started);
569
+
570
+ a.sleep = obs;
571
+ d.sleeps[0] = update(d.sleeps[0], obs);
523
572
  }
524
573
 
525
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
574
+ pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
526
575
  if (!enabled) return;
527
576
 
528
- var durations = &self.actions.get(player).durations;
529
- const n = durations.disable;
530
- if (turns == 0) {
531
- assert(n >= 1 and n <= 8);
532
- if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
533
- durations.disable = 0;
534
- } else {
535
- assert(n >= 1 and n < 8);
536
- try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
537
- durations.disable += 1;
577
+ var a = self.actions.get(player);
578
+ var d = self.durations.get(player);
579
+
580
+ if (obs == .overwritten) {
581
+ assert(a.confusion == .started or a.confusion == .continuing);
582
+ assert(a.duration > 0);
583
+ a.confusion = obs;
584
+ d.confusion = 1;
585
+ return;
538
586
  }
587
+
588
+ assert(a.confusion == .None or a.confusion == .started);
589
+
590
+ a.confusion = obs;
591
+ d.confusion = update(d.confusion, @enumFromInt(@intFromEnum(obs)));
539
592
  }
540
593
 
541
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
594
+ pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
542
595
  if (!enabled) return;
543
596
 
544
- var durations = &self.actions.get(player).durations;
545
- const n = durations.confusion;
546
- if (turns == 0) {
547
- assert(n >= 2 and n <= 5);
548
- if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
549
- durations.confusion = 0;
550
- } else {
551
- assert(n >= 1 and n < 5);
552
- if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
553
- durations.confusion += 1;
554
- }
597
+ var a = self.actions.get(player);
598
+ var d = self.durations.get(player);
599
+
600
+ assert(a.disable == .None or a.disable == .started);
601
+
602
+ a.disable = obs;
603
+ d.disable = update(d.disable, obs);
555
604
  }
556
605
 
557
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
606
+ pub fn bide(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
558
607
  if (!enabled) return;
559
608
 
560
- var durations = &self.actions.get(player).durations;
561
- const n = durations.attacking;
562
- if (turns == 0) {
563
- assert(n >= 2 and n <= 3);
564
- if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
565
- durations.attacking = 0;
566
- } else {
567
- assert(n >= 1 and n < 3);
568
- if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
569
- durations.attacking += 1;
570
- }
609
+ var a = self.actions.get(player);
610
+ var d = self.durations.get(player);
611
+
612
+ assert(a.bide == .None or a.bide == .started);
613
+
614
+ a.bide = obs;
615
+ d.bide = update(d.bide, obs);
571
616
  }
572
617
 
573
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
618
+ pub fn thrashing(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
574
619
  if (!enabled) return;
575
620
 
576
- var durations = &self.actions.get(player).durations;
577
- const n = durations.binding;
621
+ var a = self.actions.get(player);
622
+ var d = self.durations.get(player);
578
623
 
579
- assert(n > 0);
580
- const p: u4 = if (n < 3) 3 else 1;
581
- const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
624
+ assert(a.thrashing == .None or a.thrashing == .started);
582
625
 
583
- if (turns == 0) {
584
- assert(n >= 1 and n <= 4);
585
- if (n != 4) try self.probability.update(p, q);
586
- durations.binding = 0;
587
- } else {
588
- assert(n >= 1 and n < 4);
589
- try self.probability.update(q - p, q);
590
- durations.binding += 1;
591
- }
626
+ a.thrashing = obs;
627
+ d.thrashing = update(d.thrashing, obs);
628
+ }
629
+
630
+ pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
631
+ if (!enabled) return;
632
+
633
+ var a = self.actions.get(player);
634
+ var d = self.durations.get(player);
635
+
636
+ assert(a.binding == .None or a.binding == .started);
637
+
638
+ a.binding = obs;
639
+ d.binding = update(d.binding, obs);
592
640
  }
593
641
 
594
642
  pub fn psywave(self: *Self, player: Player, power: u8, max: u8) Error!void {
@@ -601,12 +649,20 @@ pub fn Chance(comptime Rational: type) type {
601
649
  pub fn metronome(self: *Self, player: Player, move: Move) Error!void {
602
650
  if (!enabled) return;
603
651
 
604
- try self.probability.update(1, Move.size - 2);
652
+ try self.probability.update(1, Move.METRONOME.len);
605
653
  self.actions.get(player).metronome = move;
606
654
  }
607
655
  };
608
656
  }
609
657
 
658
+ fn update(n: anytype, obs: Optional(Observation)) @TypeOf(n) {
659
+ return switch (obs) {
660
+ .None, .ended => 0,
661
+ .started => 1,
662
+ .continuing => n + 1,
663
+ };
664
+ }
665
+
610
666
  test "Chance.speedTie" {
611
667
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
612
668
 
@@ -626,19 +682,25 @@ test "Chance.speedTie" {
626
682
  test "Chance.hit" {
627
683
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
628
684
 
629
- chance.hit(true, 229);
630
- try expectProbability(&chance.probability, 1, 1);
631
- try expectValue(Optional(bool).None, chance.actions.p1.hit);
632
- try chance.commit(.P1, .hit);
685
+ try chance.hit(.P1, true, 229);
686
+ if (!showdown) {
687
+ try expectProbability(&chance.probability, 1, 1);
688
+ try expectValue(Optional(bool).None, chance.actions.p1.hit);
689
+
690
+ try chance.commit(.P1, .hit);
691
+ }
633
692
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
634
693
  try expectProbability(&chance.probability, 229, 256);
635
694
 
636
695
  chance.reset();
637
696
 
638
- chance.hit(false, 255);
639
- try expectProbability(&chance.probability, 1, 1);
640
- try expectValue(Optional(bool).None, chance.actions.p2.hit);
641
- try chance.commit(.P2, .miss);
697
+ try chance.hit(.P2, false, 255);
698
+ if (!showdown) {
699
+ try expectProbability(&chance.probability, 1, 1);
700
+ try expectValue(Optional(bool).None, chance.actions.p2.hit);
701
+
702
+ try chance.commit(.P2, .miss);
703
+ }
642
704
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
643
705
  try expectProbability(&chance.probability, 1, 256);
644
706
  }
@@ -654,26 +716,18 @@ test "Chance.criticalHit" {
654
716
  try expectProbability(&chance.probability, 1, 1);
655
717
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
656
718
 
657
- try chance.commit(.P1, .hit);
658
- try expectProbability(&chance.probability, 1, 1);
659
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
660
-
661
- chance.hit(true, 128);
719
+ try chance.hit(.P1, true, 128);
662
720
 
663
- try chance.commit(.P1, .hit);
664
- try expectProbability(&chance.probability, 1, 2);
721
+ try expectProbability(&chance.probability, 1, 1);
665
722
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
666
723
 
667
- chance.probability.reset();
668
724
  try chance.damage(.P1, 217);
669
725
 
670
- try chance.commit(.P1, .miss);
671
- try expectProbability(&chance.probability, 1, 2);
726
+ try expectProbability(&chance.probability, 1, 1);
672
727
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
673
728
 
674
- chance.probability.reset();
675
-
676
729
  try chance.commit(.P1, .hit);
730
+
677
731
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
678
732
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
679
733
  }
@@ -688,20 +742,14 @@ test "Chance.criticalHit" {
688
742
  try expectProbability(&chance.probability, 1, 1);
689
743
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
690
744
 
691
- try chance.commit(.P1, .hit);
692
- try expectProbability(&chance.probability, 1, 1);
693
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
694
-
695
- chance.hit(true, 128);
745
+ try chance.hit(.P1, true, 128);
696
746
 
697
- try chance.commit(.P1, .hit);
698
- try expectProbability(&chance.probability, 1, 2);
747
+ try expectProbability(&chance.probability, 1, 1);
699
748
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
700
749
 
701
- chance.probability.reset();
702
750
  try chance.damage(.P1, 217);
703
-
704
751
  try chance.commit(.P1, .hit);
752
+
705
753
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
706
754
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
707
755
  }
@@ -712,19 +760,16 @@ test "Chance.damage" {
712
760
 
713
761
  try chance.damage(.P1, 217);
714
762
  if (showdown) {
715
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
763
+ try expectValue(217, chance.actions.p1.damage);
716
764
  try expectProbability(&chance.probability, 1, 39);
717
765
  } else {
718
766
  try expectProbability(&chance.probability, 1, 1);
719
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
720
-
721
- try chance.commit(.P1, .miss);
722
- try expectProbability(&chance.probability, 1, 1);
723
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
767
+ try expectValue(0, chance.actions.p1.damage);
724
768
 
725
769
  try chance.commit(.P1, .hit);
770
+
726
771
  try expectProbability(&chance.probability, 1, 39);
727
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
772
+ try expectValue(217, chance.actions.p1.damage);
728
773
  }
729
774
  }
730
775
 
@@ -776,13 +821,13 @@ test "Chance.moveSlot" {
776
821
 
777
822
  try chance.moveSlot(.P2, 2, &ms, 2);
778
823
  try expectProbability(&chance.probability, 1, 2);
779
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
824
+ try expectValue(2, chance.actions.p2.move_slot);
780
825
 
781
826
  chance.reset();
782
827
 
783
828
  try chance.moveSlot(.P1, 1, &ms, 0);
784
829
  try expectProbability(&chance.probability, 1, 3);
785
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
830
+ try expectValue(1, chance.actions.p1.move_slot);
786
831
  }
787
832
 
788
833
  test "Chance.multiHit" {
@@ -790,127 +835,25 @@ test "Chance.multiHit" {
790
835
 
791
836
  try chance.multiHit(.P1, 3);
792
837
  try expectProbability(&chance.probability, 3, 8);
793
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
838
+ try expectValue(3, chance.actions.p1.multi_hit);
794
839
 
795
840
  chance.reset();
796
841
 
797
842
  try chance.multiHit(.P2, 5);
798
843
  try expectProbability(&chance.probability, 1, 8);
799
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
844
+ try expectValue(5, chance.actions.p2.multi_hit);
800
845
  }
801
846
 
802
847
  test "Chance.duration" {
803
848
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
804
849
 
805
- chance.duration(.sleep, .P1, .P2, 2);
806
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
807
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
850
+ chance.duration(.P1, 2);
851
+ try expectValue(2, chance.actions.p1.duration);
808
852
 
809
853
  chance.reset();
810
854
 
811
- chance.duration(.binding, .P2, .P2, 4);
812
- if (!showdown) {
813
- try expectValue(@as(u4, 0), chance.actions.p2.duration);
814
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
815
- try chance.commit(.P2, .hit);
816
- }
817
- try expectValue(@as(u4, 4), chance.actions.p2.duration);
818
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
819
- }
820
-
821
- test "Chance.sleep" {
822
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
823
-
824
- for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
825
- chance.actions.p1.durations.sleep = @intCast(i);
826
- try chance.sleep(.P1, 0);
827
- try expectProbability(&chance.probability, 1, d);
828
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
829
-
830
- chance.reset();
831
-
832
- if (i < 7) {
833
- chance.actions.p1.durations.sleep = @intCast(i);
834
- try chance.sleep(.P1, 1);
835
- try expectProbability(&chance.probability, d - 1, d);
836
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
837
-
838
- chance.reset();
839
- }
840
- }
841
- }
842
-
843
- test "Chance.confusion" {
844
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
845
-
846
- for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
847
- if (i > 1) {
848
- chance.actions.p2.durations.confusion = @intCast(i);
849
- try chance.confusion(.P2, 0);
850
- try expectProbability(&chance.probability, 1, d);
851
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
852
-
853
- chance.reset();
854
- }
855
-
856
- if (i < 5) {
857
- chance.actions.p2.durations.confusion = @intCast(i);
858
- try chance.confusion(.P2, 1);
859
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
860
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
861
-
862
- chance.reset();
863
- }
864
- }
865
- }
866
-
867
- test "Chance.attacking" {
868
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
869
-
870
- for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
871
- if (i > 1) {
872
- chance.actions.p2.durations.attacking = @intCast(i);
873
- try chance.attacking(.P2, 0);
874
- try expectProbability(&chance.probability, 1, d);
875
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
876
-
877
- chance.reset();
878
- }
879
-
880
- if (i < 3) {
881
- chance.actions.p2.durations.attacking = @intCast(i);
882
- try chance.attacking(.P2, 1);
883
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
884
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
885
-
886
- chance.reset();
887
- }
888
- }
889
- }
890
-
891
- test "Chance.binding" {
892
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
893
-
894
- const ps = [_]u8{ 3, 3, 1, 1 };
895
- const qs = [_]u8{ 8, 5, 2, 1 };
896
-
897
- for (ps, qs, 1..5) |p, q, i| {
898
- chance.actions.p1.durations.binding = @intCast(i);
899
- try chance.binding(.P1, 0);
900
- try expectProbability(&chance.probability, p, q);
901
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
902
-
903
- chance.reset();
904
-
905
- if (i < 4) {
906
- chance.actions.p1.durations.binding = @intCast(i);
907
- try chance.binding(.P1, 1);
908
- try expectProbability(&chance.probability, q - p, q);
909
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
910
-
911
- chance.reset();
912
- }
913
- }
855
+ chance.duration(.P2, 4);
856
+ try expectValue(4, chance.actions.p2.duration);
914
857
  }
915
858
 
916
859
  test "Chance.psywave" {
@@ -918,7 +861,7 @@ test "Chance.psywave" {
918
861
 
919
862
  try chance.psywave(.P2, 100, 150);
920
863
  try expectProbability(&chance.probability, 1, 150);
921
- try expectValue(@as(u8, 101), chance.actions.p2.psywave);
864
+ try expectValue(101, chance.actions.p2.psywave);
922
865
  }
923
866
 
924
867
  test "Chance.metronome" {
@@ -947,7 +890,7 @@ pub fn expectValue(a: anytype, b: anytype) !void {
947
890
 
948
891
  /// Null object pattern implementation of Generation I `Chance` which does nothing, though chance
949
892
  /// tracking should additionally be turned off entirely via `options.chance`.
950
- pub const NULL = Null{};
893
+ pub const NULL: Null = .{};
951
894
 
952
895
  const Null = struct {
953
896
  pub const Error = error{};
@@ -956,24 +899,24 @@ const Null = struct {
956
899
  _ = .{ self, player, kind };
957
900
  }
958
901
 
959
- pub fn clearPending(self: Null) void {
902
+ pub fn glitch(self: Null) void {
960
903
  _ = .{self};
961
904
  }
962
905
 
963
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
964
- _ = .{ self, player, haze };
906
+ pub fn clearPending(self: Null) void {
907
+ _ = .{self};
965
908
  }
966
909
 
967
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
968
- _ = .{ self, player, in, out };
910
+ pub fn switched(self: Null, player: Player, slot: u8) void {
911
+ _ = .{ self, player, slot };
969
912
  }
970
913
 
971
914
  pub fn speedTie(self: Null, p1: bool) Error!void {
972
915
  _ = .{ self, p1 };
973
916
  }
974
917
 
975
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
976
- _ = .{ self, ok, accuracy };
918
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
919
+ _ = .{ self, player, ok, accuracy };
977
920
  }
978
921
 
979
922
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -984,10 +927,6 @@ const Null = struct {
984
927
  _ = .{ self, player, roll };
985
928
  }
986
929
 
987
- pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
988
- _ = .{ self, player, proc, rate };
989
- }
990
-
991
930
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
992
931
  _ = .{ self, player, ok };
993
932
  }
@@ -996,6 +935,10 @@ const Null = struct {
996
935
  _ = .{ self, player, ok };
997
936
  }
998
937
 
938
+ pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
939
+ _ = .{ self, player, proc, rate };
940
+ }
941
+
999
942
  pub fn moveSlot(self: Null, player: Player, slot: u4, ms: []const MoveSlot, n: u4) Error!void {
1000
943
  _ = .{ self, player, slot, ms, n };
1001
944
  }
@@ -1004,34 +947,41 @@ const Null = struct {
1004
947
  _ = .{ self, player, n };
1005
948
  }
1006
949
 
1007
- pub fn duration(
950
+ pub fn duration(self: Null, player: Player, turns: u4) void {
951
+ _ = .{ self, player, turns };
952
+ }
953
+
954
+ pub fn observe(
1008
955
  self: Null,
1009
- comptime field: Duration.Field,
956
+ comptime field: Action.Field,
1010
957
  player: Player,
1011
- target: Player,
1012
- turns: u4,
958
+ obs: Optional(Confusion),
1013
959
  ) void {
1014
- _ = .{ self, field, player, target, turns };
960
+ _ = .{ self, field, player, obs };
1015
961
  }
1016
962
 
1017
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1018
- _ = .{ self, player, turns };
963
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
964
+ _ = .{ self, player, obs };
1019
965
  }
1020
966
 
1021
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1022
- _ = .{ self, player, turns };
967
+ pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
968
+ _ = .{ self, player, obs };
1023
969
  }
1024
970
 
1025
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1026
- _ = .{ self, player, turns };
971
+ pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
972
+ _ = .{ self, player, obs };
1027
973
  }
1028
974
 
1029
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1030
- _ = .{ self, player, turns };
975
+ pub fn bide(self: Null, player: Player, obs: Optional(Observation)) Error!void {
976
+ _ = .{ self, player, obs };
1031
977
  }
1032
978
 
1033
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1034
- _ = .{ self, player, turns };
979
+ pub fn thrashing(self: Null, player: Player, obs: Optional(Observation)) Error!void {
980
+ _ = .{ self, player, obs };
981
+ }
982
+
983
+ pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
984
+ _ = .{ self, player, obs };
1035
985
  }
1036
986
 
1037
987
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {