@pkmn/engine 0.1.0-dev.deda8593 → 0.1.0-dev.e0a786f8

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 (79) hide show
  1. package/README.md +9 -6
  2. package/build/pkg/addon/browser.d.ts +3 -0
  3. package/build/pkg/addon/browser.js +47 -0
  4. package/build/pkg/addon/browser.js.map +1 -0
  5. package/build/pkg/addon/node.d.ts +3 -0
  6. package/build/pkg/addon/node.js +110 -0
  7. package/build/pkg/addon/node.js.map +1 -0
  8. package/build/pkg/addon/wasm.d.ts +2 -0
  9. package/build/pkg/addon/wasm.js +53 -0
  10. package/build/pkg/addon/wasm.js.map +1 -0
  11. package/build/pkg/addon.d.ts +25 -3
  12. package/build/pkg/addon.js +34 -65
  13. package/build/pkg/addon.js.map +1 -1
  14. package/build/pkg/data/layout.json +15 -2
  15. package/build/pkg/data/protocol.json +23 -9
  16. package/build/pkg/data.d.ts +23 -1
  17. package/build/pkg/data.js.map +1 -1
  18. package/build/pkg/gen1.d.ts +15 -18
  19. package/build/pkg/gen1.js +28 -19
  20. package/build/pkg/gen1.js.map +1 -1
  21. package/build/pkg/index.d.ts +40 -90
  22. package/build/pkg/index.js +23 -70
  23. package/build/pkg/index.js.map +1 -1
  24. package/build/pkg/protocol.d.ts +20 -1
  25. package/build/pkg/protocol.js +105 -103
  26. package/build/pkg/protocol.js.map +1 -1
  27. package/build/tools/debug.js +52 -76
  28. package/build/tools/debug.js.map +1 -1
  29. package/build/tools/display/debug.jsx +152 -0
  30. package/build/tools/display/debug.jsx.map +1 -0
  31. package/build/tools/display/index.js +152 -0
  32. package/build/tools/display/index.js.map +1 -0
  33. package/build/tools/display/ui.jsx +359 -0
  34. package/build/tools/display/ui.jsx.map +1 -0
  35. package/build/tools/display/util.js +33 -0
  36. package/build/tools/display/util.js.map +1 -0
  37. package/build.zig +330 -85
  38. package/package.json +54 -135
  39. package/src/bin/install-pkmn-engine +10 -7
  40. package/src/bin/pkmn-debug +1 -1
  41. package/src/lib/bindings/c.zig +255 -0
  42. package/src/lib/bindings/node.zig +121 -0
  43. package/src/lib/bindings/wasm.zig +59 -0
  44. package/src/lib/common/array.zig +77 -0
  45. package/src/lib/common/data.zig +8 -8
  46. package/src/lib/common/debug.zig +39 -8
  47. package/src/lib/common/js.zig +4 -2
  48. package/src/lib/common/optional.zig +13 -9
  49. package/src/lib/common/options.zig +4 -0
  50. package/src/lib/common/protocol.zig +45 -44
  51. package/src/lib/common/rational.zig +323 -157
  52. package/src/lib/common/rng.zig +5 -7
  53. package/src/lib/common/util.zig +60 -1
  54. package/src/lib/gen1/calc.zig +527 -161
  55. package/src/lib/gen1/chance.zig +366 -240
  56. package/src/lib/gen1/data/moves.zig +2 -4
  57. package/src/lib/gen1/data/species.zig +1 -3
  58. package/src/lib/gen1/data/types.zig +1 -2
  59. package/src/lib/gen1/data.zig +35 -38
  60. package/src/lib/gen1/helpers.zig +17 -554
  61. package/src/lib/gen1/mechanics.zig +280 -193
  62. package/src/lib/gen2/calc.zig +37 -27
  63. package/src/lib/gen2/chance.zig +90 -503
  64. package/src/lib/gen2/data/items.zig +1 -3
  65. package/src/lib/gen2/data/moves.zig +2 -4
  66. package/src/lib/gen2/data/species.zig +1 -3
  67. package/src/lib/gen2/data/types.zig +1 -2
  68. package/src/lib/gen2/data.zig +26 -29
  69. package/src/lib/gen2/generated.zig +18 -23
  70. package/src/lib/gen2/helpers.zig +9 -18
  71. package/src/lib/gen2/mechanics.zig +88 -115
  72. package/src/lib/node.zig +3 -116
  73. package/src/lib/pkmn.zig +14 -7
  74. package/src/lib/wasm.zig +13 -9
  75. package/src/tools/display/dom.ts +93 -0
  76. package/src/tools/display/ui.css +354 -0
  77. package/build/test/display.js +0 -400
  78. package/build/test/display.js.map +0 -1
  79. 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,13 +19,18 @@ 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;
26
27
  const Effectiveness = data.Effectiveness;
27
28
  const TriAttack = data.TriAttack;
28
29
 
30
+ const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
31
+ const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
32
+ const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
33
+
29
34
  /// Actions taken by a hypothetical "chance player" that convey information about which RNG events
30
35
  /// were observed during a Generation II battle `update`. This can additionally be provided as input
31
36
  /// to the `update` call to override the normal behavior of the RNG in order to force specific
@@ -37,17 +42,11 @@ pub const Actions = extern struct {
37
42
  p2: Action = .{},
38
43
 
39
44
  /// 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);
45
+ pub fn get(self: anytype, player: Player) PointerType(@TypeOf(self), Action) {
46
+ assert(isPointerTo(self, Actions));
42
47
  return if (player == .P1) &self.p1 else &self.p2;
43
48
  }
44
49
 
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
50
  /// Returns true if `a` is equal to `b`.
52
51
  pub fn eql(a: Actions, b: Actions) bool {
53
52
  return @as(u128, @bitCast(a.p1)) == @as(u128, @bitCast(b.p1)) and
@@ -58,24 +57,15 @@ pub const Actions = extern struct {
58
57
  /// same shape if they have the same fields set (though those fields need not necessarily be
59
58
  /// set to the same value).
60
59
  pub fn matches(a: Actions, b: Actions) bool {
61
- inline for (@typeInfo(Actions).Struct.fields) |player| {
62
- inline for (@typeInfo(Action).Struct.fields) |field| {
60
+ inline for (@field(@typeInfo(Actions), @tagName(Struct)).fields) |player| {
61
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
63
62
  const a_val = @field(@field(a, player.name), field.name);
64
63
  const b_val = @field(@field(b, player.name), field.name);
65
64
 
66
65
  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))
66
+ Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
77
67
  return false,
78
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
68
+ Int => if ((a_val > 0) != (b_val > 0)) return false,
79
69
  else => unreachable,
80
70
  }
81
71
  }
@@ -97,17 +87,10 @@ pub const Actions = extern struct {
97
87
  }
98
88
  };
99
89
 
100
- // TODO: ziglang/zig#19730
101
90
  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 } };
91
+ const a: Actions = .{ .p1 = .{ .hit = .true, .confused = .false, .damages = 245 } };
92
+ const b: Actions = .{ .p1 = .{ .hit = .false, .confused = .true, .damages = 246 } };
104
93
  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
94
 
112
95
  try expect(a.eql(a));
113
96
  try expect(!a.eql(b));
@@ -120,17 +103,21 @@ test Actions {
120
103
  try expect(b.matches(a));
121
104
  try expect(!a.matches(c));
122
105
  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
106
  }
128
107
 
108
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
109
+ pub const Observation = enum { started, continuing, ended };
110
+
111
+ pub const Damages = Array(5, u8);
112
+ pub const Criticals = Array(5, Optional(bool));
113
+
129
114
  /// Information about the RNG that was observed during a Generation II battle `update` for a
130
115
  /// single player.
131
116
  pub const Action = packed struct(u128) {
132
- /// If not 0, the roll to be returned Rolls.damage.
133
- damage: u8 = 0,
117
+ /// TODO
118
+ damages: Damages.T = 0,
119
+ // TODO
120
+ critical_hits: Criticals.T = 0,
134
121
 
135
122
  /// If not None, the Player to be returned by Rolls.speedTie.
136
123
  speed_tie: Optional(Player) = .None,
@@ -138,50 +125,58 @@ pub const Action = packed struct(u128) {
138
125
  quick_claw: Optional(bool) = .None,
139
126
  /// If not None, the value to return for Rolls.hit.
140
127
  hit: Optional(bool) = .None,
141
- /// If not None, the value to be returned by Rolls.criticalHit.
142
- critical_hit: Optional(bool) = .None,
143
-
144
128
  /// If not None, the value to return for Rolls.confused.
145
129
  confused: Optional(bool) = .None,
130
+
146
131
  /// If not None, the value to return for Rolls.attract.
147
132
  attract: Optional(bool) = .None,
148
133
  /// If not None, the value to return for Rolls.paralyzed.
149
134
  paralyzed: Optional(bool) = .None,
150
135
  /// If not None, the value to return for Rolls.defrost.
151
136
  defrost: Optional(bool) = .None,
152
-
153
137
  /// If not None, the value to be returned for Rolls.secondaryChance.
154
138
  secondary_chance: Optional(bool) = .None,
139
+
155
140
  /// If not None, the value to be returned for Rolls.item (Focus Band / King's Rock).
156
141
  item: Optional(bool) = .None,
157
142
  /// If not None, the value to be returned for Rolls.protect.
158
143
  protect: Optional(bool) = .None,
159
144
  /// If not None, the value to return for Rolls.triAttack.
160
145
  tri_attack: Optional(TriAttack) = .None,
146
+ /// If not 0, the value to return for Rolls.tripleKick.
147
+ triple_kick: u2 = 0,
161
148
 
162
149
  /// If not 0, (present - 1) * 40 should be returned as the base power for Rolls.present.
163
150
  present: u3 = 0,
164
151
  /// If not 0, magnitude + 3 should be returned as the number for Rolls.magnitude.
165
152
  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.
153
+ /// If not 0, the amount of PP to deduct for Rolls.spite.
170
154
  spite: u3 = 0,
171
- /// If not None, the value to return for Rolls.conversion2.
172
- conversion_2: Optional(Type) = .None,
173
-
174
155
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
175
156
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
176
- move_slot: u4 = 0,
157
+ move_slot: u3 = 0,
177
158
  /// If not 0, the party slot (1-6) to return in Rolls.forceSwitch. If present as an override,
178
159
  /// invalid values (eg. due to empty party slots or fainted members) will be ignored.
179
- force_switch: u4 = 0,
180
-
160
+ force_switch: u3 = 0,
181
161
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
182
- multi_hit: u4 = 0,
162
+ multi_hit: u3 = 0,
163
+
164
+ sleep: Optional(Observation) = .None,
165
+ /// TODO
166
+ confusion: Optional(Observation) = .None,
167
+ /// TODO
168
+ disable: Optional(Observation) = .None,
169
+ /// TODO
170
+ attacking: Optional(Observation) = .None,
171
+ /// TODO
172
+ binding: Optional(Observation) = .None,
173
+ /// TODO
174
+ encore: Optional(Observation) = .None,
175
+
183
176
  /// If not 0, the value to by one of the Rolls.*Duration rolls.
184
- duration: u4 = 0,
177
+ duration: u3 = 0,
178
+ /// If not None, the value to return for Rolls.conversion2.
179
+ conversion_2: Optional(Type) = .None,
185
180
 
186
181
  /// If not 0, psywave should be returned as the damage roll for Rolls.psywave.
187
182
  psywave: u8 = 0,
@@ -189,20 +184,8 @@ pub const Action = packed struct(u128) {
189
184
  /// If not None, the Move to return for Rolls.metronome.
190
185
  metronome: Move = .None,
191
186
 
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
187
  pub const Field = std.meta.FieldEnum(Action);
200
188
 
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
189
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
207
190
  _ = .{ f, o };
208
191
  try fmt(a, w, false);
@@ -211,22 +194,10 @@ pub const Action = packed struct(u128) {
211
194
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
212
195
  try writer.writeByte('(');
213
196
  var printed = false;
214
- inline for (@typeInfo(Action).Struct.fields) |field| {
197
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
215
198
  const val = @field(self, field.name);
216
199
  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) {
200
+ Enum => if (val != .None) {
230
201
  if (printed) try writer.writeAll(", ");
231
202
  if (shape) {
232
203
  try writer.print("{s}:?", .{field.name});
@@ -240,7 +211,7 @@ pub const Action = packed struct(u128) {
240
211
  }
241
212
  printed = true;
242
213
  },
243
- .Int => if (val != 0) {
214
+ Int => if (val != 0) {
244
215
  if (printed) try writer.writeAll(", ");
245
216
  if (shape) {
246
217
  try writer.print("{s}:?", .{field.name});
@@ -256,36 +227,6 @@ pub const Action = packed struct(u128) {
256
227
  }
257
228
  };
258
229
 
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
230
  /// Tracks chance actions and their associated probability during a Generation II battle update when
290
231
  /// `options.chance` is enabled.
291
232
  pub fn Chance(comptime Rational: type) type {
@@ -298,37 +239,15 @@ pub fn Chance(comptime Rational: type) type {
298
239
  /// RNG events were observed during a battle `update`.
299
240
  actions: Actions = .{},
300
241
 
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
242
  /// Possible error returned by operations tracking chance probability.
310
243
  pub const Error = Rational.Error;
311
244
 
312
245
  /// Convenience helper to clear fields which typically should be cleared between updates.
313
246
  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
247
  if (!enabled) return;
321
248
 
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]);
249
+ self.probability.reset();
250
+ self.actions = .{};
332
251
  }
333
252
 
334
253
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -354,19 +273,21 @@ pub fn Chance(comptime Rational: type) type {
354
273
  self.actions.get(player).hit = if (ok) .true else .false;
355
274
  }
356
275
 
357
- pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
276
+ pub fn criticalHit(self: *Self, player: Player, i: u3, crit: bool, rate: u8) Error!void {
358
277
  if (!enabled) return;
359
278
 
360
279
  const n = if (crit) rate else @as(u8, @intCast(256 - @as(u9, rate)));
361
280
  try self.probability.update(n, 256);
362
- self.actions.get(player).critical_hit = if (crit) .true else .false;
281
+ var a = self.actions.get(player);
282
+ a.critical_hits = Criticals.set(a.critical_hits, i, if (crit) .true else .false);
363
283
  }
364
284
 
365
- pub fn damage(self: *Self, player: Player, roll: u8) Error!void {
285
+ pub fn damage(self: *Self, player: Player, i: u3, roll: u8) Error!void {
366
286
  if (!enabled) return;
367
287
 
368
288
  try self.probability.update(1, 39);
369
- self.actions.get(player).damage = roll;
289
+ var a = self.actions.get(player);
290
+ a.damages = Damages.set(a.damages, i, roll);
370
291
  }
371
292
 
372
293
  pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
@@ -518,122 +439,10 @@ pub fn Chance(comptime Rational: type) type {
518
439
  self.actions.get(player).multi_hit = n;
519
440
  }
520
441
 
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 {
442
+ pub fn duration(self: *Self, player: Player, turns: u3) void {
591
443
  if (!enabled) return;
592
444
 
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
- }
445
+ self.actions.get(player).duration = if (options.key) 2 else turns;
637
446
  }
638
447
  };
639
448
  }
@@ -685,22 +494,22 @@ test "Chance.hit" {
685
494
  test "Chance.criticalHit" {
686
495
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
687
496
 
688
- try chance.criticalHit(.P1, true, 17);
689
- try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
497
+ try chance.criticalHit(.P1, 0, true, 17);
498
+ try expectValue(Optional(bool).true, Criticals.get(chance.actions.p1.critical_hits, 0));
690
499
  try expectProbability(&chance.probability, 17, 256);
691
500
 
692
501
  chance.reset();
693
502
 
694
- try chance.criticalHit(.P2, false, 5);
503
+ try chance.criticalHit(.P2, 1, false, 5);
695
504
  try expectProbability(&chance.probability, 251, 256);
696
- try expectValue(Optional(bool).false, chance.actions.p2.critical_hit);
505
+ try expectValue(Optional(bool).false, Criticals.get(chance.actions.p2.critical_hits, 1));
697
506
  }
698
507
 
699
508
  test "Chance.damage" {
700
509
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
701
510
 
702
- try chance.damage(.P1, 217);
703
- try expectValue(217, chance.actions.p1.damage);
511
+ try chance.damage(.P1, 0, 219);
512
+ try expectValue(@as(u8, 219), Damages.get(chance.actions.p1.damages, 0));
704
513
  try expectProbability(&chance.probability, 1, 39);
705
514
  }
706
515
 
@@ -825,7 +634,7 @@ test "Chance.present" {
825
634
  } else {
826
635
  try expectProbability(&chance.probability, 25, 256);
827
636
  }
828
- try expectValue(4, chance.actions.p2.present);
637
+ try expectValue(@as(u3, 4), chance.actions.p2.present);
829
638
 
830
639
  chance.reset();
831
640
 
@@ -835,7 +644,7 @@ test "Chance.present" {
835
644
  } else {
836
645
  try expectProbability(&chance.probability, 51, 256);
837
646
  }
838
- try expectValue(1, chance.actions.p1.present);
647
+ try expectValue(@as(u3, 1), chance.actions.p1.present);
839
648
  }
840
649
 
841
650
  test "Chance.magnitude" {
@@ -847,7 +656,7 @@ test "Chance.magnitude" {
847
656
  } else {
848
657
  try expectProbability(&chance.probability, 13, 256);
849
658
  }
850
- try expectValue(7, chance.actions.p2.magnitude);
659
+ try expectValue(@as(u3, 7), chance.actions.p2.magnitude);
851
660
 
852
661
  chance.reset();
853
662
 
@@ -857,7 +666,7 @@ test "Chance.magnitude" {
857
666
  } else {
858
667
  try expectProbability(&chance.probability, 51, 256);
859
668
  }
860
- try expectValue(5, chance.actions.p1.magnitude);
669
+ try expectValue(@as(u3, 5), chance.actions.p1.magnitude);
861
670
  }
862
671
 
863
672
  test "Chance.tripleKick" {
@@ -865,7 +674,7 @@ test "Chance.tripleKick" {
865
674
 
866
675
  try chance.tripleKick(.P1, 2);
867
676
  try expectProbability(&chance.probability, 1, 3);
868
- try expectValue(2, chance.actions.p1.triple_kick);
677
+ try expectValue(@as(u2, 2), chance.actions.p1.triple_kick);
869
678
  }
870
679
 
871
680
  test "Chance.spite" {
@@ -873,7 +682,7 @@ test "Chance.spite" {
873
682
 
874
683
  try chance.spite(.P1, 3);
875
684
  try expectProbability(&chance.probability, 1, 4);
876
- try expectValue(3, chance.actions.p1.spite);
685
+ try expectValue(@as(u3, 3), chance.actions.p1.spite);
877
686
  }
878
687
 
879
688
  test "Chance.conversion2" {
@@ -900,7 +709,7 @@ test "Chance.psywave" {
900
709
 
901
710
  try chance.psywave(.P2, 100, 150);
902
711
  try expectProbability(&chance.probability, 1, 150);
903
- try expectValue(100, chance.actions.p2.psywave);
712
+ try expectValue(@as(u8, 100), chance.actions.p2.psywave);
904
713
  }
905
714
 
906
715
  test "Chance.metronome" {
@@ -916,19 +725,19 @@ test "Chance.moveSlot" {
916
725
 
917
726
  try chance.moveSlot(.P2, 2, 2);
918
727
  try expectProbability(&chance.probability, 1, 2);
919
- try expectValue(2, chance.actions.p2.move_slot);
728
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
920
729
 
921
730
  chance.reset();
922
731
 
923
732
  try chance.moveSlot(.P1, 1, 3);
924
733
  try expectProbability(&chance.probability, 1, 3);
925
- try expectValue(1, chance.actions.p1.move_slot);
734
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
926
735
 
927
736
  chance.reset();
928
737
 
929
738
  try chance.moveSlot(.P1, 4, 1);
930
739
  try expectProbability(&chance.probability, 1, 1);
931
- try expectValue(4, chance.actions.p1.move_slot);
740
+ try expectValue(@as(u3, 4), chance.actions.p1.move_slot);
932
741
  }
933
742
 
934
743
  test "Chance.forceSwitch" {
@@ -936,19 +745,19 @@ test "Chance.forceSwitch" {
936
745
 
937
746
  try chance.forceSwitch(.P2, 2, 2);
938
747
  try expectProbability(&chance.probability, 1, 2);
939
- try expectValue(2, chance.actions.p2.force_switch);
748
+ try expectValue(@as(u3, 2), chance.actions.p2.force_switch);
940
749
 
941
750
  chance.reset();
942
751
 
943
752
  try chance.forceSwitch(.P1, 1, 3);
944
753
  try expectProbability(&chance.probability, 1, 3);
945
- try expectValue(1, chance.actions.p1.force_switch);
754
+ try expectValue(@as(u3, 1), chance.actions.p1.force_switch);
946
755
 
947
756
  chance.reset();
948
757
 
949
758
  try chance.forceSwitch(.P1, 4, 1);
950
759
  try expectProbability(&chance.probability, 1, 1);
951
- try expectValue(4, chance.actions.p1.force_switch);
760
+ try expectValue(@as(u3, 4), chance.actions.p1.force_switch);
952
761
  }
953
762
 
954
763
  test "Chance.multiHit" {
@@ -956,212 +765,20 @@ test "Chance.multiHit" {
956
765
 
957
766
  try chance.multiHit(.P1, 3);
958
767
  try expectProbability(&chance.probability, 3, 8);
959
- try expectValue(3, chance.actions.p1.multi_hit);
768
+ try expectValue(@as(u3, 3), chance.actions.p1.multi_hit);
960
769
 
961
770
  chance.reset();
962
771
 
963
772
  try chance.multiHit(.P2, 5);
964
773
  try expectProbability(&chance.probability, 1, 8);
965
- try expectValue(5, chance.actions.p2.multi_hit);
774
+ try expectValue(@as(u3, 5), chance.actions.p2.multi_hit);
966
775
  }
967
776
 
968
777
  test "Chance.duration" {
969
778
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
970
779
 
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
- }
780
+ chance.duration(.P1, 2);
781
+ try expectValue(@as(u3, 2), chance.actions.p1.duration);
1165
782
  }
1166
783
 
1167
784
  pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
@@ -1203,12 +820,12 @@ const Null = struct {
1203
820
  _ = .{ self, player, ok, accuracy };
1204
821
  }
1205
822
 
1206
- pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
1207
- _ = .{ self, player, crit, rate };
823
+ pub fn criticalHit(self: Null, player: Player, i: u3, crit: bool, rate: u8) Error!void {
824
+ _ = .{ self, player, i, crit, rate };
1208
825
  }
1209
826
 
1210
- pub fn damage(self: Null, player: Player, roll: u8) Error!void {
1211
- _ = .{ self, player, roll };
827
+ pub fn damage(self: Null, player: Player, i: u3, roll: u8) Error!void {
828
+ _ = .{ self, player, i, roll };
1212
829
  }
1213
830
 
1214
831
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
@@ -1279,37 +896,7 @@ const Null = struct {
1279
896
  _ = .{ self, player, move, n };
1280
897
  }
1281
898
 
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 {
899
+ pub fn duration(self: Null, player: Player, turns: u3) void {
1313
900
  _ = .{ self, player, turns };
1314
901
  }
1315
902
  };