@pkmn/engine 0.1.0-dev.a0167a9d → 0.1.0-dev.a0447192

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