@pkmn/engine 0.1.0-dev.5570b5c5 → 0.1.0-dev.565de616

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