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