@pkmn/engine 0.1.0-dev.a23f64f3 → 0.1.0-dev.a27b3ff3

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 (48) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +46 -1
  3. package/build/pkg/addon/browser.js +4 -1
  4. package/build/pkg/addon/browser.js.map +1 -1
  5. package/build/pkg/addon/node.js +20 -7
  6. package/build/pkg/addon/node.js.map +1 -1
  7. package/build/pkg/addon/wasm.js +23 -14
  8. package/build/pkg/addon/wasm.js.map +1 -1
  9. package/build/pkg/addon.d.ts +1 -1
  10. package/build/pkg/gen1.d.ts +2 -1
  11. package/build/pkg/gen1.js +22 -9
  12. package/build/pkg/gen1.js.map +1 -1
  13. package/build/pkg/index.d.ts +2 -0
  14. package/build/pkg/index.js +17 -7
  15. package/build/pkg/index.js.map +1 -1
  16. package/build/tools/debug.js +17 -7
  17. package/build/tools/debug.js.map +1 -1
  18. package/build/tools/display/debug.jsx +18 -7
  19. package/build/tools/display/debug.jsx.map +1 -1
  20. package/build/tools/display/index.js +18 -7
  21. package/build/tools/display/index.js.map +1 -1
  22. package/build/tools/display/ui.jsx +17 -7
  23. package/build/tools/display/ui.jsx.map +1 -1
  24. package/build/tools/display/util.js +16 -1
  25. package/build/tools/display/util.js.map +1 -1
  26. package/build.zig +95 -26
  27. package/package.json +24 -20
  28. package/src/bin/install-pkmn-engine +1 -0
  29. package/src/lib/bindings/c.zig +255 -0
  30. package/src/lib/bindings/node.zig +117 -0
  31. package/src/lib/bindings/wasm.zig +59 -0
  32. package/src/lib/common/array.zig +1 -1
  33. package/src/lib/common/js.zig +1 -5
  34. package/src/lib/common/napi.zig +4 -0
  35. package/src/lib/common/optional.zig +1 -1
  36. package/src/lib/common/rational.zig +281 -270
  37. package/src/lib/common/util.zig +17 -2
  38. package/src/lib/gen1/calc.zig +31 -22
  39. package/src/lib/gen1/data/types.zig +1 -1
  40. package/src/lib/gen1/mechanics.zig +13 -11
  41. package/src/lib/gen2/calc.zig +1 -1
  42. package/src/lib/gen2/data/types.zig +6 -3
  43. package/src/lib/gen2/data.zig +3 -2
  44. package/src/lib/gen2/mechanics.zig +2 -2
  45. package/src/lib/node.zig +3 -118
  46. package/src/lib/pkmn.zig +3 -2
  47. package/src/lib/wasm.zig +12 -43
  48. package/src/tools/display/dom.ts +5 -8
@@ -99,7 +99,7 @@ pub const Calc = struct {
99
99
  self: Calc,
100
100
  player: Player,
101
101
  comptime field: Action.Field,
102
- ) ?std.meta.FieldType(Action, field) {
102
+ ) ?util.FieldType(Action, field) {
103
103
  if (!enabled) return null;
104
104
 
105
105
  const val = @field(self.overrides.get(player), @tagName(field));
@@ -146,7 +146,7 @@ const Null = struct {
146
146
  self: Null,
147
147
  player: Player,
148
148
  comptime field: Action.Field,
149
- ) ?std.meta.FieldType(Action, field) {
149
+ ) ?util.FieldType(Action, field) {
150
150
  _ = .{ self, player };
151
151
  return null;
152
152
  }
@@ -182,7 +182,7 @@ pub const Options = struct {
182
182
  metronome: bool = false,
183
183
  };
184
184
 
185
- pub const MAX_FRONTIER = 81;
185
+ pub const MAX_FRONTIER = 83;
186
186
 
187
187
  pub fn transitions(
188
188
  battle: anytype,
@@ -216,7 +216,7 @@ pub fn transitions(
216
216
  const p1 = b.side(.P1);
217
217
  const p2 = b.side(.P2);
218
218
 
219
- var p: Rational(u128) = .{ .p = 0, .q = 1 };
219
+ var p: Rational(u256) = .{ .p = 0, .q = 1 };
220
220
  try frontier.append(opts.chance.actions);
221
221
 
222
222
  // zig fmt: off
@@ -231,6 +231,8 @@ pub fn transitions(
231
231
  const f = frontier.items[i];
232
232
  const saved = stats.saved;
233
233
 
234
+ var r: Rational(u256) = .{ .p = 0, .q = 1 };
235
+
234
236
  try debug(writer, f, .{
235
237
  .shape = true,
236
238
  .color = i,
@@ -251,8 +253,8 @@ pub fn transitions(
251
253
  for (Rolls.disable(f.p2, d.p2, p2_slp)) |p2_dis| { a.p2.disable = p2_dis;
252
254
  for (Rolls.attacking(f.p1, d.p1, p1_slp)) |p1_atk| { a.p1.attacking = p1_atk;
253
255
  for (Rolls.attacking(f.p2, d.p2, p2_slp)) |p2_atk| { a.p2.attacking = p2_atk;
254
- for (Rolls.confusion(f.p1, d.p1, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
255
- for (Rolls.confusion(f.p2, d.p2, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
256
+ for (Rolls.confusion(f.p1, d.p1, tie, p1_atk, p1_slp)) |p1_cfz| { a.p1.confusion = p1_cfz;
257
+ for (Rolls.confusion(f.p2, d.p2, tie, p2_atk, p2_slp)) |p2_cfz| { a.p2.confusion = p2_cfz;
256
258
  for (Rolls.confused(f.p1, p1_cfz)) |p1_cfzd| { a.p1.confused = p1_cfzd;
257
259
  for (Rolls.confused(f.p2, p2_cfz)) |p2_cfzd| { a.p2.confused = p2_cfzd;
258
260
  for (Rolls.paralyzed(f.p1, p1_cfzd)) |p1_par| { a.p1.paralyzed = p1_par;
@@ -298,9 +300,9 @@ pub fn transitions(
298
300
  const p1_max: u9 = if (p2_dmg.min != p2_min)
299
301
  p1_dmg.min
300
302
  else
301
- try Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
303
+ Rolls.coalesce(.P1, @as(u8, @intCast(p1_dmg.min)), summaries, cap);
302
304
  const p2_max: u9 =
303
- try Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
305
+ Rolls.coalesce(.P2, @as(u8, @intCast(p2_dmg.min)), summaries, cap);
304
306
 
305
307
  if (opts.chance.actions.matches(f)) {
306
308
  if (!opts.chance.actions.relax().eql(a)) {
@@ -343,6 +345,8 @@ pub fn transitions(
343
345
  q.reduce();
344
346
  try p.add(q);
345
347
  p.reduce();
348
+ try r.add(q);
349
+ r.reduce();
346
350
 
347
351
  stats.saved += 1;
348
352
 
@@ -384,14 +388,15 @@ pub fn transitions(
384
388
  }}}}}}}}}}}}}}}}}}}}}}}}}}}}
385
389
 
386
390
  assert(stats.saved > saved);
387
- const old = @hasDecl(std.fmt, "formatFloatDecimal");
388
- if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
389
- p.reduce();
390
- try writer.print(
391
- " = {s} ({d:.2}%)\n\n",
392
- .{p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
393
- );
394
- }
391
+ // TODO: ziglang/zig#22667
392
+ // const old = @hasDecl(std.fmt, "formatFloatDecimal");
393
+ // if (!old and @TypeOf(writer) != @TypeOf(std.io.null_writer)) {
394
+ // try writer.print(
395
+ // " {} ({d:.2}%)\n = ──────────\n {} ({d:.2}%)\n\n",
396
+ // .{r, 100 * @as(f128, @floatFromInt(r.p)) / @as(f128, @floatFromInt(r.q)),
397
+ // p, 100 * @as(f128, @floatFromInt(p.p)) / @as(f128, @floatFromInt(p.q))},
398
+ // );
399
+ // }
395
400
 
396
401
  }
397
402
 
@@ -668,11 +673,13 @@ pub const Rolls = struct {
668
673
  const CFZ_CONTINUING = [_]Optional(Confusion){.continuing};
669
674
  const CFZ_ENDED = [_]Optional(Confusion){.ended};
670
675
  const CFZ = [_]Optional(Confusion){ .continuing, .ended };
676
+ const CFZ_TIE = [_]Optional(Confusion){ .started, .continuing, .ended };
671
677
 
672
678
  /// FIXME
673
679
  pub fn confusion(
674
680
  action: Action,
675
681
  duration: Duration,
682
+ tie: Optional(Player),
676
683
  sibling: Optional(Observation),
677
684
  parent: Optional(Observation),
678
685
  ) []const Optional(Confusion) {
@@ -686,6 +693,8 @@ pub const Rolls = struct {
686
693
  &CFZ_ENDED
687
694
  else if ((parent == .continuing and parent == .ended) or duration.confusion < 2)
688
695
  &CFZ_CONTINUING
696
+ else if (tie != .None)
697
+ &CFZ_TIE
689
698
  else
690
699
  &CFZ,
691
700
  };
@@ -744,7 +753,7 @@ pub const Rolls = struct {
744
753
 
745
754
  /// Returns the max damage roll which will produce the same damage as `roll`
746
755
  /// given the base damage in `summaries`.
747
- pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) !u8 {
756
+ pub fn coalesce(player: Player, roll: u8, summaries: *Summaries, cap: bool) u8 {
748
757
  if (roll == 0) return roll;
749
758
 
750
759
  const dmg = summaries.get(player.foe()).damage;
@@ -854,12 +863,12 @@ test "Rolls.damage" {
854
863
 
855
864
  test "Rolls.coalesce" {
856
865
  var summaries = Summaries{ .p1 = .{ .damage = .{ .base = 74, .final = 69, .capped = true } } };
857
- try expectEqual(@as(u8, 0), try Rolls.coalesce(.P2, 0, &summaries, false));
858
- try expectEqual(@as(u8, 241), try Rolls.coalesce(.P2, 238, &summaries, false));
859
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 238, &summaries, true));
866
+ try expectEqual(@as(u8, 0), Rolls.coalesce(.P2, 0, &summaries, false));
867
+ try expectEqual(@as(u8, 241), Rolls.coalesce(.P2, 238, &summaries, false));
868
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 238, &summaries, true));
860
869
  summaries.p1.damage.final = 74;
861
- try expectEqual(@as(u8, 217), try Rolls.coalesce(.P2, 217, &summaries, false));
862
- try expectEqual(@as(u8, 255), try Rolls.coalesce(.P2, 217, &summaries, true));
870
+ try expectEqual(@as(u8, 217), Rolls.coalesce(.P2, 217, &summaries, false));
871
+ try expectEqual(@as(u8, 255), Rolls.coalesce(.P2, 217, &summaries, true));
863
872
  }
864
873
 
865
874
  test "Rolls.hit" {
@@ -98,7 +98,7 @@ pub const Type = enum(u4) {
98
98
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
99
99
  }
100
100
 
101
- /// The precedence order of Type `t2` vs. Type `t1`.
101
+ /// The precedence order of Type `t1` vs. Type `t2`.
102
102
  pub fn precedence(t1: Type, t2: Type) u8 {
103
103
  for (PRECEDENCE, 0..) |matchup, i| {
104
104
  if (matchup.type1 == t1 and matchup.type2 == t2) return @intCast(i);
@@ -618,17 +618,10 @@ fn beforeMove(
618
618
  return .err;
619
619
  }
620
620
  }
621
- // Pokémon Showdown incorrectly changes the "target" of the confusion self-hit based
622
- // on the targeting behavior of the confused Pokémon's selected move which results
623
- // in the wrong behavior with respect to the Substitute + Confusion glitch
624
- const target = if (showdown and Move.get(side.last_selected_move).target == .Self)
625
- player
626
- else
627
- player.foe();
628
621
 
629
622
  const uncapped = battle.last_damage;
630
623
  // Skipping adjustDamage / randomizeDamage / checkHit
631
- _ = try applyDamage(battle, player, target, .Confusion, options);
624
+ _ = try applyDamage(battle, player, player.foe(), .Confusion, options);
632
625
  // Pokémon Showdown thinks that confusion damage is uncapped ¯\_(ツ)_/¯
633
626
  if (showdown) battle.last_damage = uncapped;
634
627
 
@@ -1170,7 +1163,7 @@ fn calcDamage(
1170
1163
  // encountered so we need to make sure that in the event of a miss we commit the status of the
1171
1164
  // crit roll. The alternative to doing this extra work is to *always* commit the crit roll but
1172
1165
  // that results in counter intuitive probabilities in the common case
1173
- if (pkmn.options.chance and !pkmn.options.mod) {
1166
+ if (pkmn.options.chance and !pkmn.options.mod and move.effect != .Explode) {
1174
1167
  atk = attack(side, !crit, special);
1175
1168
  def = defense(target, !crit, special, false);
1176
1169
  if ((atk > 255 or def > 255) and (def / 4) & 255 == 0) options.chance.glitch();
@@ -1304,7 +1297,7 @@ fn counterDamage(battle: anytype, player: Player, move: Move.Data, options: anyt
1304
1297
  }
1305
1298
 
1306
1299
  // NOTE: llvm/llvm-project#58557
1307
- battle.last_damage = if (comptime builtin.target.isWasm())
1300
+ battle.last_damage = if (comptime builtin.target.cpu.arch.isWasm())
1308
1301
  @max(battle.last_damage *% 2, std.math.maxInt(u16))
1309
1302
  else
1310
1303
  battle.last_damage *| 2;
@@ -1453,7 +1446,16 @@ fn moveHit(
1453
1446
  break :miss true;
1454
1447
  }
1455
1448
  if (!showdown) {
1456
- if (move.effect == .Swift) return true;
1449
+ if (move.effect == .Swift) {
1450
+ // If Swift could cause a division-by-zero freeze (which doesn't exist on Pokémon
1451
+ // Showdown, hence why the block above doesn't include this code), we need the
1452
+ // damage roll to be committed. However, the commit code will not save this roll if
1453
+ // the glitch occurs and the move was not marked as having hit. To work around this
1454
+ // we flag that the move hit (as Swift trivially does), but pass the probability of
1455
+ // the hit as zero to ensure it doesn't actually get saved.
1456
+ try options.chance.hit(player, true, 0);
1457
+ return true;
1458
+ }
1457
1459
  if (foe.active.volatiles.Invulnerable) break :miss true;
1458
1460
  }
1459
1461
 
@@ -148,6 +148,6 @@ fn ReturnType(comptime field: Action.Field) type {
148
148
  return ?(switch (field) {
149
149
  .damages => u8,
150
150
  .critical_hits => Optional(bool),
151
- else => std.meta.FieldType(Action, field),
151
+ else => util.FieldType(Action, field),
152
152
  });
153
153
  }
@@ -172,9 +172,12 @@ pub const Type = enum(u8) {
172
172
  return CHART[@intFromEnum(t1)][@intFromEnum(t2)];
173
173
  }
174
174
 
175
- /// The precedence order of Type `type`.
176
- pub fn precedence(self: Type) u8 {
177
- return PRECEDENCE[@intFromEnum(self)];
175
+ /// The precedence order of Type `t1` vs. Type `t2`.
176
+ pub fn precedence(t1: Type, t2: Type) u8 {
177
+ // The Ice vs. Fire matchup is out of order - return a higher
178
+ // number than anything else so that it sorts last correctly
179
+ if (t1 == .Ice and t2 == .Fire) return 18;
180
+ return PRECEDENCE[@intFromEnum(t2)];
178
181
  }
179
182
 
180
183
  /// The internal index of this Type used by Present.
@@ -449,8 +449,9 @@ test Types {
449
449
  try expect(!Type.Steel.special());
450
450
  try expect(Type.Dark.special());
451
451
 
452
- try expectEqual(@as(u8, 3), Type.Water.precedence());
453
- try expect(Type.Bug.precedence() > Type.Poison.precedence());
452
+ try expectEqual(@as(u8, 3), Type.precedence(.Water, .Water));
453
+ try expect(Type.precedence(.Poison, .Bug) > Type.precedence(.Poison, .Poison));
454
+ try expect(Type.precedence(.Ice, .Fire) > Type.precedence(.Ice, .Water));
454
455
 
455
456
  try expectEqual(Effectiveness.Super, Type.effectiveness(.Ghost, .Psychic));
456
457
  try expectEqual(Effectiveness.Super, Type.effectiveness(.Water, .Fire));
@@ -682,7 +682,7 @@ pub fn adjustDamage(battle: anytype, player: Player, state: *State, _: anytype)
682
682
 
683
683
  // Type effectiveness matchup precedence only matters with (NVE, SE)
684
684
  if (!showdown and (eff1 + eff2) == Effectiveness.mismatch and
685
- types.type1.precedence() > types.type2.precedence())
685
+ Type.precedence(state.type, types.type1) > Type.precedence(state.type, types.type2))
686
686
  {
687
687
  assert(eff2 != neutral);
688
688
  d = d *% eff2 / 10;
@@ -2841,7 +2841,7 @@ fn convertible(active: ActivePokemon, m: Move) bool {
2841
2841
 
2842
2842
  // NOTE: llvm/llvm-project#58557
2843
2843
  fn smul(a: anytype, b: @TypeOf(a)) @TypeOf(a) {
2844
- if (!comptime builtin.target.isWasm()) return a *| b;
2844
+ if (!comptime builtin.target.cpu.arch.isWasm()) return a *| b;
2845
2845
  return @max(a *% b, std.math.maxInt(@TypeOf(a)));
2846
2846
  }
2847
2847
 
package/src/lib/node.zig CHANGED
@@ -1,129 +1,14 @@
1
+ const c = @import("common/napi.zig");
1
2
  const js = @import("common/js.zig");
2
- const pkmn = @import("pkmn.zig");
3
+ const node = @import("bindings/node.zig");
3
4
  const std = @import("std");
4
5
 
5
6
  const assert = std.debug.assert;
6
7
 
7
- const c = @cImport({
8
- @cDefine("NAPI_VERSION", "8");
9
- @cInclude("node_api.h");
10
- });
11
-
12
8
  export fn napi_register_module_v1(env: c.napi_env, exports: c.napi_value) c.napi_value {
13
9
  const properties = [_]c.napi_property_descriptor{
14
- js.Property.init("engine", .{ .value = register(env) }),
10
+ js.Property.init("engine", .{ .value = node.register(env) }),
15
11
  };
16
12
  assert(c.napi_define_properties(env, exports, properties.len, &properties) == c.napi_ok);
17
13
  return exports;
18
14
  }
19
-
20
- pub fn register(env: c.napi_env) c.napi_value {
21
- const object = js.Object.init(env);
22
- const properties = [_]c.napi_property_descriptor{
23
- js.Property.init("options", .{ .value = options(env) }),
24
- js.Property.init("bindings", .{ .value = bindings(env) }),
25
- };
26
- assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
27
- return object;
28
- }
29
-
30
- fn options(env: c.napi_env) c.napi_value {
31
- const object = js.Object.init(env);
32
- const properties = [_]c.napi_property_descriptor{
33
- js.Property.init("showdown", .{ .value = js.Boolean.init(env, pkmn.options.showdown) }),
34
- js.Property.init("log", .{ .value = js.Boolean.init(env, pkmn.options.log) }),
35
- js.Property.init("chance", .{ .value = js.Boolean.init(env, pkmn.options.chance) }),
36
- js.Property.init("calc", .{ .value = js.Boolean.init(env, pkmn.options.calc) }),
37
- };
38
- assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
39
- return object;
40
- }
41
-
42
- fn bindings(env: c.napi_env) c.napi_value {
43
- const array = js.Array.init(env, .{ .length = 1 });
44
- js.Array.set(env, array, 0, bind(env, pkmn.gen1));
45
- return array;
46
- }
47
-
48
- fn bind(env: c.napi_env, gen: anytype) c.napi_value {
49
- const choices_size: u32 = @intCast(gen.CHOICES_SIZE);
50
- const logs_size: u32 = @intCast(gen.LOGS_SIZE);
51
- const object = js.Object.init(env);
52
- const properties = [_]c.napi_property_descriptor{
53
- js.Property.init("CHOICES_SIZE", .{ .value = js.Number.init(env, choices_size) }),
54
- js.Property.init("LOGS_SIZE", .{ .value = js.Number.init(env, logs_size) }),
55
- js.Property.init("update", .{ .method = update(gen) }),
56
- js.Property.init("choices", .{ .method = choices(gen) }),
57
- };
58
- assert(c.napi_define_properties(env, object, properties.len, &properties) == c.napi_ok);
59
- return object;
60
- }
61
-
62
- fn update(gen: anytype) c.napi_callback {
63
- return struct {
64
- fn call(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
65
- var argc: usize = 4;
66
- var argv: [4]c.napi_value = undefined;
67
- assert(c.napi_get_cb_info(env, info, &argc, &argv, null, null) == c.napi_ok);
68
- assert(argc == 4);
69
-
70
- var data: ?*anyopaque = undefined;
71
- var len: usize = 0;
72
- assert(c.napi_get_arraybuffer_info(env, argv[0], &data, &len) == c.napi_ok);
73
- assert(len == @sizeOf(gen.Battle(gen.PRNG)));
74
-
75
- var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
76
- const c1: pkmn.Choice = @bitCast(js.Number.get(env, argv[1], u8));
77
- const c2: pkmn.Choice = @bitCast(js.Number.get(env, argv[2], u8));
78
-
79
- var vtype: c.napi_valuetype = undefined;
80
- assert(c.napi_typeof(env, argv[3], &vtype) == c.napi_ok);
81
- const result = switch (vtype) {
82
- c.napi_undefined, c.napi_null => battle.update(c1, c2, &gen.NULL),
83
- else => result: {
84
- assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
85
- assert(len == gen.LOGS_SIZE);
86
-
87
- const buf = @as([*]u8, @ptrCast(data.?))[0..gen.LOGS_SIZE];
88
- var stream: pkmn.protocol.ByteStream = .{ .buffer = buf };
89
- // TODO: extract out
90
- var opts = pkmn.battle.options(
91
- pkmn.protocol.FixedLog{ .writer = stream.writer() },
92
- gen.chance.NULL,
93
- gen.calc.NULL,
94
- );
95
- break :result battle.update(c1, c2, &opts);
96
- },
97
- } catch unreachable;
98
-
99
- return js.Number.init(env, @as(u8, @bitCast(result)));
100
- }
101
- }.call;
102
- }
103
-
104
- fn choices(gen: anytype) c.napi_callback {
105
- return struct {
106
- fn call(env: c.napi_env, info: c.napi_callback_info) callconv(.C) c.napi_value {
107
- var argc: usize = 4;
108
- var argv: [4]c.napi_value = undefined;
109
- assert(c.napi_get_cb_info(env, info, &argc, &argv, null, null) == c.napi_ok);
110
- assert(argc == 4);
111
-
112
- var data: ?*anyopaque = undefined;
113
- var len: usize = 0;
114
- assert(c.napi_get_arraybuffer_info(env, argv[0], &data, &len) == c.napi_ok);
115
- assert(len == @sizeOf(gen.Battle(gen.PRNG)));
116
-
117
- var battle: *gen.Battle(gen.PRNG) = @alignCast(@ptrCast(data.?));
118
- const player: pkmn.Player = @enumFromInt(js.Number.get(env, argv[1], u8));
119
- const request: pkmn.Choice.Type = @enumFromInt(js.Number.get(env, argv[2], u8));
120
-
121
- assert(c.napi_get_arraybuffer_info(env, argv[3], &data, &len) == c.napi_ok);
122
- assert(len == gen.CHOICES_SIZE);
123
-
124
- const out = @as([*]pkmn.Choice, @ptrCast(data.?))[0..gen.CHOICES_SIZE];
125
- const n = battle.choices(player, request, out);
126
- return js.Number.init(env, @as(u8, @bitCast(n)));
127
- }
128
- }.call;
129
- }
package/src/lib/pkmn.zig CHANGED
@@ -129,6 +129,7 @@ pub const gen2 = struct {
129
129
  // NOTE: `pub usingnamespace struct { ... }` here results in a (false?) dependency loop
130
130
  pub const js = if (options.internal) @import("common/js.zig") else {};
131
131
  pub const bindings = if (options.internal) struct {
132
- pub const node = @import("node.zig");
133
- pub const wasm = @import("wasm.zig");
132
+ pub const c = @import("bindings/c.zig");
133
+ pub const node = @import("bindings/node.zig");
134
+ pub const wasm = @import("bindings/wasm.zig");
134
135
  } else {};
package/src/lib/wasm.zig CHANGED
@@ -1,48 +1,17 @@
1
- const pkmn = @import("pkmn.zig");
2
1
  const std = @import("std");
2
+ const wasm = @import("bindings/wasm.zig");
3
3
 
4
- const assert = std.debug.assert;
4
+ export const SHOWDOWN = wasm.options.showdown;
5
+ export const LOG = wasm.options.log;
6
+ export const CHANCE = wasm.options.chance;
7
+ export const CALC = wasm.options.calc;
5
8
 
6
- const Enum = if (@hasField(std.builtin.Type, "enum")) .@"enum" else .Enum;
9
+ export const GEN1_CHOICES_SIZE = wasm.gen(1).CHOICES_SIZE;
10
+ export const GEN1_LOGS_SIZE = wasm.gen(1).LOGS_SIZE;
7
11
 
8
- export const SHOWDOWN = pkmn.options.showdown;
9
- export const LOG = pkmn.options.log;
10
- export const CHANCE = pkmn.options.chance;
11
- export const CALC = pkmn.options.calc;
12
+ const exportable = @hasDecl(std.zig, "Zir") and !@hasField(std.zig.Zir.Inst.Tag, "export_value");
12
13
 
13
- export const GEN1_CHOICES_SIZE =
14
- std.math.ceilPowerOfTwo(u32, @as(u32, @intCast(pkmn.gen1.CHOICES_SIZE))) catch unreachable;
15
- export const GEN1_LOGS_SIZE =
16
- std.math.ceilPowerOfTwo(u32, @as(u32, @intCast(pkmn.gen1.LOGS_SIZE))) catch unreachable;
17
-
18
- export fn GEN1_update(
19
- battle: *pkmn.gen1.Battle(pkmn.gen1.PRNG),
20
- c1: pkmn.Choice,
21
- c2: pkmn.Choice,
22
- options: ?[*]u8,
23
- ) pkmn.Result {
24
- return (if (options) |o| result: {
25
- const buf = @as([*]u8, @ptrCast(o))[0..pkmn.gen1.LOGS_SIZE];
26
- var stream: pkmn.protocol.ByteStream = .{ .buffer = buf };
27
- // TODO: extract out
28
- var opts = pkmn.battle.options(
29
- pkmn.protocol.FixedLog{ .writer = stream.writer() },
30
- pkmn.gen1.chance.NULL,
31
- pkmn.gen1.calc.NULL,
32
- );
33
- break :result battle.update(c1, c2, &opts);
34
- } else battle.update(c1, c2, &pkmn.gen1.NULL)) catch unreachable;
35
- }
36
-
37
- export fn GEN1_choices(
38
- battle: *pkmn.gen1.Battle(pkmn.gen1.PRNG),
39
- player: u8,
40
- request: u8,
41
- buf: [*]u8,
42
- ) u8 {
43
- assert(player <= @field(@typeInfo(pkmn.Player), @tagName(Enum)).fields.len);
44
- assert(request <= @field(@typeInfo(pkmn.Choice.Type), @tagName(Enum)).fields.len);
45
-
46
- const len = GEN1_CHOICES_SIZE;
47
- return battle.choices(@enumFromInt(player), @enumFromInt(request), @ptrCast(buf[0..len]));
48
- }
14
+ usingnamespace if (exportable) struct {
15
+ export const GEN1_update = wasm.gen(1).update;
16
+ export const GEN1_choices = wasm.gen(1).choices;
17
+ } else wasm.exports();
@@ -1,6 +1,6 @@
1
1
  /** @license MIT modified from Vadim Demedes's https://github.com/vadimdemedes/dom-chef */
2
2
  type Attributes = JSX.IntrinsicElements['div'];
3
- type DocumentFragmentConstructor = typeof DocumentFragment;
3
+ type DocumentFragmentFunction = (props?: any) => DocumentFragment;
4
4
  type ElementFunction = (props?: any) => HTMLElement;
5
5
 
6
6
  declare global {
@@ -22,9 +22,6 @@ interface Fragment {
22
22
  // https://github.com/preactjs/preact/blob/1bbd687c/src/constants.js#L3
23
23
  const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i;
24
24
 
25
- const isFragment = (type: DocumentFragmentConstructor | ElementFunction):
26
- type is DocumentFragmentConstructor => type === DocumentFragment;
27
-
28
25
  const setCSSProps = (element: HTMLElement, style: CSSStyleDeclaration) => {
29
26
  for (const [name, value] of Object.entries(style)) {
30
27
  if (name.startsWith('-')) {
@@ -57,12 +54,12 @@ const addChildren = (parent: Element | DocumentFragment, children: Node[]) => {
57
54
  const FALSIFIABLE_ATTRIBUTES = ['contentEditable', 'draggable', 'spellCheck', 'value'];
58
55
 
59
56
  export const h = (
60
- type: DocumentFragmentConstructor | ElementFunction | string,
57
+ type: DocumentFragmentFunction | ElementFunction | string,
61
58
  attributes?: Attributes,
62
59
  ...children: Node[]
63
60
  ) => {
64
61
  if (typeof type !== 'string') {
65
- const element = isFragment(type) ? document.createDocumentFragment() : type(attributes);
62
+ const element = type(attributes);
66
63
  addChildren(element, children);
67
64
  return element;
68
65
  }
@@ -92,5 +89,5 @@ export const h = (
92
89
  return element;
93
90
  };
94
91
 
95
- export const Fragment =
96
- (typeof DocumentFragment === 'function' ? DocumentFragment : () => {}) as Fragment;
92
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
93
+ export const Fragment = (unused: any) => document.createDocumentFragment();