@pkmn/engine 0.1.0-dev.cbdbb719 → 0.1.0-dev.ce4c0b2a

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 (49) hide show
  1. package/build/pkg/addon/browser.d.ts +3 -0
  2. package/build/pkg/addon/browser.js +43 -0
  3. package/build/pkg/addon/browser.js.map +1 -0
  4. package/build/pkg/addon/node.d.ts +3 -0
  5. package/build/pkg/addon/node.js +96 -0
  6. package/build/pkg/addon/node.js.map +1 -0
  7. package/build/pkg/addon.d.ts +2 -0
  8. package/build/pkg/addon.js +29 -51
  9. package/build/pkg/addon.js.map +1 -1
  10. package/build/pkg/data.d.ts +23 -1
  11. package/build/pkg/data.js.map +1 -1
  12. package/build/pkg/gen1.js +1 -1
  13. package/build/pkg/gen1.js.map +1 -1
  14. package/build/pkg/index.d.ts +21 -8
  15. package/build/pkg/index.js +15 -10
  16. package/build/pkg/index.js.map +1 -1
  17. package/build/pkg/protocol.d.ts +20 -1
  18. package/build/pkg/protocol.js +105 -103
  19. package/build/pkg/protocol.js.map +1 -1
  20. package/build/tools/debug.js +34 -67
  21. package/build/tools/debug.js.map +1 -1
  22. package/build/tools/display/debug.jsx +128 -0
  23. package/build/tools/display/debug.jsx.map +1 -0
  24. package/build/tools/display/index.js +132 -0
  25. package/build/tools/display/index.js.map +1 -0
  26. package/build/tools/display/ui.jsx +349 -0
  27. package/build/tools/display/ui.jsx.map +1 -0
  28. package/build/tools/display/util.js +18 -0
  29. package/build/tools/display/util.js.map +1 -0
  30. package/build.zig +0 -2
  31. package/package.json +33 -23
  32. package/src/bin/install-pkmn-engine +4 -2
  33. package/src/bin/pkmn-debug +1 -1
  34. package/src/lib/common/debug.zig +9 -4
  35. package/src/lib/common/options.zig +2 -0
  36. package/src/lib/gen1/calc.zig +158 -14
  37. package/src/lib/gen1/chance.zig +3 -1
  38. package/src/lib/gen1/data.zig +2 -2
  39. package/src/lib/gen1/helpers.zig +0 -3
  40. package/src/lib/gen1/mechanics.zig +13 -6
  41. package/src/lib/gen2/calc.zig +1 -1
  42. package/src/lib/gen2/chance.zig +8 -13
  43. package/src/lib/gen2/helpers.zig +0 -4
  44. package/src/lib/gen2/mechanics.zig +3 -9
  45. package/src/tools/display/dom.ts +96 -0
  46. package/src/tools/display/ui.css +351 -0
  47. package/build/test/display.js +0 -405
  48. package/build/test/display.js.map +0 -1
  49. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -35,7 +35,11 @@ const isPointerTo = util.isPointerTo;
35
35
  const Actions = chance.Actions;
36
36
  const Action = chance.Action;
37
37
  const Chance = chance.Chance;
38
+ const Confusion = chance.Confusion;
39
+ const Duration = chance.Duration;
38
40
  const Durations = chance.Durations;
41
+ const Observation = chance.Observation;
42
+ const Sleeps = chance.Sleeps;
39
43
 
40
44
  const tty = true; // DEBUG
41
45
  const summary = false; // DEBUG
@@ -194,9 +198,11 @@ pub fn transitions(
194
198
  var frontier = std.ArrayList(Actions).init(allocator);
195
199
  defer frontier.deinit();
196
200
 
201
+ const durations = options.durations;
202
+
197
203
  var opts = pkmn.battle.options(
198
204
  protocol.NULL,
199
- Chance(Rational(u128)){ .probability = .{}, .durations = options.durations },
205
+ Chance(Rational(u128)){ .probability = .{}, .durations = durations },
200
206
  Calc{},
201
207
  );
202
208
 
@@ -220,6 +226,7 @@ pub fn transitions(
220
226
  assert(frontier.items.len == 1);
221
227
  while (i < frontier.items.len) : (i += 1) {
222
228
  const f = frontier.items[i];
229
+ const saved = stats.saved;
223
230
 
224
231
  try debug(writer, f, .{
225
232
  .shape = true,
@@ -235,10 +242,20 @@ pub fn transitions(
235
242
  };
236
243
 
237
244
  for (Rolls.speedTie(f.p1)) |tie| { a.p1.speed_tie = tie; a.p2.speed_tie = tie;
238
- for (Rolls.confused(f.p1)) |p1_cfzd| { a.p1.confused = p1_cfzd;
239
- for (Rolls.confused(f.p2)) |p2_cfzd| { a.p2.confused = p2_cfzd;
245
+ for (Rolls.sleep(f.p1, durations.p1)) |p1_slp| { a.p1.sleep = p1_slp;
246
+ for (Rolls.sleep(f.p2, durations.p2)) |p2_slp| { a.p2.sleep = p2_slp;
247
+ for (Rolls.disable(f.p1, durations.p1, p1_slp)) |p1_dis| { a.p1.disable = p1_dis;
248
+ for (Rolls.disable(f.p2, durations.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
249
+ for (Rolls.confusion(f.p1, durations.p1, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
250
+ for (Rolls.confusion(f.p2, durations.p2, p1_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
251
+ for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
252
+ for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
240
253
  for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
241
254
  for (Rolls.paralyzed(f.p2, p2_cfzd)) |p2_par| { a.p2.paralyzed = p2_par;
255
+ for (Rolls.attacking(f.p1, durations.p1, p1_par)) |p1_atk| { a.p1.attacking = p1_atk;
256
+ for (Rolls.attacking(f.p2, durations.p2, p2_par)) |p2_atk| { a.p2.attacking = p2_atk;
257
+ for (Rolls.binding(f.p1, durations.p1, p1_par)) |p1_bind| { a.p1.binding = p1_bind;
258
+ for (Rolls.binding(f.p2, durations.p2, p2_par)) |p2_bind| { a.p2.binding = p2_bind;
242
259
  for (Rolls.hit(f.p1, p1_par)) |p1_hit| { a.p1.hit = p1_hit;
243
260
  for (Rolls.hit(f.p2, p2_par)) |p2_hit| { a.p2.hit = p2_hit;
244
261
  for (Rolls.psywave(f.p1, p1, p1_hit)) |p1_psywave| { a.p1.psywave = p1_psywave;
@@ -266,7 +283,7 @@ pub fn transitions(
266
283
 
267
284
  opts.calc.overrides = a;
268
285
  opts.calc.summaries = .{};
269
- opts.chance = .{ .probability = .{}, .durations = options.durations };
286
+ opts.chance = .{ .probability = .{}, .durations = durations };
270
287
  const q = &opts.chance.probability;
271
288
 
272
289
  b = battle;
@@ -283,7 +300,7 @@ pub fn transitions(
283
300
  try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
284
301
 
285
302
  if (opts.chance.actions.matches(f)) {
286
- if (!opts.chance.actions.eql(a)) {
303
+ if (!map(opts.chance.actions).eql(a)) {
287
304
  if (!summary) {
288
305
  try debug(writer, opts.chance.actions, .{
289
306
  .p1_max = p1_max,
@@ -358,9 +375,11 @@ pub fn transitions(
358
375
  p2_dmg.min = p2_max;
359
376
  }
360
377
 
361
- }}}}}}}}}}}}}}}}}}
378
+ }}}}}}}}}}}}}}}}}}}}}}}}}}}}
362
379
 
363
- if (@TypeOf(writer) != @TypeOf(std.io.null_writer)) {
380
+ assert(stats.saved > saved);
381
+ const old = @hasDecl(std.fmt, "formatFloatDecimal");
382
+ if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
364
383
  p.reduce();
365
384
  try writer.print(
366
385
  " = {s} ({d:.2}%)\n\n",
@@ -496,6 +515,14 @@ fn unfix(actual: anytype) data.Battle(data.PRNG) {
496
515
  };
497
516
  }
498
517
 
518
+ fn map(actions: Actions) Actions {
519
+ if (!pkmn.options.overwrite) return actions;
520
+ var a = actions;
521
+ if (a.p1.confusion == .overwritten) a.p1.confusion = .continuing;
522
+ if (a.p2.confusion == .overwritten) a.p2.confusion = .continuing;
523
+ return a;
524
+ }
525
+
499
526
  fn matches(actions: Actions, i: usize, frontier: []Actions) bool {
500
527
  for (frontier, 0..) |f, j| {
501
528
  // TODO: is skipping this redundant check worth it?
@@ -577,8 +604,6 @@ fn format(writer: anytype, actions: Actions, p1: u9, p2: u9) !void {
577
604
  try writer.writeAll(output[0..len]);
578
605
  }
579
606
 
580
- const NONE = @intFromEnum(Optional(bool).None);
581
-
582
607
  /// Helper functions that efficiently return valid ranges for various RNG events based on the
583
608
  /// state of an `Action` and other events to be used to construct a "transitions" function.
584
609
  pub const Rolls = struct {
@@ -590,6 +615,101 @@ pub const Rolls = struct {
590
615
  return if (action.speed_tie == .None) &PLAYER_NONE else &PLAYERS;
591
616
  }
592
617
 
618
+ const OBS_NONE = [_]Optional(Observation){.None};
619
+ const OBS_STARTED = [_]Optional(Observation){.started};
620
+ const OBS_CONTINUING = [_]Optional(Observation){.continuing};
621
+ const OBS_ENDED = [_]Optional(Observation){.ended};
622
+ const OBS = [_]Optional(Observation){ .continuing, .ended };
623
+
624
+ /// Returns a slice with a range of values for sleep given the `action` state and observed
625
+ /// `durations`.
626
+ pub fn sleep(action: Action, duration: Duration) []const Optional(Observation) {
627
+ return switch (action.sleep) {
628
+ .None => &OBS_NONE,
629
+ .started => &OBS_STARTED,
630
+ else => if (Sleeps.get(duration.sleeps, 0) >= 7) &OBS_ENDED else &OBS,
631
+ };
632
+ }
633
+
634
+ /// Returns a slice with a range of values for disable given the `action`, observed `durations`,
635
+ /// and the state of the `parent` (observation of the player's Pokémon sleep status).
636
+ pub fn disable(
637
+ action: Action,
638
+ duration: Duration,
639
+ parent: Optional(Observation),
640
+ ) []const Optional(Observation) {
641
+ // if (parent == .started or parent == .continuing) return &OBS_NONE;
642
+ _ = parent; // FIXME
643
+ return switch (action.disable) {
644
+ .None => &OBS_NONE,
645
+ .started => &OBS_STARTED,
646
+ else => if (duration.disable >= 8) &OBS_ENDED else &OBS,
647
+ };
648
+ }
649
+
650
+ const CFZ_NONE = [_]Optional(Confusion){.None};
651
+ const CFZ_STARTED = [_]Optional(Confusion){.started};
652
+ const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
653
+ const CFZ_ENDED = [_]Optional(Confusion){.ended};
654
+ const CFZ = [_]Optional(Confusion){ .continuing, .ended };
655
+
656
+ /// Returns a slice with a range of values for confusion given the `action`, observed `durations`,
657
+ /// and the state of the `parent` (observation of the player's Pokémon sleep status).
658
+ pub fn confusion(
659
+ action: Action,
660
+ duration: Duration,
661
+ parent: Optional(Observation),
662
+ ) []const Optional(Confusion) {
663
+ // if (parent == .started or parent == .continuing) return &CFZ_NONE;
664
+ _ = parent; // FIXME
665
+ return switch (action.confusion) {
666
+ .None => &CFZ_NONE,
667
+ .started => &CFZ_STARTED,
668
+ else => if (duration.confusion >= 5)
669
+ &CFZ_ENDED
670
+ else if (duration.confusion < 2)
671
+ &CFZ_CONTINUING
672
+ else
673
+ &CFZ,
674
+ };
675
+ }
676
+
677
+ /// Returns a slice with a range of values for attacking given the `action`, observed
678
+ /// `durations`, and the state of the `parent` (whether the player's Pokémon was fully
679
+ /// paralyzed).
680
+ pub fn attacking(
681
+ action: Action,
682
+ duration: Duration,
683
+ parent: Optional(bool),
684
+ ) []const Optional(Observation) {
685
+ if (parent == .true) return &OBS_NONE;
686
+ return switch (action.attacking) {
687
+ .None => &OBS_NONE,
688
+ .started => &OBS_STARTED,
689
+ else => if (duration.attacking >= 3)
690
+ &OBS_ENDED
691
+ else if (duration.attacking < 2)
692
+ &OBS_CONTINUING
693
+ else
694
+ &OBS,
695
+ };
696
+ }
697
+
698
+ /// Returns a slice with a range of values for binding given the `action`, observed `durations`,
699
+ /// and the state of the `parent` (whether the player's Pokémon was fully paralyzed).
700
+ pub fn binding(
701
+ action: Action,
702
+ duration: Duration,
703
+ parent: Optional(bool),
704
+ ) []const Optional(Observation) {
705
+ if (parent == .true) return &OBS_NONE;
706
+ return switch (action.binding) {
707
+ .None => &OBS_NONE,
708
+ .started => &OBS_STARTED,
709
+ else => if (duration.binding >= 4) &OBS_ENDED else &OBS,
710
+ };
711
+ }
712
+
593
713
  const BOOL_NONE = [_]Optional(bool){.None};
594
714
  const BOOLS = [_]Optional(bool){ .false, .true };
595
715
 
@@ -638,9 +758,12 @@ pub const Rolls = struct {
638
758
  return @min(255, roll + ((254 - ((@as(u32, dmg.base) * roll) % 255)) / dmg.base));
639
759
  }
640
760
 
641
- /// Returns a slice with the correct range of values for confused.
642
- pub fn confused(action: Action) []const Optional(bool) {
643
- return if (action.confused == .None) &BOOL_NONE else &BOOLS;
761
+ /// Returns a slice with the correct range of values for confused given the `action` state and
762
+ /// the state of the `parent` (observation of the player's Pokémon confusion status).
763
+ pub fn confused(action: Action, parent: Optional(Confusion)) []const Optional(bool) {
764
+ const done = false; // parent == .None or parent == .ended;
765
+ _ = parent; // FIXME
766
+ return if (done or action.confused == .None) &BOOL_NONE else &BOOLS;
644
767
  }
645
768
 
646
769
  /// Returns a slice with the correct range of values for paralysis given the `action` state
@@ -707,6 +830,26 @@ test "Rolls.speedTie" {
707
830
  try expectEqualSlices(Optional(Player), &.{.None}, Rolls.speedTie(actions.p2));
708
831
  }
709
832
 
833
+ test "Rolls.sleep" {
834
+ return error.SkipZigTest; // TODO
835
+ }
836
+
837
+ test "Rolls.disable" {
838
+ return error.SkipZigTest; // TODO
839
+ }
840
+
841
+ test "Rolls.confusion" {
842
+ return error.SkipZigTest; // TODO
843
+ }
844
+
845
+ test "Rolls.atacking" {
846
+ return error.SkipZigTest; // TODO
847
+ }
848
+
849
+ test "Rolls.binding" {
850
+ return error.SkipZigTest; // TODO
851
+ }
852
+
710
853
  test "Rolls.damage" {
711
854
  const actions: Actions = .{ .p2 = .{ .damage = 221 } };
712
855
  try expectEqual(Rolls.Range{ .min = 0, .max = 1 }, Rolls.damage(actions.p1, .None));
@@ -756,8 +899,9 @@ test "Rolls.criticalHit" {
756
899
 
757
900
  test "Rolls.confused" {
758
901
  const actions: Actions = .{ .p2 = .{ .confused = .true } };
759
- try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1));
760
- try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2));
902
+ try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p1, .None));
903
+ try expectEqualSlices(Optional(bool), &.{ .false, .true }, Rolls.confused(actions.p2, .continuing));
904
+ // FIXME try expectEqualSlices(Optional(bool), &.{.None}, Rolls.confused(actions.p2, .ended));
761
905
  }
762
906
 
763
907
  test "Rolls.paralyzed" {
@@ -251,7 +251,7 @@ pub const Durations = struct {
251
251
  }
252
252
  };
253
253
 
254
- const Sleeps = Array(6, u3);
254
+ pub const Sleeps = Array(6, u3);
255
255
 
256
256
  /// TODO
257
257
  pub const Duration = packed struct(u32) {
@@ -656,6 +656,7 @@ pub fn Chance(comptime Rational: type) type {
656
656
  var d = self.durations.get(player);
657
657
  const n = d.attacking;
658
658
  if (obs == .ended) {
659
+ // "3-4 turns" includes the turn for the initial attack *plus* the forced attacks
659
660
  assert(n >= 2 and n <= 3);
660
661
  if (n != 3) try self.probability.update(1, 4 - @as(u4, n));
661
662
  d.attacking = 0;
@@ -682,6 +683,7 @@ pub fn Chance(comptime Rational: type) type {
682
683
  const q: u4 = if (n < 3) 8 - ((n - 1) * p) else 2;
683
684
 
684
685
  if (obs == .ended) {
686
+ // "2-5 turns" includes the turn for the initial attack *plus* the forced attacks
685
687
  assert(n >= 1 and n <= 4);
686
688
  if (n != 4) try self.probability.update(p, q);
687
689
  d.binding = 0;
@@ -256,7 +256,7 @@ pub const Status = enum(u8) {
256
256
 
257
257
  pub const TOX = 0b10001000;
258
258
 
259
- const SLP = 0b111;
259
+ const SLEEP = 0b111;
260
260
 
261
261
  /// Whether or not the status `num` is the same as `status`.
262
262
  pub fn is(num: u8, status: Status) bool {
@@ -284,7 +284,7 @@ pub const Status = enum(u8) {
284
284
 
285
285
  /// Returns the duration of a sleep status.
286
286
  pub fn duration(num: u8) u3 {
287
- return @intCast(num & SLP);
287
+ return @intCast(num & SLEEP);
288
288
  }
289
289
 
290
290
  /// Returns whether `num` reflects any status.
@@ -12,10 +12,8 @@ const data = @import("data.zig");
12
12
  const assert = std.debug.assert;
13
13
 
14
14
  const Choice = common.Choice;
15
- const Player = common.Player;
16
15
 
17
16
  const showdown = options.showdown;
18
- const chance = options.chance;
19
17
 
20
18
  const PSRNG = rng.PSRNG;
21
19
 
@@ -24,7 +22,6 @@ const Move = data.Move;
24
22
  const MoveSlot = data.MoveSlot;
25
23
  const Species = data.Species;
26
24
  const Stats = data.Stats;
27
- const Status = data.Status;
28
25
 
29
26
  const endian = builtin.cpu.arch.endian();
30
27
 
@@ -4,7 +4,6 @@ const pkmn = @import("../pkmn.zig");
4
4
 
5
5
  const common = @import("../common/data.zig");
6
6
  const DEBUG = @import("../common/debug.zig").print;
7
- const optional = @import("../common/optional.zig");
8
7
  const protocol = @import("../common/protocol.zig");
9
8
  const rng = @import("../common/rng.zig");
10
9
 
@@ -29,11 +28,9 @@ const Heal = protocol.Heal;
29
28
 
30
29
  const Gen12 = rng.Gen12;
31
30
 
32
- const ActivePokemon = data.ActivePokemon;
33
31
  const Effectiveness = data.Effectiveness;
34
32
  const Move = data.Move;
35
33
  const MoveSlot = data.MoveSlot;
36
- const Pokemon = data.Pokemon;
37
34
  const Side = data.Side;
38
35
  const Species = data.Species;
39
36
  const Stats = data.Stats;
@@ -344,6 +341,7 @@ fn doTurn(
344
341
  player_choice,
345
342
  player_rewrap,
346
343
  player_skip,
344
+ true,
347
345
  &residual,
348
346
  options,
349
347
  )) |r| return r;
@@ -366,6 +364,7 @@ fn doTurn(
366
364
  foe_choice,
367
365
  foe_rewrap,
368
366
  foe_skip,
367
+ false,
369
368
  &residual,
370
369
  options,
371
370
  )) |r| return r;
@@ -393,6 +392,7 @@ fn executeMove(
393
392
  choice: Choice,
394
393
  rewrap: bool,
395
394
  skip: bool,
395
+ first: bool,
396
396
  residual: *bool,
397
397
  options: anytype,
398
398
  ) !?Result {
@@ -452,7 +452,7 @@ fn executeMove(
452
452
 
453
453
  var skip_can = false;
454
454
  var skip_pp = false;
455
- switch (try beforeMove(battle, player, skip, residual, options)) {
455
+ switch (try beforeMove(battle, player, skip, first, residual, options)) {
456
456
  .done => return null,
457
457
  .skip_can => skip_can = true,
458
458
  .skip_pp => skip_pp = true,
@@ -473,6 +473,7 @@ fn beforeMove(
473
473
  battle: anytype,
474
474
  player: Player,
475
475
  skip: bool,
476
+ first: bool,
476
477
  residual: *bool,
477
478
  options: anytype,
478
479
  ) !BeforeMove {
@@ -564,7 +565,13 @@ fn beforeMove(
564
565
  if (volatiles.Confusion) {
565
566
  assert(volatiles.confusion > 0);
566
567
  if (options.calc.overridden(player, .confusion)) |obs| switch (obs) {
567
- .started => volatiles.confusion -= 1,
568
+ .started => {
569
+ if (!first or (options.calc.overridden(player, .confused) orelse .None) != .None) {
570
+ volatiles.confusion -= 1;
571
+ } else {
572
+ volatiles.confusion = 0;
573
+ }
574
+ },
568
575
  .continuing => if (volatiles.confusion > 1) {
569
576
  volatiles.confusion -= 1;
570
577
  },
@@ -696,7 +703,7 @@ fn beforeMove(
696
703
  try options.chance.attacking(player, if (volatiles.attacks == 0) .ended else .continuing);
697
704
 
698
705
  if (volatiles.attacks == 0) {
699
- const overwritten = volatiles.Confusion;
706
+ const overwritten = pkmn.options.overwrite and volatiles.Confusion;
700
707
 
701
708
  volatiles.Thrashing = false;
702
709
  volatiles.Confusion = true;
@@ -154,7 +154,7 @@ const Null = struct {
154
154
 
155
155
  fn ReturnType(comptime field: Action.Field) type {
156
156
  return ?(switch (field) {
157
- .damages => u6,
157
+ .damages => u8,
158
158
  .critical_hits => Optional(bool),
159
159
  else => std.meta.FieldType(Action, field),
160
160
  });
@@ -8,7 +8,6 @@ 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
 
14
13
  const Array = @import("../common/array.zig").Array;
@@ -20,8 +19,6 @@ const data = @import("data.zig");
20
19
  const enabled = options.chance;
21
20
  const showdown = options.showdown;
22
21
 
23
- const Gen12 = rng.Gen12;
24
-
25
22
  const PointerType = util.PointerType;
26
23
  const isPointerTo = util.isPointerTo;
27
24
 
@@ -111,7 +108,7 @@ test Actions {
111
108
  /// Observation made about a duration - whether the duration has started, been continued, or ended.
112
109
  pub const Observation = enum { started, continuing, ended };
113
110
 
114
- pub const Damages = Array(5, u6);
111
+ pub const Damages = Array(5, u8);
115
112
  pub const Criticals = Array(5, Optional(bool));
116
113
 
117
114
  /// Information about the RNG that was observed during a Generation II battle `update` for a
@@ -164,10 +161,6 @@ pub const Action = packed struct(u128) {
164
161
  /// If not 0, the value (2-5) to return for Rolls.distribution for multi hit.
165
162
  multi_hit: u3 = 0,
166
163
 
167
- _: u9 = 0,
168
-
169
- /// If not 0, the value to by one of the Rolls.*Duration rolls.
170
- duration: u4 = 0,
171
164
  sleep: Optional(Observation) = .None,
172
165
  /// TODO
173
166
  confusion: Optional(Observation) = .None,
@@ -180,6 +173,8 @@ pub const Action = packed struct(u128) {
180
173
  /// TODO
181
174
  encore: Optional(Observation) = .None,
182
175
 
176
+ /// If not 0, the value to by one of the Rolls.*Duration rolls.
177
+ duration: u3 = 0,
183
178
  /// If not None, the value to return for Rolls.conversion2.
184
179
  conversion_2: Optional(Type) = .None,
185
180
 
@@ -292,7 +287,7 @@ pub fn Chance(comptime Rational: type) type {
292
287
 
293
288
  try self.probability.update(1, 39);
294
289
  var a = self.actions.get(player);
295
- a.damages = Damages.set(a.damages, i, @intCast(roll - 216));
290
+ a.damages = Damages.set(a.damages, i, roll);
296
291
  }
297
292
 
298
293
  pub fn confused(self: *Self, player: Player, cfz: bool) Error!void {
@@ -444,7 +439,7 @@ pub fn Chance(comptime Rational: type) type {
444
439
  self.actions.get(player).multi_hit = n;
445
440
  }
446
441
 
447
- pub fn duration(self: *Self, player: Player, turns: u4) void {
442
+ pub fn duration(self: *Self, player: Player, turns: u3) void {
448
443
  if (!enabled) return;
449
444
 
450
445
  self.actions.get(player).duration = if (options.key) 2 else turns;
@@ -514,7 +509,7 @@ test "Chance.damage" {
514
509
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
515
510
 
516
511
  try chance.damage(.P1, 0, 219);
517
- try expectValue(@as(u6, 3), Damages.get(chance.actions.p1.damages, 0));
512
+ try expectValue(@as(u8, 219), Damages.get(chance.actions.p1.damages, 0));
518
513
  try expectProbability(&chance.probability, 1, 39);
519
514
  }
520
515
 
@@ -783,7 +778,7 @@ test "Chance.duration" {
783
778
  var chance: Chance(rational.Rational(u64)) = .{ .probability = .{} };
784
779
 
785
780
  chance.duration(.P1, 2);
786
- try expectValue(@as(u4, 2), chance.actions.p1.duration);
781
+ try expectValue(@as(u3, 2), chance.actions.p1.duration);
787
782
  }
788
783
 
789
784
  pub fn expectProbability(r: anytype, p: u64, q: u64) !void {
@@ -901,7 +896,7 @@ const Null = struct {
901
896
  _ = .{ self, player, move, n };
902
897
  }
903
898
 
904
- pub fn duration(self: Null, player: Player, turns: u4) void {
899
+ pub fn duration(self: Null, player: Player, turns: u3) void {
905
900
  _ = .{ self, player, turns };
906
901
  }
907
902
  };
@@ -2,7 +2,6 @@ const std = @import("std");
2
2
 
3
3
  const common = @import("../common/data.zig");
4
4
  const options = @import("../common/options.zig");
5
- const protocol = @import("../common/protocol.zig");
6
5
  const rng = @import("../common/rng.zig");
7
6
 
8
7
  const gen1 = @import("../gen1/data.zig");
@@ -11,8 +10,6 @@ const data = @import("data.zig");
11
10
 
12
11
  const assert = std.debug.assert;
13
12
 
14
- const expectEqual = std.testing.expectEqual;
15
-
16
13
  const Choice = common.Choice;
17
14
 
18
15
  const showdown = options.showdown;
@@ -25,7 +22,6 @@ const Move = data.Move;
25
22
  const MoveSlot = data.MoveSlot;
26
23
  const Species = data.Species;
27
24
  const Stats = data.Stats;
28
- const Status = data.Status;
29
25
 
30
26
  const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
31
27
 
@@ -6,7 +6,6 @@ const common = @import("../common/data.zig");
6
6
  const DEBUG = @import("../common/debug.zig").print;
7
7
  const protocol = @import("../common/protocol.zig");
8
8
  const rng = @import("../common/rng.zig");
9
- const util = @import("../common/util.zig");
10
9
 
11
10
  const data = @import("data.zig");
12
11
  const generated = @import("generated.zig");
@@ -34,17 +33,12 @@ const Start = protocol.Start;
34
33
 
35
34
  const Gen12 = rng.Gen12;
36
35
 
37
- const PointerType = util.PointerType;
38
-
39
36
  const ActivePokemon = data.ActivePokemon;
40
37
  const Boosts = data.Boosts;
41
38
  const Effectiveness = data.Effectiveness;
42
39
  const Item = data.Item;
43
40
  const Move = data.Move;
44
- const MoveSlot = data.MoveSlot;
45
- const Pokemon = data.Pokemon;
46
41
  const Side = data.Side;
47
- const Species = data.Species;
48
42
  const Stats = data.Stats;
49
43
  const Status = data.Status;
50
44
  const TriAttack = data.TriAttack;
@@ -2901,7 +2895,7 @@ pub const Rolls = struct {
2901
2895
  fn damage(battle: anytype, player: Player, i: u3, options: anytype) !u8 {
2902
2896
  _ = .{ player, options };
2903
2897
  const roll = if (options.calc.overridden(player, i, .damages)) |val|
2904
- @as(u8, val) + 216
2898
+ val
2905
2899
  else roll: {
2906
2900
  if (showdown) break :roll battle.rng.range(u8, 217, 256);
2907
2901
  while (true) {
@@ -3357,11 +3351,11 @@ pub const Rolls = struct {
3357
3351
  }
3358
3352
 
3359
3353
  fn disableDuration(battle: anytype, player: Player, options: anytype) u4 {
3360
- const duration: u4 = if (options.calc.overridden(player, 0, .duration)) |val|
3354
+ const duration: u3 = if (options.calc.overridden(player, 0, .duration)) |val|
3361
3355
  @intCast(val)
3362
3356
  else if (showdown)
3363
3357
  // Pokémon Showdown incorrectly inherits Generation III's Disable duration
3364
- battle.rng.range(u4, 2, 6)
3358
+ battle.rng.range(u3, 2, 6)
3365
3359
  else duration: {
3366
3360
  while (true) {
3367
3361
  const r = battle.rng.next() & 7;
@@ -0,0 +1,96 @@
1
+ /** @license MIT modified from Vadim Demedes's https://github.com/vadimdemedes/dom-chef */
2
+ type Attributes = JSX.IntrinsicElements['div'];
3
+ type DocumentFragmentConstructor = typeof DocumentFragment;
4
+ type ElementFunction = (props?: any) => HTMLElement;
5
+
6
+ declare global {
7
+ namespace JSX {
8
+ interface Element extends HTMLElement, DocumentFragment {
9
+ addEventListener: HTMLElement['addEventListener'];
10
+ removeEventListener: HTMLElement['removeEventListener'];
11
+ className: HTMLElement['className'];
12
+ }
13
+ }
14
+ }
15
+
16
+ interface JSXElementClassDocumentFragment extends DocumentFragment, JSX.ElementClass { }
17
+ interface Fragment {
18
+ prototype: JSXElementClassDocumentFragment;
19
+ new(): JSXElementClassDocumentFragment;
20
+ }
21
+
22
+ // https://github.com/preactjs/preact/blob/1bbd687c/src/constants.js#L3
23
+ const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
24
+
25
+ const isFragment = (type: DocumentFragmentConstructor | ElementFunction):
26
+ type is DocumentFragmentConstructor => type === DocumentFragment;
27
+
28
+ const setCSSProps = (element: HTMLElement, style: CSSStyleDeclaration) => {
29
+ for (const [name, value] of Object.entries(style)) {
30
+ if (name.startsWith('-')) {
31
+ element.style.setProperty(name, value);
32
+ } else if (typeof value === 'number' && !IS_NON_DIMENSIONAL.test(name)) {
33
+ element.style[name as any] = `${value as string}px`;
34
+ } else {
35
+ element.style[name as any] = value;
36
+ }
37
+ }
38
+ };
39
+
40
+ const setAttribute = (element: HTMLElement, name: string, value: string) => {
41
+ if (value !== undefined && value !== null) element.setAttribute(name, value);
42
+ };
43
+
44
+ const addChildren = (parent: Element | DocumentFragment, children: Node[]) => {
45
+ for (const child of children) {
46
+ if (child instanceof Node) {
47
+ parent.appendChild(child);
48
+ } else if (Array.isArray(child)) {
49
+ addChildren(parent, child);
50
+ } else if (typeof child !== 'boolean' && typeof child !== 'undefined' && child !== null) {
51
+ parent.appendChild(document.createTextNode(child));
52
+ }
53
+ }
54
+ };
55
+
56
+ // https://github.com/facebook/react/blob/3f899089/packages/react-dom/src/shared/DOMProperty.js#L288-L322
57
+ const FALSIFIABLE_ATTRIBUTES = ['contentEditable', 'draggable', 'spellCheck', 'value'];
58
+
59
+ export const h = (
60
+ type: DocumentFragmentConstructor | ElementFunction | string,
61
+ attributes?: Attributes,
62
+ ...children: Node[]
63
+ ) => {
64
+ if (typeof type !== 'string') {
65
+ const element = isFragment(type) ? document.createDocumentFragment() : type(attributes);
66
+ addChildren(element, children);
67
+ return element;
68
+ }
69
+
70
+ const element = document.createElement(type);
71
+ addChildren(element, children);
72
+ if (!attributes) return element;
73
+
74
+ for (let [name, value] of Object.entries(attributes)) {
75
+ if (name === 'htmlFor') name = 'for';
76
+
77
+ if (name === 'class' || name === 'className') {
78
+ const existingClassname = element.getAttribute('class') ?? '';
79
+ setAttribute(element, 'class', (existingClassname + ' ' + String(value)).trim());
80
+ } else if (name === 'style') {
81
+ setCSSProps(element, value);
82
+ } else if (name.startsWith('on')) {
83
+ const eventName = name.slice(2).toLowerCase().replace(/^-/, '');
84
+ element.addEventListener(eventName, value);
85
+ } else if (name === 'dangerouslySetInnerHTML' && '__html' in value) {
86
+ element.innerHTML = value.__html;
87
+ } else if (name !== 'key' && (FALSIFIABLE_ATTRIBUTES.includes(name) || value !== false)) {
88
+ setAttribute(element, name, value === true ? '' : value);
89
+ }
90
+ }
91
+
92
+ return element;
93
+ };
94
+
95
+ export const Fragment =
96
+ (typeof DocumentFragment === 'function' ? DocumentFragment : () => {}) as Fragment;