@pkmn/engine 0.1.0-dev.15f64810 → 0.1.0-dev.18478e7d

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 +30 -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 +29 -1
  26. package/build/pkg/protocol.js +105 -103
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +83 -73
  29. package/build/tools/debug.js.map +1 -1
  30. package/build/tools/display/debug.jsx +66 -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 +360 -0
  35. package/build/tools/display/ui.jsx.map +1 -0
  36. package/build/tools/display/util.js +157 -0
  37. package/build/tools/display/util.js.map +1 -0
  38. package/build.zig +346 -128
  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 +201 -0
  43. package/src/lib/bindings/node.zig +117 -0
  44. package/src/lib/bindings/wasm.zig +51 -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 +45 -29
  48. package/src/lib/common/js.zig +3 -7
  49. package/src/lib/common/optional.zig +26 -22
  50. package/src/lib/common/options.zig +4 -0
  51. package/src/lib/common/protocol.zig +196 -160
  52. package/src/lib/common/rational.zig +338 -254
  53. package/src/lib/common/rng.zig +5 -7
  54. package/src/lib/common/util.zig +53 -1
  55. package/src/lib/gen1/calc.zig +651 -205
  56. package/src/lib/gen1/chance.zig +443 -257
  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 +40 -43
  61. package/src/lib/gen1/helpers.zig +39 -574
  62. package/src/lib/gen1/mechanics.zig +328 -229
  63. package/src/lib/gen2/calc.zig +35 -28
  64. package/src/lib/gen2/chance.zig +117 -518
  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 +32 -33
  72. package/src/lib/gen2/mechanics.zig +105 -132
  73. package/src/lib/napi.h +2 -0
  74. package/src/lib/node.zig +4 -121
  75. package/src/lib/pkmn.zig +103 -22
  76. package/src/lib/wasm.zig +9 -9
  77. package/src/tools/display/dom.ts +108 -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,28 @@ 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| {
63
- const a_val = @field(@field(a, player.name), field.name);
64
- const b_val = @field(@field(b, player.name), field.name);
56
+ inline for (if (@hasField(@TypeOf(@typeInfo(Actions).@"struct"), "fields"))
57
+ @typeInfo(Actions).@"struct".fields
58
+ else
59
+ @typeInfo(Actions).@"struct".field_names) |player|
60
+ {
61
+ const player_name = if (@hasField(@TypeOf(player), "name"))
62
+ player.name
63
+ else
64
+ player;
65
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
66
+ @typeInfo(Action).@"struct".fields
67
+ else
68
+ @typeInfo(Action).@"struct".field_names) |field|
69
+ {
70
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
71
+ const a_val = @field(@field(a, player_name), field_name);
72
+ const b_val = @field(@field(b, player_name), field_name);
65
73
 
66
74
  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))
75
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
77
76
  return false,
78
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
77
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
79
78
  else => unreachable,
80
79
  }
81
80
  }
@@ -83,7 +82,7 @@ pub const Actions = extern struct {
83
82
  return true;
84
83
  }
85
84
 
86
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
85
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
87
86
  try writer.writeAll("<P1 = ");
88
87
  try self.p1.fmt(writer, shape);
89
88
  try writer.writeAll(", P2 = ");
@@ -91,23 +90,15 @@ pub const Actions = extern struct {
91
90
  try writer.writeAll(">");
92
91
  }
93
92
 
94
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
95
- _ = .{ f, o };
93
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
96
94
  try fmt(a, w, false);
97
95
  }
98
96
  };
99
97
 
100
- // TODO: ziglang/zig#19730
101
98
  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 } };
99
+ const a: Actions = .{ .p1 = .{ .hit = .true, .confused = .false, .damages = 245 } };
100
+ const b: Actions = .{ .p1 = .{ .hit = .false, .confused = .true, .damages = 246 } };
104
101
  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
102
 
112
103
  try expect(a.eql(a));
113
104
  try expect(!a.eql(b));
@@ -120,17 +111,21 @@ test Actions {
120
111
  try expect(b.matches(a));
121
112
  try expect(!a.matches(c));
122
113
  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
114
  }
128
115
 
116
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
117
+ pub const Observation = enum { started, continuing, ended };
118
+
119
+ pub const Damages = Array(5, u8);
120
+ pub const Criticals = Array(5, Optional(bool));
121
+
129
122
  /// Information about the RNG that was observed during a Generation II battle `update` for a
130
123
  /// single player.
131
124
  pub const Action = packed struct(u128) {
132
- /// If not 0, the roll to be returned Rolls.damage.
133
- damage: u8 = 0,
125
+ /// TODO
126
+ damages: Damages.T = 0,
127
+ // TODO
128
+ critical_hits: Criticals.T = 0,
134
129
 
135
130
  /// If not None, the Player to be returned by Rolls.speedTie.
136
131
  speed_tie: Optional(Player) = .None,
@@ -138,50 +133,58 @@ pub const Action = packed struct(u128) {
138
133
  quick_claw: Optional(bool) = .None,
139
134
  /// If not None, the value to return for Rolls.hit.
140
135
  hit: Optional(bool) = .None,
141
- /// If not None, the value to be returned by Rolls.criticalHit.
142
- critical_hit: Optional(bool) = .None,
143
-
144
136
  /// If not None, the value to return for Rolls.confused.
145
137
  confused: Optional(bool) = .None,
138
+
146
139
  /// If not None, the value to return for Rolls.attract.
147
140
  attract: Optional(bool) = .None,
148
141
  /// If not None, the value to return for Rolls.paralyzed.
149
142
  paralyzed: Optional(bool) = .None,
150
143
  /// If not None, the value to return for Rolls.defrost.
151
144
  defrost: Optional(bool) = .None,
152
-
153
145
  /// If not None, the value to be returned for Rolls.secondaryChance.
154
146
  secondary_chance: Optional(bool) = .None,
147
+
155
148
  /// If not None, the value to be returned for Rolls.item (Focus Band / King's Rock).
156
149
  item: Optional(bool) = .None,
157
150
  /// If not None, the value to be returned for Rolls.protect.
158
151
  protect: Optional(bool) = .None,
159
152
  /// If not None, the value to return for Rolls.triAttack.
160
153
  tri_attack: Optional(TriAttack) = .None,
154
+ /// If not 0, the value to return for Rolls.tripleKick.
155
+ triple_kick: u2 = 0,
161
156
 
162
157
  /// If not 0, (present - 1) * 40 should be returned as the base power for Rolls.present.
163
158
  present: u3 = 0,
164
159
  /// If not 0, magnitude + 3 should be returned as the number for Rolls.magnitude.
165
160
  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.
161
+ /// If not 0, the amount of PP to deduct for Rolls.spite.
170
162
  spite: u3 = 0,
171
- /// If not None, the value to return for Rolls.conversion2.
172
- conversion_2: Optional(Type) = .None,
173
-
174
163
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
175
164
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
176
- move_slot: u4 = 0,
165
+ move_slot: u3 = 0,
177
166
  /// If not 0, the party slot (1-6) to return in Rolls.forceSwitch. If present as an override,
178
167
  /// invalid values (eg. due to empty party slots or fainted members) will be ignored.
179
- force_switch: u4 = 0,
180
-
168
+ force_switch: u3 = 0,
181
169
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
182
- multi_hit: u4 = 0,
170
+ multi_hit: u3 = 0,
171
+
172
+ sleep: Optional(Observation) = .None,
173
+ /// TODO
174
+ confusion: Optional(Observation) = .None,
175
+ /// TODO
176
+ disable: Optional(Observation) = .None,
177
+ /// TODO
178
+ attacking: Optional(Observation) = .None,
179
+ /// TODO
180
+ binding: Optional(Observation) = .None,
181
+ /// TODO
182
+ encore: Optional(Observation) = .None,
183
+
183
184
  /// If not 0, the value to by one of the Rolls.*Duration rolls.
184
- duration: u4 = 0,
185
+ duration: u3 = 0,
186
+ /// If not None, the value to return for Rolls.conversion2.
187
+ conversion_2: Optional(Type) = .None,
185
188
 
186
189
  /// If not 0, psywave should be returned as the damage roll for Rolls.psywave.
187
190
  psywave: u8 = 0,
@@ -189,63 +192,43 @@ pub const Action = packed struct(u128) {
189
192
  /// If not None, the Move to return for Rolls.metronome.
190
193
  metronome: Move = .None,
191
194
 
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
195
  pub const Field = std.meta.FieldEnum(Action);
200
196
 
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 };
197
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
208
198
  try fmt(a, w, false);
209
199
  }
210
200
 
211
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
201
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
212
202
  try writer.writeByte('(');
213
203
  var printed = false;
214
- inline for (@typeInfo(Action).Struct.fields) |field| {
215
- const val = @field(self, field.name);
204
+ inline for (if (@hasField(@TypeOf(@typeInfo(Action).@"struct"), "fields"))
205
+ @typeInfo(Action).@"struct".fields
206
+ else
207
+ @typeInfo(Action).@"struct".field_names) |field|
208
+ {
209
+ const field_name = if (@hasField(@TypeOf(field), "name")) field.name else field;
210
+ const val = @field(self, field_name);
216
211
  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) {
212
+ .@"enum" => if (val != .None) {
230
213
  if (printed) try writer.writeAll(", ");
231
214
  if (shape) {
232
- try writer.print("{s}:?", .{field.name});
215
+ try writer.print("{s}:?", .{field_name});
233
216
  } else if (@TypeOf(val) == Optional(bool)) {
234
217
  try writer.print("{s}{s}", .{
235
218
  if (val == .false) "!" else "",
236
- field.name,
219
+ field_name,
237
220
  });
238
221
  } else {
239
- try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
222
+ try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
240
223
  }
241
224
  printed = true;
242
225
  },
243
- .Int => if (val != 0) {
226
+ .int => if (val != 0) {
244
227
  if (printed) try writer.writeAll(", ");
245
228
  if (shape) {
246
- try writer.print("{s}:?", .{field.name});
229
+ try writer.print("{s}:?", .{field_name});
247
230
  } else {
248
- try writer.print("{s}:{d}", .{ field.name, val });
231
+ try writer.print("{s}:{d}", .{ field_name, val });
249
232
  }
250
233
  printed = true;
251
234
  },
@@ -256,36 +239,6 @@ pub const Action = packed struct(u128) {
256
239
  }
257
240
  };
258
241
 
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
242
  /// Tracks chance actions and their associated probability during a Generation II battle update when
290
243
  /// `options.chance` is enabled.
291
244
  pub fn Chance(comptime Rational: type) type {
@@ -298,37 +251,15 @@ pub fn Chance(comptime Rational: type) type {
298
251
  /// RNG events were observed during a battle `update`.
299
252
  actions: Actions = .{},
300
253
 
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
254
  /// Possible error returned by operations tracking chance probability.
310
255
  pub const Error = Rational.Error;
311
256
 
312
257
  /// Convenience helper to clear fields which typically should be cleared between updates.
313
258
  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
259
  if (!enabled) return;
321
260
 
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]);
261
+ self.probability.reset();
262
+ self.actions = .{};
332
263
  }
333
264
 
334
265
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -354,19 +285,21 @@ pub fn Chance(comptime Rational: type) type {
354
285
  self.actions.get(player).hit = if (ok) .true else .false;
355
286
  }
356
287
 
357
- pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
288
+ pub fn criticalHit(self: *Self, player: Player, i: u3, crit: bool, rate: u8) Error!void {
358
289
  if (!enabled) return;
359
290
 
360
291
  const n = if (crit) rate else @as(u8, @intCast(256 - @as(u9, rate)));
361
292
  try self.probability.update(n, 256);
362
- self.actions.get(player).critical_hit = if (crit) .true else .false;
293
+ var a = self.actions.get(player);
294
+ a.critical_hits = Criticals.set(a.critical_hits, i, if (crit) .true else .false);
363
295
  }
364
296
 
365
- pub fn damage(self: *Self, player: Player, roll: u8) Error!void {
297
+ pub fn damage(self: *Self, player: Player, i: u3, roll: u8) Error!void {
366
298
  if (!enabled) return;
367
299
 
368
300
  try self.probability.update(1, 39);
369
- self.actions.get(player).damage = roll;
301
+ var a = self.actions.get(player);
302
+ a.damages = Damages.set(a.damages, i, roll);
370
303
  }
371
304
 
372
305
  pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
@@ -518,122 +451,10 @@ pub fn Chance(comptime Rational: type) type {
518
451
  self.actions.get(player).multi_hit = n;
519
452
  }
520
453
 
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 {
454
+ pub fn duration(self: *Self, player: Player, turns: u3) void {
539
455
  if (!enabled) return;
540
456
 
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 {
591
- if (!enabled) return;
592
-
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
- }
457
+ self.actions.get(player).duration = if (options.key) 2 else turns;
637
458
  }
638
459
  };
639
460
  }
@@ -685,22 +506,22 @@ test "Chance.hit" {
685
506
  test "Chance.criticalHit" {
686
507
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
687
508
 
688
- try chance.criticalHit(.P1, true, 17);
689
- try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
509
+ try chance.criticalHit(.P1, 0, true, 17);
510
+ try expectValue(Optional(bool).true, Criticals.get(chance.actions.p1.critical_hits, 0));
690
511
  try expectProbability(&chance.probability, 17, 256);
691
512
 
692
513
  chance.reset();
693
514
 
694
- try chance.criticalHit(.P2, false, 5);
515
+ try chance.criticalHit(.P2, 1, false, 5);
695
516
  try expectProbability(&chance.probability, 251, 256);
696
- try expectValue(Optional(bool).false, chance.actions.p2.critical_hit);
517
+ try expectValue(Optional(bool).false, Criticals.get(chance.actions.p2.critical_hits, 1));
697
518
  }
698
519
 
699
520
  test "Chance.damage" {
700
521
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
701
522
 
702
- try chance.damage(.P1, 217);
703
- try expectValue(217, chance.actions.p1.damage);
523
+ try chance.damage(.P1, 0, 219);
524
+ try expectValue(@as(u8, 219), Damages.get(chance.actions.p1.damages, 0));
704
525
  try expectProbability(&chance.probability, 1, 39);
705
526
  }
706
527
 
@@ -825,7 +646,7 @@ test "Chance.present" {
825
646
  } else {
826
647
  try expectProbability(&chance.probability, 25, 256);
827
648
  }
828
- try expectValue(4, chance.actions.p2.present);
649
+ try expectValue(@as(u3, 4), chance.actions.p2.present);
829
650
 
830
651
  chance.reset();
831
652
 
@@ -835,7 +656,7 @@ test "Chance.present" {
835
656
  } else {
836
657
  try expectProbability(&chance.probability, 51, 256);
837
658
  }
838
- try expectValue(1, chance.actions.p1.present);
659
+ try expectValue(@as(u3, 1), chance.actions.p1.present);
839
660
  }
840
661
 
841
662
  test "Chance.magnitude" {
@@ -847,7 +668,7 @@ test "Chance.magnitude" {
847
668
  } else {
848
669
  try expectProbability(&chance.probability, 13, 256);
849
670
  }
850
- try expectValue(7, chance.actions.p2.magnitude);
671
+ try expectValue(@as(u3, 7), chance.actions.p2.magnitude);
851
672
 
852
673
  chance.reset();
853
674
 
@@ -857,7 +678,7 @@ test "Chance.magnitude" {
857
678
  } else {
858
679
  try expectProbability(&chance.probability, 51, 256);
859
680
  }
860
- try expectValue(5, chance.actions.p1.magnitude);
681
+ try expectValue(@as(u3, 5), chance.actions.p1.magnitude);
861
682
  }
862
683
 
863
684
  test "Chance.tripleKick" {
@@ -865,7 +686,7 @@ test "Chance.tripleKick" {
865
686
 
866
687
  try chance.tripleKick(.P1, 2);
867
688
  try expectProbability(&chance.probability, 1, 3);
868
- try expectValue(2, chance.actions.p1.triple_kick);
689
+ try expectValue(@as(u2, 2), chance.actions.p1.triple_kick);
869
690
  }
870
691
 
871
692
  test "Chance.spite" {
@@ -873,7 +694,7 @@ test "Chance.spite" {
873
694
 
874
695
  try chance.spite(.P1, 3);
875
696
  try expectProbability(&chance.probability, 1, 4);
876
- try expectValue(3, chance.actions.p1.spite);
697
+ try expectValue(@as(u3, 3), chance.actions.p1.spite);
877
698
  }
878
699
 
879
700
  test "Chance.conversion2" {
@@ -900,7 +721,7 @@ test "Chance.psywave" {
900
721
 
901
722
  try chance.psywave(.P2, 100, 150);
902
723
  try expectProbability(&chance.probability, 1, 150);
903
- try expectValue(100, chance.actions.p2.psywave);
724
+ try expectValue(@as(u8, 100), chance.actions.p2.psywave);
904
725
  }
905
726
 
906
727
  test "Chance.metronome" {
@@ -916,19 +737,19 @@ test "Chance.moveSlot" {
916
737
 
917
738
  try chance.moveSlot(.P2, 2, 2);
918
739
  try expectProbability(&chance.probability, 1, 2);
919
- try expectValue(2, chance.actions.p2.move_slot);
740
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
920
741
 
921
742
  chance.reset();
922
743
 
923
744
  try chance.moveSlot(.P1, 1, 3);
924
745
  try expectProbability(&chance.probability, 1, 3);
925
- try expectValue(1, chance.actions.p1.move_slot);
746
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
926
747
 
927
748
  chance.reset();
928
749
 
929
750
  try chance.moveSlot(.P1, 4, 1);
930
751
  try expectProbability(&chance.probability, 1, 1);
931
- try expectValue(4, chance.actions.p1.move_slot);
752
+ try expectValue(@as(u3, 4), chance.actions.p1.move_slot);
932
753
  }
933
754
 
934
755
  test "Chance.forceSwitch" {
@@ -936,19 +757,19 @@ test "Chance.forceSwitch" {
936
757
 
937
758
  try chance.forceSwitch(.P2, 2, 2);
938
759
  try expectProbability(&chance.probability, 1, 2);
939
- try expectValue(2, chance.actions.p2.force_switch);
760
+ try expectValue(@as(u3, 2), chance.actions.p2.force_switch);
940
761
 
941
762
  chance.reset();
942
763
 
943
764
  try chance.forceSwitch(.P1, 1, 3);
944
765
  try expectProbability(&chance.probability, 1, 3);
945
- try expectValue(1, chance.actions.p1.force_switch);
766
+ try expectValue(@as(u3, 1), chance.actions.p1.force_switch);
946
767
 
947
768
  chance.reset();
948
769
 
949
770
  try chance.forceSwitch(.P1, 4, 1);
950
771
  try expectProbability(&chance.probability, 1, 1);
951
- try expectValue(4, chance.actions.p1.force_switch);
772
+ try expectValue(@as(u3, 4), chance.actions.p1.force_switch);
952
773
  }
953
774
 
954
775
  test "Chance.multiHit" {
@@ -956,212 +777,20 @@ test "Chance.multiHit" {
956
777
 
957
778
  try chance.multiHit(.P1, 3);
958
779
  try expectProbability(&chance.probability, 3, 8);
959
- try expectValue(3, chance.actions.p1.multi_hit);
780
+ try expectValue(@as(u3, 3), chance.actions.p1.multi_hit);
960
781
 
961
782
  chance.reset();
962
783
 
963
784
  try chance.multiHit(.P2, 5);
964
785
  try expectProbability(&chance.probability, 1, 8);
965
- try expectValue(5, chance.actions.p2.multi_hit);
786
+ try expectValue(@as(u3, 5), chance.actions.p2.multi_hit);
966
787
  }
967
788
 
968
789
  test "Chance.duration" {
969
790
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
970
791
 
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
- }
792
+ chance.duration(.P1, 2);
793
+ try expectValue(@as(u3, 2), chance.actions.p1.duration);
1165
794
  }
1166
795
 
1167
796
  pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
@@ -1169,7 +798,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
1169
798
 
1170
799
  r.reduce();
1171
800
  if (r.p != p or r.q != q) {
1172
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
801
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
1173
802
  return error.TestExpectedEqual;
1174
803
  }
1175
804
  }
@@ -1203,12 +832,12 @@ const Null = struct {
1203
832
  _ = .{ self, player, ok, accuracy };
1204
833
  }
1205
834
 
1206
- pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
1207
- _ = .{ self, player, crit, rate };
835
+ pub fn criticalHit(self: Null, player: Player, i: u3, crit: bool, rate: u8) Error!void {
836
+ _ = .{ self, player, i, crit, rate };
1208
837
  }
1209
838
 
1210
- pub fn damage(self: Null, player: Player, roll: u8) Error!void {
1211
- _ = .{ self, player, roll };
839
+ pub fn damage(self: Null, player: Player, i: u3, roll: u8) Error!void {
840
+ _ = .{ self, player, i, roll };
1212
841
  }
1213
842
 
1214
843
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
@@ -1279,37 +908,7 @@ const Null = struct {
1279
908
  _ = .{ self, player, move, n };
1280
909
  }
1281
910
 
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 {
911
+ pub fn duration(self: Null, player: Player, turns: u3) void {
1313
912
  _ = .{ self, player, turns };
1314
913
  }
1315
914
  };