@pkmn/engine 0.1.0-dev.b01cb48c → 0.1.0-dev.b3197aa3

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 (81) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +54 -6
  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 +25 -3
  13. package/build/pkg/addon.js +34 -65
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/layout.json +15 -2
  16. package/build/pkg/data/protocol.json +23 -9
  17. package/build/pkg/data.d.ts +23 -1
  18. package/build/pkg/data.js.map +1 -1
  19. package/build/pkg/gen1.d.ts +15 -18
  20. package/build/pkg/gen1.js +28 -19
  21. package/build/pkg/gen1.js.map +1 -1
  22. package/build/pkg/index.d.ts +40 -90
  23. package/build/pkg/index.js +23 -70
  24. package/build/pkg/index.js.map +1 -1
  25. package/build/pkg/protocol.d.ts +20 -1
  26. package/build/pkg/protocol.js +105 -103
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +52 -76
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +152 -0
  31. package/build/tools/display/debug.jsx.map +1 -0
  32. package/build/tools/display/index.js +152 -0
  33. package/build/tools/display/index.js.map +1 -0
  34. package/build/tools/display/ui.jsx +359 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +33 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +331 -85
  39. package/package.json +56 -136
  40. package/src/bin/install-pkmn-engine +10 -7
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +255 -0
  43. package/src/lib/bindings/node.zig +117 -0
  44. package/src/lib/bindings/wasm.zig +59 -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 -6
  49. package/src/lib/common/napi.zig +4 -0
  50. package/src/lib/common/optional.zig +13 -9
  51. package/src/lib/common/options.zig +4 -0
  52. package/src/lib/common/protocol.zig +44 -43
  53. package/src/lib/common/rational.zig +317 -304
  54. package/src/lib/common/rng.zig +5 -7
  55. package/src/lib/common/util.zig +65 -1
  56. package/src/lib/gen1/calc.zig +266 -135
  57. package/src/lib/gen1/chance.zig +469 -110
  58. package/src/lib/gen1/data/moves.zig +2 -4
  59. package/src/lib/gen1/data/species.zig +1 -3
  60. package/src/lib/gen1/data/types.zig +2 -3
  61. package/src/lib/gen1/data.zig +35 -38
  62. package/src/lib/gen1/helpers.zig +17 -27
  63. package/src/lib/gen1/mechanics.zig +213 -160
  64. package/src/lib/gen2/calc.zig +37 -27
  65. package/src/lib/gen2/chance.zig +87 -67
  66. package/src/lib/gen2/data/items.zig +1 -3
  67. package/src/lib/gen2/data/moves.zig +2 -4
  68. package/src/lib/gen2/data/species.zig +1 -3
  69. package/src/lib/gen2/data/types.zig +7 -5
  70. package/src/lib/gen2/data.zig +28 -30
  71. package/src/lib/gen2/generated.zig +18 -23
  72. package/src/lib/gen2/helpers.zig +9 -18
  73. package/src/lib/gen2/mechanics.zig +82 -82
  74. package/src/lib/node.zig +4 -121
  75. package/src/lib/pkmn.zig +14 -7
  76. package/src/lib/wasm.zig +13 -9
  77. package/src/tools/display/dom.ts +93 -0
  78. package/src/tools/display/ui.css +354 -0
  79. package/build/test/display.js +0 -405
  80. package/build/test/display.js.map +0 -1
  81. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,26 +1,31 @@
1
- const std = @import("std");
2
-
3
- const assert = std.debug.assert;
4
- const print = std.debug.print;
5
-
6
- const expect = std.testing.expect;
7
- const expectEqual = std.testing.expectEqual;
8
-
9
1
  const DEBUG = @import("../common/debug.zig").print;
2
+
3
+ const array = @import("../common/array.zig");
4
+ const common = @import("../common/data.zig");
5
+ const data = @import("data.zig");
6
+ const optional = @import("../common/optional.zig");
10
7
  const options = @import("../common/options.zig");
11
8
  const rational = @import("../common/rational.zig");
9
+ const std = @import("std");
12
10
  const util = @import("../common/util.zig");
13
11
 
14
- const Player = @import("../common/data.zig").Player;
15
- const Optional = @import("../common/optional.zig").Optional;
16
-
17
- const data = @import("data.zig");
18
-
12
+ const Array = array.Array;
13
+ const assert = std.debug.assert;
19
14
  const enabled = options.chance;
20
- const showdown = options.showdown;
21
-
15
+ const expect = std.testing.expect;
16
+ const expectEqual = std.testing.expectEqual;
17
+ const isPointerTo = util.isPointerTo;
22
18
  const Move = data.Move;
23
19
  const MoveSlot = data.MoveSlot;
20
+ const Optional = optional.Optional;
21
+ const Player = common.Player;
22
+ const PointerType = util.PointerType;
23
+ const print = std.debug.print;
24
+ const showdown = options.showdown;
25
+
26
+ const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
27
+ const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
28
+ const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
24
29
 
25
30
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
26
31
  /// were observed during a Generation I battle `update`. This can additionally be provided as input
@@ -37,8 +42,8 @@ pub const Actions = extern struct {
37
42
  }
38
43
 
39
44
  /// Returns the `Action` for the given `player`.
40
- pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Action) {
41
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Actions);
45
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
46
+ assert(isPointerTo(self, Actions));
42
47
  return if (player == .P1) &self.p1 else &self.p2;
43
48
  }
44
49
 
@@ -51,15 +56,15 @@ pub const Actions = extern struct {
51
56
  /// same shape if they have the same fields set (though those fields need not necessarily be
52
57
  /// set to the same value).
53
58
  pub fn matches(a: Actions, b: Actions) bool {
54
- inline for (@typeInfo(Actions).Struct.fields) |player| {
55
- 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| {
56
61
  const a_val = @field(@field(a, player.name), field.name);
57
62
  const b_val = @field(@field(b, player.name), field.name);
58
63
 
59
64
  switch (@typeInfo(@TypeOf(a_val))) {
60
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
65
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
61
66
  return false,
62
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
67
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
63
68
  else => unreachable,
64
69
  }
65
70
  }
@@ -67,6 +72,25 @@ pub const Actions = extern struct {
67
72
  return true;
68
73
  }
69
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
+
70
94
  pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
71
95
  try writer.writeAll("<P1 = ");
72
96
  try self.p1.fmt(writer, shape);
@@ -99,8 +123,19 @@ test Actions {
99
123
  try expect(!c.matches(a));
100
124
  }
101
125
 
126
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
102
127
  pub const Observation = enum { started, continuing, ended };
103
- pub const Confusion = enum { started, continuing, ended, overwritten };
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
+ };
104
139
 
105
140
  /// Information about the RNG that was observed during a Generation I battle `update` for a
106
141
  /// single player.
@@ -126,19 +161,25 @@ pub const Action = packed struct(u64) {
126
161
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
127
162
  duration: u4 = 0,
128
163
 
129
- // TODO
164
+ /// TODO
130
165
  sleep: Optional(Observation) = .None,
166
+ /// TODO
131
167
  confusion: Optional(Confusion) = .None,
168
+ /// TODO
132
169
  disable: Optional(Observation) = .None,
133
- bide: Optional(Observation) = .None,
170
+ /// TODO
171
+ attacking: Optional(Observation) = .None,
172
+ /// TODO
134
173
  binding: Optional(Observation) = .None,
135
- thrashing: Optional(Observation) = .None,
136
174
 
137
- _: u3 = 0, // TODO
175
+ _: u1 = 0,
138
176
 
139
177
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
140
178
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
141
179
  move_slot: u4 = 0,
180
+
181
+ /// TODO
182
+ pp: u4 = 0,
142
183
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
143
184
  multi_hit: u4 = 0,
144
185
 
@@ -158,10 +199,10 @@ pub const Action = packed struct(u64) {
158
199
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
159
200
  try writer.writeByte('(');
160
201
  var printed = false;
161
- inline for (@typeInfo(Action).Struct.fields) |field| {
202
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
162
203
  const val = @field(self, field.name);
163
204
  switch (@typeInfo(@TypeOf(val))) {
164
- .Enum => if (val != .None) {
205
+ Enum => if (val != .None) {
165
206
  if (printed) try writer.writeAll(", ");
166
207
  if (shape) {
167
208
  try writer.print("{s}:?", .{field.name});
@@ -170,25 +211,11 @@ pub const Action = packed struct(u64) {
170
211
  if (val == .false) "!" else "",
171
212
  field.name,
172
213
  });
173
- } else if (@TypeOf(val) == Optional(Observation)) {
174
- try writer.print("{s}{s}", .{
175
- switch (val) {
176
- .started => "+",
177
- .continuing => "",
178
- .ended => "-",
179
- else => unreachable,
180
- },
181
- field.name,
182
- });
183
- } else if (@TypeOf(val) == Optional(Confusion)) {
214
+ } else if (@TypeOf(val) == Optional(Observation) or
215
+ @TypeOf(val) == Optional(Confusion))
216
+ {
184
217
  try writer.print("{s}{s}", .{
185
- switch (val) {
186
- .started => "+",
187
- .continuing => "",
188
- .ended => "-",
189
- .overwritten => "#",
190
- else => unreachable,
191
- },
218
+ ([_][]const u8{ "+", "", "-", "#" })[@intFromEnum(val) - 1],
192
219
  field.name,
193
220
  });
194
221
  } else {
@@ -196,7 +223,7 @@ pub const Action = packed struct(u64) {
196
223
  }
197
224
  printed = true;
198
225
  },
199
- .Int => if (val != 0) {
226
+ Int => if (val != 0) {
200
227
  if (printed) try writer.writeAll(", ");
201
228
  if (shape) {
202
229
  try writer.print("{s}:?", .{field.name});
@@ -213,15 +240,19 @@ pub const Action = packed struct(u64) {
213
240
  };
214
241
 
215
242
  /// TODO
216
- pub const Durations = struct {
243
+ pub const Durations = extern struct {
217
244
  /// TODO
218
245
  p1: Duration = .{},
219
246
  /// TODO
220
247
  p2: Duration = .{},
221
248
 
249
+ comptime {
250
+ assert(@sizeOf(Durations) == 8);
251
+ }
252
+
222
253
  /// TODO
223
- pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Duration) {
224
- assert(@typeInfo(@TypeOf(self)).Pointer.child == Durations);
254
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Duration) {
255
+ assert(isPointerTo(self, Durations));
225
256
  return if (player == .P1) &self.p1 else &self.p2;
226
257
  }
227
258
 
@@ -236,15 +267,18 @@ pub const Durations = struct {
236
267
  }
237
268
  };
238
269
 
270
+ pub const Sleeps = Array(6, u3);
271
+
239
272
  /// TODO
240
- pub const Duration = struct {
241
- sleeps: [6]u3 = .{0} ** 6,
273
+ pub const Duration = packed struct(u32) {
274
+ sleeps: Sleeps.T = 0,
242
275
  confusion: u3 = 0,
243
276
  disable: u4 = 0,
244
- bide: u3 = 0,
245
- thrashing: u3 = 0,
277
+ attacking: u3 = 0,
246
278
  binding: u3 = 0,
247
279
 
280
+ _: u1 = 0,
281
+
248
282
  pub fn format(
249
283
  self: Duration,
250
284
  comptime fmt: []const u8,
@@ -254,27 +288,22 @@ pub const Duration = struct {
254
288
  _ = .{ fmt, opts };
255
289
  try writer.writeByte('(');
256
290
  var printed = false;
257
- inline for (@typeInfo(Duration).Struct.fields) |field| {
291
+ inline for (@field(@typeInfo(Duration), @tagName(Struct)).fields) |field| {
258
292
  const val = @field(self, field.name);
259
293
  switch (@typeInfo(@TypeOf(val))) {
260
- .Int => if (val != 0) {
294
+ Int => if (val != 0) {
261
295
  if (printed) try writer.writeAll(", ");
262
- try writer.print("{s}:{d}", .{ field.name, val });
263
- printed = true;
264
- },
265
- .Array => {
266
- var total: usize = 0;
267
- for (val) |v| total += v;
268
- if (total > 0) {
269
- if (printed) try writer.writeAll(", ");
296
+ if (comptime std.mem.eql(u8, field.name, "sleeps")) {
270
297
  try writer.print("{s}:[", .{field.name});
271
- for (val, 0..) |v, i| {
298
+ for (0..6) |i| {
272
299
  if (i != 0) try writer.writeByte(',');
273
- try writer.print("{d}", .{v});
300
+ try writer.print("{d}", .{Sleeps.get(val, i)});
274
301
  }
275
302
  try writer.writeAll("]");
276
- printed = true;
303
+ } else {
304
+ try writer.print("{s}:{d}", .{ field.name, val });
277
305
  }
306
+ printed = true;
278
307
  },
279
308
  else => unreachable,
280
309
  }
@@ -283,7 +312,7 @@ pub const Duration = struct {
283
312
  }
284
313
  };
285
314
 
286
- pub const Commit = enum { hit, miss, binding };
315
+ pub const Commit = enum { hit, miss, binding, err, glitch };
287
316
 
288
317
  const uN = if (options.miss) u8 else u9;
289
318
 
@@ -323,6 +352,7 @@ pub fn Chance(comptime Rational: type) type {
323
352
  damage_roll: u8 = 0,
324
353
  hit: bool = false,
325
354
  hit_probablity: uN = 0,
355
+ glitch: bool = false,
326
356
  } = .{},
327
357
 
328
358
  /// Possible error returned by operations tracking chance probability.
@@ -342,10 +372,18 @@ pub fn Chance(comptime Rational: type) type {
342
372
 
343
373
  assert(!showdown);
344
374
 
375
+ if (kind == .glitch and !self.pending.glitch) return;
376
+
345
377
  var action = self.actions.get(player);
346
378
 
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
+ }
384
+
347
385
  // Always commit the hit result if we make it here (commit won't be called at all if the
348
- // target is immune/behind a sub/etc)
386
+ // target is immune*/behind a sub/etc)
349
387
  if (self.pending.hit_probablity != 0) {
350
388
  try self.probability.update(self.pending.hit_probablity, 256);
351
389
  action.hit = if (self.pending.hit) .true else .false;
@@ -353,28 +391,61 @@ pub fn Chance(comptime Rational: type) type {
353
391
 
354
392
  // If the move actually lands we can commit any past critical hit / damage rolls. Note
355
393
  // we can't rely on the presence or absence of a damage roll to determine whether a
356
- // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
394
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't roll for
357
395
  // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
358
- // passed to the critical hit helper)
359
- if (kind != .hit) return assert(!self.pending.crit or self.pending.crit_probablity > 0);
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
+ }
360
404
 
361
405
  if (self.pending.crit_probablity != 0) {
362
406
  try self.probability.update(self.pending.crit_probablity, 256);
363
407
  action.critical_hit = if (self.pending.crit) .true else .false;
364
- }
408
+ } else assert(!self.pending.glitch);
365
409
 
366
- if (self.pending.damage_roll > 0) {
410
+ if (self.pending.damage_roll > 0 and (!self.pending.glitch or self.pending.hit)) {
367
411
  try self.probability.update(1, 39);
368
412
  action.damage = self.pending.damage_roll;
369
413
  }
370
414
  }
371
415
 
416
+ pub fn glitch(self: *Self) void {
417
+ if (!enabled) return;
418
+
419
+ assert(!showdown);
420
+ self.pending.glitch = true;
421
+ }
422
+
372
423
  pub fn clearPending(self: *Self) void {
373
424
  if (!enabled) return;
374
425
 
426
+ assert(!showdown);
375
427
  self.pending = .{};
376
428
  }
377
429
 
430
+ pub fn switched(self: *Self, player: Player, slot: u8) void {
431
+ if (!enabled) return;
432
+
433
+ assert(slot >= 1 and slot <= 6);
434
+
435
+ var d = self.durations.get(player);
436
+
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);
440
+
441
+ d.confusion = 0;
442
+ d.disable = 0;
443
+ d.attacking = 0;
444
+ d.binding = 0;
445
+
446
+ self.durations.get(player.foe()).binding = 0;
447
+ }
448
+
378
449
  pub fn speedTie(self: *Self, p1: bool) Error!void {
379
450
  if (!enabled) return;
380
451
 
@@ -474,36 +545,169 @@ pub fn Chance(comptime Rational: type) type {
474
545
  pub fn duration(self: *Self, player: Player, turns: u4) void {
475
546
  if (!enabled) return;
476
547
 
477
- self.actions.get(player).duration = if (options.key) 1 else turns;
548
+ self.actions.get(player).duration = if (options.key) 2 else turns;
478
549
  }
479
550
 
480
551
  pub fn observe(
481
552
  self: *Self,
482
553
  comptime field: Action.Field,
483
554
  player: Player,
484
- observation: Optional(Confusion),
555
+ obs: Optional(Confusion),
485
556
  ) void {
486
557
  if (!enabled) return;
487
558
 
488
- const val = @field(self.actions.get(player), @tagName(field));
489
- if (observation == .overwritten) {
490
- assert(field == .confusion);
491
- assert(val == .started or val == .continuing);
492
- assert(self.actions.get(player).duration > 0);
493
- self.actions.get(player).confusion = observation;
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,
594
+ }
595
+ }
596
+
597
+ pub fn sleep(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
598
+ if (!enabled) return;
599
+
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) {
610
+ assert(n >= 1 and n <= 7);
611
+ if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
612
+ d.sleeps = Sleeps.set(d.sleeps, 0, 0);
494
613
  } else {
495
- assert(observation == .None or val == .None or
496
- (val == .started and observation != .started) or
497
- (val == .ended and observation == .started));
498
- assert(observation != .started or (if (field == .confusion)
499
- self.actions.get(player).duration > 0 or
500
- self.actions.get(player.foe()).duration > 0
501
- else if (field == .sleep or field == .disable)
502
- self.actions.get(player.foe()).duration > 0
503
- else
504
- self.actions.get(player).duration > 0));
505
- @field(self.actions.get(player), @tagName(field)) =
506
- @enumFromInt(@intFromEnum(observation));
614
+ assert(obs == .continuing);
615
+ assert(n >= 1 and n < 7);
616
+ try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
617
+ d.sleeps = Sleeps.set(d.sleeps, 0, n + 1);
618
+ }
619
+ }
620
+
621
+ pub fn confusion(self: *Self, player: Player, obs: Optional(Confusion)) Error!void {
622
+ if (!enabled) return;
623
+
624
+ var a = self.actions.get(player);
625
+ assert(a.confusion == .None or a.confusion == .started);
626
+ a.confusion = obs;
627
+
628
+ var d = self.durations.get(player);
629
+ const n = d.confusion;
630
+
631
+ if (obs == .ended) {
632
+ assert(n >= 2 and n <= 5);
633
+ if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
634
+ d.confusion = 0;
635
+ } else {
636
+ assert(obs == .continuing);
637
+ assert(n >= 1 and n < 5);
638
+ if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
639
+ d.confusion += 1;
640
+ }
641
+ }
642
+
643
+ pub fn disable(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
644
+ if (!enabled) return;
645
+
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
676
+ assert(n >= 2 and n <= 3);
677
+ if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
678
+ d.attacking = 0;
679
+ } else {
680
+ assert(obs == .continuing);
681
+ assert(n >= 1 and n < 3);
682
+ if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
683
+ d.attacking += 1;
684
+ }
685
+ }
686
+
687
+ pub fn binding(self: *Self, player: Player, obs: Optional(Observation)) Error!void {
688
+ if (!enabled) return;
689
+
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;
696
+
697
+ assert(n > 0);
698
+ const p: u4 = if (n < 3) 3 else 1;
699
+ const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
700
+
701
+ if (obs == .ended) {
702
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
703
+ assert(n >= 1 and n <= 4);
704
+ if (n != 4) try self.probability.update(p, q);
705
+ d.binding = 0;
706
+ } else {
707
+ assert(obs == .continuing);
708
+ assert(n >= 1 and n < 4);
709
+ try self.probability.update(q - p, q);
710
+ d.binding += 1;
507
711
  }
508
712
  }
509
713
 
@@ -520,6 +724,15 @@ pub fn Chance(comptime Rational: type) type {
520
724
  try self.probability.update(1, Move.METRONOME.len);
521
725
  self.actions.get(player).metronome = move;
522
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
+ }
523
736
  };
524
737
  }
525
738
 
@@ -620,16 +833,16 @@ test "Chance.damage" {
620
833
 
621
834
  try chance.damage(.P1, 217);
622
835
  if (showdown) {
623
- try expectValue(217, chance.actions.p1.damage);
836
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
624
837
  try expectProbability(&chance.probability, 1, 39);
625
838
  } else {
626
839
  try expectProbability(&chance.probability, 1, 1);
627
- try expectValue(0, chance.actions.p1.damage);
840
+ try expectValue(@as(u8, 0), chance.actions.p1.damage);
628
841
 
629
842
  try chance.commit(.P1, .hit);
630
843
 
631
844
  try expectProbability(&chance.probability, 1, 39);
632
- try expectValue(217, chance.actions.p1.damage);
845
+ try expectValue(@as(u8, 217), chance.actions.p1.damage);
633
846
  }
634
847
  }
635
848
 
@@ -681,13 +894,13 @@ test "Chance.moveSlot" {
681
894
 
682
895
  try chance.moveSlot(.P2, 2, &ms, 2);
683
896
  try expectProbability(&chance.probability, 1, 2);
684
- try expectValue(2, chance.actions.p2.move_slot);
897
+ try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
685
898
 
686
899
  chance.reset();
687
900
 
688
901
  try chance.moveSlot(.P1, 1, &ms, 0);
689
902
  try expectProbability(&chance.probability, 1, 3);
690
- try expectValue(1, chance.actions.p1.move_slot);
903
+ try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
691
904
  }
692
905
 
693
906
  test "Chance.multiHit" {
@@ -695,25 +908,142 @@ test "Chance.multiHit" {
695
908
 
696
909
  try chance.multiHit(.P1, 3);
697
910
  try expectProbability(&chance.probability, 3, 8);
698
- try expectValue(3, chance.actions.p1.multi_hit);
911
+ try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
699
912
 
700
913
  chance.reset();
701
914
 
702
915
  try chance.multiHit(.P2, 5);
703
916
  try expectProbability(&chance.probability, 1, 8);
704
- try expectValue(5, chance.actions.p2.multi_hit);
917
+ try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
705
918
  }
706
919
 
707
920
  test "Chance.duration" {
708
921
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
709
922
 
710
923
  chance.duration(.P1, 2);
711
- try expectValue(2, chance.actions.p1.duration);
924
+ try expectValue(@as(u4, 2), chance.actions.p1.duration);
712
925
 
713
926
  chance.reset();
714
927
 
715
928
  chance.duration(.P2, 4);
716
- try expectValue(4, chance.actions.p2.duration);
929
+ try expectValue(@as(u4, 4), chance.actions.p2.duration);
930
+ }
931
+
932
+ test "Chance.sleep" {
933
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
934
+
935
+ for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
936
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
937
+ try chance.sleep(.P1, .ended);
938
+ try expectProbability(&chance.probability, 1, d);
939
+ try expectValue(@as(u3, 0), Sleeps.get(chance.durations.p1.sleeps, 0));
940
+
941
+ chance.reset();
942
+
943
+ if (i < 7) {
944
+ chance.durations.p1.sleeps = Sleeps.set(chance.durations.p1.sleeps, 0, @intCast(i));
945
+ try chance.sleep(.P1, .continuing);
946
+ try expectProbability(&chance.probability, d - 1, d);
947
+ try expectValue(@as(u3, @intCast(i)) + 1, Sleeps.get(chance.durations.p1.sleeps, 0));
948
+
949
+ chance.reset();
950
+ }
951
+ }
952
+ }
953
+
954
+ test "Chance.confusion" {
955
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
956
+
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);
963
+
964
+ chance.reset();
965
+ }
966
+
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);
972
+
973
+ chance.reset();
974
+ }
975
+ }
976
+ }
977
+
978
+ test "Chance.disable" {
979
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
980
+
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);
986
+
987
+ chance.reset();
988
+
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);
994
+
995
+ chance.reset();
996
+ }
997
+ }
998
+ }
999
+
1000
+ test "Chance.attacking" {
1001
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
1002
+
1003
+ for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
1004
+ if (i > 1) {
1005
+ chance.durations.p2.attacking = @intCast(i);
1006
+ try chance.attacking(.P2, .ended);
1007
+ try expectProbability(&chance.probability, 1, d);
1008
+ try expectValue(@as(u3, 0), chance.durations.p2.attacking);
1009
+
1010
+ chance.reset();
1011
+ }
1012
+
1013
+ if (i < 3) {
1014
+ chance.durations.p2.attacking = @intCast(i);
1015
+ try chance.attacking(.P2, .continuing);
1016
+ try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1017
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p2.attacking);
1018
+
1019
+ chance.reset();
1020
+ }
1021
+ }
1022
+ }
1023
+
1024
+ test "Chance.binding" {
1025
+ var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
1026
+
1027
+ const ps = [_]u8{ 3, 3, 1, 1 };
1028
+ const qs = [_]u8{ 8, 5, 2, 1 };
1029
+
1030
+ for (ps, qs, 1..5) |p, q, i| {
1031
+ chance.durations.p1.binding = @intCast(i);
1032
+ try chance.binding(.P1, .ended);
1033
+ try expectProbability(&chance.probability, p, q);
1034
+ try expectValue(@as(u3, 0), chance.durations.p1.binding);
1035
+
1036
+ chance.reset();
1037
+
1038
+ if (i < 4) {
1039
+ chance.durations.p1.binding = @intCast(i);
1040
+ try chance.binding(.P1, .continuing);
1041
+ try expectProbability(&chance.probability, q - p, q);
1042
+ try expectValue(@as(u3, @intCast(i)) + 1, chance.durations.p1.binding);
1043
+
1044
+ chance.reset();
1045
+ }
1046
+ }
717
1047
  }
718
1048
 
719
1049
  test "Chance.psywave" {
@@ -721,7 +1051,7 @@ test "Chance.psywave" {
721
1051
 
722
1052
  try chance.psywave(.P2, 100, 150);
723
1053
  try expectProbability(&chance.probability, 1, 150);
724
- try expectValue(101, chance.actions.p2.psywave);
1054
+ try expectValue(@as(u8, 101), chance.actions.p2.psywave);
725
1055
  }
726
1056
 
727
1057
  test "Chance.metronome" {
@@ -759,12 +1089,16 @@ const Null = struct {
759
1089
  _ = .{ self, player, kind };
760
1090
  }
761
1091
 
1092
+ pub fn glitch(self: Null) void {
1093
+ _ = .{self};
1094
+ }
1095
+
762
1096
  pub fn clearPending(self: Null) void {
763
1097
  _ = .{self};
764
1098
  }
765
1099
 
766
- pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
767
- _ = .{ self, player, in, out };
1100
+ pub fn switched(self: Null, player: Player, slot: u8) void {
1101
+ _ = .{ self, player, slot };
768
1102
  }
769
1103
 
770
1104
  pub fn speedTie(self: Null, p1: bool) Error!void {
@@ -803,17 +1137,37 @@ const Null = struct {
803
1137
  _ = .{ self, player, n };
804
1138
  }
805
1139
 
1140
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1141
+ _ = .{ self, player, turns };
1142
+ }
1143
+
806
1144
  pub fn observe(
807
1145
  self: Null,
808
1146
  comptime field: Action.Field,
809
1147
  player: Player,
810
- observation: Optional(Confusion),
1148
+ obs: Optional(Confusion),
811
1149
  ) void {
812
- _ = .{ self, field, player, observation };
1150
+ _ = .{ self, field, player, obs };
813
1151
  }
814
1152
 
815
- pub fn duration(self: Null, player: Player, turns: u4) void {
816
- _ = .{ self, player, turns };
1153
+ pub fn sleep(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1154
+ _ = .{ self, player, obs };
1155
+ }
1156
+
1157
+ pub fn confusion(self: Null, player: Player, obs: Optional(Confusion)) Error!void {
1158
+ _ = .{ self, player, obs };
1159
+ }
1160
+
1161
+ pub fn disable(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1162
+ _ = .{ self, player, obs };
1163
+ }
1164
+
1165
+ pub fn attacking(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1166
+ _ = .{ self, player, obs };
1167
+ }
1168
+
1169
+ pub fn binding(self: Null, player: Player, obs: Optional(Observation)) Error!void {
1170
+ _ = .{ self, player, obs };
817
1171
  }
818
1172
 
819
1173
  pub fn psywave(self: Null, player: Player, power: u8, max: u8) Error!void {
@@ -823,4 +1177,9 @@ const Null = struct {
823
1177
  pub fn metronome(self: Null, player: Player, move: Move) Error!void {
824
1178
  _ = .{ self, player, move };
825
1179
  }
1180
+
1181
+ pub fn pp(self: Null, player: Player, n: u4) bool {
1182
+ _ = .{ self, player, n };
1183
+ return true;
1184
+ }
826
1185
  };