@pkmn/engine 0.1.0-dev.c5fe6d07 → 0.1.0-dev.ce4c0b2a

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +46 -19
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +43 -0
  5. package/build/pkg/addon/browser.js.map +1 -0
  6. package/build/pkg/addon/node.d.ts +3 -0
  7. package/build/pkg/addon/node.js +96 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon.d.ts +2 -0
  10. package/build/pkg/addon.js +35 -58
  11. package/build/pkg/addon.js.map +1 -1
  12. package/build/pkg/data/ids.json +12 -12
  13. package/build/pkg/data/layout.json +2 -2
  14. package/build/pkg/data/protocol.json +69 -12
  15. package/build/pkg/data.d.ts +27 -5
  16. package/build/pkg/data.js.map +1 -1
  17. package/build/pkg/gen1.js +3 -3
  18. package/build/pkg/gen1.js.map +1 -1
  19. package/build/pkg/index.d.ts +24 -10
  20. package/build/pkg/index.js +15 -10
  21. package/build/pkg/index.js.map +1 -1
  22. package/build/pkg/protocol.d.ts +20 -1
  23. package/build/pkg/protocol.js +123 -102
  24. package/build/pkg/protocol.js.map +1 -1
  25. package/build/tools/debug.js +36 -68
  26. package/build/tools/debug.js.map +1 -1
  27. package/build/tools/display/debug.jsx +128 -0
  28. package/build/tools/display/debug.jsx.map +1 -0
  29. package/build/tools/display/index.js +132 -0
  30. package/build/tools/display/index.js.map +1 -0
  31. package/build/tools/display/ui.jsx +349 -0
  32. package/build/tools/display/ui.jsx.map +1 -0
  33. package/build/tools/display/util.js +18 -0
  34. package/build/tools/display/util.js.map +1 -0
  35. package/build.zig +230 -99
  36. package/package.json +63 -30
  37. package/src/bin/install-pkmn-engine +29 -19
  38. package/src/bin/pkmn-debug +1 -1
  39. package/src/lib/common/array.zig +77 -0
  40. package/src/lib/common/data.zig +12 -24
  41. package/src/lib/common/debug.zig +11 -6
  42. package/src/lib/common/js.zig +4 -2
  43. package/src/lib/common/optional.zig +21 -10
  44. package/src/lib/common/options.zig +4 -0
  45. package/src/lib/common/protocol.zig +807 -327
  46. package/src/lib/common/rational.zig +125 -31
  47. package/src/lib/common/rng.zig +22 -13
  48. package/src/lib/common/util.zig +50 -1
  49. package/src/lib/gen1/calc.zig +660 -137
  50. package/src/lib/gen1/chance.zig +409 -284
  51. package/src/lib/gen1/data/moves.zig +192 -180
  52. package/src/lib/gen1/data/species.zig +3 -3
  53. package/src/lib/gen1/data/types.zig +7 -7
  54. package/src/lib/gen1/data.zig +48 -59
  55. package/src/lib/gen1/helpers.zig +94 -552
  56. package/src/lib/gen1/mechanics.zig +580 -518
  57. package/src/lib/gen2/calc.zig +161 -0
  58. package/src/lib/gen2/chance.zig +902 -0
  59. package/src/lib/gen2/data/items.zig +12 -18
  60. package/src/lib/gen2/data/moves.zig +663 -569
  61. package/src/lib/gen2/data/species.zig +1 -1
  62. package/src/lib/gen2/data/types.zig +134 -31
  63. package/src/lib/gen2/data.zig +107 -79
  64. package/src/lib/gen2/generated.zig +806 -0
  65. package/src/lib/gen2/helpers.zig +49 -25
  66. package/src/lib/gen2/mechanics.zig +3560 -12
  67. package/src/lib/{binding/node.zig → node.zig} +10 -13
  68. package/src/lib/pkmn.zig +32 -15
  69. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  70. package/src/tools/display/dom.ts +96 -0
  71. package/src/tools/display/ui.css +351 -0
  72. package/build/test/display.js +0 -400
  73. package/build/test/display.js.map +0 -1
  74. package/src/lib/gen1/README.md +0 -892
  75. package/src/lib/gen2/README.md +0 -308
  76. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -11,15 +11,25 @@ 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
 
17
- const Move = @import("data.zig").Move;
18
- const MoveSlot = @import("data.zig").MoveSlot;
18
+ const data = @import("data.zig");
19
19
 
20
20
  const enabled = options.chance;
21
21
  const showdown = options.showdown;
22
22
 
23
+ const PointerType = util.PointerType;
24
+ const isPointerTo = util.isPointerTo;
25
+
26
+ const Move = data.Move;
27
+ const MoveSlot = data.MoveSlot;
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
+
23
33
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
24
34
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
25
35
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -35,19 +45,13 @@ pub const Actions = extern struct {
35
45
  }
36
46
 
37
47
  /// Returns the `Action` for the given `player`.
38
- pub inline fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
39
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
48
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
49
+ assert(isPointerTo(self, Actions));
40
50
  return if (player == .P1) &self.p1 else &self.p2;
41
51
  }
42
52
 
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
53
  /// Returns true if `a` is equal to `b`.
50
- pub inline fn eql(a: Actions, b: Actions) bool {
54
+ pub fn eql(a: Actions, b: Actions) bool {
51
55
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
52
56
  }
53
57
 
@@ -55,20 +59,15 @@ pub const Actions = extern struct {
55
59
  /// same shape if they have the same fields set (though those fields need not necessarily be
56
60
  /// set to the same value).
57
61
  pub fn matches(a: Actions, b: Actions) bool {
58
- inline for (@typeInfo(Actions).Struct.fields) |player| {
59
- 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| {
60
64
  const a_val = @field(@field(a, player.name), field.name);
61
65
  const b_val = @field(@field(b, player.name), field.name);
62
66
 
63
67
  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
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
68
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
70
69
  return false,
71
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
70
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
72
71
  else => unreachable,
73
72
  }
74
73
  }
@@ -94,9 +93,6 @@ test Actions {
94
93
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
95
94
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
96
95
  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
96
 
101
97
  try expect(a.eql(a));
102
98
  try expect(!a.eql(b));
@@ -109,13 +105,25 @@ test Actions {
109
105
  try expect(b.matches(a));
110
106
  try expect(!a.matches(c));
111
107
  try expect(!c.matches(a));
112
- try expect(d.matches(e));
113
- try expect(!d.matches(f));
114
108
  }
115
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
+
116
124
  /// Information about the RNG that was observed during a Generation I battle `update` for a
117
125
  /// single player.
118
- pub const Action = packed struct {
126
+ pub const Action = packed struct(u64) {
119
127
  /// If not 0, the roll to be returned Rolls.damage.
120
128
  damage: u8 = 0,
121
129
 
@@ -137,12 +145,25 @@ pub const Action = packed struct {
137
145
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
138
146
  duration: u4 = 0,
139
147
 
140
- /// Observed values of various durations. Does not influence future RNG calls. TODO
141
- 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,
142
160
 
143
161
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
144
162
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
145
163
  move_slot: u4 = 0,
164
+
165
+ /// TODO
166
+ pp: u4 = 0,
146
167
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
147
168
  multi_hit: u4 = 0,
148
169
 
@@ -152,19 +173,8 @@ pub const Action = packed struct {
152
173
  /// If not None, the Move to return for Rolls.metronome.
153
174
  metronome: Move = .None,
154
175
 
155
- pub const DURATIONS: u64 = 0x000000FFFF000000;
156
-
157
176
  pub const Field = std.meta.FieldEnum(Action);
158
177
 
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
178
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
169
179
  _ = .{ f, o };
170
180
  try fmt(a, w, false);
@@ -173,22 +183,10 @@ pub const Action = packed struct {
173
183
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
174
184
  try writer.writeByte('(');
175
185
  var printed = false;
176
- inline for (@typeInfo(Action).Struct.fields) |field| {
186
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
177
187
  const val = @field(self, field.name);
178
188
  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
- .Enum => if (val != .None) {
189
+ Enum => if (val != .None) {
192
190
  if (printed) try writer.writeAll(", ");
193
191
  if (shape) {
194
192
  try writer.print("{s}:?", .{field.name});
@@ -197,12 +195,19 @@ pub const Action = packed struct {
197
195
  if (val == .false) "!" else "",
198
196
  field.name,
199
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
+ });
200
205
  } else {
201
206
  try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
202
207
  }
203
208
  printed = true;
204
209
  },
205
- .Int => if (val != 0) {
210
+ Int => if (val != 0) {
206
211
  if (printed) try writer.writeAll(", ");
207
212
  if (shape) {
208
213
  try writer.print("{s}:?", .{field.name});
@@ -218,47 +223,82 @@ pub const Action = packed struct {
218
223
  }
219
224
  };
220
225
 
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
226
  /// TODO
229
- pub const Durations = extern struct {
230
- /// Information about the durations for Player 1.
227
+ pub const Durations = struct {
228
+ /// TODO
231
229
  p1: Duration = .{},
232
- /// Information about the durations for Player 2.
230
+ /// TODO
233
231
  p2: Duration = .{},
234
232
 
235
233
  comptime {
236
- 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 });
237
251
  }
238
252
  };
239
253
 
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.
254
+ pub const Sleeps = Array(6, u3);
255
+
256
+ /// TODO
257
+ pub const Duration = packed struct(u32) {
258
+ sleeps: Sleeps.T = 0,
246
259
  confusion: u3 = 0,
247
- /// The number of turns a Pokémon has been observed to be disabled.
248
260
  disable: u4 = 0,
249
- /// The number of turns a Pokémon has been observed to be attacking.
250
261
  attacking: u3 = 0,
251
- /// The number of turns a Pokémon has been observed to be binding their opponent.
252
262
  binding: u3 = 0,
253
263
 
254
- pub const Field = std.meta.FieldEnum(Duration);
264
+ _: u1 = 0,
255
265
 
256
- comptime {
257
- assert(@sizeOf(Duration) == 2);
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(')');
258
296
  }
259
297
  };
260
298
 
261
- 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;
262
302
 
263
303
  /// Tracks chance actions and their associated probability during a Generation I battle update when
264
304
  /// `options.chance` is enabled.
@@ -271,14 +311,8 @@ pub fn Chance(comptime Rational: type) type {
271
311
  /// The actions taken by a hypothetical "chance player" that convey information about which
272
312
  /// RNG events were observed during a battle `update`.
273
313
  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 },
314
+ /// TODO
315
+ durations: Durations = .{},
282
316
 
283
317
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
284
318
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -296,16 +330,13 @@ pub fn Chance(comptime Rational: type) type {
296
330
  //
297
331
  // Finally, because each player's actions are processed sequentially we only need a single
298
332
  // 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 {
333
+ pending: if (showdown) struct {} else struct {
303
334
  crit: bool = false,
304
335
  crit_probablity: u8 = 0,
305
336
  damage_roll: u8 = 0,
306
337
  hit: bool = false,
307
- hit_probablity: u8 = 0,
308
- binding: u8 = 0,
338
+ hit_probablity: uN = 0,
339
+ glitch: bool = false,
309
340
  } = .{},
310
341
 
311
342
  /// Possible error returned by operations tracking chance probability.
@@ -313,86 +344,90 @@ pub fn Chance(comptime Rational: type) type {
313
344
 
314
345
  /// Convenience helper to clear fields which typically should be cleared between updates.
315
346
  pub fn reset(self: *Self) void {
347
+ if (!enabled) return;
348
+
316
349
  self.probability.reset();
317
- self.actions.reset();
350
+ self.actions = .{};
318
351
  self.pending = .{};
319
352
  }
320
353
 
321
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
+
322
361
  var action = self.actions.get(player);
323
362
 
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);
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
+ }
335
368
 
336
369
  // Always commit the hit result if we make it here (commit won't be called at all if the
337
- // target is immune/behind a sub/etc)
370
+ // target is immune*/behind a sub/etc)
338
371
  if (self.pending.hit_probablity != 0) {
339
372
  try self.probability.update(self.pending.hit_probablity, 256);
340
373
  action.hit = if (self.pending.hit) .true else .false;
341
374
  }
342
375
 
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);
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
+ }
350
388
 
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
- }
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);
355
393
 
394
+ if (self.pending.damage_roll > 0) {
356
395
  try self.probability.update(1, 39);
357
396
  action.damage = self.pending.damage_roll;
358
397
  }
359
398
  }
360
399
 
361
- pub fn clearPending(self: *Self) void {
400
+ pub fn glitch(self: *Self) void {
362
401
  if (!enabled) return;
363
402
 
364
- self.pending = .{};
403
+ assert(!showdown);
404
+ self.pending.glitch = true;
365
405
  }
366
406
 
367
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
407
+ pub fn clearPending(self: *Self) void {
368
408
  if (!enabled) return;
369
409
 
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
- }
410
+ assert(!showdown);
411
+ self.pending = .{};
379
412
  }
380
413
 
381
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
414
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
382
415
  if (!enabled) return;
383
416
 
384
- assert(in >= 1 and in <= 6);
385
- assert(out >= 1 and out <= 6);
417
+ assert(slot >= 1 and slot <= 6);
386
418
 
387
- var action = self.actions.get(player);
419
+ var d = self.durations.get(player);
388
420
 
389
- const slp = action.durations.sleep;
390
- 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);
391
424
 
392
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
393
- 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;
394
429
 
395
- self.actions.get(player.foe()).durations.binding = 0;
430
+ self.durations.get(player.foe()).binding = 0;
396
431
  }
397
432
 
398
433
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -403,12 +438,17 @@ pub fn Chance(comptime Rational: type) type {
403
438
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
404
439
  }
405
440
 
406
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
441
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
407
442
  if (!enabled) return;
408
443
 
409
444
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
410
- self.pending.hit = ok;
411
- 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
+ }
412
452
  }
413
453
 
414
454
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -486,108 +526,172 @@ pub fn Chance(comptime Rational: type) type {
486
526
  self.actions.get(player).multi_hit = n;
487
527
  }
488
528
 
489
- 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(
490
536
  self: *Self,
491
- comptime field: Duration.Field,
537
+ comptime field: Action.Field,
492
538
  player: Player,
493
- target: Player,
494
- turns: u4,
539
+ obs: Optional(Confusion),
495
540
  ) void {
496
541
  if (!enabled) return;
497
542
 
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;
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,
506
578
  }
507
579
  }
508
580
 
509
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
581
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
510
582
  if (!enabled) return;
511
583
 
512
- var durations = &self.actions.get(player).durations;
513
- const n = durations.sleep;
514
- 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) {
515
594
  assert(n >= 1 and n <= 7);
516
595
  if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
517
- durations.sleep = 0;
596
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
518
597
  } else {
598
+ assert(obs == .continuing);
519
599
  assert(n >= 1 and n < 7);
520
600
  try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
521
- durations.sleep += 1;
601
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
522
602
  }
523
603
  }
524
604
 
525
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
605
+ pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
526
606
  if (!enabled) return;
527
607
 
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;
538
- }
539
- }
608
+ var a = self.actions.get(player);
609
+ assert(a.confusion == .None or a.confusion == .started);
610
+ a.confusion = obs;
540
611
 
541
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
542
- if (!enabled) return;
612
+ var d = self.durations.get(player);
613
+ const n = d.confusion;
543
614
 
544
- var durations = &self.actions.get(player).durations;
545
- const n = durations.confusion;
546
- if (turns == 0) {
615
+ if (obs == .ended) {
547
616
  assert(n >= 2 and n <= 5);
548
617
  if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
549
- durations.confusion = 0;
618
+ d.confusion = 0;
550
619
  } else {
620
+ assert(obs == .continuing);
551
621
  assert(n >= 1 and n < 5);
552
622
  if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
553
- durations.confusion += 1;
623
+ d.confusion += 1;
554
624
  }
555
625
  }
556
626
 
557
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
627
+ pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
558
628
  if (!enabled) return;
559
629
 
560
- var durations = &self.actions.get(player).durations;
561
- const n = durations.attacking;
562
- 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) {
638
+ assert(n >= 1 and n <= 8);
639
+ if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
640
+ d.disable = 0;
641
+ } else {
642
+ assert(obs == .continuing);
643
+ assert(n >= 1 and n < 8);
644
+ try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
645
+ d.disable += 1;
646
+ }
647
+ }
648
+
649
+ pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
650
+ if (!enabled) return;
651
+
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) {
659
+ // "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
563
660
  assert(n >= 2 and n <= 3);
564
661
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
565
- durations.attacking = 0;
662
+ d.attacking = 0;
566
663
  } else {
664
+ assert(obs == .continuing);
567
665
  assert(n >= 1 and n < 3);
568
666
  if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
569
- durations.attacking += 1;
667
+ d.attacking += 1;
570
668
  }
571
669
  }
572
670
 
573
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
671
+ pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
574
672
  if (!enabled) return;
575
673
 
576
- var durations = &self.actions.get(player).durations;
577
- const n = durations.binding;
674
+ var a = self.actions.get(player);
675
+ assert(a.binding == .None or a.binding == .started);
676
+ a.binding = obs;
677
+
678
+ var d = self.durations.get(player);
679
+ const n = d.binding;
578
680
 
579
681
  assert(n > 0);
580
682
  const p: u4 = if (n < 3) 3 else 1;
581
683
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
582
684
 
583
- if (turns == 0) {
685
+ if (obs == .ended) {
686
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
584
687
  assert(n >= 1 and n <= 4);
585
688
  if (n != 4) try self.probability.update(p, q);
586
- durations.binding = 0;
689
+ d.binding = 0;
587
690
  } else {
691
+ assert(obs == .continuing);
588
692
  assert(n >= 1 and n < 4);
589
693
  try self.probability.update(q - p, q);
590
- durations.binding += 1;
694
+ d.binding += 1;
591
695
  }
592
696
  }
593
697
 
@@ -601,9 +705,18 @@ pub fn Chance(comptime Rational: type) type {
601
705
  pub fn metronome(self: *Self, player: Player, move: Move) Error!void {
602
706
  if (!enabled) return;
603
707
 
604
- try self.probability.update(1, Move.size - 2);
708
+ try self.probability.update(1, Move.METRONOME.len);
605
709
  self.actions.get(player).metronome = move;
606
710
  }
711
+
712
+ pub fn pp(self: *Self, player: Player, n: u4) bool {
713
+ if (!enabled) return true;
714
+
715
+ var a = self.actions.get(player);
716
+ if (n > 1 and a.pp == n) return false;
717
+ if (a.metronome != .None) a.pp += n;
718
+ return true;
719
+ }
607
720
  };
608
721
  }
609
722
 
@@ -626,19 +739,25 @@ test "Chance.speedTie" {
626
739
  test "Chance.hit" {
627
740
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
628
741
 
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);
742
+ try chance.hit(.P1, true, 229);
743
+ if (!showdown) {
744
+ try expectProbability(&chance.probability, 1, 1);
745
+ try expectValue(Optional(bool).None, chance.actions.p1.hit);
746
+
747
+ try chance.commit(.P1, .hit);
748
+ }
633
749
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
634
750
  try expectProbability(&chance.probability, 229, 256);
635
751
 
636
752
  chance.reset();
637
753
 
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);
754
+ try chance.hit(.P2, false, 255);
755
+ if (!showdown) {
756
+ try expectProbability(&chance.probability, 1, 1);
757
+ try expectValue(Optional(bool).None, chance.actions.p2.hit);
758
+
759
+ try chance.commit(.P2, .miss);
760
+ }
642
761
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
643
762
  try expectProbability(&chance.probability, 1, 256);
644
763
  }
@@ -654,26 +773,18 @@ test "Chance.criticalHit" {
654
773
  try expectProbability(&chance.probability, 1, 1);
655
774
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
656
775
 
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);
776
+ try chance.hit(.P1, true, 128);
662
777
 
663
- try chance.commit(.P1, .hit);
664
- try expectProbability(&chance.probability, 1, 2);
778
+ try expectProbability(&chance.probability, 1, 1);
665
779
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
666
780
 
667
- chance.probability.reset();
668
781
  try chance.damage(.P1, 217);
669
782
 
670
- try chance.commit(.P1, .miss);
671
- try expectProbability(&chance.probability, 1, 2);
783
+ try expectProbability(&chance.probability, 1, 1);
672
784
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
673
785
 
674
- chance.probability.reset();
675
-
676
786
  try chance.commit(.P1, .hit);
787
+
677
788
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
678
789
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
679
790
  }
@@ -688,20 +799,14 @@ test "Chance.criticalHit" {
688
799
  try expectProbability(&chance.probability, 1, 1);
689
800
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
690
801
 
691
- try chance.commit(.P1, .hit);
692
- try expectProbability(&chance.probability, 1, 1);
693
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
802
+ try chance.hit(.P1, true, 128);
694
803
 
695
- chance.hit(true, 128);
696
-
697
- try chance.commit(.P1, .hit);
698
- try expectProbability(&chance.probability, 1, 2);
804
+ try expectProbability(&chance.probability, 1, 1);
699
805
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
700
806
 
701
- chance.probability.reset();
702
807
  try chance.damage(.P1, 217);
703
-
704
808
  try chance.commit(.P1, .hit);
809
+
705
810
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
706
811
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
707
812
  }
@@ -718,11 +823,8 @@ test "Chance.damage" {
718
823
  try expectProbability(&chance.probability, 1, 1);
719
824
  try expectValue(@as(u8, 0), chance.actions.p1.damage);
720
825
 
721
- try chance.commit(.P1, .miss);
722
- try expectProbability(&chance.probability, 1, 1);
723
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
724
-
725
826
  try chance.commit(.P1, .hit);
827
+
726
828
  try expectProbability(&chance.probability, 1, 39);
727
829
  try expectValue(@as(u8, 217), chance.actions.p1.damage);
728
830
  }
@@ -802,38 +904,31 @@ test "Chance.multiHit" {
802
904
  test "Chance.duration" {
803
905
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
804
906
 
805
- chance.duration(.sleep, .P1, .P2, 2);
907
+ chance.duration(.P1, 2);
806
908
  try expectValue(@as(u4, 2), chance.actions.p1.duration);
807
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
808
909
 
809
910
  chance.reset();
810
911
 
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
- }
912
+ chance.duration(.P2, 4);
817
913
  try expectValue(@as(u4, 4), chance.actions.p2.duration);
818
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
819
914
  }
820
915
 
821
916
  test "Chance.sleep" {
822
917
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
823
918
 
824
919
  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);
920
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
921
+ try chance.sleep(.P1, .ended);
827
922
  try expectProbability(&chance.probability, 1, d);
828
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
923
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
829
924
 
830
925
  chance.reset();
831
926
 
832
927
  if (i < 7) {
833
- chance.actions.p1.durations.sleep = @intCast(i);
834
- try chance.sleep(.P1, 1);
928
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
929
+ try chance.sleep(.P1, .continuing);
835
930
  try expectProbability(&chance.probability, d - 1, d);
836
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
931
+ try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
837
932
 
838
933
  chance.reset();
839
934
  }
@@ -845,19 +940,41 @@ test "Chance.confusion" {
845
940
 
846
941
  for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
847
942
  if (i > 1) {
848
- chance.actions.p2.durations.confusion = @intCast(i);
849
- try chance.confusion(.P2, 0);
943
+ chance.durations.p2.confusion = @intCast(i);
944
+ try chance.confusion(.P2, .ended);
850
945
  try expectProbability(&chance.probability, 1, d);
851
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
946
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
852
947
 
853
948
  chance.reset();
854
949
  }
855
950
 
856
951
  if (i < 5) {
857
- chance.actions.p2.durations.confusion = @intCast(i);
858
- try chance.confusion(.P2, 1);
952
+ chance.durations.p2.confusion = @intCast(i);
953
+ try chance.confusion(.P2, .continuing);
859
954
  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);
955
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
956
+
957
+ chance.reset();
958
+ }
959
+ }
960
+ }
961
+
962
+ test "Chance.disable" {
963
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
964
+
965
+ for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
966
+ chance.durations.p1.disable = @intCast(i);
967
+ try chance.disable(.P1, .ended);
968
+ try expectProbability(&chance.probability, 1, d);
969
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
970
+
971
+ chance.reset();
972
+
973
+ if (i < 8) {
974
+ chance.durations.p1.disable = @intCast(i);
975
+ try chance.disable(.P1, .continuing);
976
+ try expectProbability(&chance.probability, d - 1, d);
977
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
861
978
 
862
979
  chance.reset();
863
980
  }
@@ -869,19 +986,19 @@ test "Chance.attacking" {
869
986
 
870
987
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
871
988
  if (i > 1) {
872
- chance.actions.p2.durations.attacking = @intCast(i);
873
- try chance.attacking(.P2, 0);
989
+ chance.durations.p2.attacking = @intCast(i);
990
+ try chance.attacking(.P2, .ended);
874
991
  try expectProbability(&chance.probability, 1, d);
875
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
992
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
876
993
 
877
994
  chance.reset();
878
995
  }
879
996
 
880
997
  if (i < 3) {
881
- chance.actions.p2.durations.attacking = @intCast(i);
882
- try chance.attacking(.P2, 1);
998
+ chance.durations.p2.attacking = @intCast(i);
999
+ try chance.attacking(.P2, .continuing);
883
1000
  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);
1001
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
885
1002
 
886
1003
  chance.reset();
887
1004
  }
@@ -895,18 +1012,18 @@ test "Chance.binding" {
895
1012
  const qs = [_]u8{ 8, 5, 2, 1 };
896
1013
 
897
1014
  for (ps, qs, 1..5) |p, q, i| {
898
- chance.actions.p1.durations.binding = @intCast(i);
899
- try chance.binding(.P1, 0);
1015
+ chance.durations.p1.binding = @intCast(i);
1016
+ try chance.binding(.P1, .ended);
900
1017
  try expectProbability(&chance.probability, p, q);
901
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
1018
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
902
1019
 
903
1020
  chance.reset();
904
1021
 
905
1022
  if (i < 4) {
906
- chance.actions.p1.durations.binding = @intCast(i);
907
- try chance.binding(.P1, 1);
1023
+ chance.durations.p1.binding = @intCast(i);
1024
+ try chance.binding(.P1, .continuing);
908
1025
  try expectProbability(&chance.probability, q - p, q);
909
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
1026
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
910
1027
 
911
1028
  chance.reset();
912
1029
  }
@@ -947,7 +1064,7 @@ pub fn expectValue(a: anytype, b: anytype) !void {
947
1064
 
948
1065
  /// Null object pattern implementation of Generation I `Chance` which does nothing, though chance
949
1066
  /// tracking should additionally be turned off entirely via `options.chance`.
950
- pub const NULL = Null{};
1067
+ pub const NULL: Null = .{};
951
1068
 
952
1069
  const Null = struct {
953
1070
  pub const Error = error{};
@@ -956,24 +1073,24 @@ const Null = struct {
956
1073
  _ = .{ self, player, kind };
957
1074
  }
958
1075
 
959
- pub fn clearPending(self: Null) void {
1076
+ pub fn glitch(self: Null) void {
960
1077
  _ = .{self};
961
1078
  }
962
1079
 
963
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
964
- _ = .{ self, player, haze };
1080
+ pub fn clearPending(self: Null) void {
1081
+ _ = .{self};
965
1082
  }
966
1083
 
967
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
968
- _ = .{ self, player, in, out };
1084
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1085
+ _ = .{ self, player, slot };
969
1086
  }
970
1087
 
971
1088
  pub fn speedTie(self: Null, p1: bool) Error!void {
972
1089
  _ = .{ self, p1 };
973
1090
  }
974
1091
 
975
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
976
- _ = .{ self, ok, accuracy };
1092
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
1093
+ _ = .{ self, player, ok, accuracy };
977
1094
  }
978
1095
 
979
1096
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -984,10 +1101,6 @@ const Null = struct {
984
1101
  _ = .{ self, player, roll };
985
1102
  }
986
1103
 
987
- pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
988
- _ = .{ self, player, proc, rate };
989
- }
990
-
991
1104
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
992
1105
  _ = .{ self, player, ok };
993
1106
  }
@@ -996,6 +1109,10 @@ const Null = struct {
996
1109
  _ = .{ self, player, ok };
997
1110
  }
998
1111
 
1112
+ pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
1113
+ _ = .{ self, player, proc, rate };
1114
+ }
1115
+
999
1116
  pub fn moveSlot(self: Null, player: Player, slot: u4, ms: []const MoveSlot, n: u4) Error!void {
1000
1117
  _ = .{ self, player, slot, ms, n };
1001
1118
  }
@@ -1004,34 +1121,37 @@ const Null = struct {
1004
1121
  _ = .{ self, player, n };
1005
1122
  }
1006
1123
 
1007
- pub fn duration(
1124
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1125
+ _ = .{ self, player, turns };
1126
+ }
1127
+
1128
+ pub fn observe(
1008
1129
  self: Null,
1009
- comptime field: Duration.Field,
1130
+ comptime field: Action.Field,
1010
1131
  player: Player,
1011
- target: Player,
1012
- turns: u4,
1132
+ obs: Optional(Confusion),
1013
1133
  ) void {
1014
- _ = .{ self, field, player, target, turns };
1134
+ _ = .{ self, field, player, obs };
1015
1135
  }
1016
1136
 
1017
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1018
- _ = .{ self, player, turns };
1137
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1138
+ _ = .{ self, player, obs };
1019
1139
  }
1020
1140
 
1021
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1022
- _ = .{ self, player, turns };
1141
+ pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
1142
+ _ = .{ self, player, obs };
1023
1143
  }
1024
1144
 
1025
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1026
- _ = .{ self, player, turns };
1145
+ pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1146
+ _ = .{ self, player, obs };
1027
1147
  }
1028
1148
 
1029
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1030
- _ = .{ self, player, turns };
1149
+ pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1150
+ _ = .{ self, player, obs };
1031
1151
  }
1032
1152
 
1033
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1034
- _ = .{ self, player, turns };
1153
+ pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1154
+ _ = .{ self, player, obs };
1035
1155
  }
1036
1156
 
1037
1157
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -1041,4 +1161,9 @@ const Null = struct {
1041
1161
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
1042
1162
  _ = .{ self, player, move };
1043
1163
  }
1164
+
1165
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1166
+ _ = .{ self, player, n };
1167
+ return true;
1168
+ }
1044
1169
  };