@pkmn/engine 0.1.0-dev.d56bdadd → 0.1.0-dev.d73cd76c

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 (84) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +66 -32
  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 +39 -71
  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 +27 -5
  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 +42 -91
  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 +109 -107
  27. package/build/pkg/protocol.js.map +1 -1
  28. package/build/tools/debug.js +84 -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 +357 -135
  39. package/package.json +61 -132
  40. package/src/bin/install-pkmn-engine +47 -21
  41. package/src/bin/pkmn-debug +1 -1
  42. package/src/lib/bindings/c.zig +201 -0
  43. package/src/lib/{binding → bindings}/node.zig +15 -32
  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 +4 -4
  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 +27 -23
  50. package/src/lib/common/options.zig +6 -0
  51. package/src/lib/common/protocol.zig +565 -552
  52. package/src/lib/common/rational.zig +340 -253
  53. package/src/lib/common/rng.zig +7 -9
  54. package/src/lib/common/util.zig +53 -1
  55. package/src/lib/gen1/calc.zig +724 -175
  56. package/src/lib/gen1/chance.zig +486 -300
  57. package/src/lib/gen1/data/moves.zig +2 -4
  58. package/src/lib/gen1/data/species.zig +2 -4
  59. package/src/lib/gen1/data/types.zig +3 -4
  60. package/src/lib/gen1/data.zig +29 -32
  61. package/src/lib/gen1/helpers.zig +113 -561
  62. package/src/lib/gen1/mechanics.zig +611 -488
  63. package/src/lib/gen2/calc.zig +35 -28
  64. package/src/lib/gen2/chance.zig +111 -511
  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 +27 -29
  70. package/src/lib/gen2/generated.zig +22 -29
  71. package/src/lib/gen2/helpers.zig +68 -39
  72. package/src/lib/gen2/mechanics.zig +504 -430
  73. package/src/lib/napi.h +2 -0
  74. package/src/lib/node.zig +14 -0
  75. package/src/lib/pkmn.zig +125 -29
  76. package/src/lib/wasm.zig +13 -0
  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/lib/binding/wasm.zig +0 -12
  82. package/src/lib/gen1/README.md +0 -894
  83. package/src/lib/gen2/README.md +0 -329
  84. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -8,18 +8,19 @@ 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
 
17
- const data = @import("./data.zig");
17
+ const data = @import("data.zig");
18
18
 
19
19
  const enabled = options.chance;
20
20
  const showdown = options.showdown;
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,22 +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
98
  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 } };
99
+ const a: Actions = .{ .p1 = .{ .hit = .true, .confused = .false, .damages = 245 } };
100
+ const b: Actions = .{ .p1 = .{ .hit = .false, .confused = .true, .damages = 246 } };
103
101
  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
102
 
111
103
  try expect(a.eql(a));
112
104
  try expect(!a.eql(b));
@@ -119,17 +111,21 @@ test Actions {
119
111
  try expect(b.matches(a));
120
112
  try expect(!a.matches(c));
121
113
  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
114
  }
127
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
+
128
122
  /// Information about the RNG that was observed during a Generation II battle `update` for a
129
123
  /// single player.
130
124
  pub const Action = packed struct(u128) {
131
- /// If not 0, the roll to be returned Rolls.damage.
132
- damage: u8 = 0,
125
+ /// TODO
126
+ damages: Damages.T = 0,
127
+ // TODO
128
+ critical_hits: Criticals.T = 0,
133
129
 
134
130
  /// If not None, the Player to be returned by Rolls.speedTie.
135
131
  speed_tie: Optional(Player) = .None,
@@ -137,50 +133,58 @@ pub const Action = packed struct(u128) {
137
133
  quick_claw: Optional(bool) = .None,
138
134
  /// If not None, the value to return for Rolls.hit.
139
135
  hit: Optional(bool) = .None,
140
- /// If not None, the value to be returned by Rolls.criticalHit.
141
- critical_hit: Optional(bool) = .None,
142
-
143
136
  /// If not None, the value to return for Rolls.confused.
144
137
  confused: Optional(bool) = .None,
138
+
145
139
  /// If not None, the value to return for Rolls.attract.
146
140
  attract: Optional(bool) = .None,
147
141
  /// If not None, the value to return for Rolls.paralyzed.
148
142
  paralyzed: Optional(bool) = .None,
149
143
  /// If not None, the value to return for Rolls.defrost.
150
144
  defrost: Optional(bool) = .None,
151
-
152
145
  /// If not None, the value to be returned for Rolls.secondaryChance.
153
146
  secondary_chance: Optional(bool) = .None,
147
+
154
148
  /// If not None, the value to be returned for Rolls.item (Focus Band / King's Rock).
155
149
  item: Optional(bool) = .None,
156
150
  /// If not None, the value to be returned for Rolls.protect.
157
151
  protect: Optional(bool) = .None,
158
152
  /// If not None, the value to return for Rolls.triAttack.
159
153
  tri_attack: Optional(TriAttack) = .None,
154
+ /// If not 0, the value to return for Rolls.tripleKick.
155
+ triple_kick: u2 = 0,
160
156
 
161
157
  /// If not 0, (present - 1) * 40 should be returned as the base power for Rolls.present.
162
158
  present: u3 = 0,
163
159
  /// If not 0, magnitude + 3 should be returned as the number for Rolls.magnitude.
164
160
  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.
161
+ /// If not 0, the amount of PP to deduct for Rolls.spite.
169
162
  spite: u3 = 0,
170
- /// If not None, the value to return for Rolls.conversion2.
171
- conversion_2: Optional(Type) = .None,
172
-
173
163
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
174
164
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
175
- move_slot: u4 = 0,
165
+ move_slot: u3 = 0,
176
166
  /// If not 0, the party slot (1-6) to return in Rolls.forceSwitch. If present as an override,
177
167
  /// invalid values (eg. due to empty party slots or fainted members) will be ignored.
178
- force_switch: u4 = 0,
179
-
168
+ force_switch: u3 = 0,
180
169
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
181
- 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
+
182
184
  /// If not 0, the value to by one of the Rolls.*Duration rolls.
183
- duration: u4 = 0,
185
+ duration: u3 = 0,
186
+ /// If not None, the value to return for Rolls.conversion2.
187
+ conversion_2: Optional(Type) = .None,
184
188
 
185
189
  /// If not 0, psywave should be returned as the damage roll for Rolls.psywave.
186
190
  psywave: u8 = 0,
@@ -188,63 +192,43 @@ pub const Action = packed struct(u128) {
188
192
  /// If not None, the Move to return for Rolls.metronome.
189
193
  metronome: Move = .None,
190
194
 
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
195
  pub const Field = std.meta.FieldEnum(Action);
199
196
 
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
- pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
206
- _ = .{ f, o };
197
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
207
198
  try fmt(a, w, false);
208
199
  }
209
200
 
210
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
201
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
211
202
  try writer.writeByte('(');
212
203
  var printed = false;
213
- inline for (@typeInfo(Action).Struct.fields) |field| {
214
- 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);
215
211
  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) {
212
+ .@"enum" => if (val != .None) {
229
213
  if (printed) try writer.writeAll(", ");
230
214
  if (shape) {
231
- try writer.print("{s}:?", .{field.name});
215
+ try writer.print("{s}:?", .{field_name});
232
216
  } else if (@TypeOf(val) == Optional(bool)) {
233
217
  try writer.print("{s}{s}", .{
234
218
  if (val == .false) "!" else "",
235
- field.name,
219
+ field_name,
236
220
  });
237
221
  } else {
238
- try writer.print("{s}:{s}", .{ field.name, @tagName(val) });
222
+ try writer.print("{s}:{s}", .{ field_name, @tagName(val) });
239
223
  }
240
224
  printed = true;
241
225
  },
242
- .Int => if (val != 0) {
226
+ .int => if (val != 0) {
243
227
  if (printed) try writer.writeAll(", ");
244
228
  if (shape) {
245
- try writer.print("{s}:?", .{field.name});
229
+ try writer.print("{s}:?", .{field_name});
246
230
  } else {
247
- try writer.print("{s}:{d}", .{ field.name, val });
231
+ try writer.print("{s}:{d}", .{ field_name, val });
248
232
  }
249
233
  printed = true;
250
234
  },
@@ -255,36 +239,6 @@ pub const Action = packed struct(u128) {
255
239
  }
256
240
  };
257
241
 
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
242
  /// Tracks chance actions and their associated probability during a Generation II battle update when
289
243
  /// `options.chance` is enabled.
290
244
  pub fn Chance(comptime Rational: type) type {
@@ -297,37 +251,15 @@ pub fn Chance(comptime Rational: type) type {
297
251
  /// RNG events were observed during a battle `update`.
298
252
  actions: Actions = .{},
299
253
 
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
254
  /// Possible error returned by operations tracking chance probability.
309
255
  pub const Error = Rational.Error;
310
256
 
311
257
  /// Convenience helper to clear fields which typically should be cleared between updates.
312
258
  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
259
  if (!enabled) return;
320
260
 
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]);
261
+ self.probability.reset();
262
+ self.actions = .{};
331
263
  }
332
264
 
333
265
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -353,19 +285,21 @@ pub fn Chance(comptime Rational: type) type {
353
285
  self.actions.get(player).hit = if (ok) .true else .false;
354
286
  }
355
287
 
356
- 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 {
357
289
  if (!enabled) return;
358
290
 
359
291
  const n = if (crit) rate else @as(u8, @intCast(256 - @as(u9, rate)));
360
292
  try self.probability.update(n, 256);
361
- 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);
362
295
  }
363
296
 
364
- 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 {
365
298
  if (!enabled) return;
366
299
 
367
300
  try self.probability.update(1, 39);
368
- self.actions.get(player).damage = roll;
301
+ var a = self.actions.get(player);
302
+ a.damages = Damages.set(a.damages, i, roll);
369
303
  }
370
304
 
371
305
  pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
@@ -517,122 +451,10 @@ pub fn Chance(comptime Rational: type) type {
517
451
  self.actions.get(player).multi_hit = n;
518
452
  }
519
453
 
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 {
454
+ pub fn duration(self: *Self, player: Player, turns: u3) void {
538
455
  if (!enabled) return;
539
456
 
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
- }
457
+ self.actions.get(player).duration = if (options.key) 2 else turns;
636
458
  }
637
459
  };
638
460
  }
@@ -684,22 +506,22 @@ test "Chance.hit" {
684
506
  test "Chance.criticalHit" {
685
507
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
686
508
 
687
- try chance.criticalHit(.P1, true, 17);
688
- 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));
689
511
  try expectProbability(&chance.probability, 17, 256);
690
512
 
691
513
  chance.reset();
692
514
 
693
- try chance.criticalHit(.P2, false, 5);
515
+ try chance.criticalHit(.P2, 1, false, 5);
694
516
  try expectProbability(&chance.probability, 251, 256);
695
- try expectValue(Optional(bool).false, chance.actions.p2.critical_hit);
517
+ try expectValue(Optional(bool).false, Criticals.get(chance.actions.p2.critical_hits, 1));
696
518
  }
697
519
 
698
520
  test "Chance.damage" {
699
521
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
700
522
 
701
- try chance.damage(.P1, 217);
702
- try expectValue(@as(u8, 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));
703
525
  try expectProbability(&chance.probability, 1, 39);
704
526
  }
705
527
 
@@ -915,19 +737,19 @@ test "Chance.moveSlot" {
915
737
 
916
738
  try chance.moveSlot(.P2, 2, 2);
917
739
  try expectProbability(&chance.probability, 1, 2);
918
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
740
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
919
741
 
920
742
  chance.reset();
921
743
 
922
744
  try chance.moveSlot(.P1, 1, 3);
923
745
  try expectProbability(&chance.probability, 1, 3);
924
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
746
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
925
747
 
926
748
  chance.reset();
927
749
 
928
750
  try chance.moveSlot(.P1, 4, 1);
929
751
  try expectProbability(&chance.probability, 1, 1);
930
- try expectValue(@as(u4, 4), chance.actions.p1.move_slot);
752
+ try expectValue(@as(u3, 4), chance.actions.p1.move_slot);
931
753
  }
932
754
 
933
755
  test "Chance.forceSwitch" {
@@ -935,19 +757,19 @@ test "Chance.forceSwitch" {
935
757
 
936
758
  try chance.forceSwitch(.P2, 2, 2);
937
759
  try expectProbability(&chance.probability, 1, 2);
938
- try expectValue(@as(u4, 2), chance.actions.p2.force_switch);
760
+ try expectValue(@as(u3, 2), chance.actions.p2.force_switch);
939
761
 
940
762
  chance.reset();
941
763
 
942
764
  try chance.forceSwitch(.P1, 1, 3);
943
765
  try expectProbability(&chance.probability, 1, 3);
944
- try expectValue(@as(u4, 1), chance.actions.p1.force_switch);
766
+ try expectValue(@as(u3, 1), chance.actions.p1.force_switch);
945
767
 
946
768
  chance.reset();
947
769
 
948
770
  try chance.forceSwitch(.P1, 4, 1);
949
771
  try expectProbability(&chance.probability, 1, 1);
950
- try expectValue(@as(u4, 4), chance.actions.p1.force_switch);
772
+ try expectValue(@as(u3, 4), chance.actions.p1.force_switch);
951
773
  }
952
774
 
953
775
  test "Chance.multiHit" {
@@ -955,212 +777,20 @@ test "Chance.multiHit" {
955
777
 
956
778
  try chance.multiHit(.P1, 3);
957
779
  try expectProbability(&chance.probability, 3, 8);
958
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
780
+ try expectValue(@as(u3, 3), chance.actions.p1.multi_hit);
959
781
 
960
782
  chance.reset();
961
783
 
962
784
  try chance.multiHit(.P2, 5);
963
785
  try expectProbability(&chance.probability, 1, 8);
964
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
786
+ try expectValue(@as(u3, 5), chance.actions.p2.multi_hit);
965
787
  }
966
788
 
967
789
  test "Chance.duration" {
968
790
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
969
791
 
970
- chance.duration(.sleep, .P1, .P2, 2);
971
- 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
- }
792
+ chance.duration(.P1, 2);
793
+ try expectValue(@as(u3, 2), chance.actions.p1.duration);
1164
794
  }
1165
795
 
1166
796
  pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
@@ -1168,7 +798,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
1168
798
 
1169
799
  r.reduce();
1170
800
  if (r.p != p or r.q != q) {
1171
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
801
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
1172
802
  return error.TestExpectedEqual;
1173
803
  }
1174
804
  }
@@ -1202,12 +832,12 @@ const Null = struct {
1202
832
  _ = .{ self, player, ok, accuracy };
1203
833
  }
1204
834
 
1205
- pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
1206
- _ = .{ 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 };
1207
837
  }
1208
838
 
1209
- pub fn damage(self: Null, player: Player, roll: u8) Error!void {
1210
- _ = .{ self, player, roll };
839
+ pub fn damage(self: Null, player: Player, i: u3, roll: u8) Error!void {
840
+ _ = .{ self, player, i, roll };
1211
841
  }
1212
842
 
1213
843
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
@@ -1278,37 +908,7 @@ const Null = struct {
1278
908
  _ = .{ self, player, move, n };
1279
909
  }
1280
910
 
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 {
911
+ pub fn duration(self: Null, player: Player, turns: u3) void {
1312
912
  _ = .{ self, player, turns };
1313
913
  }
1314
914
  };