@pkmn/engine 0.1.0-dev.7963b917 → 0.1.0-dev.79957401

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