@pkmn/engine 0.1.0-dev.80019d10 → 0.1.0-dev.8058a1d0

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 +59 -24
  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 -75
  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 +259 -112
  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 +200 -0
  43. package/src/lib/{binding → bindings}/node.zig +15 -32
  44. package/src/lib/bindings/wasm.zig +50 -0
  45. package/src/lib/common/array.zig +70 -0
  46. package/src/lib/common/data.zig +4 -4
  47. package/src/lib/common/debug.zig +33 -28
  48. package/src/lib/common/js.zig +3 -7
  49. package/src/lib/common/optional.zig +12 -18
  50. package/src/lib/common/options.zig +6 -0
  51. package/src/lib/common/protocol.zig +256 -224
  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 +33 -1
  55. package/src/lib/gen1/calc.zig +724 -175
  56. package/src/lib/gen1/chance.zig +453 -291
  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 +91 -549
  62. package/src/lib/gen1/mechanics.zig +417 -294
  63. package/src/lib/gen2/calc.zig +35 -28
  64. package/src/lib/gen2/chance.zig +85 -503
  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 +19 -26
  71. package/src/lib/gen2/helpers.zig +45 -26
  72. package/src/lib/gen2/mechanics.zig +234 -180
  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,15 @@ pub const Actions = extern struct {
58
53
  /// same shape if they have the same fields set (though those fields need not necessarily be
59
54
  /// set to the same value).
60
55
  pub fn matches(a: Actions, b: Actions) bool {
61
- inline for (@typeInfo(Actions).Struct.fields) |player| {
62
- inline for (@typeInfo(Action).Struct.fields) |field| {
56
+ inline for (@typeInfo(Actions).@"struct".fields) |player| {
57
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
63
58
  const a_val = @field(@field(a, player.name), field.name);
64
59
  const b_val = @field(@field(b, player.name), field.name);
65
60
 
66
61
  switch (@typeInfo(@TypeOf(a_val))) {
67
- .Struct => inline for (@typeInfo(@TypeOf(a_val)).Struct.fields) |f| {
68
- const a_val_f = @field(a_val, f.name);
69
- const b_val_f = @field(b_val, f.name);
70
- switch (@typeInfo(@TypeOf(a_val_f))) {
71
- .Int => if ((a_val_f > 0) != (b_val_f > 0)) return false,
72
- .Bool => if (a_val_f != b_val_f) return false,
73
- else => unreachable,
74
- }
75
- },
76
- .Enum => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
62
+ .@"enum" => if ((@intFromEnum(a_val) > 0) != (@intFromEnum(b_val) > 0))
77
63
  return false,
78
- .Int => if ((a_val > 0) != (b_val > 0)) return false,
64
+ .int => if ((a_val > 0) != (b_val > 0)) return false,
79
65
  else => unreachable,
80
66
  }
81
67
  }
@@ -83,7 +69,7 @@ pub const Actions = extern struct {
83
69
  return true;
84
70
  }
85
71
 
86
- pub fn fmt(self: Actions, writer: anytype, shape: bool) !void {
72
+ pub fn fmt(self: Actions, writer: *std.Io.Writer, shape: bool) !void {
87
73
  try writer.writeAll("<P1 = ");
88
74
  try self.p1.fmt(writer, shape);
89
75
  try writer.writeAll(", P2 = ");
@@ -91,22 +77,15 @@ pub const Actions = extern struct {
91
77
  try writer.writeAll(">");
92
78
  }
93
79
 
94
- pub fn format(a: Actions, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
95
- _ = .{ f, o };
80
+ pub fn format(a: Actions, w: *std.Io.Writer) !void {
96
81
  try fmt(a, w, false);
97
82
  }
98
83
  };
99
84
 
100
85
  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 } };
86
+ const a: Actions = .{ .p1 = .{ .hit = .true, .confused = .false, .damages = 245 } };
87
+ const b: Actions = .{ .p1 = .{ .hit = .false, .confused = .true, .damages = 246 } };
103
88
  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
89
 
111
90
  try expect(a.eql(a));
112
91
  try expect(!a.eql(b));
@@ -119,17 +98,21 @@ test Actions {
119
98
  try expect(b.matches(a));
120
99
  try expect(!a.matches(c));
121
100
  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
101
  }
127
102
 
103
+ /// Observation made about a duration - whether the duration has started, been continued, or ended.
104
+ pub const Observation = enum { started, continuing, ended };
105
+
106
+ pub const Damages = Array(5, u8);
107
+ pub const Criticals = Array(5, Optional(bool));
108
+
128
109
  /// Information about the RNG that was observed during a Generation II battle `update` for a
129
110
  /// single player.
130
111
  pub const Action = packed struct(u128) {
131
- /// If not 0, the roll to be returned Rolls.damage.
132
- damage: u8 = 0,
112
+ /// TODO
113
+ damages: Damages.T = 0,
114
+ // TODO
115
+ critical_hits: Criticals.T = 0,
133
116
 
134
117
  /// If not None, the Player to be returned by Rolls.speedTie.
135
118
  speed_tie: Optional(Player) = .None,
@@ -137,50 +120,58 @@ pub const Action = packed struct(u128) {
137
120
  quick_claw: Optional(bool) = .None,
138
121
  /// If not None, the value to return for Rolls.hit.
139
122
  hit: Optional(bool) = .None,
140
- /// If not None, the value to be returned by Rolls.criticalHit.
141
- critical_hit: Optional(bool) = .None,
142
-
143
123
  /// If not None, the value to return for Rolls.confused.
144
124
  confused: Optional(bool) = .None,
125
+
145
126
  /// If not None, the value to return for Rolls.attract.
146
127
  attract: Optional(bool) = .None,
147
128
  /// If not None, the value to return for Rolls.paralyzed.
148
129
  paralyzed: Optional(bool) = .None,
149
130
  /// If not None, the value to return for Rolls.defrost.
150
131
  defrost: Optional(bool) = .None,
151
-
152
132
  /// If not None, the value to be returned for Rolls.secondaryChance.
153
133
  secondary_chance: Optional(bool) = .None,
134
+
154
135
  /// If not None, the value to be returned for Rolls.item (Focus Band / King's Rock).
155
136
  item: Optional(bool) = .None,
156
137
  /// If not None, the value to be returned for Rolls.protect.
157
138
  protect: Optional(bool) = .None,
158
139
  /// If not None, the value to return for Rolls.triAttack.
159
140
  tri_attack: Optional(TriAttack) = .None,
141
+ /// If not 0, the value to return for Rolls.tripleKick.
142
+ triple_kick: u2 = 0,
160
143
 
161
144
  /// If not 0, (present - 1) * 40 should be returned as the base power for Rolls.present.
162
145
  present: u3 = 0,
163
146
  /// If not 0, magnitude + 3 should be returned as the number for Rolls.magnitude.
164
147
  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.
148
+ /// If not 0, the amount of PP to deduct for Rolls.spite.
169
149
  spite: u3 = 0,
170
- /// If not None, the value to return for Rolls.conversion2.
171
- conversion_2: Optional(Type) = .None,
172
-
173
150
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
174
151
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
175
- move_slot: u4 = 0,
152
+ move_slot: u3 = 0,
176
153
  /// If not 0, the party slot (1-6) to return in Rolls.forceSwitch. If present as an override,
177
154
  /// invalid values (eg. due to empty party slots or fainted members) will be ignored.
178
- force_switch: u4 = 0,
179
-
155
+ force_switch: u3 = 0,
180
156
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
181
- multi_hit: u4 = 0,
157
+ multi_hit: u3 = 0,
158
+
159
+ sleep: Optional(Observation) = .None,
160
+ /// TODO
161
+ confusion: Optional(Observation) = .None,
162
+ /// TODO
163
+ disable: Optional(Observation) = .None,
164
+ /// TODO
165
+ attacking: Optional(Observation) = .None,
166
+ /// TODO
167
+ binding: Optional(Observation) = .None,
168
+ /// TODO
169
+ encore: Optional(Observation) = .None,
170
+
182
171
  /// If not 0, the value to by one of the Rolls.*Duration rolls.
183
- duration: u4 = 0,
172
+ duration: u3 = 0,
173
+ /// If not None, the value to return for Rolls.conversion2.
174
+ conversion_2: Optional(Type) = .None,
184
175
 
185
176
  /// If not 0, psywave should be returned as the damage roll for Rolls.psywave.
186
177
  psywave: u8 = 0,
@@ -188,44 +179,19 @@ pub const Action = packed struct(u128) {
188
179
  /// If not None, the Move to return for Rolls.metronome.
189
180
  metronome: Move = .None,
190
181
 
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
182
  pub const Field = std.meta.FieldEnum(Action);
199
183
 
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 };
184
+ pub fn format(a: Action, w: *std.Io.Writer) !void {
207
185
  try fmt(a, w, false);
208
186
  }
209
187
 
210
- pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
188
+ pub fn fmt(self: Action, writer: *std.Io.Writer, shape: bool) !void {
211
189
  try writer.writeByte('(');
212
190
  var printed = false;
213
- inline for (@typeInfo(Action).Struct.fields) |field| {
191
+ inline for (@typeInfo(Action).@"struct".fields) |field| {
214
192
  const val = @field(self, field.name);
215
193
  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) {
194
+ .@"enum" => if (val != .None) {
229
195
  if (printed) try writer.writeAll(", ");
230
196
  if (shape) {
231
197
  try writer.print("{s}:?", .{field.name});
@@ -239,7 +205,7 @@ pub const Action = packed struct(u128) {
239
205
  }
240
206
  printed = true;
241
207
  },
242
- .Int => if (val != 0) {
208
+ .int => if (val != 0) {
243
209
  if (printed) try writer.writeAll(", ");
244
210
  if (shape) {
245
211
  try writer.print("{s}:?", .{field.name});
@@ -255,36 +221,6 @@ pub const Action = packed struct(u128) {
255
221
  }
256
222
  };
257
223
 
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
224
  /// Tracks chance actions and their associated probability during a Generation II battle update when
289
225
  /// `options.chance` is enabled.
290
226
  pub fn Chance(comptime Rational: type) type {
@@ -297,37 +233,15 @@ pub fn Chance(comptime Rational: type) type {
297
233
  /// RNG events were observed during a battle `update`.
298
234
  actions: Actions = .{},
299
235
 
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
236
  /// Possible error returned by operations tracking chance probability.
309
237
  pub const Error = Rational.Error;
310
238
 
311
239
  /// Convenience helper to clear fields which typically should be cleared between updates.
312
240
  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
241
  if (!enabled) return;
320
242
 
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]);
243
+ self.probability.reset();
244
+ self.actions = .{};
331
245
  }
332
246
 
333
247
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -353,19 +267,21 @@ pub fn Chance(comptime Rational: type) type {
353
267
  self.actions.get(player).hit = if (ok) .true else .false;
354
268
  }
355
269
 
356
- pub fn criticalHit(self: *Self, player: Player, crit: bool, rate: u8) Error!void {
270
+ pub fn criticalHit(self: *Self, player: Player, i: u3, crit: bool, rate: u8) Error!void {
357
271
  if (!enabled) return;
358
272
 
359
273
  const n = if (crit) rate else @as(u8, @intCast(256 - @as(u9, rate)));
360
274
  try self.probability.update(n, 256);
361
- self.actions.get(player).critical_hit = if (crit) .true else .false;
275
+ var a = self.actions.get(player);
276
+ a.critical_hits = Criticals.set(a.critical_hits, i, if (crit) .true else .false);
362
277
  }
363
278
 
364
- pub fn damage(self: *Self, player: Player, roll: u8) Error!void {
279
+ pub fn damage(self: *Self, player: Player, i: u3, roll: u8) Error!void {
365
280
  if (!enabled) return;
366
281
 
367
282
  try self.probability.update(1, 39);
368
- self.actions.get(player).damage = roll;
283
+ var a = self.actions.get(player);
284
+ a.damages = Damages.set(a.damages, i, roll);
369
285
  }
370
286
 
371
287
  pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
@@ -517,122 +433,10 @@ pub fn Chance(comptime Rational: type) type {
517
433
  self.actions.get(player).multi_hit = n;
518
434
  }
519
435
 
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 {
538
- if (!enabled) return;
539
-
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 {
436
+ pub fn duration(self: *Self, player: Player, turns: u3) void {
590
437
  if (!enabled) return;
591
438
 
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
- }
439
+ self.actions.get(player).duration = if (options.key) 2 else turns;
636
440
  }
637
441
  };
638
442
  }
@@ -684,22 +488,22 @@ test "Chance.hit" {
684
488
  test "Chance.criticalHit" {
685
489
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
686
490
 
687
- try chance.criticalHit(.P1, true, 17);
688
- try expectValue(Optional(bool).true, chance.actions.p1.critical_hit);
491
+ try chance.criticalHit(.P1, 0, true, 17);
492
+ try expectValue(Optional(bool).true, Criticals.get(chance.actions.p1.critical_hits, 0));
689
493
  try expectProbability(&chance.probability, 17, 256);
690
494
 
691
495
  chance.reset();
692
496
 
693
- try chance.criticalHit(.P2, false, 5);
497
+ try chance.criticalHit(.P2, 1, false, 5);
694
498
  try expectProbability(&chance.probability, 251, 256);
695
- try expectValue(Optional(bool).false, chance.actions.p2.critical_hit);
499
+ try expectValue(Optional(bool).false, Criticals.get(chance.actions.p2.critical_hits, 1));
696
500
  }
697
501
 
698
502
  test "Chance.damage" {
699
503
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
700
504
 
701
- try chance.damage(.P1, 217);
702
- try expectValue(@as(u8, 217), chance.actions.p1.damage);
505
+ try chance.damage(.P1, 0, 219);
506
+ try expectValue(@as(u8, 219), Damages.get(chance.actions.p1.damages, 0));
703
507
  try expectProbability(&chance.probability, 1, 39);
704
508
  }
705
509
 
@@ -915,19 +719,19 @@ test "Chance.moveSlot" {
915
719
 
916
720
  try chance.moveSlot(.P2, 2, 2);
917
721
  try expectProbability(&chance.probability, 1, 2);
918
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
722
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
919
723
 
920
724
  chance.reset();
921
725
 
922
726
  try chance.moveSlot(.P1, 1, 3);
923
727
  try expectProbability(&chance.probability, 1, 3);
924
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
728
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
925
729
 
926
730
  chance.reset();
927
731
 
928
732
  try chance.moveSlot(.P1, 4, 1);
929
733
  try expectProbability(&chance.probability, 1, 1);
930
- try expectValue(@as(u4, 4), chance.actions.p1.move_slot);
734
+ try expectValue(@as(u3, 4), chance.actions.p1.move_slot);
931
735
  }
932
736
 
933
737
  test "Chance.forceSwitch" {
@@ -935,19 +739,19 @@ test "Chance.forceSwitch" {
935
739
 
936
740
  try chance.forceSwitch(.P2, 2, 2);
937
741
  try expectProbability(&chance.probability, 1, 2);
938
- try expectValue(@as(u4, 2), chance.actions.p2.force_switch);
742
+ try expectValue(@as(u3, 2), chance.actions.p2.force_switch);
939
743
 
940
744
  chance.reset();
941
745
 
942
746
  try chance.forceSwitch(.P1, 1, 3);
943
747
  try expectProbability(&chance.probability, 1, 3);
944
- try expectValue(@as(u4, 1), chance.actions.p1.force_switch);
748
+ try expectValue(@as(u3, 1), chance.actions.p1.force_switch);
945
749
 
946
750
  chance.reset();
947
751
 
948
752
  try chance.forceSwitch(.P1, 4, 1);
949
753
  try expectProbability(&chance.probability, 1, 1);
950
- try expectValue(@as(u4, 4), chance.actions.p1.force_switch);
754
+ try expectValue(@as(u3, 4), chance.actions.p1.force_switch);
951
755
  }
952
756
 
953
757
  test "Chance.multiHit" {
@@ -955,212 +759,20 @@ test "Chance.multiHit" {
955
759
 
956
760
  try chance.multiHit(.P1, 3);
957
761
  try expectProbability(&chance.probability, 3, 8);
958
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
762
+ try expectValue(@as(u3, 3), chance.actions.p1.multi_hit);
959
763
 
960
764
  chance.reset();
961
765
 
962
766
  try chance.multiHit(.P2, 5);
963
767
  try expectProbability(&chance.probability, 1, 8);
964
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
768
+ try expectValue(@as(u3, 5), chance.actions.p2.multi_hit);
965
769
  }
966
770
 
967
771
  test "Chance.duration" {
968
772
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
969
773
 
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
- }
774
+ chance.duration(.P1, 2);
775
+ try expectValue(@as(u3, 2), chance.actions.p1.duration);
1164
776
  }
1165
777
 
1166
778
  pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
@@ -1168,7 +780,7 @@ pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
1168
780
 
1169
781
  r.reduce();
1170
782
  if (r.p != p or r.q != q) {
1171
- print("expected {d}/{d}, found {}\n", .{ p, q, r });
783
+ print("expected {d}/{d}, found {f}\n", .{ p, q, r });
1172
784
  return error.TestExpectedEqual;
1173
785
  }
1174
786
  }
@@ -1202,12 +814,12 @@ const Null = struct {
1202
814
  _ = .{ self, player, ok, accuracy };
1203
815
  }
1204
816
 
1205
- pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
1206
- _ = .{ self, player, crit, rate };
817
+ pub fn criticalHit(self: Null, player: Player, i: u3, crit: bool, rate: u8) Error!void {
818
+ _ = .{ self, player, i, crit, rate };
1207
819
  }
1208
820
 
1209
- pub fn damage(self: Null, player: Player, roll: u8) Error!void {
1210
- _ = .{ self, player, roll };
821
+ pub fn damage(self: Null, player: Player, i: u3, roll: u8) Error!void {
822
+ _ = .{ self, player, i, roll };
1211
823
  }
1212
824
 
1213
825
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
@@ -1278,37 +890,7 @@ const Null = struct {
1278
890
  _ = .{ self, player, move, n };
1279
891
  }
1280
892
 
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 {
893
+ pub fn duration(self: Null, player: Player, turns: u3) void {
1312
894
  _ = .{ self, player, turns };
1313
895
  }
1314
896
  };