@pkmn/engine 0.1.0-dev.4f7cbb89 → 0.1.0-dev.51ebf68b

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