@pkmn/engine 0.1.0-dev.c544e10e → 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 (46) hide show
  1. package/LICENSE +1 -3
  2. package/README.md +41 -14
  3. package/build/pkg/addon.js +6 -7
  4. package/build/pkg/addon.js.map +1 -1
  5. package/build/pkg/data.d.ts +4 -4
  6. package/build/pkg/data.js.map +1 -1
  7. package/build/pkg/gen1.js.map +1 -1
  8. package/build/pkg/index.d.ts +3 -2
  9. package/build/pkg/index.js.map +1 -1
  10. package/build/pkg/protocol.js +4 -4
  11. package/build/pkg/protocol.js.map +1 -1
  12. package/build/test/display.js +8 -3
  13. package/build/test/display.js.map +1 -1
  14. package/build/tools/debug.js +2 -3
  15. package/build/tools/debug.js.map +1 -1
  16. package/build.zig +208 -81
  17. package/package.json +32 -21
  18. package/src/bin/install-pkmn-engine +25 -17
  19. package/src/lib/common/array.zig +77 -0
  20. package/src/lib/common/data.zig +4 -4
  21. package/src/lib/common/debug.zig +2 -2
  22. package/src/lib/common/js.zig +4 -2
  23. package/src/lib/common/optional.zig +14 -10
  24. package/src/lib/common/options.zig +2 -0
  25. package/src/lib/common/protocol.zig +73 -73
  26. package/src/lib/common/rational.zig +121 -27
  27. package/src/lib/common/rng.zig +7 -7
  28. package/src/lib/common/util.zig +50 -1
  29. package/src/lib/gen1/calc.zig +511 -131
  30. package/src/lib/gen1/chance.zig +374 -267
  31. package/src/lib/gen1/data/species.zig +1 -1
  32. package/src/lib/gen1/data.zig +15 -14
  33. package/src/lib/gen1/helpers.zig +96 -536
  34. package/src/lib/gen1/mechanics.zig +285 -189
  35. package/src/lib/gen2/calc.zig +34 -16
  36. package/src/lib/gen2/chance.zig +85 -492
  37. package/src/lib/gen2/data.zig +8 -7
  38. package/src/lib/gen2/generated.zig +1 -3
  39. package/src/lib/gen2/helpers.zig +41 -9
  40. package/src/lib/gen2/mechanics.zig +178 -121
  41. package/src/lib/{binding/node.zig → node.zig} +9 -12
  42. package/src/lib/pkmn.zig +32 -15
  43. package/src/lib/{binding/wasm.zig → wasm.zig} +2 -1
  44. package/src/test/display/pkmn.html.tmpl +5 -5
  45. package/src/lib/gen1/README.md +0 -894
  46. package/src/lib/gen2/README.md +0 -329
@@ -3,8 +3,8 @@ const builtin = @import("builtin");
3
3
 
4
4
  const pkmn = @import("../pkmn.zig");
5
5
 
6
- const data = @import("./data.zig");
7
- const options = @import("./options.zig");
6
+ const data = @import("data.zig");
7
+ const options = @import("options.zig");
8
8
 
9
9
  const assert = std.debug.assert;
10
10
  const print = std.debug.print;
@@ -14,6 +14,13 @@ const enabled = options.log;
14
14
  const Player = data.Player;
15
15
  const ID = data.ID;
16
16
 
17
+ const endian = builtin.cpu.arch.endian();
18
+ const Endian = std.builtin.Endian;
19
+ const big = if (@hasField(Endian, "big")) Endian.big else Endian.Big;
20
+ const little = if (@hasField(Endian, "little")) Endian.little else Endian.Little;
21
+
22
+ const Int = if (@hasField(std.builtin.Type, "int")) .int else .Int;
23
+
17
24
  pub const ArgType = enum(u8) {
18
25
  None,
19
26
 
@@ -71,7 +78,7 @@ pub const ArgType = enum(u8) {
71
78
  EndAbility,
72
79
  ClearNegativeBoost,
73
80
  FormeChange,
74
- NoTarget,
81
+ LastNoTarget, // FIXME
75
82
 
76
83
  // Gen 4
77
84
  SwapBoost,
@@ -218,6 +225,7 @@ pub const Activate = enum(u8) {
218
225
  BeatUp, // FIXME of
219
226
 
220
227
  Magnitude,
228
+ Spite,
221
229
  };
222
230
 
223
231
  pub const Start = enum(u8) {
@@ -359,8 +367,8 @@ pub fn Log(comptime Writer: type) type {
359
367
  } else {
360
368
  try self.writer.writeAll(&.{ @intFromEnum(args[1].species), args[1].level });
361
369
  }
362
- try self.writer.writeIntNative(u16, args[1].hp);
363
- try self.writer.writeIntNative(u16, args[1].stats.hp);
370
+ try self.writer.writeInt(u16, args[1].hp, endian);
371
+ try self.writer.writeInt(u16, args[1].stats.hp, endian);
364
372
  try self.writer.writeAll(&.{args[1].status});
365
373
  }
366
374
 
@@ -391,7 +399,7 @@ pub fn Log(comptime Writer: type) type {
391
399
  if (!enabled) return;
392
400
 
393
401
  try self.writer.writeByte(@intFromEnum(ArgType.Turn));
394
- try self.writer.writeIntNative(u16, args[0]);
402
+ try self.writer.writeInt(u16, args[0], endian);
395
403
  try self.writer.writeByte(@intFromEnum(ArgType.None));
396
404
  }
397
405
 
@@ -423,8 +431,8 @@ pub fn Log(comptime Writer: type) type {
423
431
  @intFromEnum(ArgType.Damage),
424
432
  @as(u8, @bitCast(args[0])),
425
433
  });
426
- try self.writer.writeIntNative(u16, args[1].hp);
427
- try self.writer.writeIntNative(u16, args[1].stats.hp);
434
+ try self.writer.writeInt(u16, args[1].hp, endian);
435
+ try self.writer.writeInt(u16, args[1].stats.hp, endian);
428
436
  try self.writer.writeAll(&.{ args[1].status, @intFromEnum(@as(Damage, args[2])) });
429
437
  if (args.len == 4) try self.writer.writeByte(@as(u8, @bitCast(args[3])));
430
438
  }
@@ -436,8 +444,8 @@ pub fn Log(comptime Writer: type) type {
436
444
 
437
445
  assert(args[2] != .Drain or args.len == 4);
438
446
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.Heal), @as(u8, @bitCast(args[0])) });
439
- try self.writer.writeIntNative(u16, args[1].hp);
440
- try self.writer.writeIntNative(u16, args[1].stats.hp);
447
+ try self.writer.writeInt(u16, args[1].hp, endian);
448
+ try self.writer.writeInt(u16, args[1].stats.hp, endian);
441
449
  try self.writer.writeAll(&.{ args[1].status, @intFromEnum(@as(Heal, args[2])) });
442
450
  if (args.len == 4) try self.writer.writeByte(@as(u8, @bitCast(args[3])));
443
451
  }
@@ -542,6 +550,7 @@ pub fn Log(comptime Writer: type) type {
542
550
  // ident: ID, reason: Activate
543
551
  // ident: ID, reason: .SubstituteBlock, m: <Move>
544
552
  // ident: ID, reason: .Magnitude, num: u8
553
+ // ident: ID, reason: .Spite, m: <Move>, num: u8
545
554
  pub fn activate(self: Self, args: anytype) Error!void {
546
555
  if (!enabled) return;
547
556
 
@@ -559,6 +568,9 @@ pub fn Log(comptime Writer: type) type {
559
568
  assert(reason == .Magnitude);
560
569
  try self.writer.writeByte(args[2]);
561
570
  }
571
+ } else if (args.len == 4) {
572
+ assert(reason == .Spite);
573
+ try self.writer.writeAll(&.{ @intFromEnum(args[2]), args[3] });
562
574
  }
563
575
  }
564
576
 
@@ -706,8 +718,8 @@ pub fn Log(comptime Writer: type) type {
706
718
  if (!enabled) return;
707
719
 
708
720
  try self.writer.writeAll(&.{ @intFromEnum(ArgType.SetHP), @as(u8, @bitCast(args[0])) });
709
- try self.writer.writeIntNative(u16, args[1].hp);
710
- try self.writer.writeIntNative(u16, args[1].stats.hp);
721
+ try self.writer.writeInt(u16, args[1].hp, endian);
722
+ try self.writer.writeInt(u16, args[1].stats.hp, endian);
711
723
  try self.writer.writeAll(&.{args[1].status});
712
724
  try self.writer.writeByte(@intFromEnum(@as(SetHP, args[2])));
713
725
  }
@@ -831,10 +843,10 @@ pub const ByteStream = struct {
831
843
  try self.stream.writeByte(byte);
832
844
  }
833
845
 
834
- pub fn writeIntNative(self: Writer, comptime T: type, value: T) Error!void {
846
+ pub fn writeInt(self: Writer, comptime T: type, v: T, end: std.builtin.Endian) Error!void {
835
847
  // TODO: rework this to write directly to the buffer?
836
- var bytes: [(@typeInfo(T).Int.bits + 7) / 8]u8 = undefined;
837
- std.mem.writeIntNative(T, &bytes, value);
848
+ var bytes: [@divExact(@field(@typeInfo(T), @tagName(Int)).bits, 8)]u8 = undefined;
849
+ std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(v)), &bytes, v, end);
838
850
  return self.writeAll(&bytes);
839
851
  }
840
852
  };
@@ -857,7 +869,7 @@ pub const ByteStream = struct {
857
869
  pub const Kind = enum { Move, Species, Type, Status, Item, Weather };
858
870
 
859
871
  pub fn format(
860
- comptime gen: anytype,
872
+ gen: anytype,
861
873
  a: []const u8,
862
874
  b: ?[]const u8,
863
875
  color: bool,
@@ -943,19 +955,19 @@ pub fn format(
943
955
  if (a[i] == N(gen.Gender.Unknown)) {
944
956
  i += 1;
945
957
  } else {
946
- var gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
958
+ const gender = if (a[i] == N(gen.Gender.Male)) "M" else "F";
947
959
  printc(", {s}", .{gender}, a, b, &i, 1, color);
948
960
  }
949
961
  }
950
962
  printc(" L{d}", .{a[i]}, a, b, &i, 1, color);
951
963
  switch (endian) {
952
- .Big => {
964
+ big => {
953
965
  var hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
954
966
  printc(" {d}", .{hp}, a, b, &i, 2, color);
955
967
  hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
956
968
  printc("/{d}", .{hp}, a, b, &i, 2, color);
957
969
  },
958
- .Little => {
970
+ little => {
959
971
  var hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
960
972
  printc(" {d}", .{hp}, a, b, &i, 2, color);
961
973
  hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
@@ -986,8 +998,8 @@ pub fn format(
986
998
  },
987
999
  .Turn => {
988
1000
  const turn = switch (endian) {
989
- .Big => @as(u16, a[i]) << 8 | @as(u16, a[i + 1]),
990
- .Little => @as(u16, a[i + 1]) << 8 | @as(u16, a[i]),
1001
+ big => @as(u16, a[i]) << 8 | @as(u16, a[i + 1]),
1002
+ little => @as(u16, a[i + 1]) << 8 | @as(u16, a[i]),
991
1003
  };
992
1004
  printc(" {d}", .{turn}, a, b, &i, 2, color);
993
1005
  },
@@ -998,13 +1010,13 @@ pub fn format(
998
1010
  var id = ID.from(@intCast(a[i]));
999
1011
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1000
1012
  switch (endian) {
1001
- .Big => {
1013
+ big => {
1002
1014
  var hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1003
1015
  printc(" {d}", .{hp}, a, b, &i, 2, color);
1004
1016
  hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1005
1017
  printc("/{d}", .{hp}, a, b, &i, 2, color);
1006
1018
  },
1007
- .Little => {
1019
+ little => {
1008
1020
  var hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
1009
1021
  printc(" {d}", .{hp}, a, b, &i, 2, color);
1010
1022
  hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
@@ -1126,13 +1138,13 @@ pub fn format(
1126
1138
  const id = ID.from(@intCast(a[i]));
1127
1139
  printc(" {s}({d})", .{ @tagName(id.player), id.id }, a, b, &i, 1, color);
1128
1140
  switch (endian) {
1129
- .Big => {
1141
+ big => {
1130
1142
  var hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1131
1143
  printc(" {d}", .{hp}, a, b, &i, 2, color);
1132
1144
  hp = @as(u16, a[i]) << 8 | @as(u16, a[i + 1]);
1133
1145
  printc("/{d}", .{hp}, a, b, &i, 2, color);
1134
1146
  },
1135
- .Little => {
1147
+ little => {
1136
1148
  var hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
1137
1149
  printc(" {d}", .{hp}, a, b, &i, 2, color);
1138
1150
  hp = @as(u16, a[i + 1]) << 8 | @as(u16, a[i]);
@@ -1200,14 +1212,11 @@ pub fn expectLog(
1200
1212
  ) !void {
1201
1213
  if (!enabled) return;
1202
1214
 
1203
- const color = color: {
1204
- if (std.process.hasEnvVarConstant("ZIG_DEBUG_COLOR")) {
1205
- break :color true;
1206
- } else if (std.process.hasEnvVarConstant("NO_COLOR")) {
1207
- break :color false;
1208
- } else {
1209
- break :color std.io.getStdErr().supportsAnsiEscapeCodes();
1210
- }
1215
+ const color = if (std.process.hasEnvVarConstant("ZIG_DEBUG_COLOR"))
1216
+ true
1217
+ else switch (std.io.tty.detectConfig(std.io.getStdErr())) {
1218
+ .escape_codes => true,
1219
+ else => false,
1211
1220
  };
1212
1221
 
1213
1222
  expectEqualBytes(expected, actual, offset) catch |err| switch (err) {
@@ -1239,8 +1248,6 @@ fn expectEqualBytes(expected: []const u8, actual: []const u8, offset: usize) !vo
1239
1248
  }
1240
1249
  }
1241
1250
 
1242
- const endian = builtin.target.cpu.arch.endian();
1243
-
1244
1251
  fn N(e: anytype) u8 {
1245
1252
  return @intFromEnum(e);
1246
1253
  }
@@ -1248,20 +1255,13 @@ fn N(e: anytype) u8 {
1248
1255
  const p1 = Player.P1;
1249
1256
  const p2 = Player.P2;
1250
1257
 
1251
- const gen1 = struct {
1252
- pub usingnamespace @import("../gen1/data.zig");
1253
- pub const helpers = @import("../gen1/helpers.zig");
1254
- };
1255
-
1256
- const gen2 = struct {
1257
- pub usingnamespace @import("../gen2/data.zig");
1258
- pub const helpers = @import("../gen2/helpers.zig");
1259
- };
1260
-
1261
1258
  var buf: [pkmn.LOGS_SIZE]u8 = undefined;
1262
1259
  var stream: ByteStream = .{ .buffer = &buf };
1263
1260
  var log: FixedLog = .{ .writer = stream.writer() };
1264
1261
 
1262
+ const gen1 = pkmn.gen1;
1263
+ const gen2 = pkmn.gen2;
1264
+
1265
1265
  const M1 = gen1.Move;
1266
1266
  const S1 = gen1.Species;
1267
1267
 
@@ -1350,8 +1350,8 @@ test "|switch|" {
1350
1350
  try log.switched(.{ p2.ident(3), &snorlax });
1351
1351
  const par = 0b1000000;
1352
1352
  var expected: []const u8 = switch (endian) {
1353
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 0, 200, 1, 144, par },
1354
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 200, 0, 144, 1, par },
1353
+ big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 0, 200, 1, 144, par },
1354
+ little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 91, 200, 0, 144, 1, par },
1355
1355
  };
1356
1356
  try expectLog1(expected, buf[0..9]);
1357
1357
  stream.reset();
@@ -1361,8 +1361,8 @@ test "|switch|" {
1361
1361
  snorlax.status = 0;
1362
1362
  try log.switched(.{ p2.ident(3), &snorlax });
1363
1363
  expected = switch (endian) {
1364
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 1, 144, 0 },
1365
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 144, 1, 0 },
1364
+ big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 1, 144, 0 },
1365
+ little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 0, 0, 144, 1, 0 },
1366
1366
  };
1367
1367
  try expectLog1(expected, buf[0..9]);
1368
1368
  stream.reset();
@@ -1370,8 +1370,8 @@ test "|switch|" {
1370
1370
  snorlax.hp = 400;
1371
1371
  try log.switched(.{ p2.ident(3), &snorlax });
1372
1372
  expected = switch (endian) {
1373
- .Big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 1, 144, 1, 144, 0 },
1374
- .Little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 144, 1, 144, 1, 0 },
1373
+ big => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 1, 144, 1, 144, 0 },
1374
+ little => &.{ N(ArgType.Switch), 0b1011, N(S1.Snorlax), 100, 144, 1, 144, 1, 0 },
1375
1375
  };
1376
1376
  try expectLog1(expected, buf[0..9]);
1377
1377
  stream.reset();
@@ -1389,8 +1389,8 @@ test "|switch|" {
1389
1389
  N(gen2.Gender.Female),
1390
1390
  91,
1391
1391
  } ++ switch (endian) {
1392
- .Big => .{ 0, 200, 1, 144, par },
1393
- .Little => .{ 200, 0, 144, 1, par },
1392
+ big => .{ 0, 200, 1, 144, par },
1393
+ little => .{ 200, 0, 144, 1, par },
1394
1394
  });
1395
1395
  try expectLog2(expected, buf[0..10]);
1396
1396
  stream.reset();
@@ -1418,9 +1418,9 @@ test "|faint|" {
1418
1418
 
1419
1419
  test "|turn|" {
1420
1420
  try log.turn(.{42});
1421
- var expected = switch (endian) {
1422
- .Big => &.{ N(ArgType.Turn), 0, 42, N(ArgType.None) },
1423
- .Little => &.{ N(ArgType.Turn), 42, 0, N(ArgType.None) },
1421
+ const expected = switch (endian) {
1422
+ big => &.{ N(ArgType.Turn), 0, 42, N(ArgType.None) },
1423
+ little => &.{ N(ArgType.Turn), 42, 0, N(ArgType.None) },
1424
1424
  };
1425
1425
  try expectLog1(expected, buf[0..4]);
1426
1426
  stream.reset();
@@ -1444,8 +1444,8 @@ test "|-damage|" {
1444
1444
  chansey.status = gen1.Status.slp(1);
1445
1445
  try log.damage(.{ p2.ident(2), &chansey, .None });
1446
1446
  var expected: []const u8 = switch (endian) {
1447
- .Big => &.{ N(ArgType.Damage), 0b1010, 2, 100, 2, 191, 1, N(Damage.None) },
1448
- .Little => &.{ N(ArgType.Damage), 0b1010, 100, 2, 191, 2, 1, N(Damage.None) },
1447
+ big => &.{ N(ArgType.Damage), 0b1010, 2, 100, 2, 191, 1, N(Damage.None) },
1448
+ little => &.{ N(ArgType.Damage), 0b1010, 100, 2, 191, 2, 1, N(Damage.None) },
1449
1449
  };
1450
1450
  try expectLog1(expected, buf[0..8]);
1451
1451
  stream.reset();
@@ -1455,8 +1455,8 @@ test "|-damage|" {
1455
1455
  chansey.status = 0;
1456
1456
  try log.damage(.{ p2.ident(2), &chansey, .Confusion });
1457
1457
  expected = switch (endian) {
1458
- .Big => &.{ N(ArgType.Damage), 0b1010, 0, 100, 1, 0, 0, N(Damage.Confusion) },
1459
- .Little => &.{ N(ArgType.Damage), 0b1010, 100, 0, 0, 1, 0, N(Damage.Confusion) },
1458
+ big => &.{ N(ArgType.Damage), 0b1010, 0, 100, 1, 0, 0, N(Damage.Confusion) },
1459
+ little => &.{ N(ArgType.Damage), 0b1010, 100, 0, 0, 1, 0, N(Damage.Confusion) },
1460
1460
  };
1461
1461
  try expectLog1(expected, buf[0..8]);
1462
1462
  stream.reset();
@@ -1464,8 +1464,8 @@ test "|-damage|" {
1464
1464
  chansey.status = gen1.Status.init(.PSN);
1465
1465
  try log.damage(.{ p2.ident(2), &chansey, .RecoilOf, p1.ident(1) });
1466
1466
  expected = switch (endian) {
1467
- .Big => &.{ N(ArgType.Damage), 0b1010, 0, 100, 1, 0, 0b1000, N(Damage.RecoilOf), 1 },
1468
- .Little => &.{ N(ArgType.Damage), 0b1010, 100, 0, 0, 1, 0b1000, N(Damage.RecoilOf), 1 },
1467
+ big => &.{ N(ArgType.Damage), 0b1010, 0, 100, 1, 0, 0b1000, N(Damage.RecoilOf), 1 },
1468
+ little => &.{ N(ArgType.Damage), 0b1010, 100, 0, 0, 1, 0b1000, N(Damage.RecoilOf), 1 },
1469
1469
  };
1470
1470
  try expectLog1(expected, buf[0..9]);
1471
1471
  stream.reset();
@@ -1477,8 +1477,8 @@ test "|-heal|" {
1477
1477
  chansey.status = gen1.Status.slp(1);
1478
1478
  try log.heal(.{ p2.ident(2), &chansey, .None });
1479
1479
  var expected: []const u8 = switch (endian) {
1480
- .Big => &.{ N(ArgType.Heal), 0b1010, 2, 100, 2, 191, 1, N(Heal.None) },
1481
- .Little => &.{ N(ArgType.Heal), 0b1010, 100, 2, 191, 2, 1, N(Heal.None) },
1480
+ big => &.{ N(ArgType.Heal), 0b1010, 2, 100, 2, 191, 1, N(Heal.None) },
1481
+ little => &.{ N(ArgType.Heal), 0b1010, 100, 2, 191, 2, 1, N(Heal.None) },
1482
1482
  };
1483
1483
  try expectLog1(expected, buf[0..8]);
1484
1484
  stream.reset();
@@ -1488,16 +1488,16 @@ test "|-heal|" {
1488
1488
  chansey.status = 0;
1489
1489
  try log.heal(.{ p2.ident(2), &chansey, .Silent });
1490
1490
  expected = switch (endian) {
1491
- .Big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Silent) },
1492
- .Little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Silent) },
1491
+ big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Silent) },
1492
+ little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Silent) },
1493
1493
  };
1494
1494
  try expectLog1(expected, buf[0..8]);
1495
1495
  stream.reset();
1496
1496
 
1497
1497
  try log.heal(.{ p2.ident(2), &chansey, .Drain, p1.ident(1) });
1498
1498
  expected = switch (endian) {
1499
- .Big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Drain), 1 },
1500
- .Little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Drain), 1 },
1499
+ big => &.{ N(ArgType.Heal), 0b1010, 0, 100, 1, 0, 0, N(Heal.Drain), 1 },
1500
+ little => &.{ N(ArgType.Heal), 0b1010, 100, 0, 0, 1, 0, N(Heal.Drain), 1 },
1501
1501
  };
1502
1502
  try expectLog1(expected, buf[0..9]);
1503
1503
  stream.reset();
@@ -1740,8 +1740,8 @@ test "|drag|" {
1740
1740
  N(gen2.Gender.Female),
1741
1741
  91,
1742
1742
  } ++ switch (endian) {
1743
- .Big => .{ 0, 200, 1, 144, par },
1744
- .Little => .{ 200, 0, 144, 1, par },
1743
+ big => .{ 0, 200, 1, 144, par },
1744
+ little => .{ 200, 0, 144, 1, par },
1745
1745
  });
1746
1746
  try expectLog2(expected, buf[0..10]);
1747
1747
  stream.reset();
@@ -1773,9 +1773,9 @@ test "|-sethp|" {
1773
1773
  var blissey = gen2.helpers.Pokemon.init(.{ .species = .Blissey, .moves = &.{.PainSplit} });
1774
1774
  blissey.hp = 612;
1775
1775
  try log.sethp(.{ p2.ident(2), &blissey, .Silent });
1776
- var expected: []const u8 = switch (endian) {
1777
- .Big => &.{ N(ArgType.SetHP), 0b1010, 2, 100, 2, 201, 0, N(SetHP.Silent) },
1778
- .Little => &.{ N(ArgType.SetHP), 0b1010, 100, 2, 201, 2, 0, N(SetHP.Silent) },
1776
+ const expected: []const u8 = switch (endian) {
1777
+ big => &.{ N(ArgType.SetHP), 0b1010, 2, 100, 2, 201, 0, N(SetHP.Silent) },
1778
+ little => &.{ N(ArgType.SetHP), 0b1010, 100, 2, 201, 2, 0, N(SetHP.Silent) },
1779
1779
  };
1780
1780
  try expectLog2(expected, buf[0..8]);
1781
1781
  stream.reset();
@@ -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
 
@@ -192,9 +198,9 @@ pub fn Rational(comptime T: type) type {
192
198
 
193
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
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
 
@@ -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
+ }