@pkmn/engine 0.1.0-dev.5570b5c5 → 0.1.0-dev.587596a6

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