@pkmn/engine 0.1.0-dev.eaae4aa6 → 0.1.0-dev.f34af416

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 (40) hide show
  1. package/LICENSE +0 -2
  2. package/README.md +15 -11
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data.d.ts +2 -3
  6. package/build/pkg/index.d.ts +1 -1
  7. package/build/pkg/index.js.map +1 -1
  8. package/build/pkg/protocol.js +4 -4
  9. package/build/pkg/protocol.js.map +1 -1
  10. package/build/test/display.js +8 -3
  11. package/build/test/display.js.map +1 -1
  12. package/build/tools/debug.js +2 -3
  13. package/build/tools/debug.js.map +1 -1
  14. package/build.zig +189 -72
  15. package/package.json +17 -16
  16. package/src/bin/install-pkmn-engine +17 -7
  17. package/src/lib/common/data.zig +8 -8
  18. package/src/lib/common/debug.zig +2 -2
  19. package/src/lib/common/js.zig +4 -2
  20. package/src/lib/common/optional.zig +13 -9
  21. package/src/lib/common/options.zig +2 -0
  22. package/src/lib/common/protocol.zig +51 -44
  23. package/src/lib/common/rational.zig +114 -24
  24. package/src/lib/common/rng.zig +10 -10
  25. package/src/lib/common/util.zig +50 -1
  26. package/src/lib/gen1/calc.zig +508 -128
  27. package/src/lib/gen1/chance.zig +383 -276
  28. package/src/lib/gen1/data/species.zig +1 -1
  29. package/src/lib/gen1/data.zig +29 -28
  30. package/src/lib/gen1/helpers.zig +96 -536
  31. package/src/lib/gen1/mechanics.zig +266 -160
  32. package/src/lib/gen2/calc.zig +34 -16
  33. package/src/lib/gen2/chance.zig +91 -500
  34. package/src/lib/gen2/data.zig +15 -14
  35. package/src/lib/gen2/helpers.zig +41 -9
  36. package/src/lib/gen2/mechanics.zig +158 -97
  37. package/src/lib/pkmn.zig +10 -16
  38. package/src/test/display/pkmn.html.tmpl +5 -5
  39. package/src/lib/gen1/README.md +0 -894
  40. package/src/lib/gen2/README.md +0 -329
@@ -11,6 +11,7 @@ const rational = @import("../common/rational.zig");
11
11
  const rng = @import("../common/rng.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
 
@@ -21,11 +22,18 @@ const showdown = options.showdown;
21
22
 
22
23
  const Gen12 = rng.Gen12;
23
24
 
25
+ const PointerType = util.PointerType;
26
+ const isPointerTo = util.isPointerTo;
27
+
24
28
  const Move = data.Move;
25
29
  const Type = data.Type;
26
30
  const Effectiveness = data.Effectiveness;
27
31
  const TriAttack = data.TriAttack;
28
32
 
33
+ const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
34
+ const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
35
+ const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
36
+
29
37
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
30
38
  /// were observed during a Generation II battle `update`. This can additionally be provided as input
31
39
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -37,17 +45,11 @@ pub const Actions = extern struct {
37
45
  p2: Action = .{},
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
 
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
53
  /// Returns true if `a` is equal to `b`.
52
54
  pub fn eql(a: Actions, b: Actions) bool {
53
55
  return @as(u128, @bitCast(a.p1)) == @as(u128, @bitCast(b.p1)) and
@@ -58,24 +60,15 @@ pub const Actions = extern struct {
58
60
  /// same shape if they have the same fields set (though those fields need not necessarily be
59
61
  /// set to the same value).
60
62
  pub fn matches(a: Actions, b: Actions) bool {
61
- inline for (@typeInfo(Actions).Struct.fields) |player| {
62
- inline for (@typeInfo(Action).Struct.fields) |field| {
63
+ inline for (@field(@typeInfo(Actions), @tagName(Struct)).fields) |player| {
64
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
63
65
  const a_val = @field(@field(a, player.name), field.name);
64
66
  const b_val = @field(@field(b, player.name), field.name);
65
67
 
66
68
  switch (@typeInfo(@TypeOf(a_val))) {
67
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
68
- const a_val_f = @field(a_val, f.name);
69
- const b_val_f = @field(b_val, f.name);
70
- switch (@typeInfo(@TypeOf(a_val_f))) {
71
- .Int => if ((a_val_f > 0) != (b_val_f > 0)) return false,
72
- .Bool => if (a_val_f != b_val_f) return false,
73
- else => unreachable,
74
- }
75
- },
76
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
69
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
77
70
  return false,
78
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
71
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
79
72
  else => unreachable,
80
73
  }
81
74
  }
@@ -98,15 +91,9 @@ pub const Actions = extern struct {
98
91
  };
99
92
 
100
93
  test Actions {
101
- const a: Actions = .{ .p1 = .{ .hit = .true, .critical_hit = .false, .damage = 245 } };
102
- const b: Actions = .{ .p1 = .{ .hit = .false, .critical_hit = .true, .damage = 246 } };
94
+ const a: Actions = .{ .p1 = .{ .hit = .true, .confused = .false, .damages = 245 } };
95
+ const b: Actions = .{ .p1 = .{ .hit = .false, .confused = .true, .damages = 246 } };
103
96
  const c: Actions = .{ .p1 = .{ .hit = .true } };
104
- const d: Actions = .{ .p2 = .{ .hit = .true, .durations = .{ .sleep = 2 } } };
105
- const e: Actions = .{ .p2 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
106
- const f: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .sleep = 4 } } };
107
- const g: Actions = .{ .p1 = .{ .hit = .false, .durations = .{ .confusion = 1 } } };
108
- const h: Actions =
109
- .{ .p1 = .{ .hit = .false, .durations = .{ .confusion = 1, .thrash = true } } };
110
97
 
111
98
  try expect(a.eql(a));
112
99
  try expect(!a.eql(b));
@@ -119,17 +106,21 @@ test Actions {
119
106
  try expect(b.matches(a));
120
107
  try expect(!a.matches(c));
121
108
  try expect(!c.matches(a));
122
- try expect(d.matches(e));
123
- try expect(!d.matches(f));
124
- try expect(!g.matches(h));
125
- try expect(h.matches(h));
126
109
  }
127
110
 
111
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
112
+ pub const Observation = enum { started, continuing, ended };
113
+
114
+ pub const Damages = Array(5, u8);
115
+ pub const Criticals = Array(5, Optional(bool));
116
+
128
117
  /// Information about the RNG that was observed during a Generation II battle `update` for a
129
118
  /// single player.
130
119
  pub const Action = packed struct(u128) {
131
- /// If not 0, the roll to be returned Rolls.damage.
132
- damage: u8 = 0,
120
+ /// TODO
121
+ damages: Damages.T = 0,
122
+ // TODO
123
+ critical_hits: Criticals.T = 0,
133
124
 
134
125
  /// If not None, the Player to be returned by Rolls.speedTie.
135
126
  speed_tie: Optional(Player) = .None,
@@ -137,50 +128,58 @@ pub const Action = packed struct(u128) {
137
128
  quick_claw: Optional(bool) = .None,
138
129
  /// If not None, the value to return for Rolls.hit.
139
130
  hit: Optional(bool) = .None,
140
- /// If not None, the value to be returned by Rolls.criticalHit.
141
- critical_hit: Optional(bool) = .None,
142
-
143
131
  /// If not None, the value to return for Rolls.confused.
144
132
  confused: Optional(bool) = .None,
133
+
145
134
  /// If not None, the value to return for Rolls.attract.
146
135
  attract: Optional(bool) = .None,
147
136
  /// If not None, the value to return for Rolls.paralyzed.
148
137
  paralyzed: Optional(bool) = .None,
149
138
  /// If not None, the value to return for Rolls.defrost.
150
139
  defrost: Optional(bool) = .None,
151
-
152
140
  /// If not None, the value to be returned for Rolls.secondaryChance.
153
141
  secondary_chance: Optional(bool) = .None,
142
+
154
143
  /// If not None, the value to be returned for Rolls.item (Focus Band / King's Rock).
155
144
  item: Optional(bool) = .None,
156
145
  /// If not None, the value to be returned for Rolls.protect.
157
146
  protect: Optional(bool) = .None,
158
147
  /// If not None, the value to return for Rolls.triAttack.
159
148
  tri_attack: Optional(TriAttack) = .None,
149
+ /// If not 0, the value to return for Rolls.tripleKick.
150
+ triple_kick: u2 = 0,
160
151
 
161
152
  /// If not 0, (present - 1) * 40 should be returned as the base power for Rolls.present.
162
153
  present: u3 = 0,
163
154
  /// If not 0, magnitude + 3 should be returned as the number for Rolls.magnitude.
164
155
  magnitude: u3 = 0,
165
- /// If not 0, the value to return for Rolls.tripleKick.
166
- triple_kick: u2 = 0,
167
-
168
- /// If not 0, the amount of PP to deduct for Rolls.spite.
156
+ /// If not 0, the amount of PP to deduct for Rolls.spite.
169
157
  spite: u3 = 0,
170
- /// If not None, the value to return for Rolls.conversion2.
171
- conversion_2: Optional(Type) = .None,
172
-
173
158
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
174
159
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
175
- move_slot: u4 = 0,
160
+ move_slot: u3 = 0,
176
161
  /// If not 0, the party slot (1-6) to return in Rolls.forceSwitch. If present as an override,
177
162
  /// invalid values (eg. due to empty party slots or fainted members) will be ignored.
178
- force_switch: u4 = 0,
179
-
163
+ force_switch: u3 = 0,
180
164
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
181
- multi_hit: u4 = 0,
165
+ multi_hit: u3 = 0,
166
+
167
+ sleep: Optional(Observation) = .None,
168
+ /// TODO
169
+ confusion: Optional(Observation) = .None,
170
+ /// TODO
171
+ disable: Optional(Observation) = .None,
172
+ /// TODO
173
+ attacking: Optional(Observation) = .None,
174
+ /// TODO
175
+ binding: Optional(Observation) = .None,
176
+ /// TODO
177
+ encore: Optional(Observation) = .None,
178
+
182
179
  /// If not 0, the value to by one of the Rolls.*Duration rolls.
183
- duration: u4 = 0,
180
+ duration: u3 = 0,
181
+ /// If not None, the value to return for Rolls.conversion2.
182
+ conversion_2: Optional(Type) = .None,
184
183
 
185
184
  /// If not 0, psywave should be returned as the damage roll for Rolls.psywave.
186
185
  psywave: u8 = 0,
@@ -188,20 +187,8 @@ pub const Action = packed struct(u128) {
188
187
  /// If not None, the Move to return for Rolls.metronome.
189
188
  metronome: Move = .None,
190
189
 
191
- _: u16 = 0,
192
-
193
- /// Observed values of various durations. Does not influence future RNG calls. TODO
194
- durations: Duration = .{},
195
-
196
- pub const DURATIONS: u128 = 0xFFFFFFFF00000000_0000000000000000;
197
-
198
190
  pub const Field = std.meta.FieldEnum(Action);
199
191
 
200
- /// Perform a reset by clearing fields which should not persist across updates.
201
- pub fn reset(self: *Action) void {
202
- self.* = @bitCast(@as(u128, @bitCast(self.*)) & DURATIONS);
203
- }
204
-
205
192
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
206
193
  _ = .{ f, o };
207
194
  try fmt(a, w, false);
@@ -210,22 +197,10 @@ pub const Action = packed struct(u128) {
210
197
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
211
198
  try writer.writeByte('(');
212
199
  var printed = false;
213
- inline for (@typeInfo(Action).Struct.fields) |field| {
200
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
214
201
  const val = @field(self, field.name);
215
202
  switch (@typeInfo(@TypeOf(val))) {
216
- .Struct => inline for (@typeInfo(@TypeOf(val)).Struct.fields) |f| {
217
- const v = @field(val, f.name);
218
- if (v != 0) {
219
- if (printed) try writer.writeAll(", ");
220
- if (shape) {
221
- try writer.print("{s}:?", .{f.name});
222
- } else {
223
- try writer.print("{s}:{d}", .{ f.name, v });
224
- }
225
- printed = true;
226
- }
227
- },
228
- .Enum => if (val != .None) {
203
+ Enum => if (val != .None) {
229
204
  if (printed) try writer.writeAll(", ");
230
205
  if (shape) {
231
206
  try writer.print("{s}:?", .{field.name});
@@ -239,7 +214,7 @@ pub const Action = packed struct(u128) {
239
214
  }
240
215
  printed = true;
241
216
  },
242
- .Int => if (val != 0) {
217
+ Int => if (val != 0) {
243
218
  if (printed) try writer.writeAll(", ");
244
219
  if (shape) {
245
220
  try writer.print("{s}:?", .{field.name});
@@ -255,36 +230,6 @@ pub const Action = packed struct(u128) {
255
230
  }
256
231
  };
257
232
 
258
- test Action {
259
- var a: Action = .{ .hit = .true, .durations = .{ .sleep = 3 }, .damage = 225 };
260
- a.reset();
261
-
262
- try expectEqual(Action{ .hit = .None, .durations = .{ .sleep = 3 }, .damage = 0 }, a);
263
- }
264
-
265
- /// Observed values for various durations that need to be tracked in order to properly
266
- /// deduplicate transitions with a primary key. TODO
267
- pub const Duration = packed struct(u32) {
268
- /// The number of turns a Pokémon has been observed to be sleeping.
269
- sleep: u3 = 0,
270
- /// The number of turns a Pokémon has been observed to be confused.
271
- confusion: u3 = 0,
272
- /// TODO
273
- thrash: bool = false,
274
- /// The number of turns a Pokémon has been observed to be disabled.
275
- disable: u4 = 0,
276
- /// The number of turns a Pokémon has been observed to be attacking.
277
- attacking: u3 = 0,
278
- /// The number of turns a Pokémon has been observed to be binding their opponent.
279
- binding: u3 = 0,
280
- /// The number of turns a Pokémon has been observed to be encored.
281
- encore: u3 = 0,
282
-
283
- _: u12 = 0,
284
-
285
- pub const Field = std.meta.FieldEnum(Duration);
286
- };
287
-
288
233
  /// Tracks chance actions and their associated probability during a Generation II battle update when
289
234
  /// `options.chance` is enabled.
290
235
  pub fn Chance(comptime Rational: type) type {
@@ -297,37 +242,15 @@ pub fn Chance(comptime Rational: type) type {
297
242
  /// RNG events were observed during a battle `update`.
298
243
  actions: Actions = .{},
299
244
 
300
- // Tracks the observed sleep durations for the Pokémon in both player's parties. Unlike
301
- // other durations which are all tied to volatiles, sleep's counter persists through
302
- // switching and so we must store it here. The indices of these arrays correspond to the
303
- // `order` field of a Side. This information could be stored in actions but size is a
304
- // concern so this is tracked separately as actions only purports to track RNG events
305
- // observed during a single `update` (not across updates).
306
- sleeps: [2][6]u3 = .{ .{0} ** 6, .{0} ** 6 },
307
-
308
245
  /// Possible error returned by operations tracking chance probability.
309
246
  pub const Error = Rational.Error;
310
247
 
311
248
  /// Convenience helper to clear fields which typically should be cleared between updates.
312
249
  pub fn reset(self: *Self) void {
313
- self.probability.reset();
314
- self.actions.reset();
315
- // TODO: self.pending = .{};
316
- }
317
-
318
- pub fn switched(self: *Self, player: Player, in: u8, out: u8) void {
319
250
  if (!enabled) return;
320
251
 
321
- assert(in >= 1 and in <= 6);
322
- assert(out >= 1 and out <= 6);
323
-
324
- var action = self.actions.get(player);
325
-
326
- const slp = action.durations.sleep;
327
- action.durations = .{};
328
-
329
- self.sleeps[@intFromEnum(player)][out - 1] = slp;
330
- action.durations.sleep = @intCast(self.sleeps[@intFromEnum(player)][in - 1]);
252
+ self.probability.reset();
253
+ self.actions = .{};
331
254
  }
332
255
 
333
256
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -353,19 +276,21 @@ pub fn Chance(comptime Rational: type) type {
353
276
  self.actions.get(player).hit = if (ok) .true else .false;
354
277
  }
355
278
 
356
- pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
279
+ pub fn criticalHit(self: *Self, player: Player, i: u3, crit: bool, rate: u8) Error!void {
357
280
  if (!enabled) return;
358
281
 
359
282
  const n = if (crit) rate else @as(u8, @intCast(256 - @as(u9, rate)));
360
283
  try self.probability.update(n, 256);
361
- self.actions.get(player).critical_hit = if (crit) .true else .false;
284
+ var a = self.actions.get(player);
285
+ a.critical_hits = Criticals.set(a.critical_hits, i, if (crit) .true else .false);
362
286
  }
363
287
 
364
- pub fn damage(self: *Self, player: Player, roll: u8) Error!void {
288
+ pub fn damage(self: *Self, player: Player, i: u3, roll: u8) Error!void {
365
289
  if (!enabled) return;
366
290
 
367
291
  try self.probability.update(1, 39);
368
- self.actions.get(player).damage = roll;
292
+ var a = self.actions.get(player);
293
+ a.damages = Damages.set(a.damages, i, roll);
369
294
  }
370
295
 
371
296
  pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
@@ -517,122 +442,10 @@ pub fn Chance(comptime Rational: type) type {
517
442
  self.actions.get(player).multi_hit = n;
518
443
  }
519
444
 
520
- pub fn duration(
521
- self: *Self,
522
- comptime field: Duration.Field,
523
- player: Player,
524
- target: Player,
525
- turns: u4,
526
- ) void {
527
- if (!enabled) return;
528
-
529
- var durations = &self.actions.get(target).durations;
530
- assert(@field(durations, @tagName(field)) == 0 or
531
- (field == .confusion and player == target));
532
- @field(durations, @tagName(field)) = 1;
533
- if (field == .confusion) durations.thrash = player == target;
534
- self.actions.get(player).duration = if (options.key) 1 else turns;
535
- }
536
-
537
- pub fn sleep(self: *Self, player: Player, turns: u4) Error!void {
445
+ pub fn duration(self: *Self, player: Player, turns: u3) void {
538
446
  if (!enabled) return;
539
447
 
540
- var durations = &self.actions.get(player).durations;
541
- const n = durations.sleep;
542
- if (turns == 0) {
543
- assert(n >= 1 and n <= 6);
544
- if (n != 6) try self.probability.update(1, 7 - @as(u4, n));
545
- durations.sleep = 0;
546
- } else {
547
- assert(n >= 1 and n < 6);
548
- try self.probability.update(7 - @as(u4, n) - 1, 7 - @as(u4, n));
549
- durations.sleep += 1;
550
- }
551
- }
552
-
553
- pub fn confusion(self: *Self, player: Player, turns: u4) Error!void {
554
- if (!enabled) return;
555
-
556
- var durations = &self.actions.get(player).durations;
557
- const n = durations.confusion;
558
- const hi: u8 = if (durations.thrash) 3 else 5;
559
- const p = hi + 1;
560
- if (turns == 0) {
561
- assert(n >= 2 and n <= hi);
562
- if (n != hi) try self.probability.update(1, p - @as(u4, n));
563
- durations.confusion = 0;
564
- } else {
565
- assert(n >= 1 and n < hi);
566
- if (n > 1) try self.probability.update(p - @as(u4, n) - 1, p - @as(u4, n));
567
- durations.confusion += 1;
568
- }
569
- }
570
-
571
- pub fn disable(self: *Self, player: Player, turns: u4) Error!void {
572
- if (!enabled) return;
573
-
574
- var durations = &self.actions.get(player).durations;
575
- const n = durations.disable;
576
- const hi = if (showdown) 5 else 8;
577
- const p = hi + 1;
578
- if (turns == 0) {
579
- assert(n >= 2 and n <= hi);
580
- if (n != hi) try self.probability.update(1, p - n);
581
- durations.disable = 0;
582
- } else {
583
- assert(n >= 1 and n < hi);
584
- if (n > 1) try self.probability.update(p - n - 1, p - n);
585
- durations.disable += 1;
586
- }
587
- }
588
-
589
- pub fn attacking(self: *Self, player: Player, turns: u4) Error!void {
590
- if (!enabled) return;
591
-
592
- var durations = &self.actions.get(player).durations;
593
- const n = durations.attacking;
594
- if (turns == 0) {
595
- assert(n >= 2 and n <= 3);
596
- if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
597
- durations.attacking = 0;
598
- } else {
599
- assert(n >= 1 and n < 3);
600
- if (n > 1) try self.probability.update(4 - @as(u4, n) - 1, 4 - @as(u4, n));
601
- durations.attacking += 1;
602
- }
603
- }
604
-
605
- pub fn binding(self: *Self, player: Player, turns: u4) Error!void {
606
- if (!enabled) return;
607
-
608
- var durations = &self.actions.get(player).durations;
609
- const n = durations.binding;
610
- if (turns == 0) {
611
- assert(n >= 2 and n <= 5);
612
- if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
613
- durations.binding = 0;
614
- } else {
615
- assert(n >= 1 and n < 5);
616
- if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
617
- durations.binding += 1;
618
- }
619
- }
620
-
621
- // TODO: consider sharing implementation with binding
622
- pub fn encore(self: *Self, player: Player, turns: u4) Error!void {
623
- if (!enabled) return;
624
-
625
- var durations = &self.actions.get(player).durations;
626
- const n = durations.encore;
627
- if (turns == 0) {
628
- assert(n >= 2 and n <= 5);
629
- if (n != 5) try self.probability.update(1, 6 - @as(u4, n));
630
- durations.encore = 0;
631
- } else {
632
- assert(n >= 1 and n < 5);
633
- if (n > 1) try self.probability.update(6 - @as(u4, n) - 1, 6 - @as(u4, n));
634
- durations.encore += 1;
635
- }
448
+ self.actions.get(player).duration = if (options.key) 2 else turns;
636
449
  }
637
450
  };
638
451
  }
@@ -684,22 +497,22 @@ test "Chance.hit" {
684
497
  test "Chance.criticalHit" {
685
498
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
686
499
 
687
- try chance.criticalHit(.P1, true, 17);
688
- try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
500
+ try chance.criticalHit(.P1, 0, true, 17);
501
+ try expectValue(Optional(bool).true, Criticals.get(chance.actions.p1.critical_hits, 0));
689
502
  try expectProbability(&chance.probability, 17, 256);
690
503
 
691
504
  chance.reset();
692
505
 
693
- try chance.criticalHit(.P2, false, 5);
506
+ try chance.criticalHit(.P2, 1, false, 5);
694
507
  try expectProbability(&chance.probability, 251, 256);
695
- try expectValue(Optional(bool).false, chance.actions.p2.critical_hit);
508
+ try expectValue(Optional(bool).false, Criticals.get(chance.actions.p2.critical_hits, 1));
696
509
  }
697
510
 
698
511
  test "Chance.damage" {
699
512
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
700
513
 
701
- try chance.damage(.P1, 217);
702
- try expectValue(217, chance.actions.p1.damage);
514
+ try chance.damage(.P1, 0, 219);
515
+ try expectValue(@as(u8, 219), Damages.get(chance.actions.p1.damages, 0));
703
516
  try expectProbability(&chance.probability, 1, 39);
704
517
  }
705
518
 
@@ -824,7 +637,7 @@ test "Chance.present" {
824
637
  } else {
825
638
  try expectProbability(&chance.probability, 25, 256);
826
639
  }
827
- try expectValue(4, chance.actions.p2.present);
640
+ try expectValue(@as(u3, 4), chance.actions.p2.present);
828
641
 
829
642
  chance.reset();
830
643
 
@@ -834,7 +647,7 @@ test "Chance.present" {
834
647
  } else {
835
648
  try expectProbability(&chance.probability, 51, 256);
836
649
  }
837
- try expectValue(1, chance.actions.p1.present);
650
+ try expectValue(@as(u3, 1), chance.actions.p1.present);
838
651
  }
839
652
 
840
653
  test "Chance.magnitude" {
@@ -846,7 +659,7 @@ test "Chance.magnitude" {
846
659
  } else {
847
660
  try expectProbability(&chance.probability, 13, 256);
848
661
  }
849
- try expectValue(7, chance.actions.p2.magnitude);
662
+ try expectValue(@as(u3, 7), chance.actions.p2.magnitude);
850
663
 
851
664
  chance.reset();
852
665
 
@@ -856,7 +669,7 @@ test "Chance.magnitude" {
856
669
  } else {
857
670
  try expectProbability(&chance.probability, 51, 256);
858
671
  }
859
- try expectValue(5, chance.actions.p1.magnitude);
672
+ try expectValue(@as(u3, 5), chance.actions.p1.magnitude);
860
673
  }
861
674
 
862
675
  test "Chance.tripleKick" {
@@ -864,7 +677,7 @@ test "Chance.tripleKick" {
864
677
 
865
678
  try chance.tripleKick(.P1, 2);
866
679
  try expectProbability(&chance.probability, 1, 3);
867
- try expectValue(2, chance.actions.p1.triple_kick);
680
+ try expectValue(@as(u2, 2), chance.actions.p1.triple_kick);
868
681
  }
869
682
 
870
683
  test "Chance.spite" {
@@ -872,7 +685,7 @@ test "Chance.spite" {
872
685
 
873
686
  try chance.spite(.P1, 3);
874
687
  try expectProbability(&chance.probability, 1, 4);
875
- try expectValue(3, chance.actions.p1.spite);
688
+ try expectValue(@as(u3, 3), chance.actions.p1.spite);
876
689
  }
877
690
 
878
691
  test "Chance.conversion2" {
@@ -899,7 +712,7 @@ test "Chance.psywave" {
899
712
 
900
713
  try chance.psywave(.P2, 100, 150);
901
714
  try expectProbability(&chance.probability, 1, 150);
902
- try expectValue(100, chance.actions.p2.psywave);
715
+ try expectValue(@as(u8, 100), chance.actions.p2.psywave);
903
716
  }
904
717
 
905
718
  test "Chance.metronome" {
@@ -915,19 +728,19 @@ test "Chance.moveSlot" {
915
728
 
916
729
  try chance.moveSlot(.P2, 2, 2);
917
730
  try expectProbability(&chance.probability, 1, 2);
918
- try expectValue(2, chance.actions.p2.move_slot);
731
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
919
732
 
920
733
  chance.reset();
921
734
 
922
735
  try chance.moveSlot(.P1, 1, 3);
923
736
  try expectProbability(&chance.probability, 1, 3);
924
- try expectValue(1, chance.actions.p1.move_slot);
737
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
925
738
 
926
739
  chance.reset();
927
740
 
928
741
  try chance.moveSlot(.P1, 4, 1);
929
742
  try expectProbability(&chance.probability, 1, 1);
930
- try expectValue(4, chance.actions.p1.move_slot);
743
+ try expectValue(@as(u3, 4), chance.actions.p1.move_slot);
931
744
  }
932
745
 
933
746
  test "Chance.forceSwitch" {
@@ -935,19 +748,19 @@ test "Chance.forceSwitch" {
935
748
 
936
749
  try chance.forceSwitch(.P2, 2, 2);
937
750
  try expectProbability(&chance.probability, 1, 2);
938
- try expectValue(2, chance.actions.p2.force_switch);
751
+ try expectValue(@as(u3, 2), chance.actions.p2.force_switch);
939
752
 
940
753
  chance.reset();
941
754
 
942
755
  try chance.forceSwitch(.P1, 1, 3);
943
756
  try expectProbability(&chance.probability, 1, 3);
944
- try expectValue(1, chance.actions.p1.force_switch);
757
+ try expectValue(@as(u3, 1), chance.actions.p1.force_switch);
945
758
 
946
759
  chance.reset();
947
760
 
948
761
  try chance.forceSwitch(.P1, 4, 1);
949
762
  try expectProbability(&chance.probability, 1, 1);
950
- try expectValue(4, chance.actions.p1.force_switch);
763
+ try expectValue(@as(u3, 4), chance.actions.p1.force_switch);
951
764
  }
952
765
 
953
766
  test "Chance.multiHit" {
@@ -955,212 +768,20 @@ test "Chance.multiHit" {
955
768
 
956
769
  try chance.multiHit(.P1, 3);
957
770
  try expectProbability(&chance.probability, 3, 8);
958
- try expectValue(3, chance.actions.p1.multi_hit);
771
+ try expectValue(@as(u3, 3), chance.actions.p1.multi_hit);
959
772
 
960
773
  chance.reset();
961
774
 
962
775
  try chance.multiHit(.P2, 5);
963
776
  try expectProbability(&chance.probability, 1, 8);
964
- try expectValue(5, chance.actions.p2.multi_hit);
777
+ try expectValue(@as(u3, 5), chance.actions.p2.multi_hit);
965
778
  }
966
779
 
967
780
  test "Chance.duration" {
968
781
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
969
782
 
970
- chance.duration(.sleep, .P1, .P2, 2);
971
- try expectValue(2, chance.actions.p1.duration);
972
- try expectValue(1, chance.actions.p2.durations.sleep);
973
- }
974
-
975
- test "Chance.sleep" {
976
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
977
-
978
- for ([_]u8{ 6, 5, 4, 3, 2, 1 }, 1..7) |d, i| {
979
- chance.actions.p1.durations.sleep = @intCast(i);
980
- try chance.sleep(.P1, 0);
981
- try expectProbability(&chance.probability, 1, d);
982
- try expectValue(0, chance.actions.p1.durations.sleep);
983
-
984
- chance.reset();
985
-
986
- if (i < 6) {
987
- chance.actions.p1.durations.sleep = @intCast(i);
988
- try chance.sleep(.P1, 1);
989
- try expectProbability(&chance.probability, d - 1, d);
990
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p1.durations.sleep);
991
-
992
- chance.reset();
993
- }
994
- }
995
- }
996
-
997
- test "Chance.confusion" {
998
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
999
-
1000
- chance.actions.p2.durations.thrash = false;
1001
- for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
1002
- if (i > 1) {
1003
- chance.actions.p2.durations.confusion = @intCast(i);
1004
- try chance.confusion(.P2, 0);
1005
- try expectProbability(&chance.probability, 1, d);
1006
- try expectValue(0, chance.actions.p2.durations.confusion);
1007
- try expectValue(false, chance.actions.p2.durations.thrash);
1008
-
1009
- chance.reset();
1010
- }
1011
-
1012
- if (i < 5) {
1013
- chance.actions.p2.durations.confusion = @intCast(i);
1014
- try chance.confusion(.P2, 1);
1015
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1016
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
1017
- try expectValue(false, chance.actions.p2.durations.thrash);
1018
-
1019
- chance.reset();
1020
- }
1021
- }
1022
-
1023
- chance.reset();
1024
-
1025
- chance.actions.p2.durations.thrash = true;
1026
- for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
1027
- if (i > 1) {
1028
- chance.actions.p2.durations.confusion = @intCast(i);
1029
- try chance.confusion(.P2, 0);
1030
- try expectProbability(&chance.probability, 1, d);
1031
- try expectValue(0, chance.actions.p2.durations.confusion);
1032
- try expectValue(true, chance.actions.p2.durations.thrash);
1033
-
1034
- chance.reset();
1035
- }
1036
-
1037
- if (i < 3) {
1038
- chance.actions.p2.durations.confusion = @intCast(i);
1039
- try chance.confusion(.P2, 1);
1040
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1041
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.confusion);
1042
- try expectValue(true, chance.actions.p2.durations.thrash);
1043
-
1044
- chance.reset();
1045
- }
1046
- }
1047
- }
1048
-
1049
- test "Chance.disable" {
1050
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
1051
- if (showdown) {
1052
- for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
1053
- if (i > 1) {
1054
- chance.actions.p2.durations.disable = @intCast(i);
1055
- try chance.disable(.P2, 0);
1056
- try expectProbability(&chance.probability, 1, d);
1057
- try expectValue(0, chance.actions.p2.durations.disable);
1058
-
1059
- chance.reset();
1060
- }
1061
-
1062
- if (i < 5) {
1063
- chance.actions.p2.durations.disable = @intCast(i);
1064
- try chance.disable(.P2, 1);
1065
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1066
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p2.durations.disable);
1067
-
1068
- chance.reset();
1069
- }
1070
- }
1071
- } else {
1072
- for ([_]u8{ 1, 7, 6, 5, 4, 3, 2, 1 }, 1..9) |d, i| {
1073
- if (i > 1) {
1074
- chance.actions.p2.durations.disable = @intCast(i);
1075
- try chance.disable(.P2, 0);
1076
- try expectProbability(&chance.probability, 1, d);
1077
- try expectValue(0, chance.actions.p2.durations.disable);
1078
-
1079
- chance.reset();
1080
- }
1081
-
1082
- if (i < 8) {
1083
- chance.actions.p2.durations.disable = @intCast(i);
1084
- try chance.disable(.P2, 1);
1085
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1086
- try expectValue(@as(u4, @intCast(i)) + 1, chance.actions.p2.durations.disable);
1087
-
1088
- chance.reset();
1089
- }
1090
- }
1091
- }
1092
- }
1093
-
1094
- test "Chance.attacking" {
1095
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
1096
-
1097
- for ([_]u8{ 1, 2, 1 }, 1..4) |d, i| {
1098
- if (i > 1) {
1099
- chance.actions.p2.durations.attacking = @intCast(i);
1100
- try chance.attacking(.P2, 0);
1101
- try expectProbability(&chance.probability, 1, d);
1102
- try expectValue(0, chance.actions.p2.durations.attacking);
1103
-
1104
- chance.reset();
1105
- }
1106
-
1107
- if (i < 3) {
1108
- chance.actions.p2.durations.attacking = @intCast(i);
1109
- try chance.attacking(.P2, 1);
1110
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1111
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.attacking);
1112
-
1113
- chance.reset();
1114
- }
1115
- }
1116
- }
1117
-
1118
- test "Chance.binding" {
1119
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
1120
-
1121
- for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
1122
- if (i > 1) {
1123
- chance.actions.p2.durations.binding = @intCast(i);
1124
- try chance.binding(.P2, 0);
1125
- try expectProbability(&chance.probability, 1, d);
1126
- try expectValue(0, chance.actions.p2.durations.binding);
1127
-
1128
- chance.reset();
1129
- }
1130
-
1131
- if (i < 5) {
1132
- chance.actions.p2.durations.binding = @intCast(i);
1133
- try chance.binding(.P2, 1);
1134
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1135
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.binding);
1136
-
1137
- chance.reset();
1138
- }
1139
- }
1140
- }
1141
-
1142
- test "Chance.encore" {
1143
- var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
1144
-
1145
- for ([_]u8{ 1, 4, 3, 2, 1 }, 1..6) |d, i| {
1146
- if (i > 1) {
1147
- chance.actions.p2.durations.encore = @intCast(i);
1148
- try chance.encore(.P2, 0);
1149
- try expectProbability(&chance.probability, 1, d);
1150
- try expectValue(0, chance.actions.p2.durations.encore);
1151
-
1152
- chance.reset();
1153
- }
1154
-
1155
- if (i < 5) {
1156
- chance.actions.p2.durations.encore = @intCast(i);
1157
- try chance.encore(.P2, 1);
1158
- try expectProbability(&chance.probability, if (d > 1) d - 1 else d, d);
1159
- try expectValue(@as(u3, @intCast(i)) + 1, chance.actions.p2.durations.encore);
1160
-
1161
- chance.reset();
1162
- }
1163
- }
783
+ chance.duration(.P1, 2);
784
+ try expectValue(@as(u3, 2), chance.actions.p1.duration);
1164
785
  }
1165
786
 
1166
787
  pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
@@ -1202,12 +823,12 @@ const Null = struct {
1202
823
  _ = .{ self, player, ok, accuracy };
1203
824
  }
1204
825
 
1205
- pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
1206
- _ = .{ self, player, crit, rate };
826
+ pub fn criticalHit(self: Null, player: Player, i: u3, crit: bool, rate: u8) Error!void {
827
+ _ = .{ self, player, i, crit, rate };
1207
828
  }
1208
829
 
1209
- pub fn damage(self: Null, player: Player, roll: u8) Error!void {
1210
- _ = .{ self, player, roll };
830
+ pub fn damage(self: Null, player: Player, i: u3, roll: u8) Error!void {
831
+ _ = .{ self, player, i, roll };
1211
832
  }
1212
833
 
1213
834
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
@@ -1278,37 +899,7 @@ const Null = struct {
1278
899
  _ = .{ self, player, move, n };
1279
900
  }
1280
901
 
1281
- pub fn duration(
1282
- self: Null,
1283
- comptime field: Duration.Field,
1284
- player: Player,
1285
- target: Player,
1286
- turns: u4,
1287
- ) void {
1288
- _ = .{ self, field, player, target, turns };
1289
- }
1290
-
1291
- pub fn sleep(self: Null, player: Player, turns: u4) Error!void {
1292
- _ = .{ self, player, turns };
1293
- }
1294
-
1295
- pub fn confusion(self: Null, player: Player, turns: u4) Error!void {
1296
- _ = .{ self, player, turns };
1297
- }
1298
-
1299
- pub fn disable(self: Null, player: Player, turns: u4) Error!void {
1300
- _ = .{ self, player, turns };
1301
- }
1302
-
1303
- pub fn attacking(self: Null, player: Player, turns: u4) Error!void {
1304
- _ = .{ self, player, turns };
1305
- }
1306
-
1307
- pub fn binding(self: Null, player: Player, turns: u4) Error!void {
1308
- _ = .{ self, player, turns };
1309
- }
1310
-
1311
- pub fn encore(self: Null, player: Player, turns: u4) Error!void {
902
+ pub fn duration(self: Null, player: Player, turns: u3) void {
1312
903
  _ = .{ self, player, turns };
1313
904
  }
1314
905
  };