@pkmn/engine 0.1.0-dev.84cc1a02 → 0.1.0-dev.8b192003

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