@pkmn/engine 0.1.0-dev.f918c3bd → 0.1.0-dev.fb6653d6

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 (83) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +51 -23
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +44 -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 +97 -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 +25 -3
  13. package/build/pkg/addon.js +39 -71
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/ids.json +12 -12
  16. package/build/pkg/data/layout.json +17 -4
  17. package/build/pkg/data/protocol.json +23 -9
  18. package/build/pkg/data.d.ts +27 -5
  19. package/build/pkg/data.js.map +1 -1
  20. package/build/pkg/gen1.d.ts +13 -17
  21. package/build/pkg/gen1.js +8 -12
  22. package/build/pkg/gen1.js.map +1 -1
  23. package/build/pkg/index.d.ts +40 -91
  24. package/build/pkg/index.js +6 -63
  25. package/build/pkg/index.js.map +1 -1
  26. package/build/pkg/protocol.d.ts +20 -1
  27. package/build/pkg/protocol.js +109 -107
  28. package/build/pkg/protocol.js.map +1 -1
  29. package/build/tools/debug.js +36 -69
  30. package/build/tools/debug.js.map +1 -1
  31. package/build/tools/display/debug.jsx +141 -0
  32. package/build/tools/display/debug.jsx.map +1 -0
  33. package/build/tools/display/index.js +142 -0
  34. package/build/tools/display/index.js.map +1 -0
  35. package/build/tools/display/ui.jsx +349 -0
  36. package/build/tools/display/ui.jsx.map +1 -0
  37. package/build/tools/display/util.js +18 -0
  38. package/build/tools/display/util.js.map +1 -0
  39. package/build.zig +303 -101
  40. package/package.json +61 -132
  41. package/src/bin/install-pkmn-engine +30 -20
  42. package/src/bin/pkmn-debug +1 -1
  43. package/src/lib/{binding → bindings}/node.zig +8 -21
  44. package/src/lib/bindings/wasm.zig +58 -0
  45. package/src/lib/common/array.zig +77 -0
  46. package/src/lib/common/data.zig +8 -8
  47. package/src/lib/common/debug.zig +39 -8
  48. package/src/lib/common/js.zig +4 -2
  49. package/src/lib/common/optional.zig +21 -10
  50. package/src/lib/common/options.zig +6 -0
  51. package/src/lib/common/protocol.zig +421 -393
  52. package/src/lib/common/rational.zig +131 -35
  53. package/src/lib/common/rng.zig +16 -9
  54. package/src/lib/common/util.zig +50 -1
  55. package/src/lib/gen1/calc.zig +687 -160
  56. package/src/lib/gen1/chance.zig +434 -307
  57. package/src/lib/gen1/data/moves.zig +26 -14
  58. package/src/lib/gen1/data/species.zig +4 -6
  59. package/src/lib/gen1/data/types.zig +7 -8
  60. package/src/lib/gen1/data.zig +44 -47
  61. package/src/lib/gen1/helpers.zig +91 -562
  62. package/src/lib/gen1/mechanics.zig +641 -557
  63. package/src/lib/gen2/calc.zig +153 -0
  64. package/src/lib/gen2/chance.zig +902 -0
  65. package/src/lib/gen2/data/items.zig +13 -21
  66. package/src/lib/gen2/data/moves.zig +67 -47
  67. package/src/lib/gen2/data/species.zig +2 -4
  68. package/src/lib/gen2/data/types.zig +134 -32
  69. package/src/lib/gen2/data.zig +56 -54
  70. package/src/lib/gen2/generated.zig +801 -0
  71. package/src/lib/gen2/helpers.zig +46 -25
  72. package/src/lib/gen2/mechanics.zig +2778 -362
  73. package/src/lib/node.zig +18 -0
  74. package/src/lib/pkmn.zig +37 -15
  75. package/src/lib/wasm.zig +17 -0
  76. package/src/tools/display/dom.ts +96 -0
  77. package/src/tools/display/ui.css +354 -0
  78. package/build/test/display.js +0 -400
  79. package/build/test/display.js.map +0 -1
  80. package/src/lib/binding/wasm.zig +0 -12
  81. package/src/lib/gen1/README.md +0 -894
  82. package/src/lib/gen2/README.md +0 -328
  83. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,25 +1,32 @@
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 Move = @import("data.zig").Move;
18
- const MoveSlot = @import("data.zig").MoveSlot;
19
-
12
+ const Array = array.Array;
13
+ const assert = std.debug.assert;
20
14
  const enabled = options.chance;
15
+ const expect = std.testing.expect;
16
+ const expectEqual = std.testing.expectEqual;
17
+ const isPointerTo = util.isPointerTo;
18
+ const Move = data.Move;
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;
21
24
  const showdown = options.showdown;
22
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;
29
+
23
30
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
24
31
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
25
32
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -35,19 +42,13 @@ pub const Actions = extern struct {
35
42
  }
36
43
 
37
44
  /// Returns the `Action` for the given `player`.
38
- pub inline fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
39
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
45
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
46
+ assert(isPointerTo(self, Actions));
40
47
  return if (player == .P1) &self.p1 else &self.p2;
41
48
  }
42
49
 
43
- /// Perform a reset by clearing fields which should not persist across updates.
44
- pub inline fn reset(self: *Actions) void {
45
- self.p1.reset();
46
- self.p2.reset();
47
- }
48
-
49
50
  /// Returns true if `a` is equal to `b`.
50
- pub inline fn eql(a: Actions, b: Actions) bool {
51
+ pub fn eql(a: Actions, b: Actions) bool {
51
52
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
52
53
  }
53
54
 
@@ -55,20 +56,15 @@ pub const Actions = extern struct {
55
56
  /// same shape if they have the same fields set (though those fields need not necessarily be
56
57
  /// set to the same value).
57
58
  pub fn matches(a: Actions, b: Actions) bool {
58
- inline for (@typeInfo(Actions).Struct.fields) |player| {
59
- 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| {
60
61
  const a_val = @field(@field(a, player.name), field.name);
61
62
  const b_val = @field(@field(b, player.name), field.name);
62
63
 
63
64
  switch (@typeInfo(@TypeOf(a_val))) {
64
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
65
- if ((@field(a_val, f.name) > 0) != (@field(b_val, f.name) > 0)) {
66
- return false;
67
- }
68
- },
69
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
65
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
70
66
  return false,
71
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
67
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
72
68
  else => unreachable,
73
69
  }
74
70
  }
@@ -76,6 +72,25 @@ pub const Actions = extern struct {
76
72
  return true;
77
73
  }
78
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
+
79
94
  pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
80
95
  try writer.writeAll("<P1 = ");
81
96
  try self.p1.fmt(writer, shape);
@@ -94,9 +109,6 @@ test Actions {
94
109
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
95
110
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
96
111
  const c: Actions = .{ .p1 = .{ .hit = .true } };
97
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
98
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
99
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
100
112
 
101
113
  try expect(a.eql(a));
102
114
  try expect(!a.eql(b));
@@ -109,10 +121,22 @@ test Actions {
109
121
  try expect(b.matches(a));
110
122
  try expect(!a.matches(c));
111
123
  try expect(!c.matches(a));
112
- try expect(d.matches(e));
113
- try expect(!d.matches(f));
114
124
  }
115
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
+
116
140
  /// Information about the RNG that was observed during a Generation I battle `update` for a
117
141
  /// single player.
118
142
  pub const Action = packed struct(u64) {
@@ -137,12 +161,25 @@ pub const Action = packed struct(u64) {
137
161
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
138
162
  duration: u4 = 0,
139
163
 
140
- /// Observed values of various durations. Does not influence future RNG calls. TODO
141
- 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,
142
176
 
143
177
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
144
178
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
145
179
  move_slot: u4 = 0,
180
+
181
+ /// TODO
182
+ pp: u4 = 0,
146
183
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
147
184
  multi_hit: u4 = 0,
148
185
 
@@ -152,15 +189,8 @@ pub const Action = packed struct(u64) {
152
189
  /// If not None, the Move to return for Rolls.metronome.
153
190
  metronome: Move = .None,
154
191
 
155
- pub const DURATIONS: u64 = 0x000000FFFF000000;
156
-
157
192
  pub const Field = std.meta.FieldEnum(Action);
158
193
 
159
- /// Perform a reset by clearing fields which should not persist across updates.
160
- pub inline fn reset(self: *Action) void {
161
- self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
162
- }
163
-
164
194
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
165
195
  _ = .{ f, o };
166
196
  try fmt(a, w, false);
@@ -169,22 +199,10 @@ pub const Action = packed struct(u64) {
169
199
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
170
200
  try writer.writeByte('(');
171
201
  var printed = false;
172
- inline for (@typeInfo(Action).Struct.fields) |field| {
202
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
173
203
  const val = @field(self, field.name);
174
204
  switch (@typeInfo(@TypeOf(val))) {
175
- .Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
176
- const v = @field(val, f.name);
177
- if (v != 0) {
178
- if (printed) try writer.writeAll(", ");
179
- if (shape) {
180
- try writer.print("{s}:?", .{f.name});
181
- } else {
182
- try writer.print("{s}:{d}", .{ f.name, v });
183
- }
184
- printed = true;
185
- }
186
- },
187
- .Enum => if (val != .None) {
205
+ Enum => if (val != .None) {
188
206
  if (printed) try writer.writeAll(", ");
189
207
  if (shape) {
190
208
  try writer.print("{s}:?", .{field.name});
@@ -193,12 +211,19 @@ pub const Action = packed struct(u64) {
193
211
  if (val == .false) "!" else "",
194
212
  field.name,
195
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
+ });
196
221
  } else {
197
222
  try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
198
223
  }
199
224
  printed = true;
200
225
  },
201
- .Int => if (val != 0) {
226
+ Int => if (val != 0) {
202
227
  if (printed) try writer.writeAll(", ");
203
228
  if (shape) {
204
229
  try writer.print("{s}:?", .{field.name});
@@ -214,43 +239,82 @@ pub const Action = packed struct(u64) {
214
239
  }
215
240
  };
216
241
 
217
- test Action {
218
- var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
219
- a.reset();
220
-
221
- try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
222
- }
223
-
224
242
  /// TODO
225
243
  pub const Durations = extern struct {
226
- /// Information about the durations for Player 1.
244
+ /// TODO
227
245
  p1: Duration = .{},
228
- /// Information about the durations for Player 2.
246
+ /// TODO
229
247
  p2: Duration = .{},
230
248
 
231
249
  comptime {
232
- 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 });
233
267
  }
234
268
  };
235
269
 
236
- /// Observed values for various durations that need to be tracked in order to properly
237
- /// deduplicate transitions with a primary key. TODO
238
- pub const Duration = packed struct(u16) {
239
- /// The number of turns a Pokémon has been observed to be sleeping.
240
- sleep: u3 = 0,
241
- /// 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,
242
275
  confusion: u3 = 0,
243
- /// The number of turns a Pokémon has been observed to be disabled.
244
276
  disable: u4 = 0,
245
- /// The number of turns a Pokémon has been observed to be attacking.
246
277
  attacking: u3 = 0,
247
- /// The number of turns a Pokémon has been observed to be binding their opponent.
248
278
  binding: u3 = 0,
249
279
 
250
- 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
+ }
251
313
  };
252
314
 
253
- 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;
254
318
 
255
319
  /// Tracks chance actions and their associated probability during a Generation I battle update when
256
320
  /// `options.chance` is enabled.
@@ -263,14 +327,8 @@ pub fn Chance(comptime Rational: type) type {
263
327
  /// The actions taken by a hypothetical "chance player" that convey information about which
264
328
  /// RNG events were observed during a battle `update`.
265
329
  actions: Actions = .{},
266
-
267
- // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
268
- // other durations which are all tied to volatiles, sleep's counter persists through
269
- // switching and so we must store it here. The indices of these arrays correspond to the
270
- // `order` field of a Side. This information could be stored in actions but size is a
271
- // concern so this is tracked separately as actions only purports to track RNG events
272
- // observed during a single `update` (not across updates).
273
- sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
330
+ /// TODO
331
+ durations: Durations = .{},
274
332
 
275
333
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
276
334
  // later checks render their result irrelevant (missing when the target is actually immune,
@@ -288,16 +346,13 @@ pub fn Chance(comptime Rational: type) type {
288
346
  //
289
347
  // Finally, because each player's actions are processed sequentially we only need a single
290
348
  // shared structure to track this information (though it needs to be cleared appropriately)
291
- pending: if (showdown) struct {
292
- hit: bool = false,
293
- hit_probablity: u8 = 0,
294
- } else struct {
349
+ pending: if (showdown) struct {} else struct {
295
350
  crit: bool = false,
296
351
  crit_probablity: u8 = 0,
297
352
  damage_roll: u8 = 0,
298
353
  hit: bool = false,
299
- hit_probablity: u8 = 0,
300
- binding: u8 = 0,
354
+ hit_probablity: uN = 0,
355
+ glitch: bool = false,
301
356
  } = .{},
302
357
 
303
358
  /// Possible error returned by operations tracking chance probability.
@@ -305,86 +360,90 @@ pub fn Chance(comptime Rational: type) type {
305
360
 
306
361
  /// Convenience helper to clear fields which typically should be cleared between updates.
307
362
  pub fn reset(self: *Self) void {
363
+ if (!enabled) return;
364
+
308
365
  self.probability.reset();
309
- self.actions.reset();
366
+ self.actions = .{};
310
367
  self.pending = .{};
311
368
  }
312
369
 
313
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
+
314
377
  var action = self.actions.get(player);
315
378
 
316
- // We need to handle binding specially because we still need to commit it when attacking
317
- // into a Pokémon that is immune
318
- if (!showdown) {
319
- if (kind != .miss and self.pending.binding != 0) {
320
- assert(action.duration == 0);
321
- action.duration = @intCast(self.pending.binding);
322
- assert(action.durations.binding == 0);
323
- action.durations.binding = 1;
324
- }
325
- if (kind == .binding) return;
326
- } 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
+ }
327
384
 
328
385
  // Always commit the hit result if we make it here (commit won't be called at all if the
329
- // target is immune/behind a sub/etc)
386
+ // target is immune*/behind a sub/etc)
330
387
  if (self.pending.hit_probablity != 0) {
331
388
  try self.probability.update(self.pending.hit_probablity, 256);
332
389
  action.hit = if (self.pending.hit) .true else .false;
333
390
  }
334
391
 
335
- if (showdown) return;
336
-
337
- // If the move actually lands we can commit any past critical hit / damage rolls. We
338
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
339
- // to a damage roll is actually a no-op
340
- if (kind == .hit and self.pending.damage_roll > 0) {
341
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
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
+ }
342
404
 
343
- if (self.pending.crit_probablity != 0) {
344
- try self.probability.update(self.pending.crit_probablity, 256);
345
- action.critical_hit = if (self.pending.crit) .true else .false;
346
- }
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);
347
409
 
410
+ if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
348
411
  try self.probability.update(1, 39);
349
412
  action.damage = self.pending.damage_roll;
350
413
  }
351
414
  }
352
415
 
353
- pub fn clearPending(self: *Self) void {
416
+ pub fn glitch(self: *Self) void {
354
417
  if (!enabled) return;
355
418
 
356
- self.pending = .{};
419
+ assert(!showdown);
420
+ self.pending.glitch = true;
357
421
  }
358
422
 
359
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
423
+ pub fn clearPending(self: *Self) void {
360
424
  if (!enabled) return;
361
425
 
362
- var action = self.actions.get(player);
363
- if (haze) |status| {
364
- if (status) action.durations.sleep = 0;
365
- action.durations.confusion = 0;
366
- action.durations.disable = 0;
367
- } else {
368
- action.durations.attacking = 0;
369
- action.durations.binding = 0;
370
- }
426
+ assert(!showdown);
427
+ self.pending = .{};
371
428
  }
372
429
 
373
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
430
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
374
431
  if (!enabled) return;
375
432
 
376
- assert(in >= 1 and in <= 6);
377
- assert(out >= 1 and out <= 6);
433
+ assert(slot >= 1 and slot <= 6);
378
434
 
379
- var action = self.actions.get(player);
435
+ var d = self.durations.get(player);
380
436
 
381
- const slp = action.durations.sleep;
382
- 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);
383
440
 
384
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
385
- 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;
386
445
 
387
- self.actions.get(player.foe()).durations.binding = 0;
446
+ self.durations.get(player.foe()).binding = 0;
388
447
  }
389
448
 
390
449
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -395,12 +454,17 @@ pub fn Chance(comptime Rational: type) type {
395
454
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
396
455
  }
397
456
 
398
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
457
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
399
458
  if (!enabled) return;
400
459
 
401
460
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
402
- self.pending.hit = ok;
403
- 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
+ }
404
468
  }
405
469
 
406
470
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -478,108 +542,172 @@ pub fn Chance(comptime Rational: type) type {
478
542
  self.actions.get(player).multi_hit = n;
479
543
  }
480
544
 
481
- 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(
482
552
  self: *Self,
483
- comptime field: Duration.Field,
553
+ comptime field: Action.Field,
484
554
  player: Player,
485
- target: Player,
486
- turns: u4,
555
+ obs: Optional(Confusion),
487
556
  ) void {
488
557
  if (!enabled) return;
489
558
 
490
- if (!showdown and field == .binding) {
491
- self.pending.binding = if (options.key) 1 else turns;
492
- } else {
493
- var durations = &self.actions.get(target).durations;
494
- assert(@field(durations, @tagName(field)) == 0 or
495
- (field == .confusion and player == target));
496
- @field(durations, @tagName(field)) = 1;
497
- 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,
498
594
  }
499
595
  }
500
596
 
501
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
597
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
502
598
  if (!enabled) return;
503
599
 
504
- var durations = &self.actions.get(player).durations;
505
- const n = durations.sleep;
506
- 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) {
507
610
  assert(n >= 1 and n <= 7);
508
611
  if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
509
- durations.sleep = 0;
612
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
510
613
  } else {
614
+ assert(obs == .continuing);
511
615
  assert(n >= 1 and n < 7);
512
616
  try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
513
- durations.sleep += 1;
617
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
514
618
  }
515
619
  }
516
620
 
517
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
621
+ pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
518
622
  if (!enabled) return;
519
623
 
520
- var durations = &self.actions.get(player).durations;
521
- const n = durations.disable;
522
- if (turns == 0) {
523
- assert(n >= 1 and n <= 8);
524
- if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
525
- durations.disable = 0;
526
- } else {
527
- assert(n >= 1 and n < 8);
528
- try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
529
- durations.disable += 1;
530
- }
531
- }
624
+ var a = self.actions.get(player);
625
+ assert(a.confusion == .None or a.confusion == .started);
626
+ a.confusion = obs;
532
627
 
533
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
534
- if (!enabled) return;
628
+ var d = self.durations.get(player);
629
+ const n = d.confusion;
535
630
 
536
- var durations = &self.actions.get(player).durations;
537
- const n = durations.confusion;
538
- if (turns == 0) {
631
+ if (obs == .ended) {
539
632
  assert(n >= 2 and n <= 5);
540
633
  if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
541
- durations.confusion = 0;
634
+ d.confusion = 0;
542
635
  } else {
636
+ assert(obs == .continuing);
543
637
  assert(n >= 1 and n < 5);
544
638
  if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
545
- durations.confusion += 1;
639
+ d.confusion += 1;
546
640
  }
547
641
  }
548
642
 
549
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
643
+ pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
550
644
  if (!enabled) return;
551
645
 
552
- var durations = &self.actions.get(player).durations;
553
- const n = durations.attacking;
554
- 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) {
654
+ assert(n >= 1 and n <= 8);
655
+ if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
656
+ d.disable = 0;
657
+ } else {
658
+ assert(obs == .continuing);
659
+ assert(n >= 1 and n < 8);
660
+ try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
661
+ d.disable += 1;
662
+ }
663
+ }
664
+
665
+ pub fn attacking(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
666
+ if (!enabled) return;
667
+
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
555
676
  assert(n >= 2 and n <= 3);
556
677
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
557
- durations.attacking = 0;
678
+ d.attacking = 0;
558
679
  } else {
680
+ assert(obs == .continuing);
559
681
  assert(n >= 1 and n < 3);
560
682
  if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
561
- durations.attacking += 1;
683
+ d.attacking += 1;
562
684
  }
563
685
  }
564
686
 
565
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
687
+ pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
566
688
  if (!enabled) return;
567
689
 
568
- var durations = &self.actions.get(player).durations;
569
- 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;
570
696
 
571
697
  assert(n > 0);
572
698
  const p: u4 = if (n < 3) 3 else 1;
573
699
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
574
700
 
575
- if (turns == 0) {
701
+ if (obs == .ended) {
702
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
576
703
  assert(n >= 1 and n <= 4);
577
704
  if (n != 4) try self.probability.update(p, q);
578
- durations.binding = 0;
705
+ d.binding = 0;
579
706
  } else {
707
+ assert(obs == .continuing);
580
708
  assert(n >= 1 and n < 4);
581
709
  try self.probability.update(q - p, q);
582
- durations.binding += 1;
710
+ d.binding += 1;
583
711
  }
584
712
  }
585
713
 
@@ -593,9 +721,18 @@ pub fn Chance(comptime Rational: type) type {
593
721
  pub fn metronome(self: *Self, player: Player, move: Move) Error!void {
594
722
  if (!enabled) return;
595
723
 
596
- try self.probability.update(1, Move.size - 2);
724
+ try self.probability.update(1, Move.METRONOME.len);
597
725
  self.actions.get(player).metronome = move;
598
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
+ }
599
736
  };
600
737
  }
601
738
 
@@ -618,19 +755,25 @@ test "Chance.speedTie" {
618
755
  test "Chance.hit" {
619
756
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
620
757
 
621
- chance.hit(true, 229);
622
- try expectProbability(&chance.probability, 1, 1);
623
- try expectValue(Optional(bool).None, chance.actions.p1.hit);
624
- 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
+ }
625
765
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
626
766
  try expectProbability(&chance.probability, 229, 256);
627
767
 
628
768
  chance.reset();
629
769
 
630
- chance.hit(false, 255);
631
- try expectProbability(&chance.probability, 1, 1);
632
- try expectValue(Optional(bool).None, chance.actions.p2.hit);
633
- 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
+ }
634
777
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
635
778
  try expectProbability(&chance.probability, 1, 256);
636
779
  }
@@ -646,26 +789,18 @@ test "Chance.criticalHit" {
646
789
  try expectProbability(&chance.probability, 1, 1);
647
790
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
648
791
 
649
- try chance.commit(.P1, .hit);
650
- try expectProbability(&chance.probability, 1, 1);
651
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
652
-
653
- chance.hit(true, 128);
792
+ try chance.hit(.P1, true, 128);
654
793
 
655
- try chance.commit(.P1, .hit);
656
- try expectProbability(&chance.probability, 1, 2);
794
+ try expectProbability(&chance.probability, 1, 1);
657
795
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
658
796
 
659
- chance.probability.reset();
660
797
  try chance.damage(.P1, 217);
661
798
 
662
- try chance.commit(.P1, .miss);
663
- try expectProbability(&chance.probability, 1, 2);
799
+ try expectProbability(&chance.probability, 1, 1);
664
800
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
665
801
 
666
- chance.probability.reset();
667
-
668
802
  try chance.commit(.P1, .hit);
803
+
669
804
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
670
805
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
671
806
  }
@@ -680,20 +815,14 @@ test "Chance.criticalHit" {
680
815
  try expectProbability(&chance.probability, 1, 1);
681
816
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
682
817
 
683
- try chance.commit(.P1, .hit);
684
- try expectProbability(&chance.probability, 1, 1);
685
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
818
+ try chance.hit(.P1, true, 128);
686
819
 
687
- chance.hit(true, 128);
688
-
689
- try chance.commit(.P1, .hit);
690
- try expectProbability(&chance.probability, 1, 2);
820
+ try expectProbability(&chance.probability, 1, 1);
691
821
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
692
822
 
693
- chance.probability.reset();
694
823
  try chance.damage(.P1, 217);
695
-
696
824
  try chance.commit(.P1, .hit);
825
+
697
826
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
698
827
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
699
828
  }
@@ -710,11 +839,8 @@ test "Chance.damage" {
710
839
  try expectProbability(&chance.probability, 1, 1);
711
840
  try expectValue(@as(u8, 0), chance.actions.p1.damage);
712
841
 
713
- try chance.commit(.P1, .miss);
714
- try expectProbability(&chance.probability, 1, 1);
715
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
716
-
717
842
  try chance.commit(.P1, .hit);
843
+
718
844
  try expectProbability(&chance.probability, 1, 39);
719
845
  try expectValue(@as(u8, 217), chance.actions.p1.damage);
720
846
  }
@@ -794,84 +920,77 @@ test "Chance.multiHit" {
794
920
  test "Chance.duration" {
795
921
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
796
922
 
797
- chance.duration(.sleep, .P1, .P2, 2);
923
+ chance.duration(.P1, 2);
798
924
  try expectValue(@as(u4, 2), chance.actions.p1.duration);
799
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
800
925
 
801
926
  chance.reset();
802
927
 
803
- chance.duration(.binding, .P2, .P2, 4);
804
- if (!showdown) {
805
- try expectValue(@as(u4, 0), chance.actions.p2.duration);
806
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
807
- try chance.commit(.P2, .hit);
808
- }
928
+ chance.duration(.P2, 4);
809
929
  try expectValue(@as(u4, 4), chance.actions.p2.duration);
810
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
811
930
  }
812
931
 
813
932
  test "Chance.sleep" {
814
933
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
815
934
 
816
935
  for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
817
- chance.actions.p1.durations.sleep = @intCast(i);
818
- 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);
819
938
  try expectProbability(&chance.probability, 1, d);
820
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
939
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
821
940
 
822
941
  chance.reset();
823
942
 
824
943
  if (i < 7) {
825
- chance.actions.p1.durations.sleep = @intCast(i);
826
- 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);
827
946
  try expectProbability(&chance.probability, d - 1, d);
828
- 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));
829
948
 
830
949
  chance.reset();
831
950
  }
832
951
  }
833
952
  }
834
953
 
835
- test "Chance.disable" {
954
+ test "Chance.confusion" {
836
955
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
837
956
 
838
- for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
839
- chance.actions.p1.durations.disable = @intCast(i);
840
- try chance.disable(.P1, 0);
841
- try expectProbability(&chance.probability, 1, d);
842
- try expectValue(@as(u4, 0), chance.actions.p1.durations.disable);
957
+ for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
958
+ if (i > 1) {
959
+ chance.durations.p2.confusion = @intCast(i);
960
+ try chance.confusion(.P2, .ended);
961
+ try expectProbability(&chance.probability, 1, d);
962
+ try expectValue(@as(u3, 0), chance.durations.p2.confusion);
843
963
 
844
- chance.reset();
964
+ chance.reset();
965
+ }
845
966
 
846
- if (i < 8) {
847
- chance.actions.p1.durations.disable = @intCast(i);
848
- try chance.disable(.P1, 1);
849
- try expectProbability(&chance.probability, d - 1, d);
850
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
967
+ if (i < 5) {
968
+ chance.durations.p2.confusion = @intCast(i);
969
+ try chance.confusion(.P2, .continuing);
970
+ try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
971
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.confusion);
851
972
 
852
973
  chance.reset();
853
974
  }
854
975
  }
855
976
  }
856
977
 
857
- test "Chance.confusion" {
978
+ test "Chance.disable" {
858
979
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
859
980
 
860
- for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
861
- if (i > 1) {
862
- chance.actions.p2.durations.confusion = @intCast(i);
863
- try chance.confusion(.P2, 0);
864
- try expectProbability(&chance.probability, 1, d);
865
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
981
+ for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
982
+ chance.durations.p1.disable = @intCast(i);
983
+ try chance.disable(.P1, .ended);
984
+ try expectProbability(&chance.probability, 1, d);
985
+ try expectValue(@as(u4, 0), chance.durations.p1.disable);
866
986
 
867
- chance.reset();
868
- }
987
+ chance.reset();
869
988
 
870
- if (i < 5) {
871
- chance.actions.p2.durations.confusion = @intCast(i);
872
- try chance.confusion(.P2, 1);
873
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
874
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
989
+ if (i < 8) {
990
+ chance.durations.p1.disable = @intCast(i);
991
+ try chance.disable(.P1, .continuing);
992
+ try expectProbability(&chance.probability, d - 1, d);
993
+ try expectValue(@as(u4, @intCast(i)) + 1, chance.durations.p1.disable);
875
994
 
876
995
  chance.reset();
877
996
  }
@@ -883,19 +1002,19 @@ test "Chance.attacking" {
883
1002
 
884
1003
  for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
885
1004
  if (i > 1) {
886
- chance.actions.p2.durations.attacking = @intCast(i);
887
- try chance.attacking(.P2, 0);
1005
+ chance.durations.p2.attacking = @intCast(i);
1006
+ try chance.attacking(.P2, .ended);
888
1007
  try expectProbability(&chance.probability, 1, d);
889
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
1008
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
890
1009
 
891
1010
  chance.reset();
892
1011
  }
893
1012
 
894
1013
  if (i < 3) {
895
- chance.actions.p2.durations.attacking = @intCast(i);
896
- try chance.attacking(.P2, 1);
1014
+ chance.durations.p2.attacking = @intCast(i);
1015
+ try chance.attacking(.P2, .continuing);
897
1016
  try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
898
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
1017
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
899
1018
 
900
1019
  chance.reset();
901
1020
  }
@@ -909,18 +1028,18 @@ test "Chance.binding" {
909
1028
  const qs = [_]u8{ 8, 5, 2, 1 };
910
1029
 
911
1030
  for (ps, qs, 1..5) |p, q, i| {
912
- chance.actions.p1.durations.binding = @intCast(i);
913
- try chance.binding(.P1, 0);
1031
+ chance.durations.p1.binding = @intCast(i);
1032
+ try chance.binding(.P1, .ended);
914
1033
  try expectProbability(&chance.probability, p, q);
915
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
1034
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
916
1035
 
917
1036
  chance.reset();
918
1037
 
919
1038
  if (i < 4) {
920
- chance.actions.p1.durations.binding = @intCast(i);
921
- try chance.binding(.P1, 1);
1039
+ chance.durations.p1.binding = @intCast(i);
1040
+ try chance.binding(.P1, .continuing);
922
1041
  try expectProbability(&chance.probability, q - p, q);
923
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
1042
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
924
1043
 
925
1044
  chance.reset();
926
1045
  }
@@ -970,24 +1089,24 @@ const Null = struct {
970
1089
  _ = .{ self, player, kind };
971
1090
  }
972
1091
 
973
- pub fn clearPending(self: Null) void {
1092
+ pub fn glitch(self: Null) void {
974
1093
  _ = .{self};
975
1094
  }
976
1095
 
977
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
978
- _ = .{ self, player, haze };
1096
+ pub fn clearPending(self: Null) void {
1097
+ _ = .{self};
979
1098
  }
980
1099
 
981
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
982
- _ = .{ self, player, in, out };
1100
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1101
+ _ = .{ self, player, slot };
983
1102
  }
984
1103
 
985
1104
  pub fn speedTie(self: Null, p1: bool) Error!void {
986
1105
  _ = .{ self, p1 };
987
1106
  }
988
1107
 
989
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
990
- _ = .{ self, ok, accuracy };
1108
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
1109
+ _ = .{ self, player, ok, accuracy };
991
1110
  }
992
1111
 
993
1112
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -998,10 +1117,6 @@ const Null = struct {
998
1117
  _ = .{ self, player, roll };
999
1118
  }
1000
1119
 
1001
- pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
1002
- _ = .{ self, player, proc, rate };
1003
- }
1004
-
1005
1120
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
1006
1121
  _ = .{ self, player, ok };
1007
1122
  }
@@ -1010,6 +1125,10 @@ const Null = struct {
1010
1125
  _ = .{ self, player, ok };
1011
1126
  }
1012
1127
 
1128
+ pub fn secondaryChance(self: Null, player: Player, proc: bool, rate: u8) Error!void {
1129
+ _ = .{ self, player, proc, rate };
1130
+ }
1131
+
1013
1132
  pub fn moveSlot(self: Null, player: Player, slot: u4, ms: []const MoveSlot, n: u4) Error!void {
1014
1133
  _ = .{ self, player, slot, ms, n };
1015
1134
  }
@@ -1018,34 +1137,37 @@ const Null = struct {
1018
1137
  _ = .{ self, player, n };
1019
1138
  }
1020
1139
 
1021
- pub fn duration(
1140
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1141
+ _ = .{ self, player, turns };
1142
+ }
1143
+
1144
+ pub fn observe(
1022
1145
  self: Null,
1023
- comptime field: Duration.Field,
1146
+ comptime field: Action.Field,
1024
1147
  player: Player,
1025
- target: Player,
1026
- turns: u4,
1148
+ obs: Optional(Confusion),
1027
1149
  ) void {
1028
- _ = .{ self, field, player, target, turns };
1150
+ _ = .{ self, field, player, obs };
1029
1151
  }
1030
1152
 
1031
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1032
- _ = .{ self, player, turns };
1153
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1154
+ _ = .{ self, player, obs };
1033
1155
  }
1034
1156
 
1035
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1036
- _ = .{ self, player, turns };
1157
+ pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
1158
+ _ = .{ self, player, obs };
1037
1159
  }
1038
1160
 
1039
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1040
- _ = .{ self, player, turns };
1161
+ pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1162
+ _ = .{ self, player, obs };
1041
1163
  }
1042
1164
 
1043
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1044
- _ = .{ self, player, turns };
1165
+ pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1166
+ _ = .{ self, player, obs };
1045
1167
  }
1046
1168
 
1047
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1048
- _ = .{ self, player, turns };
1169
+ pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1170
+ _ = .{ self, player, obs };
1049
1171
  }
1050
1172
 
1051
1173
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -1055,4 +1177,9 @@ const Null = struct {
1055
1177
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
1056
1178
  _ = .{ self, player, move };
1057
1179
  }
1180
+
1181
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1182
+ _ = .{ self, player, n };
1183
+ return true;
1184
+ }
1058
1185
  };