@pkmn/engine 0.1.0-dev.8b83af6b → 0.1.0-dev.8cf7e679

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 (83) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +100 -61
  3. package/build/pkg/addon/browser.d.ts +3 -0
  4. package/build/pkg/addon/browser.js +44 -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 +97 -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 +44 -0
  11. package/build/pkg/addon/wasm.js.map +1 -0
  12. package/build/pkg/addon.d.ts +25 -3
  13. package/build/pkg/addon.js +39 -70
  14. package/build/pkg/addon.js.map +1 -1
  15. package/build/pkg/data/ids.json +12 -12
  16. package/build/pkg/data/layout.json +39 -4
  17. package/build/pkg/data/protocol.json +84 -13
  18. package/build/pkg/data.d.ts +27 -5
  19. package/build/pkg/data.js.map +1 -1
  20. package/build/pkg/gen1.d.ts +13 -17
  21. package/build/pkg/gen1.js +21 -28
  22. package/build/pkg/gen1.js.map +1 -1
  23. package/build/pkg/index.d.ts +40 -91
  24. package/build/pkg/index.js +6 -63
  25. package/build/pkg/index.js.map +1 -1
  26. package/build/pkg/protocol.d.ts +20 -1
  27. package/build/pkg/protocol.js +123 -102
  28. package/build/pkg/protocol.js.map +1 -1
  29. package/build/tools/debug.js +36 -69
  30. package/build/tools/debug.js.map +1 -1
  31. package/build/tools/display/debug.jsx +141 -0
  32. package/build/tools/display/debug.jsx.map +1 -0
  33. package/build/tools/display/index.js +141 -0
  34. package/build/tools/display/index.js.map +1 -0
  35. package/build/tools/display/ui.jsx +349 -0
  36. package/build/tools/display/ui.jsx.map +1 -0
  37. package/build/tools/display/util.js +18 -0
  38. package/build/tools/display/util.js.map +1 -0
  39. package/build.zig +332 -132
  40. package/package.json +66 -121
  41. package/src/bin/install-pkmn-engine +40 -25
  42. package/src/bin/pkmn-debug +1 -1
  43. package/src/lib/common/array.zig +77 -0
  44. package/src/lib/common/battle.zig +17 -0
  45. package/src/lib/common/data.zig +12 -24
  46. package/src/lib/common/debug.zig +58 -18
  47. package/src/lib/common/js.zig +120 -0
  48. package/src/lib/common/optional.zig +71 -0
  49. package/src/lib/common/options.zig +15 -0
  50. package/src/lib/common/protocol.zig +851 -327
  51. package/src/lib/common/rational.zig +449 -0
  52. package/src/lib/common/rng.zig +26 -19
  53. package/src/lib/common/util.zig +61 -0
  54. package/src/lib/gen1/calc.zig +953 -0
  55. package/src/lib/gen1/chance.zig +1185 -0
  56. package/src/lib/gen1/data/moves.zig +193 -183
  57. package/src/lib/gen1/data/species.zig +4 -6
  58. package/src/lib/gen1/data/types.zig +8 -9
  59. package/src/lib/gen1/data.zig +79 -73
  60. package/src/lib/gen1/helpers.zig +130 -30
  61. package/src/lib/gen1/mechanics.zig +1137 -779
  62. package/src/lib/gen2/calc.zig +153 -0
  63. package/src/lib/gen2/chance.zig +902 -0
  64. package/src/lib/gen2/data/items.zig +13 -21
  65. package/src/lib/gen2/data/moves.zig +664 -572
  66. package/src/lib/gen2/data/species.zig +2 -4
  67. package/src/lib/gen2/data/types.zig +135 -33
  68. package/src/lib/gen2/data.zig +121 -87
  69. package/src/lib/gen2/generated.zig +801 -0
  70. package/src/lib/gen2/helpers.zig +54 -39
  71. package/src/lib/gen2/mechanics.zig +3582 -30
  72. package/src/lib/node.zig +129 -0
  73. package/src/lib/pkmn.zig +85 -5
  74. package/src/lib/wasm.zig +48 -0
  75. package/src/tools/display/dom.ts +96 -0
  76. package/src/tools/display/ui.css +354 -0
  77. package/build/test/display.js +0 -400
  78. package/build/test/display.js.map +0 -1
  79. package/src/lib/binding/node.zig +0 -226
  80. package/src/lib/binding/wasm.zig +0 -10
  81. package/src/lib/gen1/README.md +0 -814
  82. package/src/lib/gen2/README.md +0 -309
  83. package/src/test/display/pkmn.html.tmpl +0 -368
@@ -1,36 +1,42 @@
1
- const std = @import("std");
2
-
1
+ const builtin = @import("builtin");
3
2
  const common = @import("../common/data.zig");
3
+ const data = @import("data.zig");
4
4
  const options = @import("../common/options.zig");
5
5
  const protocol = @import("../common/protocol.zig");
6
6
  const rng = @import("../common/rng.zig");
7
-
8
- const data = @import("data.zig");
7
+ const std = @import("std");
8
+ const util = @import("../common/util.zig");
9
9
 
10
10
  const assert = std.debug.assert;
11
-
12
- const expectEqual = std.testing.expectEqual;
13
-
14
11
  const Choice = common.Choice;
15
-
16
- const showdown = options.showdown;
17
-
18
- const PSRNG = rng.PSRNG;
19
-
20
12
  const DVs = data.DVs;
21
13
  const Move = data.Move;
22
14
  const MoveSlot = data.MoveSlot;
15
+ const PSRNG = rng.PSRNG;
16
+ const showdown = options.showdown;
23
17
  const Species = data.Species;
24
18
  const Stats = data.Stats;
25
- const Status = data.Status;
26
19
 
27
- /// Options with dictate
20
+ const endian = builtin.cpu.arch.endian();
21
+
22
+ const Struct = if (@hasField(std.builtin.Type, "struct")) .@"struct" else .Struct;
23
+
24
+ /// Configuration options controlling the domain of valid Pokémon that can be generated by the
25
+ /// random generator helpers.
28
26
  pub const Options = struct {
27
+ /// Whether to generate Pokémon adhering to "Cleric Clause".
29
28
  cleric: bool = showdown,
29
+ /// Whether to generate Pokémon with moves that contain bugs on Pokémon Showdown that are
30
+ /// unimplementable in the engine.
30
31
  block: bool = showdown,
32
+ // FIXME: remove once durations work with -Dchance
33
+ durations: bool = false,
31
34
  };
32
35
 
36
+ /// Helpers to simplify initialization of a Generation I battle.
33
37
  pub const Battle = struct {
38
+ /// Initializes a Generation I battle with the teams specified by `p1` and `p2`
39
+ /// and an RNG whose seed is derived from `seed`.
34
40
  pub fn init(
35
41
  seed: u64,
36
42
  p1: []const Pokemon,
@@ -43,6 +49,8 @@ pub const Battle = struct {
43
49
  };
44
50
  }
45
51
 
52
+ /// Initializes a Generation I battle with the teams specified by `p1` and `p2`
53
+ /// and a FixedRNG that returns the provided `rolls`.
46
54
  pub fn fixed(
47
55
  comptime rolls: anytype,
48
56
  p1: []const Pokemon,
@@ -54,36 +62,57 @@ pub const Battle = struct {
54
62
  };
55
63
  }
56
64
 
57
- pub fn random(rand: *PSRNG, opt: Options) data.Battle(data.PRNG) {
65
+ /// Returns a Generation I battle that is randomly generated based on the `rand` and `opts`.
66
+ pub fn random(rand: *PSRNG, opts: Options) data.Battle(data.PRNG) {
58
67
  return .{
59
68
  .rng = prng(rand),
60
69
  .turn = 0,
61
70
  .last_damage = 0,
62
- .sides = .{ Side.random(rand, opt), Side.random(rand, opt) },
71
+ .sides = .{ Side.random(rand, opts), Side.random(rand, opts) },
63
72
  };
64
73
  }
65
74
  };
66
75
 
67
76
  fn prng(rand: *PSRNG) data.PRNG {
68
- // GLITCH: initial bytes in seed can only range from 0-252, not 0-255
69
- const max: u8 = 253;
70
77
  return .{
71
78
  .src = .{
72
79
  .seed = if (showdown)
73
80
  rand.newSeed()
74
- else
75
- .{
76
- rand.range(u8, 0, max), rand.range(u8, 0, max),
77
- rand.range(u8, 0, max), rand.range(u8, 0, max),
78
- rand.range(u8, 0, max), rand.range(u8, 0, max),
79
- rand.range(u8, 0, max), rand.range(u8, 0, max),
80
- rand.range(u8, 0, max), rand.range(u8, 0, max),
81
- },
81
+ else seed: {
82
+ // GLITCH: The initial RNG seed in Gen I link battles is heavily constrained
83
+ // https://www.smogon.com/forums/threads/rng-in-rby-link-battles.3746779
84
+ var seed = [_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0 };
85
+
86
+ // Bytes in seed can only range from 0-252, not 0-255
87
+ const max: u8 = 253;
88
+ // Instructions take multiples of 4 cycles to execute
89
+ var timer: u16 = 4 * rand.range(u16, 0, 16384);
90
+ var add: u8 = rand.range(u8, 0, 256);
91
+ var carry: u8 = 0;
92
+
93
+ var i: u8 = 0;
94
+ while (i < seed.len) {
95
+ const div: u8 = @intCast(timer >> 8);
96
+ add +%= div +% carry;
97
+ if (add < max) {
98
+ seed[i] = add;
99
+ i += 1;
100
+ timer +%= 472;
101
+ carry = 1;
102
+ } else {
103
+ timer +%= 452;
104
+ carry = 0;
105
+ }
106
+ }
107
+ break :seed seed;
108
+ },
82
109
  },
83
110
  };
84
111
  }
85
112
 
113
+ /// Helpers to simplify initialization of a Generation I side.
86
114
  pub const Side = struct {
115
+ /// Initializes a Generation I side with the team specified by `ps`.
87
116
  pub fn init(ps: []const Pokemon) data.Side {
88
117
  assert(ps.len > 0 and ps.len <= 6);
89
118
  var side = data.Side{};
@@ -95,12 +124,13 @@ pub const Side = struct {
95
124
  return side;
96
125
  }
97
126
 
98
- pub fn random(rand: *PSRNG, opt: Options) data.Side {
127
+ /// Returns a Generation I side that is randomly generated based on the `rand` and `opts`.
128
+ pub fn random(rand: *PSRNG, opts: Options) data.Side {
99
129
  const n = if (rand.chance(u8, 1, 100)) rand.range(u4, 1, 5 + 1) else 6;
100
130
  var side = data.Side{};
101
131
 
102
132
  for (0..n) |i| {
103
- side.pokemon[i] = Pokemon.random(rand, opt);
133
+ side.pokemon[i] = Pokemon.random(rand, opts);
104
134
  side.order[i] = @as(u4, @intCast(i)) + 1;
105
135
  }
106
136
 
@@ -108,22 +138,32 @@ pub const Side = struct {
108
138
  }
109
139
  };
110
140
 
141
+ /// The maximum stat experience possible in Generation I.
111
142
  pub const EXP = 0xFFFF;
112
143
 
144
+ /// Helpers to simplify initialization of a Generation I Pokémon.
113
145
  pub const Pokemon = struct {
146
+ /// The Pokémon's species.
114
147
  species: Species,
148
+ /// The Pokémon's moves (assumed to all have the max possible PP).
115
149
  moves: []const Move,
150
+ /// The Pokémon's current HP (defaults to 100% if not specified).
116
151
  hp: ?u16 = null,
152
+ /// The Pokémon's current status.
117
153
  status: u8 = 0,
154
+ /// The Pokémon's level.
118
155
  level: u8 = 100,
156
+ /// The Pokémon's DVs.
119
157
  dvs: DVs = .{},
158
+ /// The Pokémon's stat experience.
120
159
  stats: Stats(u16) = .{ .hp = EXP, .atk = EXP, .def = EXP, .spe = EXP, .spc = EXP },
121
160
 
161
+ /// Initializes a Generation I Pokémon based on the information in `p`.
122
162
  pub fn init(p: Pokemon) data.Pokemon {
123
163
  var pokemon = data.Pokemon{};
124
164
  pokemon.species = p.species;
125
165
  const species = Species.get(p.species);
126
- inline for (@typeInfo(@TypeOf(pokemon.stats)).Struct.fields) |field| {
166
+ inline for (@field(@typeInfo(@TypeOf(pokemon.stats)), @tagName(Struct)).fields) |field| {
127
167
  const hp = comptime std.mem.eql(u8, field.name, "hp");
128
168
  const spc =
129
169
  comptime std.mem.eql(u8, field.name, "spa") or std.mem.eql(u8, field.name, "spd");
@@ -152,13 +192,14 @@ pub const Pokemon = struct {
152
192
  return pokemon;
153
193
  }
154
194
 
195
+ /// Returns a Generation I Pokémon that is randomly generated based on the `rand` and `opts`.
155
196
  pub fn random(rand: *PSRNG, opt: Options) data.Pokemon {
156
197
  const s: Species = @enumFromInt(rand.range(u8, 1, Species.size + 1));
157
198
  const species = Species.get(s);
158
199
  const lvl = if (rand.chance(u8, 1, 20)) rand.range(u8, 1, 99 + 1) else 100;
159
200
  var stats: Stats(u16) = .{};
160
201
  const dvs = DVs.random(rand);
161
- inline for (@typeInfo(@TypeOf(stats)).Struct.fields) |field| {
202
+ inline for (@field(@typeInfo(@TypeOf(stats)), @tagName(Struct)).fields) |field| {
162
203
  @field(stats, field.name) = Stats(u16).calc(
163
204
  field.name,
164
205
  @field(species.stats, field.name),
@@ -177,6 +218,7 @@ pub const Pokemon = struct {
177
218
  var m: Move = .None;
178
219
  sample: while (true) {
179
220
  m = @enumFromInt(rand.range(u8, 1, Move.size - 1 + 1));
221
+ if (opt.durations and durations(m)) continue :sample;
180
222
  if (opt.block and blocked(m)) continue :sample;
181
223
  for (0..i) |j| if (ms[j].id == m) continue :sample;
182
224
  break;
@@ -198,6 +240,7 @@ pub const Pokemon = struct {
198
240
  .stats = stats,
199
241
  .hp = if (opt.cleric) stats.hp else rand.range(u16, 0, stats.hp + 1),
200
242
  .status = if (!opt.cleric and rand.chance(u8, 1, 6 + 1))
243
+ // NB: This will only assign sleep as 2 or 4 when rolled
201
244
  0 | (@as(u8, 1) << rand.range(u3, 1, 6 + 1))
202
245
  else
203
246
  0,
@@ -214,10 +257,67 @@ fn blocked(m: Move) bool {
214
257
  };
215
258
  }
216
259
 
260
+ fn durations(m: Move) bool {
261
+ // Any moves that have effects that result in duration tracking with -Dchance
262
+ return switch (Move.get(m).effect) {
263
+ .Haze,
264
+ .Metronome,
265
+ => true,
266
+ else => false,
267
+ };
268
+ }
269
+
270
+ /// Convenience helper to create a move-type choice for the provided move slot.
217
271
  pub fn move(slot: u4) Choice {
218
272
  return .{ .type = .Move, .data = slot };
219
273
  }
220
274
 
275
+ /// Convenience helper to create a switch-type choice for the provided team slot.
221
276
  pub fn swtch(slot: u4) Choice {
222
277
  return .{ .type = .Switch, .data = slot };
223
278
  }
279
+
280
+ /// Empirically determined maximum number of bytes possibly required to store the diff between two
281
+ /// Generation I battles.
282
+ pub const MAX_DIFFS: usize = 120;
283
+
284
+ /// The optimal size in bytes required to hold the diff between two Generation I battles.
285
+ /// At least as large as MAX_DIFFS.
286
+ pub const DIFFS_SIZE = if (builtin.mode == .ReleaseSmall)
287
+ MAX_DIFFS
288
+ else
289
+ std.math.ceilPowerOfTwo(usize, MAX_DIFFS) catch unreachable;
290
+
291
+ /// TODO
292
+ pub fn diff(
293
+ a: *const data.Battle(data.PRNG),
294
+ b: *const data.Battle(data.PRNG),
295
+ w: protocol.ByteStream.Writer,
296
+ ) !usize {
297
+ const pos = w.stream.pos;
298
+ const size = @sizeOf(data.Battle(data.PRNG)) - @sizeOf(data.PRNG);
299
+ assert(size % 2 == 0);
300
+ assert(size / 2 < 255);
301
+
302
+ var i: usize = 0;
303
+ while (i < size) : (i += 2) {
304
+ const val = util.bytesAsValue(u16, std.mem.asBytes(a)[i .. i + 2]).*;
305
+ if (val != util.bytesAsValue(u16, std.mem.asBytes(b)[i .. i + 2]).*) {
306
+ try w.writeByte(@intCast(i / 2));
307
+ try w.writeInt(u16, val, endian);
308
+ }
309
+ }
310
+
311
+ return w.stream.pos - pos;
312
+ }
313
+
314
+ /// TODO
315
+ pub fn patch(battle: *data.Battle(data.PRNG), buf: []u8) void {
316
+ var i: usize = 0;
317
+ while (i < buf.len) : (i += 3) {
318
+ const off = @as(u16, @intCast(buf[i])) * 2;
319
+ const val = std.mem.readInt(u16, @as(*const [2]u8, @ptrCast(buf[i + 1 .. i + 3])), endian);
320
+ const bytes: *[2]u8 = @ptrCast(std.mem.asBytes(battle)[off .. off + 2]);
321
+ std.mem.writeInt(u16, bytes, val, endian);
322
+ }
323
+ }