@pkmn/engine 0.1.0-dev.c544e10e → 0.1.0-dev.cbdbb719

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 (46) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +41 -14
  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 +3 -2
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.js +4 -4
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/test/display.js +8 -3
  13. package/build/test/display.js.map +1 -1
  14. package/build/tools/debug.js +2 -3
  15. package/build/tools/debug.js.map +1 -1
  16. package/build.zig +208 -81
  17. package/package.json +32 -21
  18. package/src/bin/install-pkmn-engine +25 -17
  19. package/src/lib/common/array.zig +77 -0
  20. package/src/lib/common/data.zig +4 -4
  21. package/src/lib/common/debug.zig +2 -2
  22. package/src/lib/common/js.zig +4 -2
  23. package/src/lib/common/optional.zig +14 -10
  24. package/src/lib/common/options.zig +2 -0
  25. package/src/lib/common/protocol.zig +73 -73
  26. package/src/lib/common/rational.zig +121 -27
  27. package/src/lib/common/rng.zig +7 -7
  28. package/src/lib/common/util.zig +50 -1
  29. package/src/lib/gen1/calc.zig +511 -131
  30. package/src/lib/gen1/chance.zig +374 -267
  31. package/src/lib/gen1/data/species.zig +1 -1
  32. package/src/lib/gen1/data.zig +15 -14
  33. package/src/lib/gen1/helpers.zig +96 -536
  34. package/src/lib/gen1/mechanics.zig +285 -189
  35. package/src/lib/gen2/calc.zig +34 -16
  36. package/src/lib/gen2/chance.zig +85 -492
  37. package/src/lib/gen2/data.zig +8 -7
  38. package/src/lib/gen2/generated.zig +1 -3
  39. package/src/lib/gen2/helpers.zig +41 -9
  40. package/src/lib/gen2/mechanics.zig +178 -121
  41. package/src/lib/{binding/node.zig → node.zig} +9 -12
  42. package/src/lib/pkmn.zig +32 -15
  43. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  44. package/src/test/display/pkmn.html.tmpl +5 -5
  45. package/src/lib/gen1/README.md +0 -894
  46. package/src/lib/gen2/README.md +0 -329
@@ -11,21 +11,29 @@ 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
 
17
- const data = @import("./data.zig");
18
+ const data = @import("data.zig");
18
19
 
19
20
  const enabled = options.chance;
20
21
  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, u6);
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,60 @@ 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
+ _: u9 = 0,
168
+
182
169
  /// If not 0, the value to by one of the Rolls.*Duration rolls.
183
170
  duration: u4 = 0,
171
+ sleep: Optional(Observation) = .None,
172
+ /// TODO
173
+ confusion: Optional(Observation) = .None,
174
+ /// TODO
175
+ disable: Optional(Observation) = .None,
176
+ /// TODO
177
+ attacking: Optional(Observation) = .None,
178
+ /// TODO
179
+ binding: Optional(Observation) = .None,
180
+ /// TODO
181
+ encore: Optional(Observation) = .None,
182
+
183
+ /// If not None, the value to return for Rolls.conversion2.
184
+ conversion_2: Optional(Type) = .None,
184
185
 
185
186
  /// If not 0, psywave should be returned as the damage roll for Rolls.psywave.
186
187
  psywave: u8 = 0,
@@ -188,20 +189,8 @@ pub const Action = packed struct(u128) {
188
189
  /// If not None, the Move to return for Rolls.metronome.
189
190
  metronome: Move = .None,
190
191
 
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
192
  pub const Field = std.meta.FieldEnum(Action);
199
193
 
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
194
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
206
195
  _ = .{ f, o };
207
196
  try fmt(a, w, false);
@@ -210,22 +199,10 @@ pub const Action = packed struct(u128) {
210
199
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
211
200
  try writer.writeByte('(');
212
201
  var printed = false;
213
- inline for (@typeInfo(Action).Struct.fields) |field| {
202
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
214
203
  const val = @field(self, field.name);
215
204
  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) {
205
+ Enum => if (val != .None) {
229
206
  if (printed) try writer.writeAll(", ");
230
207
  if (shape) {
231
208
  try writer.print("{s}:?", .{field.name});
@@ -239,7 +216,7 @@ pub const Action = packed struct(u128) {
239
216
  }
240
217
  printed = true;
241
218
  },
242
- .Int => if (val != 0) {
219
+ Int => if (val != 0) {
243
220
  if (printed) try writer.writeAll(", ");
244
221
  if (shape) {
245
222
  try writer.print("{s}:?", .{field.name});
@@ -255,36 +232,6 @@ pub const Action = packed struct(u128) {
255
232
  }
256
233
  };
257
234
 
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
235
  /// Tracks chance actions and their associated probability during a Generation II battle update when
289
236
  /// `options.chance` is enabled.
290
237
  pub fn Chance(comptime Rational: type) type {
@@ -297,37 +244,15 @@ pub fn Chance(comptime Rational: type) type {
297
244
  /// RNG events were observed during a battle `update`.
298
245
  actions: Actions = .{},
299
246
 
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
247
  /// Possible error returned by operations tracking chance probability.
309
248
  pub const Error = Rational.Error;
310
249
 
311
250
  /// Convenience helper to clear fields which typically should be cleared between updates.
312
251
  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
252
  if (!enabled) return;
320
253
 
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]);
254
+ self.probability.reset();
255
+ self.actions = .{};
331
256
  }
332
257
 
333
258
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -353,19 +278,21 @@ pub fn Chance(comptime Rational: type) type {
353
278
  self.actions.get(player).hit = if (ok) .true else .false;
354
279
  }
355
280
 
356
- pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
281
+ pub fn criticalHit(self: *Self, player: Player, i: u3, crit: bool, rate: u8) Error!void {
357
282
  if (!enabled) return;
358
283
 
359
284
  const n = if (crit) rate else @as(u8, @intCast(256 - @as(u9, rate)));
360
285
  try self.probability.update(n, 256);
361
- self.actions.get(player).critical_hit = if (crit) .true else .false;
286
+ var a = self.actions.get(player);
287
+ a.critical_hits = Criticals.set(a.critical_hits, i, if (crit) .true else .false);
362
288
  }
363
289
 
364
- pub fn damage(self: *Self, player: Player, roll: u8) Error!void {
290
+ pub fn damage(self: *Self, player: Player, i: u3, roll: u8) Error!void {
365
291
  if (!enabled) return;
366
292
 
367
293
  try self.probability.update(1, 39);
368
- self.actions.get(player).damage = roll;
294
+ var a = self.actions.get(player);
295
+ a.damages = Damages.set(a.damages, i, @intCast(roll - 216));
369
296
  }
370
297
 
371
298
  pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
@@ -517,122 +444,10 @@ pub fn Chance(comptime Rational: type) type {
517
444
  self.actions.get(player).multi_hit = n;
518
445
  }
519
446
 
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 {
447
+ pub fn duration(self: *Self, player: Player, turns: u4) void {
538
448
  if (!enabled) return;
539
449
 
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
- }
450
+ self.actions.get(player).duration = if (options.key) 2 else turns;
636
451
  }
637
452
  };
638
453
  }
@@ -684,22 +499,22 @@ test "Chance.hit" {
684
499
  test "Chance.criticalHit" {
685
500
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
686
501
 
687
- try chance.criticalHit(.P1, true, 17);
688
- try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
502
+ try chance.criticalHit(.P1, 0, true, 17);
503
+ try expectValue(Optional(bool).true, Criticals.get(chance.actions.p1.critical_hits, 0));
689
504
  try expectProbability(&chance.probability, 17, 256);
690
505
 
691
506
  chance.reset();
692
507
 
693
- try chance.criticalHit(.P2, false, 5);
508
+ try chance.criticalHit(.P2, 1, false, 5);
694
509
  try expectProbability(&chance.probability, 251, 256);
695
- try expectValue(Optional(bool).false, chance.actions.p2.critical_hit);
510
+ try expectValue(Optional(bool).false, Criticals.get(chance.actions.p2.critical_hits, 1));
696
511
  }
697
512
 
698
513
  test "Chance.damage" {
699
514
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
700
515
 
701
- try chance.damage(.P1, 217);
702
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
516
+ try chance.damage(.P1, 0, 219);
517
+ try expectValue(@as(u6, 3), Damages.get(chance.actions.p1.damages, 0));
703
518
  try expectProbability(&chance.probability, 1, 39);
704
519
  }
705
520
 
@@ -915,19 +730,19 @@ test "Chance.moveSlot" {
915
730
 
916
731
  try chance.moveSlot(.P2, 2, 2);
917
732
  try expectProbability(&chance.probability, 1, 2);
918
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
733
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
919
734
 
920
735
  chance.reset();
921
736
 
922
737
  try chance.moveSlot(.P1, 1, 3);
923
738
  try expectProbability(&chance.probability, 1, 3);
924
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
739
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
925
740
 
926
741
  chance.reset();
927
742
 
928
743
  try chance.moveSlot(.P1, 4, 1);
929
744
  try expectProbability(&chance.probability, 1, 1);
930
- try expectValue(@as(u4, 4), chance.actions.p1.move_slot);
745
+ try expectValue(@as(u3, 4), chance.actions.p1.move_slot);
931
746
  }
932
747
 
933
748
  test "Chance.forceSwitch" {
@@ -935,19 +750,19 @@ test "Chance.forceSwitch" {
935
750
 
936
751
  try chance.forceSwitch(.P2, 2, 2);
937
752
  try expectProbability(&chance.probability, 1, 2);
938
- try expectValue(@as(u4, 2), chance.actions.p2.force_switch);
753
+ try expectValue(@as(u3, 2), chance.actions.p2.force_switch);
939
754
 
940
755
  chance.reset();
941
756
 
942
757
  try chance.forceSwitch(.P1, 1, 3);
943
758
  try expectProbability(&chance.probability, 1, 3);
944
- try expectValue(@as(u4, 1), chance.actions.p1.force_switch);
759
+ try expectValue(@as(u3, 1), chance.actions.p1.force_switch);
945
760
 
946
761
  chance.reset();
947
762
 
948
763
  try chance.forceSwitch(.P1, 4, 1);
949
764
  try expectProbability(&chance.probability, 1, 1);
950
- try expectValue(@as(u4, 4), chance.actions.p1.force_switch);
765
+ try expectValue(@as(u3, 4), chance.actions.p1.force_switch);
951
766
  }
952
767
 
953
768
  test "Chance.multiHit" {
@@ -955,212 +770,20 @@ test "Chance.multiHit" {
955
770
 
956
771
  try chance.multiHit(.P1, 3);
957
772
  try expectProbability(&chance.probability, 3, 8);
958
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
773
+ try expectValue(@as(u3, 3), chance.actions.p1.multi_hit);
959
774
 
960
775
  chance.reset();
961
776
 
962
777
  try chance.multiHit(.P2, 5);
963
778
  try expectProbability(&chance.probability, 1, 8);
964
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
779
+ try expectValue(@as(u3, 5), chance.actions.p2.multi_hit);
965
780
  }
966
781
 
967
782
  test "Chance.duration" {
968
783
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
969
784
 
970
- chance.duration(.sleep, .P1, .P2, 2);
785
+ chance.duration(.P1, 2);
971
786
  try expectValue(@as(u4, 2), chance.actions.p1.duration);
972
- try expectValue(@as(u3, 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(@as(u3, 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(@as(u3, 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(@as(u3, 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(@as(u4, 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(@as(u4, 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(@as(u3, 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(@as(u3, 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(@as(u3, 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
- }
1164
787
  }
1165
788
 
1166
789
  pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
@@ -1202,12 +825,12 @@ const Null = struct {
1202
825
  _ = .{ self, player, ok, accuracy };
1203
826
  }
1204
827
 
1205
- pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
1206
- _ = .{ self, player, crit, rate };
828
+ pub fn criticalHit(self: Null, player: Player, i: u3, crit: bool, rate: u8) Error!void {
829
+ _ = .{ self, player, i, crit, rate };
1207
830
  }
1208
831
 
1209
- pub fn damage(self: Null, player: Player, roll: u8) Error!void {
1210
- _ = .{ self, player, roll };
832
+ pub fn damage(self: Null, player: Player, i: u3, roll: u8) Error!void {
833
+ _ = .{ self, player, i, roll };
1211
834
  }
1212
835
 
1213
836
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
@@ -1278,37 +901,7 @@ const Null = struct {
1278
901
  _ = .{ self, player, move, n };
1279
902
  }
1280
903
 
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 {
904
+ pub fn duration(self: Null, player: Player, turns: u4) void {
1312
905
  _ = .{ self, player, turns };
1313
906
  }
1314
907
  };