@pkmn/engine 0.1.0-dev.89cc1c98 → 0.1.0-dev.8b192003

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 (42) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +44 -19
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data.d.ts +4 -4
  6. package/build/pkg/data.js.map +1 -1
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/index.d.ts +2 -1
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.js +5 -5
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/test/display.js +2 -2
  13. package/build/test/display.js.map +1 -1
  14. package/build/tools/debug.js +5 -4
  15. package/build/tools/debug.js.map +1 -1
  16. package/build.zig +100 -102
  17. package/package.json +31 -23
  18. package/src/bin/install-pkmn-engine +26 -18
  19. package/src/lib/common/data.zig +4 -4
  20. package/src/lib/common/debug.zig +2 -2
  21. package/src/lib/common/optional.zig +1 -1
  22. package/src/lib/common/options.zig +2 -0
  23. package/src/lib/common/protocol.zig +393 -396
  24. package/src/lib/common/rational.zig +109 -15
  25. package/src/lib/common/rng.zig +20 -11
  26. package/src/lib/gen1/calc.zig +478 -92
  27. package/src/lib/gen1/chance.zig +195 -428
  28. package/src/lib/gen1/data/species.zig +1 -1
  29. package/src/lib/gen1/data.zig +21 -21
  30. package/src/lib/gen1/helpers.zig +88 -534
  31. package/src/lib/gen1/mechanics.zig +377 -360
  32. package/src/lib/gen2/chance.zig +25 -457
  33. package/src/lib/gen2/data.zig +13 -11
  34. package/src/lib/gen2/generated.zig +27 -78
  35. package/src/lib/gen2/helpers.zig +37 -7
  36. package/src/lib/gen2/mechanics.zig +1022 -456
  37. package/src/lib/{binding/node.zig → node.zig} +9 -12
  38. package/src/lib/pkmn.zig +27 -12
  39. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  40. package/src/test/display/pkmn.html.tmpl +5 -5
  41. package/src/lib/gen1/README.md +0 -894
  42. package/src/lib/gen2/README.md +0 -330
@@ -14,7 +14,7 @@ const util = @import("../common/util.zig");
14
14
  const Player = @import("../common/data.zig").Player;
15
15
  const Optional = @import("../common/optional.zig").Optional;
16
16
 
17
- const data = @import("./data.zig");
17
+ const data = @import("data.zig");
18
18
 
19
19
  const enabled = options.chance;
20
20
  const showdown = options.showdown;
@@ -42,12 +42,6 @@ pub const Actions = extern struct {
42
42
  return if (player == .P1) &self.p1 else &self.p2;
43
43
  }
44
44
 
45
- /// Perform a reset by clearing fields which should not persist across updates.
46
- pub fn reset(self: *Actions) void {
47
- self.p1.reset();
48
- self.p2.reset();
49
- }
50
-
51
45
  /// Returns true if `a` is equal to `b`.
52
46
  pub fn eql(a: Actions, b: Actions) bool {
53
47
  return @as(u128, @bitCast(a)) == @as(u128, @bitCast(b));
@@ -63,11 +57,6 @@ pub const Actions = extern struct {
63
57
  const b_val = @field(@field(b, player.name), field.name);
64
58
 
65
59
  switch (@typeInfo(@TypeOf(a_val))) {
66
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
67
- if ((@field(a_val, f.name) > 0) != (@field(b_val, f.name) > 0)) {
68
- return false;
69
- }
70
- },
71
60
  .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
72
61
  return false,
73
62
  .Int => if ((a_val > 0) != (b_val > 0)) return false,
@@ -96,9 +85,6 @@ test Actions {
96
85
  const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
97
86
  const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
98
87
  const c: Actions = .{ .p1 = .{ .hit = .true } };
99
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
100
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
101
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
102
88
 
103
89
  try expect(a.eql(a));
104
90
  try expect(!a.eql(b));
@@ -111,10 +97,10 @@ test Actions {
111
97
  try expect(b.matches(a));
112
98
  try expect(!a.matches(c));
113
99
  try expect(!c.matches(a));
114
- try expect(d.matches(e));
115
- try expect(!d.matches(f));
116
100
  }
117
101
 
102
+ pub const Observation = enum { started, continuing, ended };
103
+
118
104
  /// Information about the RNG that was observed during a Generation I battle `update` for a
119
105
  /// single player.
120
106
  pub const Action = packed struct(u64) {
@@ -139,8 +125,15 @@ pub const Action = packed struct(u64) {
139
125
  /// or Rolls.{sleepDuration,disableDuration,confusionDuration,attackingDuration} otherwise.
140
126
  duration: u4 = 0,
141
127
 
142
- /// Observed values of various durations. Does not influence future RNG calls. TODO
143
- durations: Duration = .{},
128
+ // TODO
129
+ sleep: Optional(Observation) = .None,
130
+ confusion: Optional(Observation) = .None,
131
+ disable: Optional(Observation) = .None,
132
+ bide: Optional(Observation) = .None,
133
+ binding: Optional(Observation) = .None,
134
+ thrashing: Optional(Observation) = .None,
135
+
136
+ _: u4 = 0, // TODO
144
137
 
145
138
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
146
139
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
@@ -154,15 +147,8 @@ pub const Action = packed struct(u64) {
154
147
  /// If not None, the Move to return for Rolls.metronome.
155
148
  metronome: Move = .None,
156
149
 
157
- pub const DURATIONS: u64 = 0x000000FFFF000000;
158
-
159
150
  pub const Field = std.meta.FieldEnum(Action);
160
151
 
161
- /// Perform a reset by clearing fields which should not persist across updates.
162
- pub fn reset(self: *Action) void {
163
- self.* = @bitCast(@as(u64, @bitCast(self.*)) & DURATIONS);
164
- }
165
-
166
152
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
167
153
  _ = .{ f, o };
168
154
  try fmt(a, w, false);
@@ -174,18 +160,6 @@ pub const Action = packed struct(u64) {
174
160
  inline for (@typeInfo(Action).Struct.fields) |field| {
175
161
  const val = @field(self, field.name);
176
162
  switch (@typeInfo(@TypeOf(val))) {
177
- .Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
178
- const v = @field(val, f.name);
179
- if (v != 0) {
180
- if (printed) try writer.writeAll(", ");
181
- if (shape) {
182
- try writer.print("{s}:?", .{f.name});
183
- } else {
184
- try writer.print("{s}:{d}", .{ f.name, v });
185
- }
186
- printed = true;
187
- }
188
- },
189
163
  .Enum => if (val != .None) {
190
164
  if (printed) try writer.writeAll(", ");
191
165
  if (shape) {
@@ -195,6 +169,19 @@ pub const Action = packed struct(u64) {
195
169
  if (val == .false) "!" else "",
196
170
  field.name,
197
171
  });
172
+ } else if (@TypeOf(val) == Optional(Observation)) {
173
+ try writer.print("{s}{s}", .{
174
+ switch (val) {
175
+ .started => "+",
176
+ .continuing => "",
177
+ .ended => if (std.mem.eql(u8, field.name, "thrashing"))
178
+ "~"
179
+ else
180
+ "-",
181
+ else => unreachable,
182
+ },
183
+ field.name,
184
+ });
198
185
  } else {
199
186
  try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
200
187
  }
@@ -216,43 +203,111 @@ pub const Action = packed struct(u64) {
216
203
  }
217
204
  };
218
205
 
219
- test Action {
220
- var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
221
- a.reset();
222
-
223
- try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
224
- }
225
-
226
206
  /// TODO
227
- pub const Durations = extern struct {
228
- /// Information about the durations for Player 1.
207
+ pub const Durations = struct {
208
+ /// TODO
229
209
  p1: Duration = .{},
230
- /// Information about the durations for Player 2.
210
+ /// TODO
231
211
  p2: Duration = .{},
232
212
 
233
- comptime {
234
- assert(@sizeOf(Durations) == 4);
213
+ /// TODO
214
+ pub fn get(self: anytype, player: Player) util.PointerType(@TypeOf(self), Duration) {
215
+ assert(@typeInfo(@TypeOf(self)).Pointer.child == Durations);
216
+ return if (player == .P1) &self.p1 else &self.p2;
217
+ }
218
+
219
+ /// TODO
220
+ pub fn update(self: *Durations, battle: anytype, q: anytype, actions: Actions) !void {
221
+ for (&battle.sides, 0..) |*s, i| {
222
+ const p: Player = @enumFromInt(i);
223
+ const action = actions.get(p);
224
+ var duration = self.get(p);
225
+
226
+ // TODO sleeps
227
+
228
+ duration.confusion =
229
+ if (@intFromEnum(action.confusion) > 1) duration.confusion + 1 else 0;
230
+ duration.disable =
231
+ if (@intFromEnum(action.disable) > 1) duration.disable + 1 else 0;
232
+ duration.bide =
233
+ if (@intFromEnum(action.bide) > 1) duration.bide + 1 else 0;
234
+ duration.binding =
235
+ if (@intFromEnum(action.binding) > 1) duration.binding + 1 else 0;
236
+
237
+ // NOTE: this relies on private information!
238
+ if (action.thrashing == .ended) {
239
+ duration.thrashing =
240
+ if (s.active.volatiles.Thrashing) duration.thrashing + 1 else 0;
241
+ } else {
242
+ duration.thrashing =
243
+ if (@intFromEnum(action.thrashing) > 1) duration.thrashing + 1 else 0;
244
+ }
245
+
246
+ _ = q;
247
+ }
248
+ }
249
+
250
+ pub fn format(
251
+ self: Durations,
252
+ comptime fmt: []const u8,
253
+ opts: std.fmt.FormatOptions,
254
+ writer: anytype,
255
+ ) !void {
256
+ _ = .{ fmt, opts };
257
+ try writer.print("[P1 = {}, P2 = {}]", .{ self.p1, self.p2 });
235
258
  }
236
259
  };
237
260
 
238
- /// Observed values for various durations that need to be tracked in order to properly
239
- /// deduplicate transitions with a primary key. TODO
240
- pub const Duration = packed struct(u16) {
241
- /// The number of turns a Pokémon has been observed to be sleeping.
242
- sleep: u3 = 0,
243
- /// The number of turns a Pokémon has been observed to be confused.
261
+ /// TODO
262
+ pub const Duration = struct {
263
+ sleeps: [6]u3 = .{0} ** 6,
244
264
  confusion: u3 = 0,
245
- /// The number of turns a Pokémon has been observed to be disabled.
246
265
  disable: u4 = 0,
247
- /// The number of turns a Pokémon has been observed to be attacking.
248
- attacking: u3 = 0,
249
- /// The number of turns a Pokémon has been observed to be binding their opponent.
266
+ bide: u3 = 0,
267
+ thrashing: u3 = 0,
250
268
  binding: u3 = 0,
251
269
 
252
- pub const Field = std.meta.FieldEnum(Duration);
270
+ pub fn format(
271
+ self: Duration,
272
+ comptime fmt: []const u8,
273
+ opts: std.fmt.FormatOptions,
274
+ writer: anytype,
275
+ ) !void {
276
+ _ = .{ fmt, opts };
277
+ try writer.writeByte('(');
278
+ var printed = false;
279
+ inline for (@typeInfo(Duration).Struct.fields) |field| {
280
+ const val = @field(self, field.name);
281
+ switch (@typeInfo(@TypeOf(val))) {
282
+ .Int => if (val != 0) {
283
+ if (printed) try writer.writeAll(", ");
284
+ try writer.print("{s}:{d}", .{ field.name, val });
285
+ printed = true;
286
+ },
287
+ .Array => {
288
+ var total: usize = 0;
289
+ for (val) |v| total += v;
290
+ if (total > 0) {
291
+ if (printed) try writer.writeAll(", ");
292
+ try writer.print("{s}:[", .{field.name});
293
+ for (val, 0..) |v, i| {
294
+ if (i != 0) try writer.writeByte(',');
295
+ try writer.print("{d}", .{v});
296
+ }
297
+ try writer.writeAll("]");
298
+ printed = true;
299
+ }
300
+ },
301
+ else => unreachable,
302
+ }
303
+ }
304
+ try writer.writeByte(')');
305
+ }
253
306
  };
254
307
 
255
- pub const Commit = enum { hit, miss, binding };
308
+ pub const Commit = enum { hit, miss };
309
+
310
+ const uN = if (options.miss) u8 else u9;
256
311
 
257
312
  /// Tracks chance actions and their associated probability during a Generation I battle update when
258
313
  /// `options.chance` is enabled.
@@ -266,14 +321,6 @@ pub fn Chance(comptime Rational: type) type {
266
321
  /// RNG events were observed during a battle `update`.
267
322
  actions: Actions = .{},
268
323
 
269
- // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
270
- // other durations which are all tied to volatiles, sleep's counter persists through
271
- // switching and so we must store it here. The indices of these arrays correspond to the
272
- // `order` field of a Side. This information could be stored in actions but size is a
273
- // concern so this is tracked separately as actions only purports to track RNG events
274
- // observed during a single `update` (not across updates).
275
- sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
276
-
277
324
  // In many cases on both the cartridge and on Pokémon Showdown rolls are made even though
278
325
  // later checks render their result irrelevant (missing when the target is actually immune,
279
326
  // rolling for damage when the move is later revealed to have missed, etc). We can't reorder
@@ -290,16 +337,12 @@ pub fn Chance(comptime Rational: type) type {
290
337
  //
291
338
  // Finally, because each player's actions are processed sequentially we only need a single
292
339
  // shared structure to track this information (though it needs to be cleared appropriately)
293
- pending: if (showdown) struct {
294
- hit: bool = false,
295
- hit_probablity: u8 = 0,
296
- } else struct {
340
+ pending: if (showdown) struct {} else struct {
297
341
  crit: bool = false,
298
342
  crit_probablity: u8 = 0,
299
343
  damage_roll: u8 = 0,
300
344
  hit: bool = false,
301
- hit_probablity: u8 = 0,
302
- binding: u8 = 0,
345
+ hit_probablity: uN = 0,
303
346
  } = .{},
304
347
 
305
348
  /// Possible error returned by operations tracking chance probability.
@@ -307,25 +350,19 @@ pub fn Chance(comptime Rational: type) type {
307
350
 
308
351
  /// Convenience helper to clear fields which typically should be cleared between updates.
309
352
  pub fn reset(self: *Self) void {
353
+ if (!enabled) return;
354
+
310
355
  self.probability.reset();
311
- self.actions.reset();
356
+ self.actions = .{};
312
357
  self.pending = .{};
313
358
  }
314
359
 
315
360
  pub fn commit(self: *Self, player: Player, kind: Commit) Error!void {
316
- var action = self.actions.get(player);
361
+ if (!enabled) return;
317
362
 
318
- // We need to handle binding specially because we still need to commit it when attacking
319
- // into a Pokémon that is immune
320
- if (!showdown) {
321
- if (kind != .miss and self.pending.binding != 0) {
322
- assert(action.duration == 0);
323
- action.duration = @intCast(self.pending.binding);
324
- assert(action.durations.binding == 0);
325
- action.durations.binding = 1;
326
- }
327
- if (kind == .binding) return;
328
- } else assert(kind != .binding);
363
+ assert(!showdown);
364
+
365
+ var action = self.actions.get(player);
329
366
 
330
367
  // Always commit the hit result if we make it here (commit won't be called at all if the
331
368
  // target is immune/behind a sub/etc)
@@ -334,19 +371,19 @@ pub fn Chance(comptime Rational: type) type {
334
371
  action.hit = if (self.pending.hit) .true else .false;
335
372
  }
336
373
 
337
- if (showdown) return;
338
-
339
- // If the move actually lands we can commit any past critical hit / damage rolls. We
340
- // avoid updating anything if there wasn't a damage roll as any "critical hit" not tied
341
- // to a damage roll is actually a no-op
342
- if (kind == .hit and self.pending.damage_roll > 0) {
343
- assert(!self.pending.crit or self.pending.crit_probablity > 0);
374
+ // If the move actually lands we can commit any past critical hit / damage rolls. Note
375
+ // we can't rely on the presence or absence of a damage roll to determine whether a
376
+ // "critical hit" was spurious or not as moves that do 1 or less damage don't rol for
377
+ // damage but can still crit (instead, whether or not a roll is deemed to be a no-op is
378
+ // passed to the critical hit helper)
379
+ if (kind != .hit) return assert(!self.pending.crit or self.pending.crit_probablity > 0);
344
380
 
345
- if (self.pending.crit_probablity != 0) {
346
- try self.probability.update(self.pending.crit_probablity, 256);
347
- action.critical_hit = if (self.pending.crit) .true else .false;
348
- }
381
+ if (self.pending.crit_probablity != 0) {
382
+ try self.probability.update(self.pending.crit_probablity, 256);
383
+ action.critical_hit = if (self.pending.crit) .true else .false;
384
+ }
349
385
 
386
+ if (self.pending.damage_roll > 0) {
350
387
  try self.probability.update(1, 39);
351
388
  action.damage = self.pending.damage_roll;
352
389
  }
@@ -358,37 +395,6 @@ pub fn Chance(comptime Rational: type) type {
358
395
  self.pending = .{};
359
396
  }
360
397
 
361
- pub fn clearDurations(self: *Self, player: Player, haze: ?bool) void {
362
- if (!enabled) return;
363
-
364
- var action = self.actions.get(player);
365
- if (haze) |status| {
366
- if (status) action.durations.sleep = 0;
367
- action.durations.confusion = 0;
368
- action.durations.disable = 0;
369
- } else {
370
- action.durations.attacking = 0;
371
- action.durations.binding = 0;
372
- }
373
- }
374
-
375
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
376
- if (!enabled) return;
377
-
378
- assert(in >= 1 and in <= 6);
379
- assert(out >= 1 and out <= 6);
380
-
381
- var action = self.actions.get(player);
382
-
383
- const slp = action.durations.sleep;
384
- action.durations = .{};
385
-
386
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
387
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
388
-
389
- self.actions.get(player.foe()).durations.binding = 0;
390
- }
391
-
392
398
  pub fn speedTie(self: *Self, p1: bool) Error!void {
393
399
  if (!enabled) return;
394
400
 
@@ -397,12 +403,17 @@ pub fn Chance(comptime Rational: type) type {
397
403
  self.actions.p2.speed_tie = self.actions.p1.speed_tie;
398
404
  }
399
405
 
400
- pub fn hit(self: *Self, ok: bool, accuracy: u8) void {
406
+ pub fn hit(self: *Self, player: Player, ok: bool, accuracy: uN) Error!void {
401
407
  if (!enabled) return;
402
408
 
403
409
  const p = if (ok) accuracy else @as(u8, @intCast(256 - @as(u9, accuracy)));
404
- self.pending.hit = ok;
405
- self.pending.hit_probablity = p;
410
+ if (showdown) {
411
+ try self.probability.update(p, 256);
412
+ self.actions.get(player).hit = if (ok) .true else .false;
413
+ } else {
414
+ self.pending.hit = ok;
415
+ self.pending.hit_probablity = p;
416
+ }
406
417
  }
407
418
 
408
419
  pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
@@ -480,109 +491,21 @@ pub fn Chance(comptime Rational: type) type {
480
491
  self.actions.get(player).multi_hit = n;
481
492
  }
482
493
 
483
- pub fn duration(
484
- self: *Self,
485
- comptime field: Duration.Field,
486
- player: Player,
487
- target: Player,
488
- turns: u4,
489
- ) void {
490
- if (!enabled) return;
491
-
492
- if (!showdown and field == .binding) {
493
- self.pending.binding = if (options.key) 1 else turns;
494
- } else {
495
- var durations = &self.actions.get(target).durations;
496
- assert(@field(durations, @tagName(field)) == 0 or
497
- (field == .confusion and player == target));
498
- @field(durations, @tagName(field)) = 1;
499
- self.actions.get(player).duration = if (options.key) 1 else turns;
500
- }
501
- }
502
-
503
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
504
- if (!enabled) return;
505
-
506
- var durations = &self.actions.get(player).durations;
507
- const n = durations.sleep;
508
- if (turns == 0) {
509
- assert(n >= 1 and n <= 7);
510
- if (n != 7) try self.probability.update(1, 8 - @as(u4, n));
511
- durations.sleep = 0;
512
- } else {
513
- assert(n >= 1 and n < 7);
514
- try self.probability.update(8 - @as(u4, n) - 1, 8 - @as(u4, n));
515
- durations.sleep += 1;
516
- }
517
- }
518
-
519
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
494
+ pub fn duration(self: *Self, player: Player, turns: u4) void {
520
495
  if (!enabled) return;
521
496
 
522
- var durations = &self.actions.get(player).durations;
523
- const n = durations.confusion;
524
- if (turns == 0) {
525
- assert(n >= 2 and n <= 5);
526
- if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
527
- durations.confusion = 0;
528
- } else {
529
- assert(n >= 1 and n < 5);
530
- if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
531
- durations.confusion += 1;
532
- }
497
+ self.actions.get(player).duration = if (options.key) 1 else turns;
533
498
  }
534
499
 
535
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
536
- if (!enabled) return;
537
-
538
- var durations = &self.actions.get(player).durations;
539
- const n = durations.disable;
540
- if (turns == 0) {
541
- assert(n >= 1 and n <= 8);
542
- if (n != 8) try self.probability.update(1, 9 - @as(u4, n));
543
- durations.disable = 0;
544
- } else {
545
- assert(n >= 1 and n < 8);
546
- try self.probability.update(9 - @as(u4, n) - 1, 9 - @as(u4, n));
547
- durations.disable += 1;
548
- }
549
- }
550
-
551
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
552
- if (!enabled) return;
553
-
554
- var durations = &self.actions.get(player).durations;
555
- const n = durations.attacking;
556
- if (turns == 0) {
557
- assert(n >= 2 and n <= 3);
558
- if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
559
- durations.attacking = 0;
560
- } else {
561
- assert(n >= 1 and n < 3);
562
- if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
563
- durations.attacking += 1;
564
- }
565
- }
566
-
567
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
500
+ pub fn durations(
501
+ self: *Self,
502
+ comptime f: Action.Field,
503
+ player: Player,
504
+ observation: Optional(Observation),
505
+ ) void {
568
506
  if (!enabled) return;
569
507
 
570
- var durations = &self.actions.get(player).durations;
571
- const n = durations.binding;
572
-
573
- assert(n > 0);
574
- const p: u4 = if (n < 3) 3 else 1;
575
- const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
576
-
577
- if (turns == 0) {
578
- assert(n >= 1 and n <= 4);
579
- if (n != 4) try self.probability.update(p, q);
580
- durations.binding = 0;
581
- } else {
582
- assert(n >= 1 and n < 4);
583
- try self.probability.update(q - p, q);
584
- durations.binding += 1;
585
- }
508
+ @field(self.actions.get(player), @tagName(f)) = observation;
586
509
  }
587
510
 
588
511
  pub fn psywave(self: *Self, player: Player, power: u8, max: u8) Error!void {
@@ -620,19 +543,25 @@ test "Chance.speedTie" {
620
543
  test "Chance.hit" {
621
544
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
622
545
 
623
- chance.hit(true, 229);
624
- try expectProbability(&chance.probability, 1, 1);
625
- try expectValue(Optional(bool).None, chance.actions.p1.hit);
626
- try chance.commit(.P1, .hit);
546
+ try chance.hit(.P1, true, 229);
547
+ if (!showdown) {
548
+ try expectProbability(&chance.probability, 1, 1);
549
+ try expectValue(Optional(bool).None, chance.actions.p1.hit);
550
+
551
+ try chance.commit(.P1, .hit);
552
+ }
627
553
  try expectValue(Optional(bool).true, chance.actions.p1.hit);
628
554
  try expectProbability(&chance.probability, 229, 256);
629
555
 
630
556
  chance.reset();
631
557
 
632
- chance.hit(false, 255);
633
- try expectProbability(&chance.probability, 1, 1);
634
- try expectValue(Optional(bool).None, chance.actions.p2.hit);
635
- try chance.commit(.P2, .miss);
558
+ try chance.hit(.P2, false, 255);
559
+ if (!showdown) {
560
+ try expectProbability(&chance.probability, 1, 1);
561
+ try expectValue(Optional(bool).None, chance.actions.p2.hit);
562
+
563
+ try chance.commit(.P2, .miss);
564
+ }
636
565
  try expectValue(Optional(bool).false, chance.actions.p2.hit);
637
566
  try expectProbability(&chance.probability, 1, 256);
638
567
  }
@@ -648,26 +577,18 @@ test "Chance.criticalHit" {
648
577
  try expectProbability(&chance.probability, 1, 1);
649
578
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
650
579
 
651
- try chance.commit(.P1, .hit);
652
- try expectProbability(&chance.probability, 1, 1);
653
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
580
+ try chance.hit(.P1, true, 128);
654
581
 
655
- chance.hit(true, 128);
656
-
657
- try chance.commit(.P1, .hit);
658
- try expectProbability(&chance.probability, 1, 2);
582
+ try expectProbability(&chance.probability, 1, 1);
659
583
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
660
584
 
661
- chance.probability.reset();
662
585
  try chance.damage(.P1, 217);
663
586
 
664
- try chance.commit(.P1, .miss);
665
- try expectProbability(&chance.probability, 1, 2);
587
+ try expectProbability(&chance.probability, 1, 1);
666
588
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
667
589
 
668
- chance.probability.reset();
669
-
670
590
  try chance.commit(.P1, .hit);
591
+
671
592
  try expectProbability(&chance.probability, 17, 19968); // (1/2) * (17/256) * (1/39)
672
593
  try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
673
594
  }
@@ -682,20 +603,14 @@ test "Chance.criticalHit" {
682
603
  try expectProbability(&chance.probability, 1, 1);
683
604
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
684
605
 
685
- try chance.commit(.P1, .hit);
686
- try expectProbability(&chance.probability, 1, 1);
687
- try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
688
-
689
- chance.hit(true, 128);
606
+ try chance.hit(.P1, true, 128);
690
607
 
691
- try chance.commit(.P1, .hit);
692
- try expectProbability(&chance.probability, 1, 2);
608
+ try expectProbability(&chance.probability, 1, 1);
693
609
  try expectValue(Optional(bool).None, chance.actions.p1.critical_hit);
694
610
 
695
- chance.probability.reset();
696
611
  try chance.damage(.P1, 217);
697
-
698
612
  try chance.commit(.P1, .hit);
613
+
699
614
  try expectProbability(&chance.probability, 251, 19968); // (1/2) * (251/256) * (1/39)
700
615
  try expectValue(Optional(bool).false, chance.actions.p1.critical_hit);
701
616
  }
@@ -706,19 +621,16 @@ test "Chance.damage" {
706
621
 
707
622
  try chance.damage(.P1, 217);
708
623
  if (showdown) {
709
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
624
+ try expectValue(217, chance.actions.p1.damage);
710
625
  try expectProbability(&chance.probability, 1, 39);
711
626
  } else {
712
627
  try expectProbability(&chance.probability, 1, 1);
713
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
714
-
715
- try chance.commit(.P1, .miss);
716
- try expectProbability(&chance.probability, 1, 1);
717
- try expectValue(@as(u8, 0), chance.actions.p1.damage);
628
+ try expectValue(0, chance.actions.p1.damage);
718
629
 
719
630
  try chance.commit(.P1, .hit);
631
+
720
632
  try expectProbability(&chance.probability, 1, 39);
721
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
633
+ try expectValue(217, chance.actions.p1.damage);
722
634
  }
723
635
  }
724
636
 
@@ -770,13 +682,13 @@ test "Chance.moveSlot" {
770
682
 
771
683
  try chance.moveSlot(.P2, 2, &ms, 2);
772
684
  try expectProbability(&chance.probability, 1, 2);
773
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
685
+ try expectValue(2, chance.actions.p2.move_slot);
774
686
 
775
687
  chance.reset();
776
688
 
777
689
  try chance.moveSlot(.P1, 1, &ms, 0);
778
690
  try expectProbability(&chance.probability, 1, 3);
779
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
691
+ try expectValue(1, chance.actions.p1.move_slot);
780
692
  }
781
693
 
782
694
  test "Chance.multiHit" {
@@ -784,149 +696,25 @@ test "Chance.multiHit" {
784
696
 
785
697
  try chance.multiHit(.P1, 3);
786
698
  try expectProbability(&chance.probability, 3, 8);
787
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
699
+ try expectValue(3, chance.actions.p1.multi_hit);
788
700
 
789
701
  chance.reset();
790
702
 
791
703
  try chance.multiHit(.P2, 5);
792
704
  try expectProbability(&chance.probability, 1, 8);
793
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
705
+ try expectValue(5, chance.actions.p2.multi_hit);
794
706
  }
795
707
 
796
708
  test "Chance.duration" {
797
709
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
798
710
 
799
- chance.duration(.sleep, .P1, .P2, 2);
800
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
801
- try expectValue(@as(u3, 1), chance.actions.p2.durations.sleep);
711
+ chance.duration(.P1, 2);
712
+ try expectValue(2, chance.actions.p1.duration);
802
713
 
803
714
  chance.reset();
804
715
 
805
- chance.duration(.binding, .P2, .P2, 4);
806
- if (!showdown) {
807
- try expectValue(@as(u4, 0), chance.actions.p2.duration);
808
- try expectValue(@as(u3, 0), chance.actions.p2.durations.binding);
809
- try chance.commit(.P2, .hit);
810
- }
811
- try expectValue(@as(u4, 4), chance.actions.p2.duration);
812
- try expectValue(@as(u3, 1), chance.actions.p2.durations.binding);
813
- }
814
-
815
- test "Chance.sleep" {
816
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
817
-
818
- for ([_]u8{ 7, 6, 5, 4, 3, 2, 1 }, 1..8) |d, i| {
819
- chance.actions.p1.durations.sleep = @intCast(i);
820
- try chance.sleep(.P1, 0);
821
- try expectProbability(&chance.probability, 1, d);
822
- try expectValue(@as(u3, 0), chance.actions.p1.durations.sleep);
823
-
824
- chance.reset();
825
-
826
- if (i < 7) {
827
- chance.actions.p1.durations.sleep = @intCast(i);
828
- try chance.sleep(.P1, 1);
829
- try expectProbability(&chance.probability, d - 1, d);
830
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
831
-
832
- chance.reset();
833
- }
834
- }
835
- }
836
-
837
- test "Chance.confusion" {
838
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
839
-
840
- for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
841
- if (i > 1) {
842
- chance.actions.p2.durations.confusion = @intCast(i);
843
- try chance.confusion(.P2, 0);
844
- try expectProbability(&chance.probability, 1, d);
845
- try expectValue(@as(u3, 0), chance.actions.p2.durations.confusion);
846
-
847
- chance.reset();
848
- }
849
-
850
- if (i < 5) {
851
- chance.actions.p2.durations.confusion = @intCast(i);
852
- try chance.confusion(.P2, 1);
853
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
854
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
855
-
856
- chance.reset();
857
- }
858
- }
859
- }
860
-
861
- test "Chance.disable" {
862
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
863
-
864
- for ([_]u8{ 8, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
865
- chance.actions.p1.durations.disable = @intCast(i);
866
- try chance.disable(.P1, 0);
867
- try expectProbability(&chance.probability, 1, d);
868
- try expectValue(@as(u4, 0), chance.actions.p1.durations.disable);
869
-
870
- chance.reset();
871
-
872
- if (i < 8) {
873
- chance.actions.p1.durations.disable = @intCast(i);
874
- try chance.disable(.P1, 1);
875
- try expectProbability(&chance.probability, d - 1, d);
876
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p1.durations.disable);
877
-
878
- chance.reset();
879
- }
880
- }
881
- }
882
-
883
- test "Chance.attacking" {
884
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
885
-
886
- for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
887
- if (i > 1) {
888
- chance.actions.p2.durations.attacking = @intCast(i);
889
- try chance.attacking(.P2, 0);
890
- try expectProbability(&chance.probability, 1, d);
891
- try expectValue(@as(u3, 0), chance.actions.p2.durations.attacking);
892
-
893
- chance.reset();
894
- }
895
-
896
- if (i < 3) {
897
- chance.actions.p2.durations.attacking = @intCast(i);
898
- try chance.attacking(.P2, 1);
899
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
900
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
901
-
902
- chance.reset();
903
- }
904
- }
905
- }
906
-
907
- test "Chance.binding" {
908
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
909
-
910
- const ps = [_]u8{ 3, 3, 1, 1 };
911
- const qs = [_]u8{ 8, 5, 2, 1 };
912
-
913
- for (ps, qs, 1..5) |p, q, i| {
914
- chance.actions.p1.durations.binding = @intCast(i);
915
- try chance.binding(.P1, 0);
916
- try expectProbability(&chance.probability, p, q);
917
- try expectValue(@as(u3, 0), chance.actions.p1.durations.binding);
918
-
919
- chance.reset();
920
-
921
- if (i < 4) {
922
- chance.actions.p1.durations.binding = @intCast(i);
923
- try chance.binding(.P1, 1);
924
- try expectProbability(&chance.probability, q - p, q);
925
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.binding);
926
-
927
- chance.reset();
928
- }
929
- }
716
+ chance.duration(.P2, 4);
717
+ try expectValue(4, chance.actions.p2.duration);
930
718
  }
931
719
 
932
720
  test "Chance.psywave" {
@@ -934,7 +722,7 @@ test "Chance.psywave" {
934
722
 
935
723
  try chance.psywave(.P2, 100, 150);
936
724
  try expectProbability(&chance.probability, 1, 150);
937
- try expectValue(@as(u8, 101), chance.actions.p2.psywave);
725
+ try expectValue(101, chance.actions.p2.psywave);
938
726
  }
939
727
 
940
728
  test "Chance.metronome" {
@@ -976,10 +764,6 @@ const Null = struct {
976
764
  _ = .{self};
977
765
  }
978
766
 
979
- pub fn clearDurations(self: Null, player: Player, haze: ?bool) void {
980
- _ = .{ self, player, haze };
981
- }
982
-
983
767
  pub fn switched(self: Null, player: Player, in: u8, out: u8) void {
984
768
  _ = .{ self, player, in, out };
985
769
  }
@@ -988,8 +772,8 @@ const Null = struct {
988
772
  _ = .{ self, p1 };
989
773
  }
990
774
 
991
- pub fn hit(self: Null, ok: bool, accuracy: u8) void {
992
- _ = .{ self, ok, accuracy };
775
+ pub fn hit(self: Null, player: Player, ok: bool, accuracy: uN) Error!void {
776
+ _ = .{ self, player, ok, accuracy };
993
777
  }
994
778
 
995
779
  pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
@@ -1020,33 +804,16 @@ const Null = struct {
1020
804
  _ = .{ self, player, n };
1021
805
  }
1022
806
 
1023
- pub fn duration(
807
+ pub fn durations(
1024
808
  self: Null,
1025
- comptime field: Duration.Field,
809
+ comptime f: Action.Field,
1026
810
  player: Player,
1027
- target: Player,
1028
- turns: u4,
811
+ observation: Optional(Observation),
1029
812
  ) void {
1030
- _ = .{ self, field, player, target, turns };
1031
- }
1032
-
1033
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1034
- _ = .{ self, player, turns };
1035
- }
1036
-
1037
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1038
- _ = .{ self, player, turns };
1039
- }
1040
-
1041
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1042
- _ = .{ self, player, turns };
1043
- }
1044
-
1045
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1046
- _ = .{ self, player, turns };
813
+ _ = .{ self, f, player, observation };
1047
814
  }
1048
815
 
1049
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
816
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1050
817
  _ = .{ self, player, turns };
1051
818
  }
1052
819