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

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 (56) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +46 -19
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data/ids.json +12 -12
  6. package/build/pkg/data/layout.json +2 -2
  7. package/build/pkg/data/protocol.json +69 -12
  8. package/build/pkg/data.d.ts +4 -4
  9. package/build/pkg/data.js.map +1 -1
  10. package/build/pkg/gen1.js +2 -2
  11. package/build/pkg/gen1.js.map +1 -1
  12. package/build/pkg/index.d.ts +3 -2
  13. package/build/pkg/index.js.map +1 -1
  14. package/build/pkg/protocol.js +61 -42
  15. package/build/pkg/protocol.js.map +1 -1
  16. package/build/test/display.js +8 -3
  17. package/build/test/display.js.map +1 -1
  18. package/build/tools/debug.js +5 -4
  19. package/build/tools/debug.js.map +1 -1
  20. package/build.zig +232 -99
  21. package/package.json +46 -23
  22. package/src/bin/install-pkmn-engine +25 -17
  23. package/src/lib/common/array.zig +77 -0
  24. package/src/lib/common/data.zig +12 -24
  25. package/src/lib/common/debug.zig +2 -2
  26. package/src/lib/common/js.zig +4 -2
  27. package/src/lib/common/optional.zig +21 -10
  28. package/src/lib/common/options.zig +2 -0
  29. package/src/lib/common/protocol.zig +807 -327
  30. package/src/lib/common/rational.zig +125 -31
  31. package/src/lib/common/rng.zig +22 -13
  32. package/src/lib/common/util.zig +50 -1
  33. package/src/lib/gen1/calc.zig +514 -135
  34. package/src/lib/gen1/chance.zig +407 -284
  35. package/src/lib/gen1/data/moves.zig +192 -180
  36. package/src/lib/gen1/data/species.zig +3 -3
  37. package/src/lib/gen1/data/types.zig +7 -7
  38. package/src/lib/gen1/data.zig +46 -57
  39. package/src/lib/gen1/helpers.zig +95 -550
  40. package/src/lib/gen1/mechanics.zig +571 -516
  41. package/src/lib/gen2/calc.zig +161 -0
  42. package/src/lib/gen2/chance.zig +907 -0
  43. package/src/lib/gen2/data/items.zig +12 -18
  44. package/src/lib/gen2/data/moves.zig +663 -569
  45. package/src/lib/gen2/data/species.zig +1 -1
  46. package/src/lib/gen2/data/types.zig +134 -31
  47. package/src/lib/gen2/data.zig +107 -79
  48. package/src/lib/gen2/generated.zig +806 -0
  49. package/src/lib/gen2/helpers.zig +49 -21
  50. package/src/lib/gen2/mechanics.zig +3563 -9
  51. package/src/lib/{binding/node.zig → node.zig} +10 -13
  52. package/src/lib/pkmn.zig +32 -15
  53. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  54. package/src/test/display/pkmn.html.tmpl +5 -5
  55. package/src/lib/gen1/README.md +0 -892
  56. package/src/lib/gen2/README.md +0 -308
@@ -6,6 +6,13 @@ const expect = std.testing.expect;
6
6
  const expectEqual = std.testing.expectEqual;
7
7
  const expectError = std.testing.expectError;
8
8
 
9
+ const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
10
+ const Float = if (@hasField(std.builtin.Type, "float")) .float else .Float;
11
+ const ComptimeInt =
12
+ if (@hasField(std.builtin.Type, "comptime_int")) .comptime_int else .ComptimeInt;
13
+ const ComptimeFloat =
14
+ if (@hasField(std.builtin.Type, "comptime_float")) .comptime_float else .ComptimeFloat;
15
+
9
16
  /// Specialization of a rational number used by the engine to compute probabilties.
10
17
  /// For performance reasons the rational is only reduced lazily and thus `reduce` must be
11
18
  /// invoked explicitly before reading.
@@ -22,15 +29,15 @@ pub fn Rational(comptime T: type) type {
22
29
  // start reducing when we get sufficiently close to the limit of the mantissa (in our domain
23
30
  // we expect updates to involve numbers < 2**10, so we should be safe not reducing before we
24
31
  // are 2**10 away from "overflowing" the mantissa)
25
- const REDUCE = if (@typeInfo(T) == .Float)
32
+ const REDUCE = if (@typeInfo(T) == Float)
26
33
  std.math.pow(T, 2, std.math.floatMantissaBits(T) - 10)
27
34
  else
28
35
  0;
29
36
 
30
37
  /// Possible error returned by operations on the Rational.
31
38
  pub const Error = switch (@typeInfo(T)) {
32
- .Int => error{Overflow},
33
- .Float => error{},
39
+ Int => error{Overflow},
40
+ Float => error{},
34
41
  else => unreachable,
35
42
  };
36
43
 
@@ -43,20 +50,19 @@ pub fn Rational(comptime T: type) type {
43
50
  /// Update the rational by multiplying its numerator by p and its denominator by q.
44
51
  /// Both p and q must be >= 1, and if computable at comptime must have no common factors.
45
52
  pub fn update(r: *Self, p: anytype, q: anytype) Error!void {
46
- // FIXME
47
- // std.debug.print("({d}/{d}) * ", .{p, q});
53
+ // std.debug.print("({d}/{d}) * ", .{ p, q }); // DEBUG
48
54
  assert(p >= 1);
49
55
  assert(q >= 1);
50
56
 
51
57
  // If our parameters are not fully reduced they may prematurely
52
58
  // cause overflow/loss of precision after the multiplication below
53
59
  assert(switch (@typeInfo(@TypeOf(p, q))) {
54
- .ComptimeInt, .ComptimeFloat => comptime gcd(p, q),
60
+ ComptimeInt, ComptimeFloat => comptime gcd(p, q),
55
61
  else => 1,
56
62
  } == 1);
57
63
 
58
64
  switch (@typeInfo(T)) {
59
- .Int => {
65
+ Int => {
60
66
  // Greedily attempt to multiply and if it fails, reduce and try again
61
67
  r.multiplication(p, q) catch |err| switch (err) {
62
68
  error.Overflow => {
@@ -66,16 +72,16 @@ pub fn Rational(comptime T: type) type {
66
72
  else => unreachable,
67
73
  };
68
74
  },
69
- .Float => {
75
+ Float => {
70
76
  // Reduce in situations where we're likely to start losing precision
71
77
  if (r.q > REDUCE or r.p > REDUCE) r.reduce();
72
78
 
73
79
  r.p *= switch (@typeInfo(@TypeOf(p))) {
74
- .Float, .ComptimeFloat => p,
80
+ Float, ComptimeFloat => p,
75
81
  else => @floatFromInt(p),
76
82
  };
77
83
  r.q *= switch (@typeInfo(@TypeOf(q))) {
78
- .Float, .ComptimeFloat => q,
84
+ Float, ComptimeFloat => q,
79
85
  else => @floatFromInt(q),
80
86
  };
81
87
 
@@ -90,7 +96,7 @@ pub fn Rational(comptime T: type) type {
90
96
  /// Add two rationals using the identity (a/b) + (c/d) = (ad+bc)/(bd).
91
97
  pub fn add(r: *Self, s: *Self) Error!void {
92
98
  switch (@typeInfo(T)) {
93
- .Int => {
99
+ Int => {
94
100
  if (r.q == s.q) {
95
101
  r.p = std.math.add(T, r.p, s.p) catch |err| switch (err) {
96
102
  error.Overflow => val: {
@@ -111,7 +117,7 @@ pub fn Rational(comptime T: type) type {
111
117
  };
112
118
  }
113
119
  },
114
- .Float => {
120
+ Float => {
115
121
  if (r.q == s.q) {
116
122
  if (r.p > REDUCE) r.reduce();
117
123
  if (s.p > REDUCE) s.reduce();
@@ -136,7 +142,7 @@ pub fn Rational(comptime T: type) type {
136
142
  /// Multiplies two rationals.
137
143
  pub fn mul(r: *Self, s: *Self) Error!void {
138
144
  switch (@typeInfo(T)) {
139
- .Int => {
145
+ Int => {
140
146
  r.multiplication(s.p, s.q) catch |err| switch (err) {
141
147
  error.Overflow => {
142
148
  r.reduce();
@@ -146,7 +152,7 @@ pub fn Rational(comptime T: type) type {
146
152
  else => unreachable,
147
153
  };
148
154
  },
149
- .Float => {
155
+ Float => {
150
156
  if (r.q > REDUCE or r.p > REDUCE) r.reduce();
151
157
  if (s.q > REDUCE or s.p > REDUCE) s.reduce();
152
158
 
@@ -185,16 +191,16 @@ pub fn Rational(comptime T: type) type {
185
191
  try writer.print("{d}/{d}", .{ self.p, self.q });
186
192
  }
187
193
 
188
- inline fn multiplication(r: *Self, p: anytype, q: anytype) !void {
194
+ fn multiplication(r: *Self, p: anytype, q: anytype) !void {
189
195
  r.p = try std.math.mul(T, r.p, p);
190
196
  r.q = try std.math.mul(T, r.q, q);
191
197
  }
192
198
 
193
- inline fn addition(r: *Self, p: anytype, q: anytype) !void {
199
+ fn addition(r: *Self, p: anytype, q: anytype) !void {
194
200
  // (a/b) + (c/d) = (ad+bc)/(bd)
195
- var d = try std.math.mul(T, r.q, q);
196
- var n1 = try std.math.mul(T, r.p, q);
197
- var n2 = try std.math.mul(T, r.q, p);
201
+ const d = try std.math.mul(T, r.q, q);
202
+ const n1 = try std.math.mul(T, r.p, q);
203
+ const n2 = try std.math.mul(T, r.q, p);
198
204
 
199
205
  r.p = try std.math.add(T, n1, n2);
200
206
  r.q = d;
@@ -202,23 +208,74 @@ pub fn Rational(comptime T: type) type {
202
208
  };
203
209
  }
204
210
 
205
- // https://en.wikipedia.org/wiki/Euclidean_algorithm
206
- inline fn gcd(p: anytype, q: anytype) @TypeOf(p, q) {
211
+ fn gcd(p: anytype, q: anytype) @TypeOf(p, q) {
207
212
  assert(p >= 1);
208
213
  assert(q >= 1);
209
214
 
210
- var a = p;
211
- var b = q;
212
- var c: @TypeOf(p, q) = undefined;
215
+ // convert comptime_int to a sized integer within this function so that @ctz will work
216
+ const T = switch (@TypeOf(p, q)) {
217
+ comptime_int => std.math.IntFittingRange(@min(p, q), @max(p, q)),
218
+ else => |U| U,
219
+ };
220
+
221
+ switch (@typeInfo(T)) {
222
+ Int => {
223
+ // std.math.gcd but without some checks because we have a stricter range
224
+ var x: T = @intCast(p);
225
+ var y: T = @intCast(q);
226
+
227
+ const xz = @ctz(x);
228
+ const yz = @ctz(y);
229
+ const shift = @min(xz, yz);
230
+ x >>= @intCast(xz);
231
+ y >>= @intCast(yz);
232
+
233
+ var diff = y -% x;
234
+ while (diff != 0) : (diff = y -% x) {
235
+ const zeros = @ctz(diff);
236
+ if (x > y) diff = -%diff;
237
+ y = @min(x, y);
238
+ x = diff >> @intCast(zeros);
239
+ }
240
+
241
+ const result = y << @intCast(shift);
242
+ assert(result > 0);
243
+ return result;
244
+ },
245
+ else => {
246
+ var a = p;
247
+ var b = q;
248
+ var c: T = undefined;
249
+
250
+ while (b != 0) {
251
+ c = b;
252
+ b = @mod(a, b);
253
+ a = c;
254
+ }
255
+
256
+ assert(a > 0);
257
+ return a;
258
+ },
259
+ }
260
+ }
261
+
262
+ test gcd {
263
+ const seed = if (@hasDecl(std.testing, "random_seed")) std.testing.random_seed else 0x12345678;
264
+ var prng = (if (@hasDecl(std, "Random")) std.Random else std.rand).DefaultPrng.init(seed);
265
+ var random = prng.random();
266
+
267
+ for (0..1000) |_| {
268
+ const a = random.int(u32);
269
+ const b = random.int(u32);
270
+ if (a == 0 or b == 0) continue;
213
271
 
214
- while (b != 0) {
215
- c = b;
216
- b = @mod(a, b);
217
- a = c;
272
+ try expectEqual(
273
+ gcd(a, b),
274
+ @as(u32, @intFromFloat(gcd(@as(f64, @floatFromInt(a)), @as(f64, @floatFromInt(b))))),
275
+ );
218
276
  }
219
277
 
220
- assert(a > 0);
221
- return a;
278
+ try expectEqual(gcd(300_000, @as(u32, 2_300_000)), 100_000); // NB: @ctz requires an @intCast
222
279
  }
223
280
 
224
281
  fn doTurn(r: anytype) !void {
@@ -234,6 +291,8 @@ test Rational {
234
291
  var r: Rational(t) = .{};
235
292
 
236
293
  var c: t = 128;
294
+ _ = &c;
295
+
237
296
  try r.update(c, 256);
238
297
  try doTurn(&r);
239
298
 
@@ -275,7 +334,7 @@ pub const BigRational = struct {
275
334
  /// Create a new BigRational wrapper. A small amount of memory will be allocated on
276
335
  /// initialization. Not default initialized to 1 - you must explicitly `reset` first.
277
336
  pub fn init(alloc: std.mem.Allocator) !BigRational {
278
- return BigRational{ .val = try std.math.big.Rational.init(alloc) };
337
+ return .{ .val = try std.math.big.Rational.init(alloc) };
279
338
  }
280
339
 
281
340
  /// Frees all memory associated with a rational.
@@ -320,6 +379,8 @@ test BigRational {
320
379
  try r.reset();
321
380
 
322
381
  var c: u8 = 128;
382
+ _ = &c;
383
+
323
384
  try r.update(c, 256);
324
385
  try doTurn(&r);
325
386
 
@@ -351,3 +412,36 @@ test BigRational {
351
412
  try s.setRatio(71, 78);
352
413
  try expect((try s.order(r.val)) == .eq);
353
414
  }
415
+
416
+ test "minimum" {
417
+ // TODO: ziglang/zig#21069
418
+ if (builtin.mode == .ReleaseFast or builtin.mode == .ReleaseSmall) {
419
+ return error.SkipZigTest;
420
+ }
421
+ var r = try BigRational.init(std.testing.allocator);
422
+ defer r.deinit();
423
+ var s = try std.math.big.Rational.init(std.testing.allocator);
424
+ defer s.deinit();
425
+
426
+ try r.reset();
427
+
428
+ try r.update(1, 2); // Speed Tie
429
+ for (0..2) |_| {
430
+ try r.update(1, 2); // Confusion
431
+ try r.update(1, 2); // Infatuation
432
+ try r.update(3, 4); // Paralysis
433
+ // Population Bomb
434
+ for (0..10) |_| {
435
+ try r.update(9, 10); // Accuracy
436
+ try r.update(1, 15); // Damage roll
437
+ try r.update(1, 24); // Critical Hit
438
+ try r.update(9, 10); // King's Rock non-proc
439
+ }
440
+ }
441
+
442
+ const ZEROS = std.math.pow(u310, 10, 31);
443
+ const n = 36472996377170786403;
444
+ const d: u310 = 146267071761884981524886149560653378220687632500762654519853056 * ZEROS;
445
+ try s.setRatio(n, d);
446
+ try expect((try s.order(r.val)) == .eq);
447
+ }
@@ -1,6 +1,6 @@
1
1
  const std = @import("std");
2
2
 
3
- const options = @import("./options.zig");
3
+ const options = @import("options.zig");
4
4
 
5
5
  const assert = std.debug.assert;
6
6
  const expectEqual = std.testing.expectEqual;
@@ -25,6 +25,11 @@ pub fn PRNG(comptime gen: comptime_int) type {
25
25
  pub fn next(self: *Self) Output(gen) {
26
26
  return self.src.next();
27
27
  }
28
+
29
+ pub fn advance(self: *Self, n: usize) void {
30
+ if (!options.advance) return;
31
+ for (0..n) |_| self.src.advance();
32
+ }
28
33
  };
29
34
  }
30
35
 
@@ -61,10 +66,14 @@ pub const PSRNG = extern struct {
61
66
  (@as(u64, self.range(u16, 0, 0x10000)) << 16) |
62
67
  (@as(u64, self.range(u16, 0, 0x10000)));
63
68
  }
69
+
70
+ pub fn Bound(comptime T: type) type {
71
+ return std.math.IntFittingRange(0, std.math.maxInt(T) + 1);
72
+ }
64
73
  };
65
74
 
66
75
  test PSRNG {
67
- var psrng = PSRNG{ .src = .{ .seed = 0x0001000200030004 } };
76
+ var psrng: PSRNG = .{ .src = .{ .seed = 0x0001000200030004 } };
68
77
  try expectEqual(@as(u8, 121), psrng.range(u8, 0, 256));
69
78
  try expectEqual(false, psrng.chance(u8, 128, 256)); // 226 < 128
70
79
  }
@@ -79,7 +88,7 @@ pub const Gen12 = extern struct {
79
88
  assert(@sizeOf(Gen12) == 10);
80
89
  }
81
90
 
82
- pub fn percent(p: u8) u8 {
91
+ pub fn percent(comptime p: comptime_int) u8 {
83
92
  return @intCast((@as(u16, p) * 0xFF) / 100);
84
93
  }
85
94
 
@@ -89,11 +98,15 @@ pub const Gen12 = extern struct {
89
98
  self.index = (self.index + 1) % 9;
90
99
  return val;
91
100
  }
101
+
102
+ pub fn advance(self: *Gen12) void {
103
+ _ = self.next();
104
+ }
92
105
  };
93
106
 
94
107
  test Gen12 {
95
108
  const expected = [_]u8{ 6, 11, 16, 21, 26, 31, 36, 41, 46, 31, 56, 81 };
96
- var rng = Gen12{ .seed = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9 } };
109
+ var rng: Gen12 = .{ .seed = .{ 1, 2, 3, 4, 5, 6, 7, 8, 9 } };
97
110
  for (expected) |e| {
98
111
  try expectEqual(e, rng.next());
99
112
  }
@@ -129,7 +142,7 @@ test Gen34 {
129
142
  .{ 0x80000000, 5, 0x0E425287 }, .{ 0x80000000, 10, 0x6F2CF4B2 },
130
143
  };
131
144
  for (data) |d| {
132
- var rng = Gen34{ .seed = d[0] };
145
+ var rng: Gen34 = .{ .seed = d[0] };
133
146
  for (0..d[1]) |_| _ = rng.next();
134
147
  try expectEqual(d[2], rng.seed);
135
148
  }
@@ -161,7 +174,7 @@ test Gen56 {
161
174
  .{ 0x8000000000000000, 10, 0xE7795501267F125A },
162
175
  };
163
176
  for (data) |d| {
164
- var rng = Gen56{ .seed = d[0] };
177
+ var rng: Gen56 = .{ .seed = d[0] };
165
178
  for (0..d[1]) |_| _ = rng.next();
166
179
  try expectEqual(d[2], rng.seed);
167
180
  }
@@ -188,7 +201,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
188
201
  for (0..n) |_| _ = self.next();
189
202
  }
190
203
 
191
- pub fn range(self: *Self, comptime T: type, from: T, to: Bound(T)) T {
204
+ pub fn range(self: *Self, comptime T: type, from: T, to: PSRNG.Bound(T)) T {
192
205
  assert(showdown);
193
206
  return @intCast(@as(u64, self.next()) * (to - from) / divisor + from);
194
207
  }
@@ -197,7 +210,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
197
210
  self: *Self,
198
211
  comptime T: type,
199
212
  numerator: T,
200
- denominator: Bound(T),
213
+ denominator: PSRNG.Bound(T),
201
214
  ) bool {
202
215
  assert(showdown);
203
216
  assert(denominator > 0);
@@ -213,7 +226,7 @@ pub fn FixedRNG(comptime gen: comptime_int, comptime len: usize) type {
213
226
  test FixedRNG {
214
227
  const Type = if (showdown) u32 else u8;
215
228
  const expected = [_]Type{ 42, 255, 0 };
216
- var rng = FixedRNG(1, expected.len){ .rolls = expected };
229
+ var rng: FixedRNG(1, expected.len) = .{ .rolls = expected };
217
230
  for (expected) |e| {
218
231
  try expectEqual(e, rng.next());
219
232
  }
@@ -228,7 +241,3 @@ fn Output(comptime gen: comptime_int) type {
228
241
  else => unreachable,
229
242
  };
230
243
  }
231
-
232
- fn Bound(comptime T: type) type {
233
- return std.math.IntFittingRange(0, std.math.maxInt(T) + 1);
234
- }
@@ -1,12 +1,61 @@
1
1
  const std = @import("std");
2
2
 
3
+ const expect = std.testing.expect;
3
4
  const expectEqual = std.testing.expectEqual;
4
5
 
6
+ const Pointer = if (@hasField(std.builtin.Type, "pointer")) .pointer else .Pointer;
7
+
5
8
  pub fn PointerType(comptime P: type, comptime C: type) type {
6
- return if (@typeInfo(P).Pointer.is_const) *const C else *C;
9
+ return if (@field(@typeInfo(P), @tagName(Pointer)).is_const) *const C else *C;
7
10
  }
8
11
 
9
12
  test PointerType {
10
13
  try expectEqual(*bool, PointerType(*u8, bool));
11
14
  try expectEqual(*const f64, PointerType(*const i32, f64));
12
15
  }
16
+
17
+ pub fn isPointerTo(p: anytype, comptime P: type) bool {
18
+ const info = @typeInfo(@TypeOf(p));
19
+ return switch (info) {
20
+ Pointer => @field(info, @tagName(Pointer)).child == P,
21
+ else => false,
22
+ };
23
+ }
24
+
25
+ test isPointerTo {
26
+ const S = struct {};
27
+ const s: S = .{};
28
+ try expect(!isPointerTo(s, S));
29
+ try expect(isPointerTo(&s, S));
30
+ }
31
+
32
+ // NOTE: std.mem.bytesAsValue backported from ziglang/zig#18061
33
+ pub fn bytesAsValue(comptime T: type, bytes: anytype) BytesAsValueReturnType(T, @TypeOf(bytes)) {
34
+ return @as(BytesAsValueReturnType(T, @TypeOf(bytes)), @ptrCast(bytes));
35
+ }
36
+
37
+ fn BytesAsValueReturnType(comptime T: type, comptime B: type) type {
38
+ return CopyPtrAttrs(B, .One, T);
39
+ }
40
+
41
+ fn CopyPtrAttrs(
42
+ comptime source: type,
43
+ comptime size: std.builtin.Type.Pointer.Size,
44
+ comptime child: type,
45
+ ) type {
46
+ const info = @field(@typeInfo(source), @tagName(Pointer));
47
+ const args = .{
48
+ .size = size,
49
+ .is_const = info.is_const,
50
+ .is_volatile = info.is_volatile,
51
+ .is_allowzero = info.is_allowzero,
52
+ .alignment = info.alignment,
53
+ .address_space = info.address_space,
54
+ .child = child,
55
+ .sentinel = null,
56
+ };
57
+ return @Type(if (@hasField(std.builtin.Type, "pointer"))
58
+ .{ .pointer = args }
59
+ else
60
+ .{ .Pointer = args });
61
+ }