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

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 (67) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +46 -19
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +43 -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 +96 -0
  8. package/build/pkg/addon/node.js.map +1 -0
  9. package/build/pkg/addon.d.ts +2 -0
  10. package/build/pkg/addon.js +35 -58
  11. package/build/pkg/addon.js.map +1 -1
  12. package/build/pkg/data.d.ts +27 -5
  13. package/build/pkg/data.js.map +1 -1
  14. package/build/pkg/gen1.js +1 -1
  15. package/build/pkg/gen1.js.map +1 -1
  16. package/build/pkg/index.d.ts +24 -10
  17. package/build/pkg/index.js +15 -10
  18. package/build/pkg/index.js.map +1 -1
  19. package/build/pkg/protocol.d.ts +20 -1
  20. package/build/pkg/protocol.js +109 -107
  21. package/build/pkg/protocol.js.map +1 -1
  22. package/build/tools/debug.js +36 -68
  23. package/build/tools/debug.js.map +1 -1
  24. package/build/tools/display/debug.jsx +128 -0
  25. package/build/tools/display/debug.jsx.map +1 -0
  26. package/build/tools/display/index.js +132 -0
  27. package/build/tools/display/index.js.map +1 -0
  28. package/build/tools/display/ui.jsx +349 -0
  29. package/build/tools/display/ui.jsx.map +1 -0
  30. package/build/tools/display/util.js +18 -0
  31. package/build/tools/display/util.js.map +1 -0
  32. package/build.zig +206 -81
  33. package/package.json +51 -30
  34. package/src/bin/install-pkmn-engine +29 -19
  35. package/src/bin/pkmn-debug +1 -1
  36. package/src/lib/common/array.zig +77 -0
  37. package/src/lib/common/data.zig +4 -4
  38. package/src/lib/common/debug.zig +11 -6
  39. package/src/lib/common/js.zig +4 -2
  40. package/src/lib/common/optional.zig +14 -10
  41. package/src/lib/common/options.zig +4 -0
  42. package/src/lib/common/protocol.zig +389 -407
  43. package/src/lib/common/rational.zig +121 -27
  44. package/src/lib/common/rng.zig +7 -7
  45. package/src/lib/common/util.zig +50 -1
  46. package/src/lib/gen1/calc.zig +657 -133
  47. package/src/lib/gen1/chance.zig +376 -267
  48. package/src/lib/gen1/data/species.zig +1 -1
  49. package/src/lib/gen1/data.zig +17 -16
  50. package/src/lib/gen1/helpers.zig +95 -538
  51. package/src/lib/gen1/mechanics.zig +492 -389
  52. package/src/lib/gen2/calc.zig +34 -16
  53. package/src/lib/gen2/chance.zig +84 -496
  54. package/src/lib/gen2/data.zig +8 -7
  55. package/src/lib/gen2/generated.zig +4 -6
  56. package/src/lib/gen2/helpers.zig +41 -13
  57. package/src/lib/gen2/mechanics.zig +448 -377
  58. package/src/lib/{binding/node.zig → node.zig} +9 -12
  59. package/src/lib/pkmn.zig +32 -15
  60. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  61. package/src/tools/display/dom.ts +96 -0
  62. package/src/tools/display/ui.css +351 -0
  63. package/build/test/display.js +0 -400
  64. package/build/test/display.js.map +0 -1
  65. package/src/lib/gen1/README.md +0 -894
  66. package/src/lib/gen2/README.md +0 -329
  67. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -8,24 +8,29 @@ 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;
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
  }
@@ -98,15 +88,9 @@ pub const Actions = extern struct {
98
88
  };
99
89
 
100
90
  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 } };
91
+ const a: Actions = .{ .p1 = .{ .hit = .true, .confused = .false, .damages = 245 } };
92
+ const b: Actions = .{ .p1 = .{ .hit = .false, .confused = .true, .damages = 246 } };
103
93
  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
94
 
111
95
  try expect(a.eql(a));
112
96
  try expect(!a.eql(b));
@@ -119,17 +103,21 @@ test Actions {
119
103
  try expect(b.matches(a));
120
104
  try expect(!a.matches(c));
121
105
  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
106
  }
127
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
+
128
114
  /// Information about the RNG that was observed during a Generation II battle `update` for a
129
115
  /// single player.
130
116
  pub const Action = packed struct(u128) {
131
- /// If not 0, the roll to be returned Rolls.damage.
132
- damage: u8 = 0,
117
+ /// TODO
118
+ damages: Damages.T = 0,
119
+ // TODO
120
+ critical_hits: Criticals.T = 0,
133
121
 
134
122
  /// If not None, the Player to be returned by Rolls.speedTie.
135
123
  speed_tie: Optional(Player) = .None,
@@ -137,50 +125,58 @@ pub const Action = packed struct(u128) {
137
125
  quick_claw: Optional(bool) = .None,
138
126
  /// If not None, the value to return for Rolls.hit.
139
127
  hit: Optional(bool) = .None,
140
- /// If not None, the value to be returned by Rolls.criticalHit.
141
- critical_hit: Optional(bool) = .None,
142
-
143
128
  /// If not None, the value to return for Rolls.confused.
144
129
  confused: Optional(bool) = .None,
130
+
145
131
  /// If not None, the value to return for Rolls.attract.
146
132
  attract: Optional(bool) = .None,
147
133
  /// If not None, the value to return for Rolls.paralyzed.
148
134
  paralyzed: Optional(bool) = .None,
149
135
  /// If not None, the value to return for Rolls.defrost.
150
136
  defrost: Optional(bool) = .None,
151
-
152
137
  /// If not None, the value to be returned for Rolls.secondaryChance.
153
138
  secondary_chance: Optional(bool) = .None,
139
+
154
140
  /// If not None, the value to be returned for Rolls.item (Focus Band / King's Rock).
155
141
  item: Optional(bool) = .None,
156
142
  /// If not None, the value to be returned for Rolls.protect.
157
143
  protect: Optional(bool) = .None,
158
144
  /// If not None, the value to return for Rolls.triAttack.
159
145
  tri_attack: Optional(TriAttack) = .None,
146
+ /// If not 0, the value to return for Rolls.tripleKick.
147
+ triple_kick: u2 = 0,
160
148
 
161
149
  /// If not 0, (present - 1) * 40 should be returned as the base power for Rolls.present.
162
150
  present: u3 = 0,
163
151
  /// If not 0, magnitude + 3 should be returned as the number for Rolls.magnitude.
164
152
  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.
153
+ /// If not 0, the amount of PP to deduct for Rolls.spite.
169
154
  spite: u3 = 0,
170
- /// If not None, the value to return for Rolls.conversion2.
171
- conversion_2: Optional(Type) = .None,
172
-
173
155
  /// If not 0, the move slot (1-4) to return in Rolls.moveSlot. If present as an override,
174
156
  /// invalid values (eg. due to empty move slots or 0 PP) will be ignored.
175
- move_slot: u4 = 0,
157
+ move_slot: u3 = 0,
176
158
  /// If not 0, the party slot (1-6) to return in Rolls.forceSwitch. If present as an override,
177
159
  /// invalid values (eg. due to empty party slots or fainted members) will be ignored.
178
- force_switch: u4 = 0,
179
-
160
+ force_switch: u3 = 0,
180
161
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
181
- 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
+
182
176
  /// If not 0, the value to by one of the Rolls.*Duration rolls.
183
- duration: u4 = 0,
177
+ duration: u3 = 0,
178
+ /// If not None, the value to return for Rolls.conversion2.
179
+ conversion_2: Optional(Type) = .None,
184
180
 
185
181
  /// If not 0, psywave should be returned as the damage roll for Rolls.psywave.
186
182
  psywave: u8 = 0,
@@ -188,20 +184,8 @@ pub const Action = packed struct(u128) {
188
184
  /// If not None, the Move to return for Rolls.metronome.
189
185
  metronome: Move = .None,
190
186
 
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
187
  pub const Field = std.meta.FieldEnum(Action);
199
188
 
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
189
  pub fn format(a: Action, comptime f: []const u8, o: std.fmt.FormatOptions, w: anytype) !void {
206
190
  _ = .{ f, o };
207
191
  try fmt(a, w, false);
@@ -210,22 +194,10 @@ pub const Action = packed struct(u128) {
210
194
  pub fn fmt(self: Action, writer: anytype, shape: bool) !void {
211
195
  try writer.writeByte('(');
212
196
  var printed = false;
213
- inline for (@typeInfo(Action).Struct.fields) |field| {
197
+ inline for (@field(@typeInfo(Action), @tagName(Struct)).fields) |field| {
214
198
  const val = @field(self, field.name);
215
199
  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) {
200
+ Enum => if (val != .None) {
229
201
  if (printed) try writer.writeAll(", ");
230
202
  if (shape) {
231
203
  try writer.print("{s}:?", .{field.name});
@@ -239,7 +211,7 @@ pub const Action = packed struct(u128) {
239
211
  }
240
212
  printed = true;
241
213
  },
242
- .Int => if (val != 0) {
214
+ Int => if (val != 0) {
243
215
  if (printed) try writer.writeAll(", ");
244
216
  if (shape) {
245
217
  try writer.print("{s}:?", .{field.name});
@@ -255,36 +227,6 @@ pub const Action = packed struct(u128) {
255
227
  }
256
228
  };
257
229
 
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
230
  /// Tracks chance actions and their associated probability during a Generation II battle update when
289
231
  /// `options.chance` is enabled.
290
232
  pub fn Chance(comptime Rational: type) type {
@@ -297,37 +239,15 @@ pub fn Chance(comptime Rational: type) type {
297
239
  /// RNG events were observed during a battle `update`.
298
240
  actions: Actions = .{},
299
241
 
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
242
  /// Possible error returned by operations tracking chance probability.
309
243
  pub const Error = Rational.Error;
310
244
 
311
245
  /// Convenience helper to clear fields which typically should be cleared between updates.
312
246
  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
247
  if (!enabled) return;
320
248
 
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]);
249
+ self.probability.reset();
250
+ self.actions = .{};
331
251
  }
332
252
 
333
253
  pub fn speedTie(self: *Self, p1: bool) Error!void {
@@ -353,19 +273,21 @@ pub fn Chance(comptime Rational: type) type {
353
273
  self.actions.get(player).hit = if (ok) .true else .false;
354
274
  }
355
275
 
356
- 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 {
357
277
  if (!enabled) return;
358
278
 
359
279
  const n = if (crit) rate else @as(u8, @intCast(256 - @as(u9, rate)));
360
280
  try self.probability.update(n, 256);
361
- 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);
362
283
  }
363
284
 
364
- 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 {
365
286
  if (!enabled) return;
366
287
 
367
288
  try self.probability.update(1, 39);
368
- self.actions.get(player).damage = roll;
289
+ var a = self.actions.get(player);
290
+ a.damages = Damages.set(a.damages, i, roll);
369
291
  }
370
292
 
371
293
  pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
@@ -517,122 +439,10 @@ pub fn Chance(comptime Rational: type) type {
517
439
  self.actions.get(player).multi_hit = n;
518
440
  }
519
441
 
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 {
442
+ pub fn duration(self: *Self, player: Player, turns: u3) void {
590
443
  if (!enabled) return;
591
444
 
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
- }
445
+ self.actions.get(player).duration = if (options.key) 2 else turns;
636
446
  }
637
447
  };
638
448
  }
@@ -684,22 +494,22 @@ test "Chance.hit" {
684
494
  test "Chance.criticalHit" {
685
495
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
686
496
 
687
- try chance.criticalHit(.P1, true, 17);
688
- 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));
689
499
  try expectProbability(&chance.probability, 17, 256);
690
500
 
691
501
  chance.reset();
692
502
 
693
- try chance.criticalHit(.P2, false, 5);
503
+ try chance.criticalHit(.P2, 1, false, 5);
694
504
  try expectProbability(&chance.probability, 251, 256);
695
- try expectValue(Optional(bool).false, chance.actions.p2.critical_hit);
505
+ try expectValue(Optional(bool).false, Criticals.get(chance.actions.p2.critical_hits, 1));
696
506
  }
697
507
 
698
508
  test "Chance.damage" {
699
509
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
700
510
 
701
- try chance.damage(.P1, 217);
702
- try expectValue(@as(u8, 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));
703
513
  try expectProbability(&chance.probability, 1, 39);
704
514
  }
705
515
 
@@ -915,19 +725,19 @@ test "Chance.moveSlot" {
915
725
 
916
726
  try chance.moveSlot(.P2, 2, 2);
917
727
  try expectProbability(&chance.probability, 1, 2);
918
- try expectValue(@as(u4, 2), chance.actions.p2.move_slot);
728
+ try expectValue(@as(u3, 2), chance.actions.p2.move_slot);
919
729
 
920
730
  chance.reset();
921
731
 
922
732
  try chance.moveSlot(.P1, 1, 3);
923
733
  try expectProbability(&chance.probability, 1, 3);
924
- try expectValue(@as(u4, 1), chance.actions.p1.move_slot);
734
+ try expectValue(@as(u3, 1), chance.actions.p1.move_slot);
925
735
 
926
736
  chance.reset();
927
737
 
928
738
  try chance.moveSlot(.P1, 4, 1);
929
739
  try expectProbability(&chance.probability, 1, 1);
930
- try expectValue(@as(u4, 4), chance.actions.p1.move_slot);
740
+ try expectValue(@as(u3, 4), chance.actions.p1.move_slot);
931
741
  }
932
742
 
933
743
  test "Chance.forceSwitch" {
@@ -935,19 +745,19 @@ test "Chance.forceSwitch" {
935
745
 
936
746
  try chance.forceSwitch(.P2, 2, 2);
937
747
  try expectProbability(&chance.probability, 1, 2);
938
- try expectValue(@as(u4, 2), chance.actions.p2.force_switch);
748
+ try expectValue(@as(u3, 2), chance.actions.p2.force_switch);
939
749
 
940
750
  chance.reset();
941
751
 
942
752
  try chance.forceSwitch(.P1, 1, 3);
943
753
  try expectProbability(&chance.probability, 1, 3);
944
- try expectValue(@as(u4, 1), chance.actions.p1.force_switch);
754
+ try expectValue(@as(u3, 1), chance.actions.p1.force_switch);
945
755
 
946
756
  chance.reset();
947
757
 
948
758
  try chance.forceSwitch(.P1, 4, 1);
949
759
  try expectProbability(&chance.probability, 1, 1);
950
- try expectValue(@as(u4, 4), chance.actions.p1.force_switch);
760
+ try expectValue(@as(u3, 4), chance.actions.p1.force_switch);
951
761
  }
952
762
 
953
763
  test "Chance.multiHit" {
@@ -955,212 +765,20 @@ test "Chance.multiHit" {
955
765
 
956
766
  try chance.multiHit(.P1, 3);
957
767
  try expectProbability(&chance.probability, 3, 8);
958
- try expectValue(@as(u4, 3), chance.actions.p1.multi_hit);
768
+ try expectValue(@as(u3, 3), chance.actions.p1.multi_hit);
959
769
 
960
770
  chance.reset();
961
771
 
962
772
  try chance.multiHit(.P2, 5);
963
773
  try expectProbability(&chance.probability, 1, 8);
964
- try expectValue(@as(u4, 5), chance.actions.p2.multi_hit);
774
+ try expectValue(@as(u3, 5), chance.actions.p2.multi_hit);
965
775
  }
966
776
 
967
777
  test "Chance.duration" {
968
778
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
969
779
 
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
- }
780
+ chance.duration(.P1, 2);
781
+ try expectValue(@as(u3, 2), chance.actions.p1.duration);
1164
782
  }
1165
783
 
1166
784
  pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
@@ -1202,12 +820,12 @@ const Null = struct {
1202
820
  _ = .{ self, player, ok, accuracy };
1203
821
  }
1204
822
 
1205
- pub fn criticalHit(self: Null, player: Player, crit: bool, rate: u8) Error!void {
1206
- _ = .{ 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 };
1207
825
  }
1208
826
 
1209
- pub fn damage(self: Null, player: Player, roll: u8) Error!void {
1210
- _ = .{ self, player, roll };
827
+ pub fn damage(self: Null, player: Player, i: u3, roll: u8) Error!void {
828
+ _ = .{ self, player, i, roll };
1211
829
  }
1212
830
 
1213
831
  pub fn confused(self: Null, player: Player, ok: bool) Error!void {
@@ -1278,37 +896,7 @@ const Null = struct {
1278
896
  _ = .{ self, player, move, n };
1279
897
  }
1280
898
 
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 {
899
+ pub fn duration(self: Null, player: Player, turns: u3) void {
1312
900
  _ = .{ self, player, turns };
1313
901
  }
1314
902
  };